From 561c68511a2aa272c658192b6e1c08fe666196ed Mon Sep 17 00:00:00 2001 From: Christophe Rhodes Date: Mon, 11 Jun 2012 11:33:21 +0100 Subject: [PATCH] rework commands in light of long cache refresh time The issue is that of get-iplayer itself managing its cache: if at any point it decides that the cache is sufficiently stale, it goes off and refreshes everything, which seems to take a long time -- if I'm unlucky, breaking my ERC sessions (and in any case making the rest of the editor session unusable). Construct a background process and sentinel to manage the cache updating before calling the real get-iplayer command. The tricky bit here is to make sure that the sentinel and real body get executed in the right context. Many things in the world are simpler with lexical scoping; this would have been one of them, but I am still on emacs 23 and lexical binding is a thing of the future, so instead of making a bunch of thunks to be called later, we have to save commands (well, key sequences), but then those key sequences are divorced from the window and frame that they were originally typed into, so we have to get that context back. Naive ways of doing that turn out not to work, because execute-kbd-macro runs its own command loop, which first of all selects a current frame and window, so if we've navigated away from the *iplayer* window/buffer we end up adding keystrokes to e.g. an ERC buffer, which is suboptimal; however, (ab)using the pre and post command hooks seems to make things work. So do that, and all is well. --- iplayer.el | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/iplayer.el b/iplayer.el index 68d3a01..882e5e9 100644 --- a/iplayer.el +++ b/iplayer.el @@ -1,3 +1,66 @@ +(defvar iplayer-updating-cache-process nil) +(defvar iplayer-updating-cache-sentinel-info nil) +(defvar iplayer-updating-cache-sentinel-executing nil) + +(defun iplayer-updating-cache-sentinel (process event) + ;; FIXME: assumes that all went well + (let* ((iplayer-updating-cache-sentinel-executing t) + (info (reverse iplayer-updating-cache-sentinel-info))) + (setq iplayer-updating-cache-process nil + iplayer-updating-cache-sentinel-info nil) + (dolist (info info) + (let ((iplayer-command-frame (car info)) + (iplayer-command-window (cadr info)) + (iplayer-command-buffer (caddr info)) + (keys (car (cdddr info)))) + (when (and (frame-live-p iplayer-command-frame) + (window-live-p iplayer-command-window) + (buffer-live-p iplayer-command-buffer)) + (let ((old-frame (selected-frame)) + (old-window (selected-window)) + (old-buffer (current-buffer))) + (let ((pre-command-hook + (lambda () + (select-frame iplayer-command-frame) + (select-window iplayer-command-window) + (set-buffer iplayer-command-buffer))) + (post-command-hook + (lambda () + (select-window old-window) + (select-frame old-frame) + (set-buffer old-buffer)))) + ;; KLUDGE: execute-kbd-macro executes a normal + ;; command-loop, whose first action is to select the + ;; current frame and window, which is why we contort + ;; things to select the frame/window/buffer we actually + ;; want in pre-command-hook. I'm actually surprised + ;; that it works, but mine is not too much to reason + ;; why; lots of other ways to try to achieve this didn't + ;; in fact work. + (execute-kbd-macro keys)))))) + (message "Done updating iPlayer cache"))) + +(defmacro define-iplayer-command (name arglist &rest body) + (let (docstring interactive) + (when (stringp (car body)) + (setq docstring (car body) body (cdr body))) + (when (and (consp (car body)) (eql (caar body) 'interactive)) + (setq interactive (car body) body (cdr body))) + `(defun ,name ,arglist + ,@(when docstring (list docstring)) + ,@(when interactive (list interactive)) + (unless iplayer-updating-cache-process + (setq iplayer-updating-cache-process + (start-process "updating-iplayer" " *updating-iplayer*" + "get-iplayer" "--type" "radio,tv" "-q")) + (set-process-sentinel iplayer-updating-cache-process + 'iplayer-updating-cache-sentinel) + (message "Updating iPlayer cache")) + (if iplayer-updating-cache-sentinel-executing + (progn ,@body) + (push (list (selected-frame) (selected-window) (current-buffer) (this-command-keys)) + iplayer-updating-cache-sentinel-info))))) + (defun get-iplayer-tree (&rest args) (with-temp-buffer (apply #'call-process "get-iplayer" nil t nil "--nocopyright" "--type" "radio,tv" "--tree" "--terse" args) @@ -54,9 +117,15 @@ ("%" . "BBC Radio 5 live") ("^" . "BBC 6 Music") ("&" . "BBC 7") - ("*" . "BBC Radio 4 Extra"))) + ("*" . "BBC Radio 4 Extra")) + "Alist mapping keys to iPlayer channels. + +Used in the `iplayer-preset' command.") + +(define-iplayer-command iplayer-preset (&optional prefix) + "Switch display to a preset channel. -(defun iplayer-preset (&optional prefix) +The presets are defined in the variable `iplayer-presets'." (interactive "p") (let ((keys (this-command-keys)) (presets (mapcar (lambda (x) (cons (read-kbd-macro (car x)) (cdr x))) iplayer-presets))) @@ -101,7 +170,8 @@ (use-local-map iplayer-mode-map) (setq major-mode 'iplayer-mode mode-name "iPlayer")) -(defun iplayer () +(define-iplayer-command iplayer () + "Start the emacs iPlayer interface." (interactive) (setq mode-line-process nil) (display-iplayer-tree (get-iplayer-tree))) -- 2.39.5