doomemacs/modules/email/mu4e/autoload/mu-lock.el
TEC a4db56be2f
Mu4e: Confirm that lockfile pid is an emacs proc
Or else another app can be given the same PID and we'll be complaining
another Emacs process is using mu4e when that simply isn't true.
2021-07-30 01:56:53 +08:00

91 lines
4.1 KiB
EmacsLisp

;;; email/mu4e/autoload/mu-lock.el -*- lexical-binding: t; -*-
(defvar +mu4e-lock-file (expand-file-name "mu4e_lock" temporary-file-directory)
"Location of the lock file which stores the PID of the process currenty running mu4e")
(defvar +mu4e-lock-request-file (expand-file-name "mu4e_lock_request" temporary-file-directory)
"Location of the lock file for which creating indicated that another process wants the lock to be released")
(defvar +mu4e-lock-greedy nil
"Whether to 'grab' the `+mu4e-lock-file' if nobody else has it, i.e. start Mu4e")
(defvar +mu4e-lock-relaxed t
"Whether if someone else wants the lock (signaled via `+mu4e-lock-request-file'), we should stop Mu4e and let go of it")
(defun +mu4e-lock-pid-info ()
"Get info on the PID refered to in `+mu4e-lock-file' in the form (pid . process-attributes)
If the file or process do not exist, the lock file is deleted an nil returned."
(when (file-exists-p +mu4e-lock-file)
(let* ((pid (string-to-number
(with-temp-buffer
(setq coding-system-for-read 'utf-8)
(insert-file-contents +mu4e-lock-file)
(buffer-string))))
(process (process-attributes pid)))
(if (and process (string-match-p "emacs" (alist-get 'args process)))
(cons pid process)
(delete-file +mu4e-lock-file) nil))))
(defun +mu4e-lock-available (&optional strict)
"If the `+mu4e-lock-file' is available (unset or owned by this emacs) return t.
If STRICT only accept an unset lock file."
(not (when-let* ((lock-info (+mu4e-lock-pid-info))
(pid (car lock-info)))
(when (or strict (/= (emacs-pid) pid)) t))))
;;;###autoload
(defun +mu4e-lock-file-delete-maybe ()
"Check `+mu4e-lock-file', and delete it if this process is responsible for it."
(when (+mu4e-lock-available)
(delete-file +mu4e-lock-file)
(file-notify-rm-watch +mu4e-lock--request-watcher)))
;;;###autoload
(defun +mu4e-lock-start (orig-fun &optional callback)
"Check `+mu4e-lock-file', and if another process is responsible for it, abort starting.
Else, write to this process' PID to the lock file"
(unless (+mu4e-lock-available)
(call-process "touch" nil nil nil +mu4e-lock-request-file)
(message "Lock file exists, requesting that it be given up")
(sleep-for 0.1)
(delete-file +mu4e-lock-request-file))
(if (not (+mu4e-lock-available))
(user-error "Unfortunately another Emacs is already doing stuff with Mu4e, and you can only have one at a time")
(write-region (number-to-string (emacs-pid)) nil +mu4e-lock-file)
(delete-file +mu4e-lock-request-file)
(call-process "touch" nil nil nil +mu4e-lock-request-file)
(funcall orig-fun callback)
(setq +mu4e-lock--request-watcher
(file-notify-add-watch +mu4e-lock-request-file
'(change)
#'+mu4e-lock-request))))
(defvar +mu4e-lock--file-watcher nil)
(defvar +mu4e-lock--file-just-deleted nil)
(defvar +mu4e-lock--request-watcher nil)
(defun +mu4e-lock-add-watcher ()
(setq +mu4e-lock--file-just-deleted nil)
(file-notify-rm-watch +mu4e-lock--file-watcher)
(setq +mu4e-lock--file-watcher
(file-notify-add-watch +mu4e-lock-file
'(change)
#'+mu4e-lock-file-updated)))
(defun +mu4e-lock-request (event)
"Handle another process requesting the Mu4e lock."
(when (equal (nth 1 event) 'created)
(when +mu4e-lock-relaxed
(mu4e~stop)
(file-notify-rm-watch +mu4e-lock--file-watcher)
(message "Someone else wants to use Mu4e, releasing lock")
(delete-file +mu4e-lock-file)
(run-at-time 0.2 nil #'+mu4e-lock-add-watcher))
(delete-file +mu4e-lock-request-file)))
(defun +mu4e-lock-file-updated (event)
(if +mu4e-lock--file-just-deleted
(+mu4e-lock-add-watcher)
(when (equal (nth 1 event) 'deleted)
(setq +mu4e-lock--file-just-deleted t)
(when (and +mu4e-lock-greedy (+mu4e-lock-available t))
(message "Noticed Mu4e lock was available, grabbed it")
(run-at-time 0.2 nil #'mu4e~start)))))