Rewrite the transfert queue #1: Replace several global variables by a single object.

This commit is contained in:
Renaud Casenave-Péré 2011-08-23 14:39:03 +09:00
parent 7b65049e50
commit 730902179e

236
empc.el
View file

@ -54,25 +54,62 @@
string) string)
:group 'empc-debug) :group 'empc-debug)
(defvar empc-process nil) (defconst empc-buffer-name "*empc-process*"
(defvar empc-stream-process nil) "The name of the buffer receiving output from server.")
(defvar empc-queue nil)
(defvar empc-idle-state nil)
(defvar empc-available-commands nil)
(defvar empc-last-crossfade nil)
(defvar empc-current-status nil)
(defvar empc-current-playlist nil)
(defvar empc-current-playlist-songs nil)
(defvar empc-mode-line-string "")
(defvar empc-may-pulse nil)
(when (require 'pulse nil t)
(setq empc-may-pulse t))
(defconst empc-response-regexp (defconst empc-response-regexp
"^\\(OK\\( MPD \\)?\\|ACK \\[\\([0-9]+\\)@[0-9]+\\] \\(.+\\)\\)\n+\\'" "^\\(OK\\( MPD \\)?\\|ACK \\[\\([0-9]+\\)@[0-9]+\\] \\(.+\\)\\)\n+\\'"
"Regexp that matches the valid status strings that MusicPD can "Regexp that matches the valid status strings that MusicPD can
return at the end of a request.") return at the end of a request.")
(defvar empc-object nil
"Structure containing data for empc as a Property list.
The structure has the following fields:
:process keeps the network-stream object.
:queue stores the commands to send to the server as well as the
function to call when receiving the response, and a closure to
pass to the function together with the response.
This structure has the following type:
(command closure . fn).
:stream keeps the process playing the stream.
:available-commands keeps the commands available to the
user. There is no need to send a command to the server if we
know already this command is unavailable due to restricted
permissions or because the server has an older version.
:status is another plist describing the server status.
:playlist-songs is a hash table storing informations about the songs
currently in the playlist.
:playlist is a vector of song ids, keeping the order of the songs
in the playlist.")
;; Accessors for the empc object.
(defun empc-process (object) (plist-get object :process))
(defun empc-queue (object) (plist-get object :queue))
(defun empc-stream (object) (plist-get object :stream))
(defun empc-available-commands (object) (plist-get object :available-commands))
(defun empc-status (object) (plist-get object :status))
(defun empc-playlist-songs (object) (plist-get object :playlist-songs))
(defun empc-playlist (object) (plist-get object :playlist))
(defun empc-status-get (object attr) (plist-get (empc-status object) attr))
(defun empc-current-song (object) (gethash (empc-status-get object :songid) (empc-playlist-songs object)))
(defvar empc-idle-state nil)
(defvar empc-last-crossfade nil)
(defvar empc-mode-line-string "")
(defvar empc-may-pulse nil)
(when (require 'pulse nil t)
(setq empc-may-pulse t))
(defconst empc-playlist-map (make-keymap) "Keymap for `empc'") (defconst empc-playlist-map (make-keymap) "Keymap for `empc'")
(define-key empc-playlist-map "q" 'empc-bury-buffers) (define-key empc-playlist-map "q" 'empc-bury-buffers)
(define-key empc-playlist-map "Q" 'empc-quit) (define-key empc-playlist-map "Q" 'empc-quit)
@ -100,12 +137,13 @@ return at the end of a request.")
(defun empc-stream-process-sentinel (proc event) (defun empc-stream-process-sentinel (proc event)
"Process sentinel for `empc-stream-process'." "Process sentinel for `empc-stream-process'."
(when (and (eq (process-status proc) 'exit) (let ((process (empc-process empc-object)))
empc-process (when (and (eq (process-status proc) 'exit)
(processp empc-process) process
(eq (process-status empc-process) 'open) (processp process)
(eq (plist-get empc-current-status :state) 'play)) (eq (process-status process) 'open)
(empc-toggle-pause 1)) (eq (empc-status-get empc-object :state) 'play))
(empc-toggle-pause 1)))
(setq empc-stream-process nil)) (setq empc-stream-process nil))
(defun empc-echo-minibuffer (msg) (defun empc-echo-minibuffer (msg)
@ -125,9 +163,9 @@ return at the end of a request.")
"Notify SONG in the echo area." "Notify SONG in the echo area."
(interactive) (interactive)
(unless song (unless song
(setq song (gethash (plist-get empc-current-status :songid) empc-current-playlist-songs))) (setq song (empc-current-song empc-object)))
(empc-echo-notify (concat "[" (int-to-string (+ (plist-get song :pos) 1)) (empc-echo-notify (concat "[" (int-to-string (+ (plist-get song :pos) 1))
"/" (int-to-string (plist-get empc-current-status :playlistlength)) "] " "/" (int-to-string (empc-status-get empc-object :playlistlength)) "] "
(if (and (plist-get song :artist) (plist-get song :title)) (if (and (plist-get song :artist) (plist-get song :title))
(concat (plist-get song :artist) " - " (plist-get song :title)) (concat (plist-get song :artist) " - " (plist-get song :title))
(plist-get song :file))))) (plist-get song :file)))))
@ -140,9 +178,9 @@ return at the end of a request.")
(defun empc-mode-line-song (&optional song) (defun empc-mode-line-song (&optional song)
"Notify SONG in the mode-line." "Notify SONG in the mode-line."
(unless song (unless song
(setq song (gethash (plist-get empc-current-status :songid) empc-current-playlist-songs))) (setq song (empc-current-song empc-object)))
(empc-mode-line-notify (concat "[" (int-to-string (+ (plist-get song :pos) 1)) (empc-mode-line-notify (concat "[" (int-to-string (+ (plist-get song :pos) 1))
"/" (int-to-string (plist-get empc-current-status :playlistlength)) "] " "/" (int-to-string (empc-status-get empc-object :playlistlength)) "] "
(if (and (plist-get song :artist) (plist-get song :title)) (if (and (plist-get song :artist) (plist-get song :title))
(concat (plist-get song :artist) " - " (plist-get song :title)) (concat (plist-get song :artist) " - " (plist-get song :title))
(plist-get song :file))))) (plist-get song :file)))))
@ -182,9 +220,10 @@ form '('error (error-code . error-message))."
(defun empc-response-get-commands (data) (defun empc-response-get-commands (data)
"Parse DATA to get the available commands." "Parse DATA to get the available commands."
(setq empc-available-commands nil) (let ((available-commands))
(dolist (cell data) (dolist (cell data)
(setq empc-available-commands (cons (cdr cell) empc-available-commands)))) (setq available-commands (cons (cdr cell) available-commands)))
(setq empc-object (plist-put empc-object :available-commands available-commands))))
(defun empc-status-on/off-stringify (status key) (defun empc-status-on/off-stringify (status key)
"Return `on' or `off' if KEY is active or inactive in STATUS." "Return `on' or `off' if KEY is active or inactive in STATUS."
@ -199,15 +238,15 @@ According to what is in the diff, several actions can be performed:
(let ((status-diff (empc-diff-status data)) (let ((status-diff (empc-diff-status data))
(notify nil)) (notify nil))
(when (plist-get status-diff :songid) (when (plist-get status-diff :songid)
(setq notify '(lambda () (when empc-current-playlist-songs (setq notify '(lambda () (when (empc-playlist-songs empc-object)
(empc-mode-line-song (gethash (plist-get status-diff :songid) (empc-mode-line-song (gethash (plist-get status-diff :songid)
empc-current-playlist-songs))))) (empc-playlist-songs empc-object))))))
(empc-playlist-goto-current-song)) (empc-playlist-goto-current-song))
(when (plist-get status-diff :state) (when (plist-get status-diff :state)
(if (eq (plist-get status-diff :state) 'play) (if (eq (plist-get status-diff :state) 'play)
(progn (progn
(unless notify (unless notify
(setq notify '(lambda () (when empc-current-playlist-songs (setq notify '(lambda () (when (empc-playlist-songs empc-object)
(empc-mode-line-song))))) (empc-mode-line-song)))))
(empc-stream-start)) (empc-stream-start))
(setq notify '(lambda () (empc-mode-line-notify (symbol-name (plist-get status-diff :state))))))) (setq notify '(lambda () (empc-mode-line-notify (symbol-name (plist-get status-diff :state)))))))
@ -215,11 +254,11 @@ According to what is in the diff, several actions can be performed:
(plist-member status-diff :single) (plist-member status-diff :consume) (plist-member status-diff :single) (plist-member status-diff :consume)
(plist-member status-diff :xfade)) (plist-member status-diff :xfade))
(setq notify '(lambda () (empc-echo-notify (format "repeat: %s, random: %s, single: %s, consume: %s, crossfade: %s" (setq notify '(lambda () (empc-echo-notify (format "repeat: %s, random: %s, single: %s, consume: %s, crossfade: %s"
(empc-status-on/off-stringify empc-current-status :repeat) (empc-status-on/off-stringify (empc-status empc-object) :repeat)
(empc-status-on/off-stringify empc-current-status :random) (empc-status-on/off-stringify (empc-status empc-object) :random)
(empc-status-on/off-stringify empc-current-status :single) (empc-status-on/off-stringify (empc-status empc-object) :single)
(empc-status-on/off-stringify empc-current-status :consume) (empc-status-on/off-stringify (empc-status empc-object) :consume)
(empc-status-on/off-stringify empc-current-status :xfade)))))) (empc-status-on/off-stringify (empc-status empc-object) :xfade))))))
(when notify (when notify
(funcall notify)))) (funcall notify))))
@ -227,7 +266,7 @@ According to what is in the diff, several actions can be performed:
"Parse a single attribute from status." "Parse a single attribute from status."
(cond (cond
((eq value nil) nil) ((eq value nil) nil)
((member attr '(:volume :repeat :random :single :consume :playlist :playlistlength ((memq attr '(:volume :repeat :random :single :consume :playlist :playlistlength
:song :songid :nextsong :nextsongid :bitrate :xfade :mixrampdb :song :songid :nextsong :nextsongid :bitrate :xfade :mixrampdb
:mixrampdelay :updating_db)) :mixrampdelay :updating_db))
(string-to-number value)) (string-to-number value))
@ -241,14 +280,17 @@ According to what is in the diff, several actions can be performed:
(defun empc-diff-status (data) (defun empc-diff-status (data)
"Get the diff from `empc-current-status' and server response." "Get the diff from `empc-current-status' and server response."
(let ((status-diff nil) (let ((status-diff nil)
(new-status (empc-status empc-object))
(attributes '(:volume :repeat :random :single :consume :playlist :playlistlength :state (attributes '(:volume :repeat :random :single :consume :playlist :playlistlength :state
:song :songid :nextsong :nextsongid :time :elapsed :bitrate :xfade :song :songid :nextsong :nextsongid :time :elapsed :bitrate :xfade
:mixrampdb :mixrampdelay :audio :updating_db :error))) :mixrampdb :mixrampdelay :audio :updating_db :error)))
(dolist (attr attributes status-diff) (prog1
(let ((value (empc-parse-status-attr attr (cdr (assoc (substring (symbol-name attr) 1) data))))) (dolist (attr attributes status-diff)
(unless (equal (plist-get empc-current-status attr) value) (let ((value (empc-parse-status-attr attr (cdr (assoc (substring (symbol-name attr) 1) data)))))
(setq status-diff (plist-put status-diff attr value)) (unless (equal (empc-status-get empc-object attr) value)
(setq empc-current-status (plist-put empc-current-status attr value))))))) (setq status-diff (plist-put status-diff attr value))
(setq new-status (plist-put new-status attr value)))))
(setq empc-object (plist-put empc-object :status new-status)))))
(defun empc-playlist-goto-current-song () (defun empc-playlist-goto-current-song ()
"Put point at currently playing song." "Put point at currently playing song."
@ -262,14 +304,14 @@ According to what is in the diff, several actions can be performed:
(when bwindow (when bwindow
(with-selected-window bwindow (with-selected-window bwindow
(goto-char (point-min)) (goto-char (point-min))
(forward-line (plist-get empc-current-status :song)) (forward-line (empc-status-get empc-object :song))
(when (and (not buffer) empc-may-pulse) (when (and (not buffer) empc-may-pulse)
(pulse-momentary-highlight-one-line (point)))) (pulse-momentary-highlight-one-line (point))))
(setq buffer bwindow)))))) (setq buffer bwindow))))))
(unless buffer (unless buffer
(with-current-buffer "*empc*" (with-current-buffer "*empc*"
(goto-char (point-min)) (goto-char (point-min))
(forward-line (plist-get empc-current-status :song)) (forward-line (empc-status-get empc-object :song))
(when (and (called-interactively-p) empc-may-pulse) (when (and (called-interactively-p) empc-may-pulse)
(pulse-momentary-highlight-one-line (point)))))))) (pulse-momentary-highlight-one-line (point))))))))
@ -279,9 +321,9 @@ According to what is in the diff, several actions can be performed:
(empc-switch-to-playlist) (empc-switch-to-playlist)
(let ((buffer-read-only nil)) (let ((buffer-read-only nil))
(erase-buffer) (erase-buffer)
(when empc-current-playlist-songs (when (empc-playlist-songs empc-object)
(dotimes (pos (length empc-current-playlist)) (dotimes (pos (length (empc-playlist empc-object)))
(let ((song (gethash (elt empc-current-playlist pos) empc-current-playlist-songs))) (let ((song (gethash (elt (empc-playlist empc-object) pos) (empc-playlist-songs empc-object))))
(insert (if (and (plist-member song :artist) (plist-member song :title)) (insert (if (and (plist-member song :artist) (plist-member song :title))
(concat (plist-get song :artist) " - " (plist-get song :title)) (concat (plist-get song :artist) " - " (plist-get song :title))
(plist-get song :file)) "\n")))))) (plist-get song :file)) "\n"))))))
@ -291,26 +333,29 @@ According to what is in the diff, several actions can be performed:
"Parse information regarding songs in current playlist and arrange it into a "Parse information regarding songs in current playlist and arrange it into a
hash table `empc-current-playlist-songs'sorted by songid. hash table `empc-current-playlist-songs'sorted by songid.
songs order is kept into an avector `empc-current-playlist'." songs order is kept into an avector `empc-current-playlist'."
(setq empc-current-playlist-songs (make-hash-table :rehash-threshold 1.0 :size (plist-get empc-current-status :playlistlength))) ;; (setq empc-current-playlist-songs (make-hash-table :rehash-threshold 1.0 :size (plist-get empc-current-status :playlistlength)))
(setq empc-current-playlist (make-vector (plist-get empc-current-status :playlistlength) nil)) ;; (setq empc-current-playlist (make-vector (plist-get empc-current-status :playlistlength) nil))
(let ((song nil) (let* ((playlist-songs (make-hash-table :rehash-threshold 1.0 :size (empc-status-get empc-object :playlistlength)))
(index (- (length empc-current-playlist) 1))) (playlist (make-vector (empc-status-get empc-object :playlistlength) nil))
(song nil)
(index (- (length playlist) 1)))
(dolist (cell data) (dolist (cell data)
(let ((field (intern (concat ":" (car cell))))) (let ((field (intern (concat ":" (car cell)))))
(when (and (eq field :id) song) (when (and (eq field :id) song)
(puthash (plist-get song :id) song empc-current-playlist-songs) (puthash (plist-get song :id) song playlist-songs)
(aset empc-current-playlist index (plist-get song :id)) (aset playlist index (plist-get song :id))
(setq song nil) (setq song nil)
(decf index)) (decf index))
(cond (cond
((member field '(:time :track :date :pos :id)) ((memq field '(:time :track :date :pos :id))
(setq song (plist-put song field (string-to-number (cdr cell))))) (setq song (plist-put song field (string-to-number (cdr cell)))))
(t (if (plist-get song field) (t (if (plist-get song field)
(setq song (plist-put song field (concat (plist-get song field) ", " (cdr cell)))) (setq song (plist-put song field (concat (plist-get song field) ", " (cdr cell))))
(setq song (plist-put song field (cdr cell)))))))) (setq song (plist-put song field (cdr cell))))))))
(when (and song (>= index 0)) (when (and song (>= index 0))
(puthash (plist-get song :id) song empc-current-playlist-songs) (puthash (plist-get song :id) song playlist-songs)
(aset empc-current-playlist index (plist-get song :id)))) (aset playlist index (plist-get song :id)))
(setq empc-object (plist-put (plist-put empc-object :playlist playlist) :playlist-songs playlist-songs)))
(empc-populate-playlist-buffer)) (empc-populate-playlist-buffer))
(defun empc-response-idle (data) (defun empc-response-idle (data)
@ -330,7 +375,7 @@ songs order is kept into an avector `empc-current-playlist'."
"If CLOSURES is a list of function, call them in turn with DATA "If CLOSURES is a list of function, call them in turn with DATA
as parameter." as parameter."
(when closures (when closures
(if (and (listp closures) (not (member (car closures) '(quote lambda)))) (if (and (listp closures) (not (memq (car closures) '(quote lambda))))
(dolist (closure closures (reverse notifications)) (dolist (closure closures (reverse notifications))
(funcall closure data)) (funcall closure data))
(funcall closures data)))) (funcall closures data))))
@ -378,17 +423,18 @@ Send the password or retrieve available commands."
(defun empc-ensure-connected () (defun empc-ensure-connected ()
"Make sure empc is connected and ready to talk to mpd." "Make sure empc is connected and ready to talk to mpd."
(unless (and empc-process (let ((process (empc-process empc-object)))
(processp empc-process) (unless (and process
(eq (process-status empc-process) 'open)) (processp process)
(setq empc-process (open-network-stream "empc" empc-buffer-name empc-server-host empc-server-port)) (eq (process-status process) 'open))
(set-process-sentinel empc-process 'empc-process-sentinel) (setq process (open-network-stream "empc" empc-buffer-name empc-server-host empc-server-port))
(if (fboundp 'set-process-query-on-exit-flag) (set-process-sentinel process 'empc-process-sentinel)
(set-process-query-on-exit-flag empc-process nil) (if (fboundp 'set-process-query-on-exit-flag)
(process-kill-without-query empc-process)) (set-process-query-on-exit-flag process nil)
(set-process-coding-system empc-process 'utf-8-unix 'utf-8-unix) (process-kill-without-query process))
(setq empc-queue (tq-create empc-process)) (set-process-coding-system process 'utf-8-unix 'utf-8-unix)
(empc-initialize))) (setq empc-object (plist-put (plist-put empc-object :process process) :queue (tq-create process)))
(empc-initialize))))
(defun empc-bury-buffers () (defun empc-bury-buffers ()
"Bury all empc related buffers." "Bury all empc related buffers."
@ -399,24 +445,21 @@ Send the password or retrieve available commands."
(defun empc-quit () (defun empc-quit ()
"Close connection between empc and mpd." "Close connection between empc and mpd."
(interactive) (interactive)
(when (and empc-process (let ((process (empc-process empc-object)))
(processp empc-process) (when (and process
(eq (process-status empc-process) 'open)) (processp process)
(empc-leave-idle-state) (eq (process-status process) 'open))
(empc-send-close)) (empc-leave-idle-state)
(when empc-queue (empc-send-close)))
(tq-close empc-queue)) (when (empc-queue empc-object)
(tq-close (empc-queue empc-object)))
(empc-mode-line nil) (empc-mode-line nil)
(when (get-buffer "*empc*") (when (get-buffer "*empc*")
(kill-buffer "*empc*")) (kill-buffer "*empc*"))
(setq empc-process nil (setq empc-object nil
empc-queue nil
empc-idle-state nil empc-idle-state nil
empc-available-commands nil empc-last-crossfade nil))
empc-last-crossfade nil
empc-current-status nil
empc-current-playlist-songs nil
empc-current-playlist nil))
(defun empc () (defun empc ()
"Emacs MPC (not really the most original name, but oh well…)." "Emacs MPC (not really the most original name, but oh well…)."
@ -428,14 +471,14 @@ Send the password or retrieve available commands."
"If not already in idle state and there is no other commands pending, "If not already in idle state and there is no other commands pending,
enter idle state to accept notifications from the server." enter idle state to accept notifications from the server."
(unless (or empc-idle-state (unless (or empc-idle-state
(cdr (tq-queue empc-queue))) (cdr (tq-queue (empc-queue empc-object))))
(empc-send-idle) (empc-send-idle)
(setq empc-idle-state t))) (setq empc-idle-state t)))
(defun empc-leave-idle-state () (defun empc-leave-idle-state ()
"If in idle state, regain control." "If in idle state, regain control."
(when empc-idle-state (when empc-idle-state
(process-send-string empc-process "noidle\n") (process-send-string (empc-process empc-object) "noidle\n")
(setq empc-idle-state nil))) (setq empc-idle-state nil)))
(defun empc-send (command &optional closure handler) (defun empc-send (command &optional closure handler)
@ -445,7 +488,7 @@ CLOSURE will be called on the parsed response."
(empc-leave-idle-state) (empc-leave-idle-state)
(unless (string= (substring command -1) "\n") (unless (string= (substring command -1) "\n")
(setq command (concat command "\n"))) (setq command (concat command "\n")))
(tq-enqueue empc-queue command empc-response-regexp (tq-enqueue (empc-queue empc-object) command empc-response-regexp
closure (if handler handler 'empc-handle-response) t)) closure (if handler handler 'empc-handle-response) t))
(defun empc-send-list (&rest commands) (defun empc-send-list (&rest commands)
@ -463,10 +506,12 @@ where CLOSURE (may be a list of functions) will be called on the parsed response
(defun empc-stream-start () (defun empc-stream-start ()
"Start the stream process if the command to mpd returned successfully. "Start the stream process if the command to mpd returned successfully.
If the stream process is killed for whatever the reason, pause mpd if possible." If the stream process is killed for whatever the reason, pause mpd if possible."
(when (and (not empc-stream-process) (let ((stream-process (empc-stream empc-object)))
empc-stream-url empc-stream-program) (when (and (not stream-process)
(setq empc-stream-process (start-process "empc-stream" nil empc-stream-program empc-stream-url)) empc-stream-url empc-stream-program)
(set-process-sentinel empc-stream-process 'empc-stream-process-sentinel))) (setq stream-process (start-process "empc-stream" nil empc-stream-program empc-stream-url))
(set-process-sentinel stream-process 'empc-stream-process-sentinel)
(setq empc-object (plist-put empc-object :stream stream-process)))))
(defun empc-playlist-mode () (defun empc-playlist-mode ()
"empc playlist mode." "empc playlist mode."
@ -486,7 +531,7 @@ If the stream process is killed for whatever the reason, pause mpd if possible."
(defmacro with-updated-status (&rest body) (defmacro with-updated-status (&rest body)
"Update the status and execute the forms in BODY." "Update the status and execute the forms in BODY."
`(if empc-current-status `(if (empc-status empc-object)
,@body ,@body
(empc-send "status\n" '(empc-response-get-status (lambda (data) ,@body))))) (empc-send "status\n" '(empc-response-get-status (lambda (data) ,@body)))))
@ -510,9 +555,9 @@ If the stream process is killed for whatever the reason, pause mpd if possible."
(with-updated-status (with-updated-status
(let ((,(if attr attr (let ((,(if attr attr
(intern command)) (intern command))
(plist-get empc-current-status (quote ,(intern (concat ":" (if state-name (empc-status-get empc-object (quote ,(intern (concat ":" (if state-name
state-name state-name
command))))))) command)))))))
,(if body ,(if body
`(progn ,@body) `(progn ,@body)
`(empc-send (concat ,command (if (= ,(if attr attr `(empc-send (concat ,command (if (= ,(if attr attr
@ -528,7 +573,7 @@ computed using point in buffer."
(empc-leave-idle-state) (empc-leave-idle-state)
(unless pos (unless pos
(setq pos (count-lines (point-min) (point)))) (setq pos (count-lines (point-min) (point))))
(let ((id (elt empc-current-playlist pos))) (let ((id (elt (empc-playlist empc-object) pos)))
(empc-send (concat ,(concat command "id ") (number-to-string id) "\n") ,closure)))) (empc-send (concat ,(concat command "id ") (number-to-string id) "\n") ,closure))))
(defmacro empc-define-command-with-current-id (command &optional closure) (defmacro empc-define-command-with-current-id (command &optional closure)
@ -538,7 +583,7 @@ computed using point in buffer."
(interactive) (interactive)
(empc-leave-idle-state) (empc-leave-idle-state)
(empc-send (concat ,(concat command "id ") (empc-send (concat ,(concat command "id ")
(number-to-string (plist-get empc-current-status :songid)) (number-to-string (empc-status-get empc-object :songid))
(when arg (concat " " (if (stringp arg) arg (number-to-string arg)))) "\n") (when arg (concat " " (if (stringp arg) arg (number-to-string arg)))) "\n")
,closure))) ,closure)))
@ -600,10 +645,7 @@ computed using point in buffer."
;; Audio output devices ;; Audio output devices
;; Reflection ;; Reflection
(empc-define-simple-command "commands" '(lambda (data) (empc-define-simple-command "commands" 'empc-response-get-commands)
(setq empc-available-commands nil)
(dolist (cell data)
(setq empc-available-commands (cons (cdr cell) empc-available-commands)))))
;; Client to client ;; Client to client