Andrey Listopadov

Automatic refreshing of package archives for Emacs

I was going through my git commit history in my public dotfiles repository, and noticed that prior to using straight.el I’ve, like many, used inbuilt solution for managing packages, called package.el. However, there was one thing that bothered me, and was one of the reasons that made me think about switching to straight was the fact, that a lot of times when I wanted to update or install packages, I couldn’t, due to an outdated package cache. So I’ve fixed this, but since I’ve moved to straight I no longer needed this piece of code, and hence I’ve deleted it. But it might be useful for people who use package.el, so I’ve decided to share it here.

First thing needed is a custom variable to store the last refresh date. I’ve chosen Custom because it is persisted on disk, which allows me to use customize-save-variable later to make the value persist across sessions, and be easily reset via the customization interface:

(defcustom package-last-refresh-date nil
  "Date and time when package lists have been refreshed.

  This variable is then used to check whether
  `package-refresh-contents' call is needed before calling
  `package-install'. Value of this varialbe is updated when
  `package-refresh-contents' is called.

  See `package-refresh-hour-threshold' for amount of time needed to
  trigger refresh."
  :type 'string
  :group 'package)

This variable is useful on its own, but we also need another one, that will specify how much time needs to pass since the last refresh to trigger package refreshing again. I’ve chosen to store amount of hours, since you don’t need to refresh contents really often, but it may be a personal preference. For example, if you update or install packages once a week, it doesn’t really matter, but if you, like me often tried cool packages that people share over the internet, the refresh rate may be set a bit lower than that.

(defcustom package-automatic-refresh-threshold 24
  "Amount of hours since last `package-refresh-contents' call
  needed to trigger automatic refresh before calling `package-install'."
  :type 'number
  :group 'package)

With these two helper variables we can start “patching” package.el so it would use it. We don’t need to modify code, thanks to advanced advice system:

(define-advice package-install (:before (&rest _) package-refresh-contents-maybe)
  (when (or (null package-last-refresh-date)
            (> (/ (float-time
                   (time-subtract (date-to-time (format-time-string "%Y-%m-%dT%H:%M"))
                                  (date-to-time package-last-refresh-date)))
                  3600)
               package-automatic-refresh-threshold))
    (package-refresh-contents)))

(define-advice package-refresh-contents (:after (&rest _) update-package-refresh-date)
  (customize-save-variable 'package-last-refresh-date
                           (format-time-string "%Y-%m-%dT%H:%M")))

The first advice ensure that before installing a package we’ll refresh package contents, if enough time has passed since the last refresh, that is. The second one simply updates the last refresh time, and stores it in the custom file, which I, personally, changed to custom.el in my Emacs directory, like this:

(use-package cus-edit
  :custom (custom-file (expand-file-name "custom.el" user-emacs-directory))
  :init (load custom-file :noerror))

This file is not a part of my version control system, and hosts a lot of machine local configuration, so it was natural to store last refresh date in it too. Of course, it is possible to store it in an arbitrary file, since it’s just a string, and read it inside the package-install advice, but customize makes it seamless, and worked for me quite well.

Hope this was a useful tip!