/external/libmpdee.el
Emacs Lisp | 1639 lines | 1288 code | 169 blank | 182 comment | 127 complexity | e164f5f7bef8f40a413994ec251ad7e7 MD5 | raw file
Possible License(s): LGPL-2.0, GPL-3.0, GPL-2.0
- ;;; LIBMPDEE.EL --- Client end library for mpd, a music playing daemon
- ;; Copyright (C) 2004, 2005, 2006 R.Ramkumar
- ;; Author: R.Ramkumar <andyetitmoves@gmail.com>
- ;; Created: 10 May 2004
- ;; Version: 2.1
- ;; Keywords: music, mpd
- ;; This file is *NOT* part of GNU Emacs
- ;; This program is free software; you can redistribute it and/or modify
- ;; it under the terms of the GNU General Public License as published by
- ;; the Free Software Foundation; either version 2, or (at your option)
- ;; any later version.
- ;; This program is distributed in the hope that it will be useful,
- ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;; GNU General Public License for more details.
- ;; A copy of the GNU General Public License can be obtained from this
- ;; program's author (send electronic mail to andyetitmoves@gmail.com)
- ;; or from the Free Software Foundation, Inc.,
- ;; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- ;;; Commentary:
- ;; This package is a client end library to the wonderful music playing daemon by
- ;; name mpd. Hence, an obvious prerequisite for this package is mpd itself,
- ;; which you can get at http://www.musicpd.org. For those who haven't heard of
- ;; mpd, all I can say is that it is definitely worth a try, and is surely
- ;; different from the rest. This package is aimed at developers, and though
- ;; there are some interactive functions, much of the power of the library lies
- ;; away from the user.
- ;; This package started off to implement libmpdclient.c, which came with mpd, in
- ;; elisp. But as it stands of now, this package is not an exact translation.
- ;; Notable amongst the deviations are -
- ;;
- ;; - This package contains quite a bit of higher level functionality as
- ;; compared with the original. An action or a query needs only a single
- ;; call and the library user can choose to get either the raw response, the
- ;; formatted response, or hook on a callback for each logical token of the
- ;; response. However, dig deeper, and you will find the lower level
- ;; functionality available as well.
- ;; - The error throwing scheme consistent with what is expected of elisp
- ;; programs.
- ;; - Command list mode is transparent in most cases, as wherever
- ;; appropriate, functions taking arguments can accept either a single item
- ;; or a list of it for each argument.
- ;; - Apart from this, command list functionality is limited to actions
- ;; rather than queries, as it is anyway not that easy to parse out the
- ;; individual responses from command-list queries (it is firstly possible
- ;; only from 0.11, which allows for a list_OK to be added at the end of
- ;; response for each command in the list).
- ;; - command_list_ok_begin isn't implemented. It is still possible to
- ;; explicitly use "command_list_(ok)begin\n..\ncommand_list_end" for
- ;; `mpd-execute-command' and get the response tokens for queries. A better
- ;; interface may be available in the future.
- ;; - There is a small interactive interface as well, but this is
- ;; intentionally incomplete. The point is that this exists only to the
- ;; extent of adding an interactive part to the functions, without modifying
- ;; the functions per se.
- ;; Most of the functions below require a connection object as an argument, which
- ;; you can create by a call to `mpd-conn-new'. The recommended way to use
- ;; customization to choose parameters for mpd connections is to use the widget
- ;; `mpd-connection'.
- ;; As this package caters to developers, it should be helpful to browse the file
- ;; in order for atleast the functions and the documentation. The file is well
- ;; structured and documented, so go for it. The impatient could do a selective
- ;; display to 3 (C-u 3 C-x $) before proceeding.
- ;;; Installation:
- ;; Put this file somewhere on your load-path. Then, you could use
- ;; (require 'libmpdee) whenever the services of this package are needed.
- ;; Parameters used for the interactive calls can be customized in the group mpd
- ;; Use:
- ;; M-x customize-group mpd
- ;; to change the values to your liking.
- ;;; History: (See the SVN logs/ChangeLog for the list of all changes)
- ;; v2.1
- ;; Introducing automatic mode with hooking, for connections.
- ;; See `mpd-set-automatic-mode'.
- ;; v2.0
- ;; The interface has changed since version 1 of this library and there is no
- ;; backward compatibility. This change applies to functions which returned
- ;; vector whose descriptions were given `*-data' variables. Such functions have
- ;; been modified to return property lists, whose keys now describe the values
- ;; returned. This should hopefully be a much more scalable representation.
- ;;; Code:
- (defvar libmpdee-version "2.1"
- "libmpdee version information.")
- ;;;; User serviceable variable(s).
- (require 'custom)
- (require 'wid-edit)
- (defun widget-mpd-format-handler (widget esc)
- "Widget format handler for the MPD connection widget."
- (cond
- ((eq esc ?l)
- (widget-create-child-and-convert widget 'documentation-string "\
- This variable specifies a MPD connection.
- The value is a list of the mpd host name, port number, and the timeout for
- server replies. See `mpd-conn-new' for more details.")
- (insert "To know more about libmpdee, read ")
- (widget-create 'emacs-commentary-link :tag "this" "libmpdee"))
- (t (funcall (widget-get (widget-convert 'lazy) :format-handler)
- widget esc))))
- (defun mpd-connection-tidy (val)
- "Initialize unset parameters for a MPD connection.
- Replace parameters in the MPD connection VAL with sane defaults and return."
- (or val (setq val '(nil nil nil)))
- (let ((val val))
- (and (listp val)
- (or (car val)
- (setcar val (or (getenv "MPD_HOST") "localhost")))
- (setq val (cdr val))
- (or (car val) (setcar val 6600))
- (setq val (cdr val))
- (or (car val) (setcar val 10.0)))) val)
- (define-widget 'mpd-connection 'lazy
- "A widget for a MPD connection."
- :tag "MPD Connection"
- :format "%{%t%}:\n\n%l\n\n%v"
- :format-handler 'widget-mpd-format-handler
- :value-to-internal '(lambda (wid val) (mpd-connection-tidy val))
- :type '(list :format "%v"
- (string :format "%t: %v\n" :tag "Hostname" :size 15)
- (integer :format "%t: %v\n" :tag "Port" :size 5
- :match (lambda (widget value) (> value 0))
- :type-error "Port must be a natural number")
- (float :format "%t: %v\n\n" :tag "Timeout" :size 10
- :match (lambda (widget value) (> value 0))
- :type-error "Timeout must be a positive number")))
- (defgroup mpd nil
- "The client end library for MPD, the music playing daemon."
- :group 'external :group 'multimedia
- :link '(emacs-commentary-link "libmpdee"))
- (defcustom mpd-db-root (getenv "MPD_DB_ROOT")
- "*MPD database directory root."
- :type 'directory :group 'mpd)
- (defcustom mpd-interactive-connection-parameters (mpd-connection-tidy nil)
- "Parameters for the interactive mpd connection.
- These determine the connection used by interactive functions in `libmpdee'."
- :type 'mpd-connection :group 'mpd)
- (defface mpd-separator-face '((((background dark)) (:foreground "lightyellow"))
- (((background light)) (:foreground "darkgreen")))
- "Face for display of separator lines in interactive mpd queries."
- :group 'mpd)
- (defface mpd-header-face '((((background dark)) (:foreground "gold"))
- (((background light)) (:foreground "brown")))
- "Face for display of header lines in interactive mpd queries."
- :group 'mpd)
- (defface mpd-first-field-face '((((background dark)) (:foreground "cyan"))
- (((background light)) (:foreground "orange")))
- "Face for display of the first field in interactive mpd queries.
- Most lines in interactive displays are split into two fields."
- :group 'mpd)
- (defface mpd-second-field-face
- '((((background dark)) (:foreground "lightgreen"))
- (((background light)) (:foreground "blue")))
- "Face for display of the second field in interactive mpd queries.
- Most lines in interactive displays are split into two fields."
- :group 'mpd)
- ;;;; Constants and internal variables.
- (eval-and-compile (defconst mpd-welcome-message " MPD "))
- (defmacro mpd-welcome-length () (length mpd-welcome-message))
- (defconst mpd-ver-string-length 3)
- ;;;; Package independent helper functions.
- (defmacro mpd-assert-type (obj func)
- "Ensure that OBJ succeeds on type checking with predicate FUNC.
- The function emits a \"Wrong type argument\" signal on failure.
- Note that the arguments are evalled twice in this process."
- `(or (,func ,obj) (signal 'wrong-type-argument (list (quote ,func) ,obj))))
- (defmacro mpd-assert-string (obj) `(mpd-assert-type ,obj stringp))
- (defmacro mpd-assert-wholenump (obj) `(mpd-assert-type ,obj wholenump))
- (defmacro mpd-assert-numberp (obj) `(mpd-assert-type ,obj numberp))
- (defun mpd-string-to-number-strict (str &optional allowneg)
- "Convert string STR to a number strictly.
- Return nil if there are any unmatched characters.
- Allow negative numbers if ALLOWNEG is non-nil."
- (let ((num (string-to-number str)))
- (and (if allowneg (numberp num) (wholenump num))
- (string-equal str (number-to-string num)) num)))
- (put 'mpd-string-to-number-strict 'side-effect-free t)
- (defun mpd-get-lines (str)
- "Split STR into newline separated lines.
- Differ from `split-string' in that tokens are created
- for leading and trailing newlines."
- (let ((packets (split-string str "\n")))
- (when packets
- (if (= (aref str 0) ?\n)
- (setq packets (cons "" packets)))
- (if (= (aref str (1- (length str))) ?\n)
- (nconc packets (list ""))
- packets))))
- ;;; Modified from the pcomplete package.
- (defun mpd-sort-uniq-list (l lessp eqp)
- "Sort and remove multiples in list L.
- Use LESSP and EQP as predicates for the \"lesser\" and \"equal\" operations."
- (setq l (sort l lessp))
- (let ((m l))
- (while m
- (while (and (cdr m) (funcall eqp (car m) (cadr m)))
- (setcdr m (cddr m)))
- (setq m (cdr m)))) l)
- ;;; For edebug macro specifications.
- (eval-when-compile (require 'edebug))
- (defmacro with-mpd-temp-widen (&rest args)
- "Evaluate ARGS while temporarily widening the current buffer."
- `(save-restriction
- (widen)
- ,@args))
- (put 'with-mpd-temp-widen 'lisp-indent-function 0)
- (def-edebug-spec with-mpd-temp-widen (body))
- (defmacro with-mpd-free-buffer (&rest args)
- "Evaluate ARGS with temporary widening and saved excursion."
- `(save-excursion
- (with-mpd-temp-widen ,@args)))
- (put 'with-mpd-free-buffer 'lisp-indent-function 0)
- (def-edebug-spec with-mpd-free-buffer (body))
- (defmacro mpd-safe-nreverse (list)
- "Reverse LIST if it is a list, leave alone otherwise.
- Note that LIST is evaluated thrice."
- `(if (listp ,list) (nreverse ,list) ,list))
- (defun mpd-seq-add (seq &optional spec &rest args)
- "Operate the sequence SEQ on SPEC depending on its type.
- Add a copy of SEQ to SPEC, if it's a list and call it with SEQ as an argument
- followed by other ARGS specified, if it is a function. Return SPEC."
- (if (not (functionp spec))
- (cons (copy-sequence seq) spec)
- (apply spec seq args) spec))
- (defun mpd-elt-add (elt &optional spec &rest args)
- "Operate the object ELT on SPEC depending on its type.
- Add ELT to SPEC, if it's a list and call it with ELT as an argument
- followed by other ARGS specified, if it is a function. Return SPEC."
- (if (not (functionp spec))
- (cons elt spec)
- (apply spec elt args) spec))
- (defun mpd-parse-line (str)
- "Parse line STR of form \"KEY: VALUE\" to a cons (KEY . VALUE).
- Return (STR . nil) on a parse failure."
- (if (string-match "^\\([^:]*\\): ?\\(.*\\)$" str)
- (cons (match-string 1 str) (match-string 2 str))
- (cons str nil)))
- (defun mpd-safe-string (str)
- "Quote and escape string STR for sending to the mpd server."
- (if str
- (let ((start 0))
- (while (string-match "[\\\"]" str start)
- ;; We add an extra character,
- ;; so place start a character beyond end of match.
- (setq start (1+ (match-end 0)))
- (setq str (replace-match "\\\\\\&" t nil str)))
- (if (string-match " " str) (concat "\"" str "\"") str))))
- ;;; (defun mpd-log (fmt &rest args)
- ;;; (write-region (concat (apply 'format fmt args) "\n") nil "~/mpd.log" t 1))
- ;;; (write-region "" nil "~/mpd.log" nil 1)
- ;;;; Connection object internals, library users... please close ur eyes ;)
- ;;; A connection to mpd is represented by the vector whose elements are below:
- ;;; This is just for the hackers amongst you, and for my reference :)
- ;;; *WARNING* No program using this package should depend on this description.
- ;;; 0 : vector of mpd-ver-string-length version numbers
- ;;; 1 : process object for the connection to the server.
- ;;; 2 : transaction-buffer used by the process filter to handle partial recvs.
- ;;; 3 : list-mode - refer `mpd-execute-command'
- ;;; 4 : connection-status: should be t at the beginning of a command.
- ;;; stringp -> Result of command, contains the message after OK/ACK
- ;;; till the next end of line. No filtering occurs after this
- ;;; stage is reached.
- ;;; numberp -> Intermediate state, when OK/ACK has been got
- ;;; but no end of line found. Then store start pos of the OK/ACK.
- ;;; Hope that eol will come in a subsequent packet.
- ;;; listp -> In commandlist mode, the list is what is to be sent to the
- ;;; server after the commandlist gets over.
- ;;; 5 : last-command-result-flag - t if OK, nil if ACK.
- ;;; 6 : timeout - The timeout used for replies from server.
- ;;; 7 : host - The name of the host used to connect to mpd.
- ;;; 8 : port number - The port number of mpd.
- ;;; 9 : automode - not of the noauto argument to `mpd-conn-new'.
- ;; Don't expose these macros, unless required.
- (eval-when-compile
- (defmacro _mpdgv () `(aref conn 0))
- (defmacro _mpdsv (val) `(aset conn 0 ,val))
- (defmacro _mpdgo () `(aref conn 1))
- (defmacro _mpdso (val) `(aset conn 1 ,val))
- (defmacro _mpdgb () `(aref conn 2))
- (defmacro _mpdsb (val) `(aset conn 2 ,val))
- (defmacro _mpdgl () `(aref conn 3))
- (defmacro _mpdsl (val) `(aset conn 3 ,val))
- (defmacro _mpdgs () `(aref conn 4))
- (defmacro _mpdss (val) `(aset conn 4 ,val))
- (defmacro _mpdgf () `(aref conn 5))
- (defmacro _mpdsf (val) `(aset conn 5 ,val))
- (defmacro _mpdgt () `(aref conn 6))
- (defmacro _mpdst (val) `(aset conn 6 ,val))
- (defmacro _mpdgh () `(aref conn 7))
- (defmacro _mpdsh (val) `(aset conn 7 ,val))
- (defmacro _mpdgp () `(aref conn 8))
- (defmacro _mpdsp (val) `(aset conn 8 ,val))
- (defmacro _mpdga () `(aref conn 9))
- (defmacro _mpdsa (val) `(aset conn 9 ,val)))
- ;;;; Sanity check functions.
- (defun mpd-connp (conn)
- "Return t if CONN is a connection to the mpd server."
- (and (vectorp conn) (= (length conn) 10)))
- (put 'mpd-connp 'side-effect-free 'error-free)
- (defun mpd-conn-strongp (conn)
- (and (mpd-connp conn)
- (vectorp (_mpdgv))
- (= (length (_mpdgv)) mpd-ver-string-length)
- (or (not (_mpdgo)) (processp (_mpdgo)))
- (stringp (_mpdgb))
- (or (not (_mpdgt)) (numberp (_mpdgt)))
- (stringp (_mpdgh))
- (wholenump (_mpdgp))))
- (put 'mpd-conn-strongp 'side-effect-free 'error-free)
- (defmacro mpd-assert-mpd-conn (conn) `(mpd-assert-type ,conn mpd-connp))
- (defun mpd-assert-idle (conn)
- "Assert mpd connection CONN to be free to receive a command."
- (mpd-assert-mpd-conn conn)
- (or (stringp (_mpdgs))
- (error (if (listp (_mpdgs)) "Command list mode has not ended"
- "Not done processing current command"))))
- (defun mpd-end-conn (conn fmt &rest args)
- "Abort mpd conection CONN and signal error.
- `format' error message using FMT and ARGS."
- (when (_mpdgo)
- (delete-process (_mpdgo))
- (_mpdso nil))
- (signal 'error (list (apply 'format fmt args))))
- (defun mpd-end-cmd (conn fmt &rest args)
- "Abort current mpd command for connection CONN and signal error.
- `format' error message using FMT and ARGS."
- (_mpdsf nil)
- (_mpdss (apply 'format fmt args))
- (signal 'error (list (apply 'format fmt args))))
- ;;;; Internal functions.
- (defun mpd-process-filter (conn str)
- "Filter replies received from the mpd server.
- CONN represents the connection object for which the filter is invoked.
- STR is the packet received from the server to be processed.
- This is an internal function, do not use this in your code."
- (cond
- ((eq (_mpdgs) t)
- (let ((start (length (_mpdgb))) status (case-fold-search nil))
- ;; Can be 4 if not for the : as mentioned below
- (setq start (if (< start 5) 0 (- start 5)))
- (_mpdsb (concat (_mpdgb) str))
- ;; The optional : in ACK accomodates the ACK: reply to a failed `add'
- ;; given by MPD servers till version 0.10.*
- (setq status (string-match "^\\(ACK:? \\|OK\\)"
- (substring (_mpdgb) start)))
- (if status (setq status (+ status start)))
- (or (eq (_mpdgl) t)
- (let ((packets
- (if (not (equal status 0))
- (mpd-get-lines (substring (_mpdgb) 0
- (and status (1- status)))))))
- (if status
- (progn
- (_mpdsb (substring (_mpdgb) status))
- (setq status 0))
- (if (cdr packets)
- (let ((last (last packets 2)))
- (_mpdsb (cadr last))
- (setcdr last nil))
- (_mpdsb (car packets))
- (setq packets nil)))
- (cond
- ((functionp (_mpdgl))
- (mapcar '(lambda (str)
- (funcall (_mpdgl) conn (mpd-parse-line str)))
- packets))
- ((listp (_mpdgl))
- (_mpdsl (nconc (mapcar 'mpd-parse-line packets) (_mpdgl))))
- (t (error "Invalid line mode filter")))))
- (when status
- (_mpdss status)
- (mpd-process-filter conn ""))))
- ((numberp (_mpdgs))
- (_mpdsb (concat (_mpdgb) str))
- (let* ((resp-end (_mpdgs))
- (bufend (string-match "\n" (_mpdgb) resp-end)))
- (when bufend
- (_mpdss (substring
- (_mpdgb)
- ;; The `4` below should be `5` if ACK: is found, see above.
- ;; This may then leave a space before the error message.
- (+ resp-end (if (_mpdsf (eq (aref (_mpdgb) resp-end)?O)) 2 4))
- bufend))
- (_mpdsb (substring (_mpdgb) 0 resp-end))
- (throw 'mpd-response-over nil))))))
- (defun mpd-transact (conn &optional mode input)
- "Do an I/O transacton with the mpd server.
- Use connection CONN for contacting the server. MODE is as described in
- `mpd-execute-command'. Send INPUT, if non-nil, to the server before waiting for
- the response. This is an internal function, do not use this in your code."
- (_mpdsb "")
- (_mpdsl mode)
- (_mpdss t)
- (unwind-protect
- (let ((timeout (_mpdgt)))
- (and input (process-send-string (_mpdgo) input))
- (while (not (stringp (_mpdgs)))
- (catch 'mpd-response-over
- (or (accept-process-output
- (_mpdgo) (and timeout (/ timeout 1000))
- (and timeout (mod timeout 1000)))
- ;; This essentially reduces the probability of timeout due to
- ;; program suspension to a really small probability.
- (accept-process-output (_mpdgo) 0 0)
- (error "Timed out getting a response from the mpd server")))))
- (unless (stringp (_mpdgs))
- (_mpdsf nil)
- (_mpdss "")
- (_mpdsb ""))))
- ;;;; Low level public interface.
- (defun mpd-conn-new (host port &optional timeout noauto)
- "Construct a mpd connection object from given parameters.
- Connections made using this object are made to host HOST at port number PORT.
- TIMEOUT is a floating-point value specifying the number of seconds to wait
- before giving up waiting for a reply from the server. Unspecified or zero
- TIMEOUT correspond to infinite timeout. Set automatic mode if NOAUTO is nil,
- hooked automatic function if NOAUTO is a function, and do not set automatic mode
- otherwise. See `mpd-set-automatic-mode' for a description of automatic mode."
- (or (and (stringp host) (wholenump port) (or (not timeout) (numberp timeout)))
- (error "Invalid parameters passed for making new connection"))
- (and timeout
- (if (= timeout 0)
- (setq timeout nil)
- (or (> timeout 0) (error "Invalid (negative) timeout value"))))
- ;; Declaration conn object structure dependent.
- (vector (make-vector mpd-ver-string-length nil) nil
- "" nil "" nil (and timeout (floor (* timeout 1000)))
- host port (if noauto (if (functionp noauto) noauto nil) t)))
- (eval-when-compile
- (or (fboundp 'set-process-query-on-exit-flag)
- (defmacro set-process-query-on-exit-flag (process flag)
- `(process-kill-without-query process flag))))
- (defun mpd-connect (conn)
- "Connect to the mpd server using the connection object CONN.
- The connection object is constructed using `mpd-conn-new'. Note that this
- function doesn't need to be explicitly called when the connection is in
- automatic mode (the default). Close the connection using `mpd-close-connection'
- when you are done."
- (let (proc rt welc)
- (setq proc (or (open-network-stream "mpd" nil (_mpdgh) (_mpdgp))
- (error "Unable to open connection with mpd")))
- (and (_mpdgo) (delete-process (_mpdgo)))
- (_mpdso proc)
- (set-process-query-on-exit-flag proc nil)
- (set-process-coding-system proc 'utf-8-unix 'utf-8-unix)
- (set-process-filter proc
- `(lambda (proc str)
- (mpd-process-filter ,conn str)))
- (unwind-protect
- (mpd-transact conn t)
- (or (_mpdgf)
- (mpd-end-conn conn "Handshake failed, server returned: %s" (_mpdgs))))
- (setq welc (_mpdgs))
- (or (string-equal (substring welc 0 (mpd-welcome-length))
- mpd-welcome-message)
- (mpd-end-conn conn "Process mpd not running on port %d in host \"%s\""
- (_mpdgp) (_mpdgh)))
- (let ((verlist (split-string (substring welc (mpd-welcome-length))
- "[\\.\n]")))
- (or (= (length verlist) mpd-ver-string-length)
- (mpd-end-conn conn "Error parsing version information from server"))
- (let ((i 0))
- (while (< i mpd-ver-string-length)
- (or (aset (_mpdgv) i (mpd-string-to-number-strict
- (pop verlist)))
- (mpd-end-conn
- conn "Error parsing version information from server"))
- (setq i (1+ i))))))
- (message "Opened connection with mpd"))
- (make-obsolete 'mpd-new-connection "use `mpd-conn-new' to create a connection \
- object. If automatic mode is not used, a call to `mpd-connect' might be \
- necessary." "2.1")
- (defun mpd-new-connection (host port &optional timeout noauto)
- "Create a new connection and connect using it to the mpd server.
- Return a connection object, which could be used for further transactions with
- the server. See `mpd-conn-new' for a description of the parameters. Close the
- connection using `mpd-close-connection' when you are done.
- This function is deprecated since version 2.1.
- Use `mpd-conn-new' to create a connection object.
- If automatic mode is not used, a call to `mpd-connect' might be necessary."
- (let ((conn (mpd-conn-new host port timeout noauto)))
- (mpd-connect conn) conn))
- (defun mpd-conn-wakeup (conn)
- "Try to ensure that the MPD connection is alive, when in automatic mode."
- (mpd-assert-idle conn)
- (when (and (not (and (_mpdgo) (eq (process-status (_mpdgo)) 'open)))
- (_mpdga) (not (and (functionp (_mpdga))
- (funcall (_mpdga) conn 'pre))))
- (and (_mpdgo) (message "Connection with mpd broken, attempting reconnect"))
- (mpd-connect conn)
- (and (functionp (_mpdga)) (funcall (_mpdga) conn 'post))))
- (defun mpd-execute-command (conn cmd &optional mode)
- "Send the command CMD to the mpd server using CONN.
- Append a newline to CMD before sending to the server. Use the value of MODE to
- decide how the response of the command is processed. MODE could take one of the
- following values:
- - A list, usually nil, to which cons-cells got by formatting each line
- of the response, except the last one, using `mpd-parse-line', are appended.
- The new list thus got is the result of the function.
- - t, by which all response before the last line, as a string,
- is the result of the function.
- - A function, by which each cons-cell, got as described above, is sent to
- this function. Two parameters are passed to the function, the connection
- object and this cons-cell. An empty string is the result.
- Return a cons-cell, whose car is non-nil if the server succeeds, and cdr is the
- result as specified in the description of MODE above."
- (mpd-conn-wakeup conn)
- (mpd-transact conn mode (concat cmd "\n"))
- (prog1
- (cons (_mpdgf)
- ;; Declaration conn object structure dependent.
- (aref conn (if (_mpdgf) (if (or (eq (_mpdgl) 't)
- (functionp (_mpdgl))) 2 3) 4)))
- (_mpdsb "")))
- (defun mpd-simple-exec (conn cmd)
- "Execute mpd command CMD using CONN ignoring response.
- Note that an OK/ACK message still has to come. Return non-nil iff the command
- succeeds. `mpd-get-last-error' gives the server error message in case of
- failure. See also `mpd-execute-command'."
- (if (not (listp (_mpdgs)))
- (car (mpd-execute-command conn cmd t))
- (_mpdss (cons cmd (_mpdgs))) t))
- (defun mpd-close-connection (conn)
- "Close the mpd server connection given by CONN."
- (mpd-assert-idle conn)
- (when (_mpdgo)
- (delete-process (_mpdgo))
- (_mpdso nil)))
- (defvar mpd-inter-conn
- (apply 'mpd-conn-new `(,@(mpd-connection-tidy
- mpd-interactive-connection-parameters) nil))
- "The global mpd connection used for interactive queries.")
- (defsubst mpd-get-version (conn)
- "Get version information for the mpd server CONN is connected to.
- Return a vector of three numbers, for the major, minor and patch levels."
- (mpd-assert-mpd-conn conn)
- (or (aref (_mpdgv) 0) (mpd-conn-wakeup conn))
- (_mpdgv))
- (put 'mpd-get-version 'side-effect-free t)
- (defsubst mpd-get-last-error (conn)
- "Get the last server error message for mpd connection CONN.
- Return nil in case of a successful last command."
- (mpd-assert-mpd-conn conn)
- (and (not (_mpdgf)) (_mpdgs)))
- (put 'mpd-get-last-error 'side-effect-free t)
- (defsubst mpd-get-connection-timeout (conn)
- "Get the timeout of mpd connection CONN.
- Return nil if CONN isn't a mpd connection object."
- (mpd-assert-mpd-conn conn)
- (_mpdgt))
- (put 'mpd-get-connection-timeout 'side-effect-free t)
- ;;;###autoload
- (defun mpd-set-connection-timeout (conn timeout)
- "Set the timeout of mpd connection object CONN to TIMEOUT.
- See also `mpd-new-connection'."
- (interactive
- (list mpd-inter-conn
- (string-to-number
- (read-string
- "Connection timeout (None): "
- (let ((timeout (mpd-get-connection-timeout mpd-inter-conn)))
- (and timeout (number-to-string timeout)))))))
- (if (or (not (mpd-connp conn)) (and timeout (not (numberp timeout))))
- (error "Invalid parameters used to set connection timeout")
- (and (= timeout 0) (setq timeout nil))
- (_mpdst timeout)))
- (defsubst mpd-get-automatic-mode (conn)
- "Return the automatic mode value for mpd connection CONN.
- See `mpd-set-automatic-mode' for a description of automatic mode. Return value
- is like the MODE parameter of that function."
- (mpd-assert-mpd-conn conn)
- (_mpdga))
- (put 'mpd-get-automatic-mode 'side-effect-free t)
- (defsubst mpd-set-automatic-mode (conn mode)
- "Set the automatic mode for mpd connection CONN.
- Set automatic mode iff MODE is non-nil. Set to hooked automatic mode if the
- non-nil MODE is a function.
- The connection is said to be in automatic mode if it connects on demand (usually
- as a result of a server request using the connection) and takes care of
- reconnecting whenever the connection gets broken. The automatic mode could be
- hooked by specifying a function. In this case, the function is called with two
- parameters, the connection object and the second parameter being the symbol 'pre
- or 'post. The function is called with 'pre when reconnection is needed. If the
- function returns a non-nil value, it is assumed that the function has done the
- reconnection. Else, the library reconnects by itself, and the function is once
- again called after that, with the parameter 'post (Return value is ignored)."
- (mpd-assert-mpd-conn conn)
- (_mpdsa mode))
- (make-obsolete 'mpd-get-reconnectible 'mpd-get-automatic-mode "2.1")
- (defalias 'mpd-get-reconnectible 'mpd-get-automatic-mode
- "Return t if CONN reconnects when its mpd connection breaks.
- This function is deprecated since version 2.1.
- Use `mpd-get-automatic-mode' instead.")
- (make-obsolete 'mpd-make-reconnectible 'mpd-set-automatic-mode "2.1")
- (defsubst mpd-make-reconnectible (conn &optional noreconn)
- "Make CONN reconnectible when its mpd connection breaks.
- Unset the reconnectibility on non-nil prefix arg NORECONN.
- This function is deprecated since version 2.1.
- Use `mpd-set-automatic-mode' instead."
- (mpd-set-automatic-mode conn (not noreconn)))
- (defun mpd-force-accept-command (conn)
- "Force the mpd connection CONN to accept the next command.
- *WARNING* DON'T use this unless you are really desperate. Shelf this off for
- debugging purposes. Normally, the package should signal correctly whether it is
- safe to receive the next command. Doing this could mean losing out on the
- response of the current command, and worse, the response of the current command
- could creep into the next one."
- (if (mpd-command-list-mode-p conn)
- (error "Command list mode has not ended")
- (_mpdsf nil)
- (_mpdss "")
- (_mpdsb "")))
- (defun mpd-command-list-begin (conn)
- "Start the command-list mode for CONN.
- Only commands that can be used with `mpd-simple-exec' are allowed in
- command-list mode. Commands can be issued as they are usually done.
- Always return t, as the commands are queued up and not sent to the server.
- `mpd-command-list-end' ends the command-list and executes the list built up."
- (mpd-assert-idle conn)
- (_mpdsf nil) ; FIXME: Why is this here ??
- (_mpdss nil))
- (defun mpd-command-list-mode-p (conn)
- "Return non-nil if mpd connection CONN is in command list mode."
- (mpd-assert-mpd-conn conn)
- (listp (_mpdgs)))
- (put 'mpd-command-list-mode-p 'side-effect-free t)
- (defun mpd-command-list-end (conn)
- "End command-list mode for CONN.
- This function needs to be preceded by a call to `mpd-command-list-begin'."
- (or (mpd-command-list-mode-p conn)
- (error "The connection is not in command-list mode"))
- (let (str)
- (setq str (concat "command_list_begin\n"
- (mapconcat '(lambda (item) item) (nreverse (_mpdgs)) "\n")
- "\ncommand_list_end"))
- (_mpdss "")
- (mpd-simple-exec conn str)))
- (defun mpd-connection-status (conn)
- "Get the status of mpd connection CONN.
- Return one of 'busy for being in the midst of a request, 'ready for the ready
- state, and 'command-list to indicate being in command-list mode."
- (mpd-assert-mpd-conn conn)
- (cond
- ((listp (_mpdgs)) 'command-list)
- ((stringp (_mpdgs)) 'ready)
- (t 'busy)))
- (put 'mpd-connection-status 'side-effect-free t)
- (defmacro with-mpd-timeout-disabled (&rest args)
- `(progn
- (mpd-assert-mpd-conn conn)
- (let ((timeout (_mpdgt)))
- (unwind-protect
- (progn
- (_mpdst nil)
- ,@args)
- (_mpdst timeout)))))
- (put 'with-mpd-timeout-disabled 'lisp-indent-function 0)
- (def-edebug-spec with-mpd-timeout-disabled (body))
- ;;; High level public interface helper functions.
- ;;; Silence the compiler.
- (defvar mpd-song-receiver)
- (defvar foreach)
- (defvar mpd-song-receiver-args)
- (defun mpd-song-receiver (conn cell)
- "Handle song data response from the mpd server.
- See `mpd-execute-command' for a description of response handlers.
- This is an internal function, do not use this in your code."
- (let ((key (car cell)))
- (unless (or (string-equal key "directory")
- (string-equal key "playlist"))
- (when (string-equal key "file")
- (when (plist-get mpd-song-receiver 'file)
- (setq foreach (apply 'mpd-seq-add mpd-song-receiver
- foreach mpd-song-receiver-args)))
- (setq mpd-song-receiver nil))
- (setq mpd-song-receiver
- (plist-put mpd-song-receiver (intern key)
- (if (member key '("Time" "Pos" "Id"))
- (string-to-number (cdr cell)) (cdr cell)))))))
- (defun mpd-get-songs (conn cmd &optional foreach)
- "Get details of songs from the mpd server using connection CONN.
- Use command CMD to get the songs. Call function FOREACH, if specified, for each
- song, with the song provided as the argument. Return list of all songs if
- FOREACH is not specified and FOREACH otherwise. When a list is returned, each
- element of the list is a property list, some known keys being `file', `Pos' and
- `Id'. The last two keys, along with `Time' if present, have integers as their
- value. `Time' refers to the total length of the song. `Pos' and `Id' are present
- only when the song retrieved is a part of a playlist. Other song tag data might
- be present as well."
- (or (functionp foreach) (setq foreach nil))
- (let (mpd-song-receiver mpd-song-receiver-args)
- (mpd-execute-command conn cmd 'mpd-song-receiver)
- (and mpd-song-receiver
- (setq foreach (mpd-seq-add mpd-song-receiver foreach)))
- (mpd-safe-nreverse foreach)))
- (defun mpd-make-cmd-concat (cmd arg &optional normal-nil)
- "Make mpd command string using command CMD and argument ARG.
- ARG could be a string or a list of strings. If NORMAL-NIL is non-nil, do nothing
- if ARG is nil, and return CMD for nil NORMAL-NIL and ARG. Use command list
- mode implicitly for lists. Sanitize arguments before composition.
- This is an internal function, do not use this in your code."
- (cond
- ((not (or arg normal-nil)) cmd)
- ((listp arg)
- (concat "command_list_begin\n"
- (mapconcat '(lambda (item) (concat cmd " " (mpd-safe-string item)))
- arg "\n") "\ncommand_list_end"))
- (t (concat cmd " " (mpd-safe-string arg)))))
- (put 'mpd-make-cmd-concat 'side-effect-free t)
- (defun mpd-make-cmd-format (cmd validate arg1 &optional arg2)
- "Make mpd command string with format CMD and arguments ARG1, ARG2.
- ARG1/ARG2 could be a list of arguments each. Use command list mode implicitly
- for lists. Send each of the arguments pairs to VALIDATE before composition.
- This is an internal function, do not use this in your code."
- (if (listp arg1)
- (let ((tail2 arg2))
- (and arg2 (or (= (length arg1) (length arg2))
- (error "Argument lists are of unequal lengths")))
- (concat "command_list_begin\n"
- (mapconcat '(lambda (item)
- (and validate (funcall validate item (car tail2)))
- (prog1
- (format cmd item (car tail2))
- (setq tail2 (cdr tail2)))) arg1 "\n")
- "\ncommand_list_end"))
- (and validate (funcall validate arg1 arg2))
- (format cmd arg1 arg2)))
- ;;; Helper functions for interactive display.
- (defun mpd-line-to-buffer (str)
- "Insert STR as a line to the mpd display buffer."
- (with-current-buffer (get-buffer-create "*mpd-display*")
- (with-mpd-free-buffer
- (goto-char (point-max))
- (insert (concat str "\n"))
- (display-buffer (current-buffer)))))
- (defsubst mpd-separator-line (&optional num)
- "Make a separator line for insertion to the mpd display buffer.
- The number of columns used is 80, unless specified using NUM."
- (propertize (concat (make-string (or num 80) ?*) "\n")
- 'face 'mpd-separator-face))
- (defun mpd-init-buffer (&optional str1 str2 str3)
- "Initialize the mpd display buffer using strings STR1, STR2, STR3.
- Layout as follows:
- STR1 if non-empty
- ***...*** if STR1 is non-nil
- STR2 if non-empty
- ***...*** if STR2 is non-nil
- STR3 if non-empty"
- (let ((max (max (length str1) (length str2) (length str3))))
- (with-current-buffer (get-buffer-create "*mpd-display*")
- (erase-buffer)
- (insert
- (concat "\n" (and str1 (not (string-equal str1 ""))
- (propertize (concat str1 "\n") 'face 'mpd-header-face))
- (and str1 (mpd-separator-line max))
- (and str2 (propertize (concat str2 "\n") 'face 'mpd-header-face))
- (and str3 (mpd-separator-line max))
- (and str3 (not (string-equal str3 ""))
- (propertize (concat str3 "\n")
- 'face 'mpd-header-face)) "\n")))))
- (defun mpd-render-field (desc val &optional nosep)
- "Format to a colorized line of form \"\\nDESC: VAL\".
- Include the separating colon unless NOSEP is non-nil."
- (and val (not (string-equal val ""))
- (concat (propertize desc 'face 'mpd-first-field-face)
- (and (not nosep) ": ")
- (propertize val 'face 'mpd-second-field-face) "\n")))
- (put 'mpd-render-field 'side-effect-free t)
- (defun mpd-render-plist (plist table)
- "Display property list PLIST as a pretty table in the display buffer.
- TABLE is a list of entries is used to translate between keys and strings
- displayed. Each entry of the table is a list of the key symbol, the
- corresponding string to display, and optionally a function to call with the
- value to get the string to display as the value. If the string to display is
- nil, then those keys are ignored and not displayed at all."
- (when plist
- (let ((ptr plist) (str "") key value entry trans filter)
- (while ptr
- (setq key (car ptr))
- (setq value (cadr ptr))
- (when (and key value)
- (setq entry (assq key table))
- (setq trans (if entry (cadr entry) (symbol-name key)))
- (setq filter (cadr (cdr entry)))
- (when trans
- (setq str (concat str (mpd-render-field
- (format "%-13s" trans)
- (format "%s" (if filter
- (funcall filter value)
- value)))))))
- (setq ptr (cddr ptr)))
- (mpd-line-to-buffer (concat str "\n" (mpd-separator-line))))))
- (defconst mpd-display-song-key-table
- '((Time "Length" (lambda (time) (format "%s seconds" time)))
- (file "Filename")
- (Pos)
- (Id)))
- (defsubst mpd-display-song (song)
- "Display mpd song data SONG in the mpd display buffer."
- (mpd-render-plist song mpd-display-song-key-table))
- (defun mpd-display-playlist-item (title num)
- "Display playlist item with TITLE and index NUM in mpd buffer."
- (mpd-line-to-buffer
- (concat (propertize (format "%4d " (1+ num)) 'face 'mpd-first-field-face)
- (propertize title 'face 'mpd-second-field-face))))
- (defun mpd-display-dir-info (item type)
- "Display mpd directory information to the mpd display buffer."
- (if (eq type 'file)
- (mpd-display-song item)
- (mpd-line-to-buffer
- (concat (mpd-render-field
- (if (eq type 'playlist) "Playlist " "Directory ") item)
- "\n" (mpd-separator-line)))))
- (defun mpd-display-dir-listing (item dir)
- "Display mpd directory listing to the mpd display buffer."
- (mpd-line-to-buffer
- (concat (mpd-render-field (if dir "Directory " "File " ) item)
- "\n" (mpd-separator-line))))
- (defsubst mpd-display-bullet (str)
- "Display a bulleted line to the mpd display buffer."
- (mpd-line-to-buffer (mpd-render-field " o " (format "%s" str) t)))
- (defconst mpd-display-output-key-table
- '((outputid "ID")
- (outputname "Name")
- (outputenabled "Enabled"
- (lambda (enabled)
- (cond
- ((eq enabled t) "Yes")
- ((eq enabled nil) "No")
- (t enabled))))))
- (defsubst mpd-display-output (output)
- "Display mpd output OUTPUT in the mpd display buffer."
- (mpd-render-plist output mpd-display-output-key-table))
- (defun mpd-read-item (prompt &optional default zero allowneg)
- "Read a number from the minibuffer.
- Display PROMPT as the prompt string prefix. Append the DEFAULT value,
- if present, in brackets. Return the number read. Unless ZERO is non-nil,
- add default value by one before operation, and decrement number read
- by 1 before returning. If ALLOWNEG is non-nil, allow negative numbers."
- (let (num str)
- (and default (setq str (number-to-string (if zero default (1+ default)))))
- (setq
- num (mpd-string-to-number-strict
- (read-string (concat prompt (if default (concat " (" str ")")) ": ")
- nil nil str) allowneg))
- (if (and num (not zero)) (1- num) num)))
- ;;;; High level public interface.
- ;;; These functions require response, and hence cannot be queued by using the
- ;;; command-line mode.
- (defun mpd-status-receiver (lsym cell)
- "Handle response for the 'status' command to the mpd server.
- See `mpd-execute-command' for a description of response handlers.
- This is an internal function, do not use this in your code."
- (let ((sym (car cell)))
- (cond
- ((member sym '("volume" "repeat" "random" "playlist" "playlistlength"
- "bitrate" "song" "songid" "xfade" "updating_db"))
- (put lsym (intern sym) (string-to-number (cdr cell))))
- ((string-equal sym "state")
- (and (member (cdr cell) '("play" "pause" "stop"))
- (put lsym (intern sym) (intern (cdr cell)))))
- ((string-equal sym "time")
- (when (string-match "^\\([0-9]*\\):\\([0-9]*\\)$" (cdr cell))
- (put lsym 'time-elapsed (string-to-number (match-string 1 (cdr cell))))
- (put lsym 'time-total (string-to-number (match-string 2 (cdr cell))))))
- ((string-equal sym "audio")
- (when (string-match "^\\([0-9]*\\):\\([0-9]*\\):\\([0-9]*\\)$" (cdr cell))
- (put lsym 'sample-rate (string-to-number (match-string 1 (cdr cell))))
- (put lsym 'bits-per-sample
- (string-to-number (match-string 2 (cdr cell))))
- (put lsym 'channels (string-to-number (match-string 3 (cdr cell))))))
- ;; currently only "error"
- (t (put lsym (intern sym) (cdr cell))))))
- (defun mpd-get-status (conn)
- "Get status of the mpd server, using connection CONN.
- Return a property list, the known keys being `volume', `repeat', `random',
- `playlist', `playlistlength', `bitrate', `song', `songid', `xfade', `state',
- `time-elapsed', `time-total', `sample-rate', `bits-per-sample', `channels'
- and `error'. Other keys might be present in the plist depending on the version
- of MPD used. Some of the less obvious descriptions are:
- +-----------+------------------------------------------------------------------+
- | playlist |The playlist version number - a 'checksum' for the current |
- | |playlist, guaranteed to change after a change in the playlist. |
- +-----------+------------------------------------------------------------------+
- | xfade |The crossfade time in seconds between two songs occurs. This value|
- | |could be zero in case of no crossfade. |
- +-----------+------------------------------------------------------------------+
- | song |Position of the current song in the playlist. |
- +-----------+------------------------------------------------------------------+
- | songid |Song ID of the current song in the playlist. |
- +-----------+------------------------------------------------------------------+
- | state |Could be one of 'play, 'pause or 'stop |
- +-----------+------------------------------------------------------------------+
- |updating_db|The updating job id, displayed when there is a update taking |
- | |place. |
- +-----------+------------------------------------------------------------------+
- | error |A description of the error, if one occurs. Could be nil, in case |
- | |there is no error. |
- +-----------+------------------------------------------------------------------+
- All fields except error and state are whole numbers. `repeat' and `random' are
- in addition, bi-state variables (0/1)"
- (setplist 'mpd-get-status-local-sym nil)
- (mpd-execute-command
- conn "status" '(lambda (conn cell)
- (mpd-status-receiver 'mpd-get-status-local-sym cell)))
- (symbol-plist 'mpd-get-status-local-sym))
- (defun mpd-stats-receiver (lsym cell)
- "Handle response for the 'stats' command to the mpd server.
- See `mpd-execute-command' for a description of response handlers.
- This is an internal function, do not use this in your code."
- (let ((sym (car cell)))
- (cond
- ((member sym '("artists" "albums" "songs" "uptime"
- "playtime" "db_playtime" "db_update"))
- (put lsym (intern sym) (string-to-number (cdr cell))))
- (t (put lsym (intern sym) (cdr cell))))))
- (defun mpd-get-stats (conn)
- "Get statistics for the mpd server connected using CONN.
- Return a property list, the known keys being `artists', `albums', `songs',
- `db_playtime' corresponding to the number of artists, albums and songs and the
- total time in seconds of all songs in the database respectively; `db_update',
- the time stamp of the last update to the database; `playtime', the total time
- for which music has been played in seconds and `uptime', the server uptime,
- in seconds as well. Other keys might be present in the plist depending on the
- version of MPD used."
- (setplist 'mpd-get-stats-local-sym nil)
- (mpd-execute-command
- conn "stats" '(lambda (conn cell)
- (mpd-stats-receiver 'mpd-get-stats-local-sym cell)))
- (symbol-plist 'mpd-get-stats-local-sym))
- ;;;###autoload
- (defun mpd-get-playlist (conn &optional foreach)
- "Get all songs in the current playlist managed by the mpd server.
- CONN and FOREACH are as in `mpd-get-songs'."
- (interactive (progn (mpd-init-buffer "" "Current MPD Playlist" "")
- (list mpd-inter-conn 'mpd-display-playlist-item)))
- (or (functionp foreach) (setq foreach nil))
- (mpd-execute-command
- conn "playlist" '(lambda (conn cell)
- (setq foreach (mpd-elt-add
- (cdr cell) foreach
- (string-to-number (car cell))))))
- (mpd-safe-nreverse foreach))
- ;;;###autoload
- (defun mpd-get-playlist-entry (conn &optional item foreach use-id)
- "Get song data for entr(y/ies) ITEM in the current mpd playlist.
- CONN, FOREACH and the return value are as in `mpd-get-songs'.
- ITEM is the item position/id or a list of it. Note that ITEM as nil fetches
- data for all entries in the current playlist rather than not doing anything.
- Interpret ITEM as song id(s) iff USE-ID is non-nil."
- (interactive
- (let ((item (mpd-read-item
- "Enter item number"
- (plist-get (mpd-get-status mpd-inter-conn) 'song))))
- (mpd-init-buffer "" (format "MPD Playlist Item # %d" (1+ item)) "")
- (list mpd-inter-conn item 'mpd-display-song)))
- (setq use-id (if use-id "playlistid" "playlistinfo"))
- (mpd-get-songs
- conn (if item
- (mpd-make-cmd-format
- (concat use-id " %d")
- '(lambda (item item2) (mpd-assert-wholenump item))
- item) use-id) foreach))
- ;;;###autoload
- (defun mpd-get-current-song (conn &optional foreach)
- "Get song data for the current song in mpd.
- CONN and FOREACH are as in `mpd-get-songs'. Return FOREACH if specified, and
- the current song (see `mpd-get-songs' for how a song is represented) otherwise."
- (interactive (progn (mpd-init-buffer "" "Current MPD Song" "")
- (list mpd-inter-conn 'mpd-display-song)))
- (setq foreach (mpd-get-songs conn "currentsong" foreach))
- (or (functionp foreach) (setq foreach (car foreach))) foreach)
- (defun mpd-get-playlist-changes (conn version &optional foreach nometa)
- "Get a list of changed song entries in the current mpd playlist.
- CONN, FOREACH and the return value are as in `mpd-get-songs'.
- Calculate the change between the current playlist and the playlist
- with version number VERSION. Do not fetch complete metadata (only position and
- song id is returned for each song) if NOMETA is non-nil."
- (mpd-assert-numberp version)
- (mpd-get-songs conn (format "%s %d" (if nometa "plchangesposid" "plchanges")
- version) foreach))
- ;;;###autoload
- (defun mpd-get-directory-songs (conn &optional directory foreach)
- "Get all songs in a directory of the mpd database.
- CONN, FOREACH and the return value are as in `mpd-get-songs'.
- DIRECTORY is the relative directory path wrt the database root.
- DIRECTORY could be a list as well, the action then corresponds to all songs
- in all the directories. Note that the nil value for DIRECTORY corresponds
- to the database toplevel rather than an empty list."
- (interactive
- (let ((str (read-string "Enter relative directory: ")))
- (progn (mpd-init-buffer "" (concat "Songs in directory " str) "")
- (list mpd-inter-conn str 'mpd-display-song))))
- (mpd-get-songs
- conn (mpd-make-cmd-concat "listallinfo" directory) foreach))
- ;;;###autoload
- (defun mpd-get-directory-info (conn &optional directory foreach)
- "Get directory info for DIRECTORY in the mpd database.
- Use CONN as the mpd connection for the purpose and call function FOREACH,
- if specified, with each information field. The arguments passed to FOREACH is a
- song object, directory string or playlist string and one of the symbols 'file,
- 'playlist or 'directory describing the data sent. DIRECTORY could be a list as
- well, the action then corresponds information for all the directories. Note that
- a nil for DIRECTORY corresponds to the database toplevel rather than an empty
- list. Return FOREACH, if non-nil; else a vector of three elements: a list of
- songs in the directory (see `mpd-get-songs' for how a song is represented),
- a list of playlists and a list of subdirectories."
- (interactive
- (let ((str (read-string "Enter relative directory: ")))
- (progn (mpd-init-buffer "" (concat "Information on directory " str) "")
- (list mpd-inter-conn str 'mpd-display-dir-info))))
- (or (functionp foreach) (setq foreach nil))
- (let (filemode (pl foreach) (dir foreach)
- (mpd-song-receiver-args '(file)) mpd-song-receiver)
- (mpd-execute-command
- conn (mpd-make-cmd-concat "lsinfo" directory)
- '(lambda (conn cell)
- (if (string-equal (car cell) "directory")
- (setq dir (mpd-elt-add (cdr cell) dir 'directory))
- (if (string-equal (car cell) "playlist")
- (setq pl (mpd-elt-add (cdr cell) pl 'playlist))
- (mpd-song-receiver conn cell)))))
- (and mpd-song-receiver
- (setq foreach (mpd-seq-add mpd-song-receiver foreach 'file)))
- (if (functionp foreach) foreach
- (vector (nreverse foreach) (nreverse pl) (nreverse dir)))))
- ;;;###autoload
- (defun mpd-list-directory-recursive (conn foreach &optional directory)
- "Get the file-directory hierarchy of a directory in the mpd database.
- Use CONN for the connection and use function FOREACH to report each entry,
- along with a non-nil second argument if the entry is a directory. DIRECTORY
- could be a list as well, the action then corresponds to listing of all the
- directories. Note that the nil value for DIRECTORY corresponds to the
- database toplevel rather than an empty list."
- (interactive
- (let ((str (read-string "Enter relative directory: ")))
- (progn
- (mpd-init-buffer "" (concat "Recursive listing of directory " str) "")
- (list mpd-inter-conn 'mpd-display-dir-listing str))))
- (mpd-assert-type foreach functionp)
- (mpd-execute-command conn (mpd-make-cmd-concat "listall" directory)
- '(lambda (conn cell)
- (funcall foreach (cdr cell)
- (string-equal (car cell) "directory")))))
- ;;;###autoload
- (defun mpd-search (conn by for &optional foreach)
- "Search for songs in the mpd database.
- CONN, FOREACH and the return values are as in `mpd-get-songs'.
- The valid values for BY are 'artist, 'album and 'title;
- indicating the field to search for; and FOR is the search string.
- If FOR is a non-empty list, search by BY for all FOR."
- (interactive
- (let ((reqb (intern-soft
- (completing-read "Search by: "
- '(("artist") ("album") ("title")) nil t)))
- (reqf (read-string "Search for: ")))
- (mpd-init-buffer "" (format "Search results for %s %s" reqb reqf) "")
- (list mpd-inter-conn reqb reqf 'mpd-display-song)))
- (or (eq by 'artist) (eq by 'album) (eq by 'title)
- (error "Invalid mpd search field %s" by))
- (mpd-get-songs
- conn
- (mpd-make-cmd-concat (concat "find " (symbol-name by)) for t) foreach))
- ;;;###autoload
- (defun mpd-get-artists (conn &optional foreach)
- "Get the names of all artists whose songs are in the mpd database.
- Use CONN for the connection, and call function FOREACH, if specified, with
- each name. If FOREACH is a function, return FOREACH, else return a list of
- artist names."
- (interactive
- (progn
- (mpd-init-buffer "" "List of artists" "")
- (list mpd-inter-conn 'mpd-display-bullet)))
- (or (functionp foreach) (setq foreach nil))
- (mpd-execute-command
- conn "list artist"
- '(lambda (conn cell)
- (and (string-equal (car cell) "Artist")
- (setq foreach (mpd-elt-add (cdr cell) foreach)))))
- (mpd-safe-nreverse foreach))
- ;;;###autoload
- (defun mpd-get-artist-albums (conn &optional artist foreach)
- "Get all albums in the mpd database featuring artist(s) ARTIST.
- Get all albums if ARTIST is not specified. If ARTIST is a list, find the albums
- of all the artists in the list. Use CONN for the connection, and call function
- FOREACH, if specified, with each name. If FOREACH is a function, return FOREACH,
- else return the list of albums."
- (interactive
- (let ((str (read-string "Name of the artist (All): ")))
- (and (string-equal str "") (setq str nil))
- (mpd-init-buffer "" (if str (concat "Albums of artist " str)
- "List of albums") "")
- (list mpd-inter-conn str 'mpd-display-bullet)))
- (or (functionp foreach) (setq foreach nil))
- (mpd-execute-command
- conn (mpd-make-cmd-concat "list album" artist)
- '(lambda (conn cell)
- (and (string-equal (car cell) "Album")
- (setq foreach (mpd-elt-add (cdr cell) foreach)))))
- (mpd-safe-nreverse foreach))
- ;;;###autoload
- (defun mpd-get-handled-remote-url-prefixes (conn &optional foreach)
- "Use CONN to query remote URL prefixes handled by the mpd server.
- Call function FOREACH, if specified, with each prefix.
- If FOREACH is a function, return FOREACH, else return the URL prefixes."
- (interactive
- (progn
- (mpd-init-buffer "" "List of remote URL prefixes handled" "")
- (list mpd-inter-conn 'mpd-display-bullet)))
- (or (functionp foreach) (setq foreach nil))
- (mpd-execute-command
- conn "urlhandlers"
- '(lambda (conn cell)
- (and (string-equal (car cell) "handler")
- (setq foreach (mpd-elt-add (cdr cell) foreach)))))
- (mpd-safe-nreverse foreach))
- (defvar mpd-output-receiver)
- (defun mpd-output-receiver (conn cell)
- "Handle output descriptions from the mpd server.
- See `mpd-execute-command' for a description of response handlers.
- This is an internal function, do not use this in your code."
- (let ((key (car cell)))
- (when (string-equal key "outputid")
- (when (plist-get mpd-output-receiver 'outputid)
- (setq foreach (apply 'mpd-seq-add mpd-output-receiver foreach)))
- (setq mpd-output-receiver nil))
- (setq mpd-output-receiver
- (plist-put mpd-output-receiver (intern key)
- (cond
- ((string-equal key "outputid")
- (string-to-number (cdr cell)))
- ((string-equal key "outputenabled")
- (cond
- ((string-equal (cdr cell) "0") nil)
- ((string-equal (cdr cell) "1") t)
- (t (cdr cell))))
- (t (cdr cell)))))))
- ;;;###autoload
- (defun mpd-get-outputs (conn &optional foreach)
- "Get output descriptions from the mpd server using connection CONN.
- Call function FOREACH, if specified, for each output, with the output provided
- as the argument. Return list of all outputs if FOREACH is not specified and
- FOREACH otherwise. When a list is returned, each element of the list is a
- property list, some known keys being `outputid' (integer), `outputname' (string)
- and `outputenabled' (boolean)."
- (interactive
- (progn
- (mpd-init-buffer "" "List of outputs" "")
- (list mpd-inter-conn 'mpd-display-output)))
- (or (functionp foreach) (setq foreach nil))
- (let (mpd-output-receiver)
- (mpd-execute-command conn "outputs" 'mpd-output-receiver)
- (and mpd-output-receiver
- (setq foreach (mpd-seq-add mpd-output-receiver foreach)))
- (mpd-safe-nreverse foreach)))
- ;;; These are command functions. These functions can be queued by using the
- ;;; command-list mode. See `mpd-command-list-begin' and `mpd-command-list-end'.
- (defun mpd-file-to-mpd-resource (file)
- "Convert FILE to a resource string understood by mpd."
- (let ((prefixes
- (mpd-get-handled-remote-url-prefixes mpd-inter-conn)) length)
- (catch
- 'mpd-enqueue-is-url
- (while prefixes
- (setq length (length (car prefixes)))
- (and (>= (length file) length)
- (string-equal (substring file 0 length) (car prefixes))
- (throw 'mpd-enqueue-is-url file))
- (setq prefixes (cdr prefixes)))
- (file-relative-name file mpd-db-root))))
- ;;;###autoload
- (defun mpd-enqueue (conn res)
- "Enqueue resource RES (or list of RES) to the mpd playlist.
- Each of RES can be a file or a directory in the mpd database, or an URL."
- (interactive
- (list mpd-inter-conn
- (if (and (stringp mpd-db-root) (not (string-equal mpd-db-root "")))
- (mpd-file-to-mpd-resource
- (read-file-name "Enqueue what: "
- (file-name-as-directory mpd-db-root)))
- (read-string "Enqueue what: "))))
- (mpd-simple-exec conn (mpd-make-cmd-concat "add" res)))
- ;;;###autoload
- (defun mpd-delete (conn pos &optional use-id never-sort)
- "Delete song at position/id POS from the mpd playlist.
- Interpret POS as a list of song id's if USE-ID is non-nil. POS could be a list
- to delete as well. If POS is a list and USE-ID is nil, sort it in descending
- order and remove duplicates before proceeding, unless NEVER-SORT is non-nil.
- Note that this is necessary for the correctness of the deletion, and NEVER-SORT
- is only provided in case the arguments already satisfy the condition."
- (interactive (list mpd-inter-conn (mpd-read-item "Enter item to be deleted")))
- ;; Deletion changes the playlist ordering of all those below the deleted item.
- ;; Hence, sort and uniquify the list in descending order.
- (and (not (or use-id (not (listp pos)) never-sort))
- (setq pos (mpd-sort-uniq-list pos '> '=)))
- (mpd-simple-exec
- conn
- (mpd-make-cmd-format (if use-id "deleteid %d" "delete %d")
- '(lambda (item ig) (mpd-assert-wholenump item)) pos)))
- ;;;###autoload
- (defun mpd-save-playlist (conn file)
- "Save current mpd playlist to FILE (or list of FILE)."
- (interactive (list mpd-inter-conn (read-string "Save playlist to: ")))
- (mpd-simple-exec conn (mpd-make-cmd-concat "save" file t)))
- ;;;###autoload
- (defun mpd-load-playlist (conn plname)
- "Load playlist PLNAME (or list of PLNAME) to the mpd server."
- (interactive (list mpd-inter-conn (read-string "Load playlist: ")))
- (mpd-simple-exec conn (mpd-make-cmd-concat "load" plname t)))
- ;;;###autoload
- (defun mpd-remove-playlist (conn plname)
- "Remove playlist PLNAME from the mpd playlist directory.
- PLNAME could as well be a list of playlist names."
- (interactive (list mpd-inter-conn (read-string "Remove playlist: ")))
- (mpd-simple-exec conn (mpd-make-cmd-concat "rm" plname t)))
- ;;;###autoload
- (defun mpd-shuffle-playlist (conn)
- "Shuffle current mpd playlist using connection CONN."
- (interactive (list mpd-inter-conn))
- (mpd-simple-exec conn "shuffle"))
- ;;;###autoload
- (defun mpd-clear-playlist (conn)
- "Clear current mpd playlist using connection CONN."
- (interactive (list mpd-inter-conn))
- (mpd-simple-exec conn "clear"))
- ;;;###autoload
- (defun mpd-play (conn &optional pos use-id)
- "Play song at position/id POS (default first) in the mpd playlist.
- Interpret POS as a song id iff USE-ID is non-nil."
- (interactive (list mpd-inter-conn (mpd-read-item "Enter item to play" 0)))
- (and pos (mpd-assert-wholenump pos))
- (mpd-simple-exec
- conn (concat (if use-id "playid" "play")
- (and pos (concat " " (number-to-string pos))))))
- ;;;###autoload
- (defun mpd-stop (conn)
- "Stop playing the current mpd playlist."
- (interactive (list mpd-inter-conn))
- (mpd-simple-exec conn "stop"))
- ;;;###autoload
- (defun mpd-pause (conn &optional arg)
- "Toggle the pause state of the current mpd playlist.
- If prefix argument ARG is non-nil, pause iff ARG is positive
- and resume playing otherwise."
- (interactive (list mpd-inter-conn current-prefix-arg))
- (mpd-simple-exec
- conn
- (if arg
- (format "pause %d" (if (> (prefix-numeric-value arg) 0) 1 0))
- "pause")))
- ;;;###autoload
- (defun mpd-next (conn)
- "Play next song in the current mpd playlist."
- (interactive (list mpd-inter-conn))
- (mpd-simple-exec conn "next"))
- ;;;###autoload
- (defun mpd-prev (conn)
- "Play previous song in the current mpd playlist."
- (interactive (list mpd-inter-conn))
- (mpd-simple-exec conn "previous"))
- ;;;###autoload
- (defun mpd-move (conn from to &optional use-id)
- "Move item from position/id FROM in the current mpd playlist to TO.
- For lists of FROM and TO, do action in that order for each pair of items.
- Interpret FROM as a list of song ids iff USE-ID is non-nil. Retain TO as a list
- of positions irrespective of the value of USE-ID. If sending a list for FROM and
- TO, note that every move changes the order of items in the playlist."
- (interactive (list mpd-inter-conn (mpd-read-item "Source item number")
- (mpd-read-item "Destination item number")))
- (and from to
- (mpd-simple-exec
- conn (mpd-make-cmd-format
- (concat (if use-id "moveid" "move") " %d %d")
- '(lambda (i j)
- (mpd-assert-wholenump i)
- (mpd-assert-wholenump j))
- from to))))
- ;;;###autoload
- (defun mpd-swap (conn first second &optional use-id)
- "Swap positions/ids FIRST and SECOND in the current mpd playlist.
- For lists of FROM and TO, do action in that order for each pair of items.
- Interpret FIRST and SECOND as song ids iff USE-ID is non-nil.
- See also `mpd-move'."
- (interactive (list mpd-inter-conn (mpd-read-item "Swap item at number")
- (mpd-read-item "With item at number")))
- (and first second
- (mpd-simple-exec
- conn (mpd-make-cmd-format
- (concat (if use-id "swapid" "swap") " %d %d")
- '(lambda (i j)
- (mpd-assert-wholenump i)
- (mpd-assert-wholenump j))
- first second))))
- ;;;###autoload
- (defun mpd-seek (conn song &optional time use-id)
- "Seek to song position/id SONG and time TIME in the mpd playlist.
- Take TIME to be 0 by default. Interpret SONG as a song id
- iff USE-ID is non-nil."
- (interactive
- (let (status)
- (and (eq (mpd-connection-status mpd-inter-conn) 'ready)
- (setq status (mpd-get-status mpd-inter-conn)))
- (list mpd-inter-conn
- (mpd-read-item "Seek to song" (and status (plist-get status 'song)))
- (mpd-read-item "Time in seconds"
- (and status (plist-get status 'time-elapsed)) t))))
- (mpd-assert-wholenump song)
- (if time (mpd-assert-wholenump time) (setq time 0))
- (mpd-simple-exec conn (format "%s %d %d"
- (if use-id "seekid" "seek") song time)))
- ;;;###autoload
- (defun mpd-toggle-random (conn &optional arg)
- "Change random mode of mpd using connection CONN.
- With ARG, set random on iff ARG is positive."
- (interactive (list mpd-inter-conn current-prefix-arg))
- (setq arg (if arg (> (prefix-numeric-value arg) 0)
- (= (plist-get (mpd-get-status conn) 'random) 0)))
- (mpd-simple-exec conn (concat "random " (if arg "1" "0"))))
- ;;;###autoload
- (defun mpd-toggle-repeat (conn &optional arg)
- "Change repeat mode of mpd using connection CONN.
- With ARG, set repeat on iff ARG is positive."
- (interactive (list mpd-inter-conn current-prefix-arg))
- (setq arg (if arg (> (prefix-numeric-value arg) 0)
- (= (plist-get (mpd-get-status conn) 'repeat) 0)))
- (mpd-simple-exec conn (concat "repeat " (if arg "1" "0"))))
- ;;;###autoload
- (defun mpd-set-volume (conn vol)
- "Set the volume for the mpd player to volume VOL."
- (interactive
- (list mpd-inter-conn
- (mpd-read-item
- "New volume"
- (and (eq (mpd-connection-status mpd-inter-conn) 'ready)
- (plist-get (mpd-get-status mpd-inter-conn) 'volume))
- t)))
- (mpd-assert-wholenump vol)
- (mpd-simple-exec conn (format "setvol %d" vol)))
- ;;;###autoload
- (defun mpd-adjust-volume (conn vol)
- "Adjust the volume for the mpd player by volume VOL.
- If VOL is positive, increase the volume, and decrease otherwise."
- (interactive (list mpd-inter-conn
- (mpd-string-to-number-strict
- (read-string "Adjust volume by: ") t)))
- (mpd-assert-numberp vol)
- (mpd-simple-exec conn (format "volume %d" vol)))
- ;;;###autoload
- (defun mpd-set-crossfade (conn time)
- "Set cross-fading time for the mpd player to TIME in seconds.
- Turn off cross-fading if TIME is 0."
- (interactive (list mpd-inter-conn
- (mpd-read-item "New crossfade time in seconds"
- (plist-get (mpd-get-status mpd-inter-conn)
- 'xfade) t)))
- (mpd-assert-wholenump time)
- (mpd-simple-exec conn (format "crossfade %d" time)))
- (defvar mpd-inter-password-remember-queried nil)
- (defvar mpd-inter-password nil)
- ;; For temporary binding
- (defvar mpd-inter-password-inhibit-update nil)
- (defun mpd-inter-password-update (conn when)
- (when (and (eq when 'post) (not mpd-inter-password-inhibit-update))
- (let ((passwd mpd-inter-password)
- (mpd-inter-password-inhibit-update t))
- (or passwd (setq passwd (read-passwd "Enter MPD password: ")))
- (or (mpd-set-password conn passwd)
- (message "Unable to set password: %s" (mpd-get-last-error conn))))))
- ;;;###autoload
- (defun mpd-set-password (conn pass)
- "Set the password for access to the mpd server.
- *WARNING* The password is sent to the server in plaintext. The processing done
- by libmpdee to send the command for setting the password also has its data as
- plaintext. When called interactively, offer to store and set the password on
- reconnection. Note that this is not done when the call is not interactive.
- Use hooked automatic mode (see `mpd-set-automatic-mode') to achieve the same."
- (interactive
- (let ((password (read-passwd "Enter password: ")))
- ;; Prevent password update triggering for this call itself
- ;; in case the connection is dead currently.
- (let ((mpd-inter-password-inhibit-update t))
- (mpd-conn-wakeup mpd-inter-conn))
- (if mpd-inter-password-remember-queried
- (and mpd-inter-password (setq mpd-inter-password password))
- (when (yes-or-no-p "Do you want the password to be set again \
- if we happen to reconnect? ")
- (mpd-set-automatic-mode mpd-inter-conn 'mpd-inter-password-update)
- (when (yes-or-no-p "Would you like me to remember your password \
- for this session, in case it needs to be set again? ")
- (setq mpd-inter-password password)))
- (setq mpd-inter-password-remember-queried t))
- (list mpd-inter-conn password)))
- (mpd-assert-string pass)
- (mpd-simple-exec conn (concat "password " (mpd-safe-string pass))))
- (defun mpd-update-1 (conn path)
- "Internal function instructing the mpd server to update.
- Please use `mpd-update' for updation purposes."
- (let ((response (mpd-execute-command
- conn (mpd-make-cmd-concat "update" path))))
- (and (car response)
- (string-to-number (cdr (assoc "updating_db" (cdr response)))))))
- ;;;###autoload
- (defun mpd-update (conn &optional path ignore-timeout)
- "Instruct the mpd server using CONN to update its database.
- PATH is the path or a list of paths to be updated. Note that PATH as nil updates
- the root directory rather than not updating at all. Ignore connection timeout,
- if IGNORE-TIMEOUT is non-nil and the connection is not in command list mode.
- Return update job id on success."
- (interactive (list mpd-inter-conn (read-string "Enter relative path: ")))
- (if (or (not ignore-timeout) (mpd-command-list-mode-p conn))
- (mpd-update-1 conn path)
- (with-mpd-timeout-disabled (mpd-update-1 conn path))))
- ;;;###autoload
- (defun mpd-output-enable (conn id)
- "Use connection CONN to enable mpd output ID."
- (interactive (list mpd-inter-conn (mpd-read-item "Output ID" 0 t)))
- (mpd-assert-wholenump id)
- (mpd-simple-exec conn (format "enableoutput %d" id)))
- ;;;###autoload
- (defun mpd-output-disable (conn id)
- "Use connection CONN to disable mpd output ID."
- (interactive (list mpd-inter-conn (mpd-read-item "Output ID" 0 t)))
- (mpd-assert-wholenump id)
- (mpd-simple-exec conn (format "disableoutput %d" id)))
- (defun mpd-ping (conn)
- "Use connection CONN to ping the mpd server.
- Return non-nil on success."
- (mpd-simple-exec conn "ping"))
- (defun mpd-clear-status-error (conn)
- "Use connection CONN to clear the error status of the mpd server.
- Return non-nil on success."
- (mpd-simple-exec conn "clearerror"))
- ;;; Adapted from bbdb.el
- ;;;###autoload
- (defun mpd-libmpdee-submit-bug-report ()
- "Interactively submit a bug report about `libmpdee'."
- (interactive)
- (eval-and-compile
- (require 'reporter)
- (require 'sendmail))
- (delete-other-windows)
- (reporter-submit-bug-report
- "andyetitmoves@gmail.com"
- (concat "libmpdee " libmpdee-version)
- (append
- ;; all mpd connections
- (apropos-internal
- "" '(lambda (sym)
- (and (boundp sym) (mpd-conn-strongp (symbol-value sym)))))
- ;; some variables
- '(emacs-version features))
- nil nil
- "Please change the Subject header to a concise bug description.
- In this report, remember to cover the basics, that is,
- what you expected to happen and what in fact did happen.
- If you got an error, please enable debugging by
- M-x set-variable debug-on-error t
- or if you have `dbfrobs', M-x debug-on-interesting-errors
- Then reproduce the error and immediately call
- M-x mpd-libmpdee-submit-bug-report
- The backtrace will be automatically included with the report.
- Please remove these instructions from your message.")
- ;; insert the backtrace buffer content if present
- (let ((backtrace (get-buffer "*Backtrace*")))
- (when backtrace
- (goto-char (point-max))
- (insert "\nPossible backtrace for libmpdee:\n\n")
- (insert-buffer-substring backtrace)))
- (goto-char (point-min))
- (mail-position-on-field "Subject"))
- (provide 'libmpdee)
- ;;; LIBMPDEE.EL ends here