branch: elpa/hyperdrive
commit c33cf48e01e9dd1bd07759f1753a395280583a85
Author: Adam Porter <[email protected]>
Commit: Joseph Turner <[email protected]>
Refactor: Gateway status things
---
hyperdrive-lib.el | 215 ++++++++++++++++++-----------------------------------
hyperdrive-menu.el | 2 +-
hyperdrive-vars.el | 37 ++++++++-
hyperdrive.el | 45 +++++------
4 files changed, 131 insertions(+), 168 deletions(-)
diff --git a/hyperdrive-lib.el b/hyperdrive-lib.el
index 58c0e5918f..283293b6f5 100644
--- a/hyperdrive-lib.el
+++ b/hyperdrive-lib.el
@@ -239,14 +239,6 @@ PLZ-ERR should be a `plz-error' struct."
(_
(signal 'plz-error (list "plz error" plz-err)))))
-;;;###autoload
-(defun hyperdrive-status ()
- "Return non-nil if `hyper-gateway-ushin' is running and accessible."
- ;; FIXME: Ensure a very short timeout for this request.
- (condition-case nil
- (plz 'get (format "http://localhost:%d/" h/hyper-gateway-ushin-port))
- (error nil)))
-
(defun h//httpify-url (url)
"Return localhost HTTP URL for HYPER-URL."
(format "http://localhost:%d/hyper/%s"
@@ -1420,97 +1412,6 @@ Then calls THEN if given."
;;;; Gateway process
-;; NOTE: The below involves some slightly hacky workarounds due to using a
-;; setter for the `h/gateway-process-type' option. The setter gets called
-;; unexpectedly early in the compilation and/or load process, which causes
-;; errors if the functions/methods and variables involved are not yet defined.
-;; So we define the variable first, giving it a nil value, and define a default
-;; for the running-p method (because the setter gets called before the option
is
-;; given its default value), and then the variable is redefined as an option
and
-;; given its default value.
-
-(defvar h/gateway-process-type nil)
-
-(cl-defmethod h//gateway-running-p ()
- "Return non-nil if the gateway process is running.")
-
-(cl-defmethod h//gateway-running-p (&context (h/gateway-process-type (eql
'systemd)))
- "Return non-nil if the gateway process is running.
-This does not mean that the gateway is responsive, only that the
-process is running. Used when HYPERDRIVE-GATEWAY-PROCESS-TYPE
-is the symbol `systemd'."
- (zerop (call-process "systemctl" nil nil nil
- "--user" "is-active" "hyper-gateway-ushin.service")))
-
-(cl-defmethod h//gateway-running-p (&context (h/gateway-process-type (eql
'subprocess)))
- "Return non-nil if the gateway process is running.
-This does not mean that the gateway is responsive, only that the
-process is running. Used when HYPERDRIVE-GATEWAY-PROCESS-TYPE
-is the symbol `subprocess'."
- (process-live-p h/gateway-process))
-
-(defcustom h/gateway-process-type nil
- "How to run the gateway process.
-Value may be one of
-
-- nil :: Autodetect
-- \\+`systemd' :: systemd user-level service
-- \\+`subprocess' :: Emacs subprocess
-
-To customize the command run as a subprocess, see
-`hyperdrive-gateway-command'."
- ;; TODO: Can or should we use the :initialize function here?
- :set (lambda (option value)
- "Stop the gateway process before changing the type."
- (let ((value-changing-p (not (equal h/gateway-process-type value))))
- (unless value
- ;; Try to autodetect whether the gateway is already installed as a
- ;; systemd service. (If systemd is not installed, it will
default to
- ;; `subprocess'.)
- (setf value
- (if (ignore-errors
- (zerop (call-process
- "systemctl" nil nil nil "--user" "is-enabled"
- "hyper-gateway-ushin.service")))
- 'systemd
- 'subprocess)))
- (let ((runningp (h//gateway-running-p)))
- (when (and runningp value-changing-p)
- (h//gateway-stop))
- (set-default option value)
- (when (and runningp value-changing-p)
- (h//gateway-start)))))
- :type '(choice (const :tag "systemd service" systemd)
- (const :tag "Emacs subprocess"
- :description "When Emacs exits, the gateway will be
terminated."
- subprocess)
- (const :tag "Autodetect" nil))
- :group 'hyperdrive)
-
-(defcustom h/gateway-command-args "run --writable true --silent true"
- ;; TODO: File Emacs bug report because the customization formatter handles
the
- ;; "symbol `subprocess'" part differently than `describe-variable' does.
- "Arguments passed to hyper-gateway-ushin.
-Only used when `hyperdrive-gateway-process-type' is the symbol `subprocess'."
- :type 'string
- :group 'hyperdrive)
-
-(cl-defmethod h//gateway-start (&context (h/gateway-process-type (eql
'systemd)))
- "Start the gateway as a systemd service.
-Used when HYPERDRIVE-GATEWAY-PROCESS-TYPE is the symbol
-`systemd'."
- (when (h//gateway-running-p)
- (user-error "Gateway already running"))
- (let ((buffer (get-buffer-create " *hyperdrive-start*")))
- (unwind-protect
- (unless (zerop (call-process
- "systemctl" nil (list buffer t) nil
- "--user" "start" "hyper-gateway-ushin.service"))
- (h/error "Unable to start hyper-gateway-ushin: %S"
- (with-current-buffer buffer
- (string-trim-right (buffer-string)))))
- (kill-buffer buffer))))
-
(defun h//hyper-gateway-ushin-path ()
"Return path to hyper-gateway-ushin executable, or nil if not found."
(cond ((file-exists-p
@@ -1518,19 +1419,22 @@ Used when HYPERDRIVE-GATEWAY-PROCESS-TYPE is the symbol
(expand-file-name "hyper-gateway-ushin" hyperdrive-gateway-directory))
((executable-find "hyper-gateway-ushin"))))
-(cl-defmethod h//gateway-start
- (&context (h/gateway-process-type (eql 'subprocess)))
+(defun h//gateway-start-default ()
"Start the gateway as an Emacs subprocess.
-Used when HYPERDRIVE-GATEWAY-PROCESS-TYPE is the symbol
-`subprocess'."
- (when (h//gateway-running-p)
- (user-error "Gateway already running"))
+Default function; see variable `h/gateway-start-function'."
+ (cond ((and (h//gateway-ready-p)
+ (h//gateway-live-p))
+ (h/error "Gateway already running"))
+ ((h//gateway-ready-p)
+ (h/error "Gateway already running outside of Emacs"))
+ ((h//gateway-live-p)
+ (h/error "Gateway already starting")))
(let ((hyper-gateway-ushin-path
(or (h//hyper-gateway-ushin-path)
(if (yes-or-no-p "hyper-gateway-ushin not installed; install? ")
(h/install)
(h/error "Gateway not installed; aborted")))))
- (when (defvar h/install-in-progress-p)
+ (when h/install-in-progress-p
(h/error "Gateway installation in-progress"))
(setf h/gateway-process
(make-process
@@ -1539,48 +1443,71 @@ Used when HYPERDRIVE-GATEWAY-PROCESS-TYPE is the symbol
:command (cons hyper-gateway-ushin-path
(split-string-and-unquote h/gateway-command-args))
:connection-type 'pipe))
- (sleep-for 0.5)
- (unless (process-live-p h/gateway-process)
- (if (h/status)
- (user-error "Gateway is already running outside of Emacs (see option
`hyperdrive-gateway-process-type')")
- (pop-to-buffer " *hyperdrive-start*")
- (h/error "Gateway failed to start (see process buffer for errors)")))))
-
-(cl-defmethod h//gateway-stop (&context (h/gateway-process-type (eql
'systemd)))
- "Stop the gateway service.
-Used when HYPERDRIVE-GATEWAY-PROCESS-TYPE is the symbol
-`systemd'."
- (unless (h//gateway-running-p)
- (user-error "Gateway not running"))
- (let ((buffer (get-buffer-create " *hyperdrive-stop*")))
- (unwind-protect
- (unless (zerop (call-process "systemctl" nil (list buffer t) nil
- "--user" "stop"
"hyper-gateway-ushin.service"))
- (h/error "Unable to stop hyper-gateway-ushin: %S"
- (with-current-buffer buffer
- (string-trim-right (buffer-string)))))
- (cl-loop for i below 40
- do (sleep-for 0.1)
- while (h//gateway-running-p))
- (when (h//gateway-running-p)
- (h/error "Gateway still running"))
- (kill-buffer buffer))))
-
-(cl-defmethod h//gateway-stop (&context (h/gateway-process-type (eql
'subprocess)))
- "Stop the gateway subprocess.
-Used when HYPERDRIVE-GATEWAY-PROCESS-TYPE is the symbol
-`subprocess'."
- (unless (h//gateway-running-p)
- (user-error "Gateway not running"))
+ (h/message "Starting gateway...")
+ ;; TODO: Update hyperdrive-menu to indicate "gateway starting...".
+ (h//gateway-wait-for-ready)))
+
+(defun h//gateway-after-start-announce ()
+ "Announce that the gateway is ready."
+ ;; TODO: Update hyperdrive-menu to indicate "gateway ready".
+ (h/message "Gateway ready."))
+
+(defun h//gateway-stop-default ()
+ "Stop the gateway subprocess."
+ (unless (h//gateway-live-p)
+ ;; NOTE: We do not try to stop the process if we didn't start it ourselves.
+ (h/error "Gateway not running as subprocess"))
(interrupt-process h/gateway-process)
- (cl-loop for i below 40
- do (sleep-for 0.1)
- while (h//gateway-running-p))
- (when (h//gateway-running-p)
- (h/error "Gateway still running"))
+ (with-timeout (4 (h/error "Gateway still running"))
+ (cl-loop while (h//gateway-live-p)
+ do (sleep-for 0.2)))
+ ;; TODO: Consider killing the process buffer and setting the variable nil in
+ ;; the sentinel.
(kill-buffer (process-buffer h/gateway-process))
(setf h/gateway-process nil))
+(defun h//gateway-live-p ()
+ "Return non-nil if the gateway process is running.
+Calls function set in option `hyperdrive-gateway-live-predicate'.
+This does not mean that the gateway is responsive, only that the
+process is running. See also function
+`hyperdrive--gateway-ready-p'."
+ (funcall h/gateway-live-predicate))
+
+(defun h//gateway-live-p-default ()
+ "Return non-nil if the gateway process is running.
+This does not mean that the gateway is responsive, only that the
+process is running. See also function
+`hyperdrive--gateway-ready-p'."
+ (process-live-p h/gateway-process))
+
+(defun h//gateway-ready-p ()
+ "Return non-nil if the gateway is running and accessible.
+Times out after 2 seconds."
+ (ignore-errors
+ (plz 'get (format "http://localhost:%d/" h/hyper-gateway-ushin-port)
+ :connect-timeout 2 :timeout 2)))
+
+(defun h//gateway-wait-for-ready ()
+ "Run `hyperdrive-gateway-ready-hook' after gateway is ready.
+Or if gateway isn't ready within timeout, show an error."
+ (letrec
+ ((start-time (float-time))
+ (check
+ (lambda ()
+ (cond ((h//gateway-live-p)
+ (run-hooks 'h/gateway-ready-hook))
+ ((h//gateway-ready-p)
+ ;; Gateway is responsive, so must be running from outside
Emacs.
+ (run-hooks 'h/gateway-ready-hook)
+ (h/message "Gateway is already running outside of Emacs"))
+ ((> 10 (time-subtract (float-time) start-time))
+ ;; Gateway still not responsive: show error.
+ (pop-to-buffer " *hyperdrive-start*")
+ (h/error "Gateway failed to start (see process buffer for
errors)"))
+ (t (run-at-time 0.1 nil check))))))
+ (funcall check)))
+
;;;; Misc.
(defun h//get-buffer-create (entry)
diff --git a/hyperdrive-menu.el b/hyperdrive-menu.el
index f0c89acbf8..fa529e2455 100644
--- a/hyperdrive-menu.el
+++ b/hyperdrive-menu.el
@@ -205,7 +205,7 @@
:description
(lambda ()
(concat (propertize "Gateway: " 'face 'transient-heading)
- (propertize (if (h/status) "on" "off")
+ (propertize (if (h//gateway-ready-p) "on" "off")
'face 'transient-value)))
("G s" "Start" h/start
:transient t)
diff --git a/hyperdrive-vars.el b/hyperdrive-vars.el
index bf46ef988c..bd8a441c4b 100644
--- a/hyperdrive-vars.el
+++ b/hyperdrive-vars.el
@@ -370,8 +370,10 @@ May also be non-nil if the user has chosen to use whatever
version is currently installed.")
(defvar h/gateway-process nil
- "Hyper-gateway-ushin process.
-Only used when `hyperdrive-gateway-process-type' is `subprocess'.")
+ "Hyper-gateway-ushin process.")
+
+(defvar h/install-in-progress-p nil
+ "Non-nil while hyperdrive is installing or upgrading the gateway.")
(defvar-local h/current-entry nil
"Entry for current buffer.")
@@ -403,6 +405,37 @@ Keys are regexps matched against MIME types.")
:desc "Name"))
"Fields for sorting hyperdrive directory buffer columns.")
+(declare-function h//gateway-start-default "hyperdrive-lib")
+(defcustom h/gateway-start-function #'h//gateway-start-default
+ "Function called to start the gateway.
+Should call `hyperdrive--gateway-wait-for-ready' after starting
+the gateway process."
+ :type 'function)
+
+(declare-function h//gateway-stop-default "hyperdrive-lib")
+(defcustom h/gateway-stop-function #'h//gateway-stop-default
+ "Function called to stop the gateway.
+This function should signal an error if it fails to stop the
+gateway process."
+ :type 'function)
+
+(declare-function h//gateway-live-p-default "hyperdrive-lib")
+(defcustom h/gateway-live-predicate #'h//gateway-live-p-default
+ "Predicate function which returns non-nil if the gateway process is live."
+ :type 'function)
+
+(defcustom h/gateway-ready-hook
+ '(h//gateway-after-start-announce)
+ "Hook called when gateway is ready after starting it.
+This hook is called by `hyperdrive--gateway-wait-for-ready' after
+`hyperdrive-start'."
+ :type 'hook)
+
+(defcustom h/gateway-command-args "run --writable true --silent true"
+ "Arguments passed to hyper-gateway-ushin."
+ :type 'string
+ :group 'hyperdrive)
+
;;;; Footer
(provide 'hyperdrive-vars)
diff --git a/hyperdrive.el b/hyperdrive.el
index 9d69b82366..227e1ac0c8 100644
--- a/hyperdrive.el
+++ b/hyperdrive.el
@@ -96,17 +96,20 @@
;;;###autoload
(defun hyperdrive-start ()
"Start `hyper-gateway-ushin' if not already running.
-Customize behavior with `hyperdrive-gateway-process-type', which see."
+Calls function set in option `hyperdrive-gateway-start-function',
+which see."
(interactive)
;; TODO: Verify that the expected version, e.g., 3.7.0, is installed.
- (h//gateway-start))
+ (funcall h/gateway-start-function))
;;;###autoload
(defun hyperdrive-stop ()
"Stop `hyper-gateway-ushin' if running.
-Customize behavior with `hyperdrive-gateway-process-type', which see."
+Calls function set in option `hyperdrive-gateway-stop-function',
+which see."
(interactive)
- (h//gateway-stop))
+ (funcall h/gateway-stop-function)
+ (h/message "Gateway stopped."))
;;;###autoload
(defun hyperdrive-hyper-gateway-ushin-version ()
@@ -816,7 +819,7 @@ The return value of this function is the retrieval buffer."
(defvar h/menu-bar-menu
'(("Gateway"
:label
- (format "Gateway (%s)" (if (h/status) "on" "off"))
+ (format "Gateway (%s)" (if (h//gateway-ready-p) "on" "off"))
["Start Gateway" h/start
:help "Start hyper-gateway-ushin"]
["Stop Gateway" h/stop
@@ -1285,15 +1288,11 @@ Intended for relative (i.e. non-full) URLs."
:sha256 "")))
"Alist mapping `system-type' to URLs where hyper-gateway-ushin can be
downloaded.")
-(defvar h/install-in-progress-p nil
- "Non-nil while hyperdrive is installing or upgrading the gateway.")
-
;;;###autoload
(defun h/install (&optional forcep)
"Install hyper-gateway-ushin.
-Sets `hyperdrive-gateway-process-type' to \\+`subprocess'. If
-FORCEP, force downloading and installing of the expected gateway
-version."
+If FORCEP, force downloading and installing of the expected
+gateway version."
(interactive (list current-prefix-arg))
(when h/install-in-progress-p
(h/error "Installation of gateway already in progress"))
@@ -1335,22 +1334,26 @@ version."
(then file-name)
(try)))
(then (file-name)
- (defvar h/gateway-directory)
- (let ((destination-name (expand-file-name "hyper-gateway-ushin"
h/gateway-directory)))
+ (let ((destination-name
+ (expand-file-name "hyper-gateway-ushin"
h/gateway-directory)))
(when (file-exists-p destination-name)
(move-file-to-trash destination-name))
(unless (file-directory-p h/gateway-directory)
(mkdir h/gateway-directory t))
(rename-file file-name destination-name)
(chmod destination-name #o755))
- ;; NOTE: While `h/gateway-process-type' still exists, this is subtly
- ;; broken, but we will remove that.
- ;; FIXME: Remove `h/gateway-process-type' option.
(setf h/install-in-progress-p nil)
- (if (h//gateway-running-p)
- (when (yes-or-no-p "Installed hyper-gateway-ushin. Restart
gateway?")
- (h/restart))
- (h/message "hyper-gateway-ushin installed. Try
\\[hyperdrive-start]."))))
+ (cond ((h//gateway-live-p)
+ ;; Gateway running inside of Emacs: prompt to restart it.
+ (when (yes-or-no-p "Installed hyper-gateway-ushin. Restart
gateway?")
+ (h/restart)))
+ ((h//gateway-ready-p)
+ ;; Gateway appears to be running outside of Emacs: the user
+ ;; must stop it manually before we can start it.
+ (h/message "New gateway installed but an existing gateway
process is running outside of Emacs; you must manually stop it before the new
version can be started with \\[hyperdrive-start]"))
+ (t
+ ;; Gateway not running: prompt the user to start it.
+ (h/message "hyper-gateway-ushin installed. Try
\\[hyperdrive-start].")))))
(try))))
(defun h/restart ()
@@ -1359,7 +1362,7 @@ version."
(h/message "Restarting gateway...")
(hyperdrive-stop)
(with-timeout (5 (h/message "Timed out waiting for gateway to stop"))
- (cl-loop while (h//gateway-running-p)
+ (cl-loop while (h//gateway-live-p)
do (sleep-for 0.2)))
(hyperdrive-start)
(h/message "Gateway restarted."))