PageRenderTime 7ms CodeModel.GetById 2ms app.highlight 62ms RepoModel.GetById 1ms app.codeStats 0ms

/external/libmpdee.el

https://bitbucket.org/henrik/emacs-old
Lisp | 1639 lines | 1288 code | 169 blank | 182 comment | 127 complexity | e164f5f7bef8f40a413994ec251ad7e7 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1;;; LIBMPDEE.EL --- Client end library for mpd, a music playing daemon
   2
   3;; Copyright (C) 2004, 2005, 2006 R.Ramkumar
   4
   5;; Author: 	R.Ramkumar <andyetitmoves@gmail.com>
   6;; Created: 	10 May 2004
   7;; Version: 	2.1
   8;; Keywords:	music, mpd
   9
  10;; This file is *NOT* part of GNU Emacs
  11
  12;; This program is free software; you can redistribute it and/or modify
  13;; it under the terms of the GNU General Public License as published by
  14;; the Free Software Foundation; either version 2, or (at your option)
  15;; any later version.
  16
  17;; This program is distributed in the hope that it will be useful,
  18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20;; GNU General Public License for more details.
  21
  22;; A copy of the GNU General Public License can be obtained from this
  23;; program's author (send electronic mail to andyetitmoves@gmail.com)
  24;; or from the Free Software Foundation, Inc.,
  25;; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  26
  27;;; Commentary:
  28
  29;; This package is a client end library to the wonderful music playing daemon by
  30;; name mpd. Hence, an obvious prerequisite for this package is mpd itself,
  31;; which you can get at http://www.musicpd.org. For those who haven't heard of
  32;; mpd, all I can say is that it is definitely worth a try, and is surely
  33;; different from the rest. This package is aimed at developers, and though
  34;; there are some interactive functions, much of the power of the library lies
  35;; away from the user.
  36
  37;; This package started off to implement libmpdclient.c, which came with mpd, in
  38;; elisp. But as it stands of now, this package is not an exact translation.
  39;; Notable amongst the deviations are -
  40;;
  41;;      - This package contains quite a bit of higher level functionality as
  42;;      compared with the original. An action or a query needs only a single
  43;;      call and the library user can choose to get either the raw response, the
  44;;      formatted response, or hook on a callback for each logical token of the
  45;;      response. However, dig deeper, and you will find the lower level
  46;;      functionality available as well.
  47;;      - The error throwing scheme consistent with what is expected of elisp
  48;;      programs.
  49;;      - Command list mode is transparent in most cases, as wherever
  50;;      appropriate, functions taking arguments can accept either a single item
  51;;      or a list of it for each argument.
  52;;      - Apart from this, command list functionality is limited to actions
  53;;      rather than queries, as it is anyway not that easy to parse out the
  54;;      individual responses from command-list queries (it is firstly possible
  55;;      only from 0.11, which allows for a list_OK to be added at the end of
  56;;      response for each command in the list).
  57;;      - command_list_ok_begin isn't implemented. It is still possible to
  58;;      explicitly use "command_list_(ok)begin\n..\ncommand_list_end" for
  59;;      `mpd-execute-command' and get the response tokens for queries. A better
  60;;      interface may be available in the future.
  61;;      - There is a small interactive interface as well, but this is
  62;;      intentionally incomplete. The point is that this exists only to the
  63;;      extent of adding an interactive part to the functions, without modifying
  64;;      the functions per se.
  65
  66;; Most of the functions below require a connection object as an argument, which
  67;; you can create by a call to `mpd-conn-new'. The recommended way to use
  68;; customization to choose parameters for mpd connections is to use the widget
  69;; `mpd-connection'.
  70
  71;; As this package caters to developers, it should be helpful to browse the file
  72;; in order for atleast the functions and the documentation. The file is well
  73;; structured and documented, so go for it. The impatient could do a selective
  74;; display to 3 (C-u 3 C-x $) before proceeding.
  75
  76;;; Installation:
  77
  78;; Put this file somewhere on your load-path. Then, you could use
  79;; (require 'libmpdee) whenever the services of this package are needed.
  80
  81;; Parameters used for the interactive calls can be customized in the group mpd
  82;; Use:
  83;;         M-x customize-group mpd
  84;; to change the values to your liking.
  85
  86
  87;;; History: (See the SVN logs/ChangeLog for the list of all changes)
  88
  89;; v2.1
  90;; Introducing automatic mode with hooking, for connections.
  91;; See `mpd-set-automatic-mode'.
  92
  93;; v2.0
  94;; The interface has changed since version 1 of this library and there is no
  95;; backward compatibility. This change applies to functions which returned
  96;; vector whose descriptions were given `*-data' variables. Such functions have
  97;; been modified to return property lists, whose keys now describe the values
  98;; returned. This should hopefully be a much more scalable representation.
  99
 100;;; Code:
 101
 102(defvar libmpdee-version "2.1"
 103  "libmpdee version information.")
 104
 105;;;; User serviceable variable(s).
 106
 107(require 'custom)
 108(require 'wid-edit)
 109
 110(defun widget-mpd-format-handler (widget esc)
 111  "Widget format handler for the MPD connection widget."
 112  (cond
 113   ((eq esc ?l)
 114    (widget-create-child-and-convert widget 'documentation-string "\
 115This variable specifies a MPD connection.
 116The value is a list of the mpd host name, port number, and the timeout for
 117server replies. See `mpd-conn-new' for more details.")
 118    (insert "To know more about libmpdee, read ")
 119    (widget-create 'emacs-commentary-link :tag "this" "libmpdee"))
 120   (t (funcall (widget-get (widget-convert 'lazy) :format-handler)
 121	       widget esc))))
 122
 123(defun mpd-connection-tidy (val)
 124  "Initialize unset parameters for a MPD connection.
 125Replace parameters in the MPD connection VAL with sane defaults and return."
 126  (or val (setq val '(nil nil nil)))
 127  (let ((val val))
 128    (and (listp val)
 129	 (or (car val)
 130	     (setcar val (or (getenv "MPD_HOST") "localhost")))
 131	 (setq val (cdr val))
 132	 (or (car val) (setcar val 6600))
 133	 (setq val (cdr val))
 134	 (or (car val) (setcar val 10.0)))) val)
 135
 136(define-widget 'mpd-connection 'lazy
 137  "A widget for a MPD connection."
 138  :tag "MPD Connection"
 139  :format "%{%t%}:\n\n%l\n\n%v"
 140  :format-handler 'widget-mpd-format-handler
 141  :value-to-internal '(lambda (wid val) (mpd-connection-tidy val))
 142  :type '(list :format "%v"
 143	       (string :format "%t: %v\n" :tag "Hostname" :size 15)
 144	       (integer :format "%t:     %v\n" :tag "Port" :size 5
 145			:match (lambda (widget value) (> value 0))
 146			:type-error "Port must be a natural number")
 147	       (float :format "%t:  %v\n\n" :tag "Timeout" :size 10
 148		      :match (lambda (widget value) (> value 0))
 149		      :type-error "Timeout must be a positive number")))
 150
 151(defgroup mpd nil
 152  "The client end library for MPD, the music playing daemon."
 153  :group 'external :group 'multimedia
 154  :link '(emacs-commentary-link "libmpdee"))
 155
 156(defcustom mpd-db-root (getenv "MPD_DB_ROOT")
 157  "*MPD database directory root."
 158  :type 'directory :group 'mpd)
 159
 160(defcustom mpd-interactive-connection-parameters (mpd-connection-tidy nil)
 161  "Parameters for the interactive mpd connection.
 162These determine the connection used by interactive functions in `libmpdee'."
 163  :type 'mpd-connection :group 'mpd)
 164
 165(defface mpd-separator-face '((((background dark)) (:foreground "lightyellow"))
 166			      (((background light)) (:foreground "darkgreen")))
 167  "Face for display of separator lines in interactive mpd queries."
 168  :group 'mpd)
 169
 170(defface mpd-header-face '((((background dark)) (:foreground "gold"))
 171			   (((background light)) (:foreground "brown")))
 172  "Face for display of header lines in interactive mpd queries."
 173  :group 'mpd)
 174
 175(defface mpd-first-field-face '((((background dark)) (:foreground "cyan"))
 176				(((background light)) (:foreground "orange")))
 177  "Face for display of the first field in interactive mpd queries.
 178Most lines in interactive displays are split into two fields."
 179  :group 'mpd)
 180
 181(defface mpd-second-field-face
 182  '((((background dark)) (:foreground "lightgreen"))
 183    (((background light)) (:foreground "blue")))
 184  "Face for display of the second field in interactive mpd queries.
 185Most lines in interactive displays are split into two fields."
 186  :group 'mpd)
 187
 188;;;; Constants and internal variables.
 189
 190(eval-and-compile (defconst mpd-welcome-message " MPD "))
 191(defmacro mpd-welcome-length () (length mpd-welcome-message))
 192(defconst mpd-ver-string-length 3)
 193
 194;;;; Package independent helper functions.
 195
 196(defmacro mpd-assert-type (obj func)
 197  "Ensure that OBJ succeeds on type checking with predicate FUNC.
 198The function emits a \"Wrong type argument\" signal on failure.
 199Note that the arguments are evalled twice in this process."
 200  `(or (,func ,obj) (signal 'wrong-type-argument (list (quote ,func) ,obj))))
 201
 202(defmacro mpd-assert-string (obj) `(mpd-assert-type ,obj stringp))
 203(defmacro mpd-assert-wholenump (obj) `(mpd-assert-type ,obj wholenump))
 204(defmacro mpd-assert-numberp (obj) `(mpd-assert-type ,obj numberp))
 205
 206(defun mpd-string-to-number-strict (str &optional allowneg)
 207  "Convert string STR to a number strictly.
 208Return nil if there are any unmatched characters.
 209Allow negative numbers if ALLOWNEG is non-nil."
 210  (let ((num (string-to-number str)))
 211    (and (if allowneg (numberp num) (wholenump num))
 212	 (string-equal str (number-to-string num)) num)))
 213(put 'mpd-string-to-number-strict 'side-effect-free t)
 214
 215(defun mpd-get-lines (str)
 216  "Split STR into newline separated lines.
 217Differ from `split-string' in that tokens are created
 218for leading and trailing newlines."
 219  (let ((packets (split-string str "\n")))
 220    (when packets
 221      (if (= (aref str 0) ?\n)
 222	  (setq packets (cons "" packets)))
 223      (if (= (aref str (1- (length str))) ?\n)
 224	  (nconc packets (list ""))
 225	packets))))
 226
 227;;; Modified from the pcomplete package.
 228(defun mpd-sort-uniq-list (l lessp eqp)
 229  "Sort and remove multiples in list L.
 230Use LESSP and EQP as predicates for the \"lesser\" and \"equal\" operations."
 231  (setq l (sort l lessp))
 232  (let ((m l))
 233    (while m
 234      (while (and (cdr m) (funcall eqp (car m) (cadr m)))
 235	(setcdr m (cddr m)))
 236      (setq m (cdr m)))) l)
 237
 238;;; For edebug macro specifications.
 239(eval-when-compile (require 'edebug))
 240
 241(defmacro with-mpd-temp-widen (&rest args)
 242  "Evaluate ARGS while temporarily widening the current buffer."
 243  `(save-restriction
 244     (widen)
 245     ,@args))
 246(put 'with-mpd-temp-widen 'lisp-indent-function 0)
 247(def-edebug-spec with-mpd-temp-widen (body))
 248
 249(defmacro with-mpd-free-buffer (&rest args)
 250  "Evaluate ARGS with temporary widening and saved excursion."
 251  `(save-excursion
 252     (with-mpd-temp-widen ,@args)))
 253(put 'with-mpd-free-buffer 'lisp-indent-function 0)
 254(def-edebug-spec with-mpd-free-buffer (body))
 255
 256(defmacro mpd-safe-nreverse (list)
 257  "Reverse LIST if it is a list, leave alone otherwise.
 258Note that LIST is evaluated thrice."
 259  `(if (listp ,list) (nreverse ,list) ,list))
 260
 261(defun mpd-seq-add (seq &optional spec &rest args)
 262  "Operate the sequence SEQ on SPEC depending on its type.
 263Add a copy of SEQ to SPEC, if it's a list and call it with SEQ as an argument
 264followed by other ARGS specified, if it is a function. Return SPEC."
 265  (if (not (functionp spec))
 266      (cons (copy-sequence seq) spec)
 267    (apply spec seq args) spec))
 268
 269(defun mpd-elt-add (elt &optional spec &rest args)
 270  "Operate the object ELT on SPEC depending on its type.
 271Add ELT to SPEC, if it's a list and call it with ELT as an argument
 272followed by other ARGS specified, if it is a function. Return SPEC."
 273  (if (not (functionp spec))
 274      (cons elt spec)
 275    (apply spec elt args) spec))
 276
 277(defun mpd-parse-line (str)
 278  "Parse line STR of form \"KEY: VALUE\" to a cons (KEY . VALUE).
 279Return (STR . nil) on a parse failure."
 280  (if (string-match "^\\([^:]*\\): ?\\(.*\\)$" str)
 281      (cons (match-string 1 str) (match-string 2 str))
 282    (cons str nil)))
 283
 284(defun mpd-safe-string (str)
 285  "Quote and escape string STR for sending to the mpd server."
 286  (if str
 287      (let ((start 0))
 288	(while (string-match "[\\\"]" str start)
 289	  ;; We add an extra character,
 290	  ;; so place start a character beyond end of match.
 291	  (setq start (1+ (match-end 0)))
 292	  (setq str (replace-match "\\\\\\&" t nil str)))
 293	(if (string-match " " str) (concat "\"" str "\"") str))))
 294
 295;;; (defun mpd-log (fmt &rest args)
 296;;;   (write-region (concat (apply 'format fmt args) "\n") nil "~/mpd.log" t 1))
 297
 298;;; (write-region "" nil "~/mpd.log" nil 1)
 299
 300;;;; Connection object internals, library users... please close ur eyes ;)
 301
 302;;; A connection to mpd is represented by the vector whose elements are below:
 303;;; This is just for the hackers amongst you, and for my reference :)
 304;;; *WARNING* No program using this package should depend on this description.
 305;;; 0 : vector of mpd-ver-string-length version numbers
 306;;; 1 : process object for the connection to the server.
 307;;; 2 : transaction-buffer used by the process filter to handle partial recvs.
 308;;; 3 : list-mode - refer `mpd-execute-command'
 309;;; 4 : connection-status: should be t at the beginning of a command.
 310;;;     stringp -> Result of command, contains the message after OK/ACK
 311;;;                till the next end of line. No filtering occurs after this
 312;;;                stage is reached.
 313;;;     numberp -> Intermediate state, when OK/ACK has been got
 314;;;                but no end of line found. Then store start pos of the OK/ACK.
 315;;;                Hope that eol will come in a subsequent packet.
 316;;;     listp   -> In commandlist mode, the list is what is to be sent to the
 317;;;                server after the commandlist gets over.
 318;;; 5 : last-command-result-flag - t if OK, nil if ACK.
 319;;; 6 : timeout - The timeout used for replies from server.
 320;;; 7 : host - The name of the host used to connect to mpd.
 321;;; 8 : port number - The port number of mpd.
 322;;; 9 : automode - not of the noauto argument to `mpd-conn-new'.
 323
 324;; Don't expose these macros, unless required.
 325(eval-when-compile
 326  (defmacro _mpdgv () `(aref conn 0))
 327  (defmacro _mpdsv (val) `(aset conn 0 ,val))
 328  (defmacro _mpdgo () `(aref conn 1))
 329  (defmacro _mpdso (val) `(aset conn 1 ,val))
 330  (defmacro _mpdgb () `(aref conn 2))
 331  (defmacro _mpdsb (val) `(aset conn 2 ,val))
 332  (defmacro _mpdgl () `(aref conn 3))
 333  (defmacro _mpdsl (val) `(aset conn 3 ,val))
 334  (defmacro _mpdgs () `(aref conn 4))
 335  (defmacro _mpdss (val) `(aset conn 4 ,val))
 336  (defmacro _mpdgf () `(aref conn 5))
 337  (defmacro _mpdsf (val) `(aset conn 5 ,val))
 338  (defmacro _mpdgt () `(aref conn 6))
 339  (defmacro _mpdst (val) `(aset conn 6 ,val))
 340  (defmacro _mpdgh () `(aref conn 7))
 341  (defmacro _mpdsh (val) `(aset conn 7 ,val))
 342  (defmacro _mpdgp () `(aref conn 8))
 343  (defmacro _mpdsp (val) `(aset conn 8 ,val))
 344  (defmacro _mpdga () `(aref conn 9))
 345  (defmacro _mpdsa (val) `(aset conn 9 ,val)))
 346
 347;;;; Sanity check functions.
 348
 349(defun mpd-connp (conn)
 350  "Return t if CONN is a connection to the mpd server."
 351  (and (vectorp conn) (= (length conn) 10)))
 352(put 'mpd-connp 'side-effect-free 'error-free)
 353
 354(defun mpd-conn-strongp (conn)
 355  (and (mpd-connp conn)
 356       (vectorp (_mpdgv))
 357       (= (length (_mpdgv)) mpd-ver-string-length)
 358       (or (not (_mpdgo)) (processp (_mpdgo)))
 359       (stringp (_mpdgb))
 360       (or (not (_mpdgt)) (numberp (_mpdgt)))
 361       (stringp (_mpdgh))
 362       (wholenump (_mpdgp))))
 363(put 'mpd-conn-strongp 'side-effect-free 'error-free)
 364
 365(defmacro mpd-assert-mpd-conn (conn) `(mpd-assert-type ,conn mpd-connp))
 366
 367(defun mpd-assert-idle (conn)
 368  "Assert mpd connection CONN to be free to receive a command."
 369  (mpd-assert-mpd-conn conn)
 370  (or (stringp (_mpdgs))
 371      (error (if (listp (_mpdgs)) "Command list mode has not ended"
 372	       "Not done processing current command"))))
 373
 374(defun mpd-end-conn (conn fmt &rest args)
 375  "Abort mpd conection CONN and signal error.
 376`format' error message using FMT and ARGS."
 377  (when (_mpdgo)
 378    (delete-process (_mpdgo))
 379    (_mpdso nil))
 380  (signal 'error (list (apply 'format fmt args))))
 381
 382(defun mpd-end-cmd (conn fmt &rest args)
 383  "Abort current mpd command for connection CONN and signal error.
 384`format' error message using FMT and ARGS."
 385  (_mpdsf nil)
 386  (_mpdss (apply 'format fmt args))
 387  (signal 'error (list (apply 'format fmt args))))
 388
 389;;;; Internal functions.
 390
 391(defun mpd-process-filter (conn str)
 392  "Filter replies received from the mpd server.
 393CONN represents the connection object for which the filter is invoked.
 394STR is the packet received from the server to be processed.
 395This is an internal function, do not use this in your code."
 396  (cond
 397   ((eq (_mpdgs) t)
 398    (let ((start (length (_mpdgb))) status (case-fold-search nil))
 399      ;; Can be 4 if not for the : as mentioned below
 400      (setq start (if (< start 5) 0 (- start 5)))
 401      (_mpdsb (concat (_mpdgb) str))
 402      ;; The optional : in ACK accomodates the ACK: reply to a failed `add'
 403      ;; given by MPD servers till version 0.10.*
 404      (setq status (string-match "^\\(ACK:? \\|OK\\)"
 405				 (substring (_mpdgb) start)))
 406      (if status (setq status (+ status start)))
 407      (or (eq (_mpdgl) t)
 408	  (let ((packets
 409		 (if (not (equal status 0))
 410		     (mpd-get-lines (substring (_mpdgb) 0
 411					       (and status (1- status)))))))
 412	    (if status
 413		(progn
 414		  (_mpdsb (substring (_mpdgb) status))
 415		  (setq status 0))
 416	      (if (cdr packets)
 417		  (let ((last (last packets 2)))
 418		    (_mpdsb (cadr last))
 419		    (setcdr last nil))
 420		(_mpdsb (car packets))
 421		(setq packets nil)))
 422	    (cond
 423	     ((functionp (_mpdgl))
 424	      (mapcar '(lambda (str)
 425			 (funcall (_mpdgl) conn (mpd-parse-line str)))
 426		      packets))
 427	     ((listp (_mpdgl))
 428	      (_mpdsl (nconc (mapcar 'mpd-parse-line packets) (_mpdgl))))
 429	     (t (error "Invalid line mode filter")))))
 430      (when status
 431	(_mpdss status)
 432	(mpd-process-filter conn ""))))
 433   ((numberp (_mpdgs))
 434    (_mpdsb (concat (_mpdgb) str))
 435    (let* ((resp-end (_mpdgs))
 436	   (bufend (string-match "\n" (_mpdgb) resp-end)))
 437      (when bufend
 438	(_mpdss (substring
 439		 (_mpdgb)
 440		 ;; The `4` below should be `5` if ACK: is found, see above.
 441		 ;; This may then leave a space before the error message.
 442		 (+ resp-end (if (_mpdsf (eq (aref (_mpdgb) resp-end)?O)) 2 4))
 443		 bufend))
 444	(_mpdsb (substring (_mpdgb) 0 resp-end))
 445	(throw 'mpd-response-over nil))))))
 446
 447(defun mpd-transact (conn &optional mode input)
 448  "Do an I/O transacton with the mpd server.
 449Use connection CONN for contacting the server. MODE is as described in
 450`mpd-execute-command'. Send INPUT, if non-nil, to the server before waiting for
 451the response. This is an internal function, do not use this in your code."
 452  (_mpdsb "")
 453  (_mpdsl mode)
 454  (_mpdss t)
 455  (unwind-protect
 456      (let ((timeout (_mpdgt)))
 457	(and input (process-send-string (_mpdgo) input))
 458	(while (not (stringp (_mpdgs)))
 459	  (catch 'mpd-response-over
 460	    (or (accept-process-output
 461		 (_mpdgo) (and timeout (/ timeout 1000))
 462		 (and timeout (mod timeout 1000)))
 463		;; This essentially reduces the probability of timeout due to
 464		;; program suspension to a really small probability.
 465		(accept-process-output (_mpdgo) 0 0)
 466		(error "Timed out getting a response from the mpd server")))))
 467    (unless (stringp (_mpdgs))
 468      (_mpdsf nil)
 469      (_mpdss "")
 470      (_mpdsb ""))))
 471
 472;;;; Low level public interface.
 473
 474(defun mpd-conn-new (host port &optional timeout noauto)
 475  "Construct a mpd connection object from given parameters.
 476Connections made using this object are made to host HOST at port number PORT.
 477TIMEOUT is a floating-point value specifying the number of seconds to wait
 478before giving up waiting for a reply from the server. Unspecified or zero
 479TIMEOUT correspond to infinite timeout. Set automatic mode if NOAUTO is nil,
 480hooked automatic function if NOAUTO is a function, and do not set automatic mode
 481otherwise. See `mpd-set-automatic-mode' for a description of automatic mode."
 482  (or (and (stringp host) (wholenump port) (or (not timeout) (numberp timeout)))
 483      (error "Invalid parameters passed for making new connection"))
 484  (and timeout
 485       (if (= timeout 0)
 486	   (setq timeout nil)
 487	 (or (> timeout 0) (error "Invalid (negative) timeout value"))))
 488  ;; Declaration conn object structure dependent.
 489  (vector (make-vector mpd-ver-string-length nil) nil
 490	  "" nil "" nil (and timeout (floor (* timeout 1000)))
 491	  host port (if noauto (if (functionp noauto) noauto nil) t)))
 492
 493(eval-when-compile
 494  (or (fboundp 'set-process-query-on-exit-flag)
 495      (defmacro set-process-query-on-exit-flag (process flag)
 496	`(process-kill-without-query process flag))))
 497
 498(defun mpd-connect (conn)
 499  "Connect to the mpd server using the connection object CONN.
 500The connection object is constructed using `mpd-conn-new'. Note that this
 501function doesn't need to be explicitly called when the connection is in
 502automatic mode (the default). Close the connection using `mpd-close-connection'
 503when you are done."
 504  (let (proc rt welc)
 505    (setq proc (or (open-network-stream "mpd" nil (_mpdgh) (_mpdgp))
 506		   (error "Unable to open connection with mpd")))
 507    (and (_mpdgo) (delete-process (_mpdgo)))
 508    (_mpdso proc)
 509    (set-process-query-on-exit-flag proc nil)
 510    (set-process-coding-system proc 'utf-8-unix 'utf-8-unix)
 511    (set-process-filter proc
 512			`(lambda (proc str)
 513			   (mpd-process-filter ,conn str)))
 514    (unwind-protect
 515	(mpd-transact conn t)
 516      (or (_mpdgf)
 517	  (mpd-end-conn conn "Handshake failed, server returned: %s" (_mpdgs))))
 518    (setq welc (_mpdgs))
 519    (or (string-equal (substring welc 0 (mpd-welcome-length))
 520		      mpd-welcome-message)
 521	(mpd-end-conn conn "Process mpd not running on port %d in host \"%s\""
 522		      (_mpdgp) (_mpdgh)))
 523    (let ((verlist (split-string (substring welc (mpd-welcome-length))
 524				 "[\\.\n]")))
 525      (or (= (length verlist) mpd-ver-string-length)
 526	  (mpd-end-conn conn "Error parsing version information from server"))
 527      (let ((i 0))
 528	(while (< i mpd-ver-string-length)
 529	  (or (aset (_mpdgv) i (mpd-string-to-number-strict
 530				(pop verlist)))
 531	      (mpd-end-conn
 532	       conn "Error parsing version information from server"))
 533	  (setq i (1+ i))))))
 534  (message "Opened connection with mpd"))
 535
 536(make-obsolete 'mpd-new-connection "use `mpd-conn-new' to create a connection \
 537object. If automatic mode is not used, a call to `mpd-connect' might be \
 538necessary." "2.1")
 539(defun mpd-new-connection (host port &optional timeout noauto)
 540  "Create a new connection and connect using it to the mpd server.
 541Return a connection object, which could be used for further transactions with
 542the server. See `mpd-conn-new' for a description of the parameters. Close the
 543connection using `mpd-close-connection' when you are done.
 544
 545This function is deprecated since version 2.1.
 546Use `mpd-conn-new' to create a connection object.
 547If automatic mode is not used, a call to `mpd-connect' might be necessary."
 548  (let ((conn (mpd-conn-new host port timeout noauto)))
 549    (mpd-connect conn) conn))
 550
 551(defun mpd-conn-wakeup (conn)
 552  "Try to ensure that the MPD connection is alive, when in automatic mode."
 553  (mpd-assert-idle conn)
 554  (when (and (not (and (_mpdgo) (eq (process-status (_mpdgo)) 'open)))
 555	     (_mpdga) (not (and (functionp (_mpdga))
 556				(funcall (_mpdga) conn 'pre))))
 557    (and (_mpdgo) (message "Connection with mpd broken, attempting reconnect"))
 558    (mpd-connect conn)
 559    (and (functionp (_mpdga)) (funcall (_mpdga) conn 'post))))
 560
 561(defun mpd-execute-command (conn cmd &optional mode)
 562  "Send the command CMD to the mpd server using CONN.
 563Append a newline to CMD before sending to the server. Use the value of MODE to
 564decide how the response of the command is processed. MODE could take one of the
 565following values:
 566 - A list, usually nil, to which cons-cells got by formatting each line
 567   of the response, except the last one, using `mpd-parse-line', are appended.
 568   The new list thus got is the result of the function.
 569 - t, by which all response before the last line, as a string,
 570   is the result of the function.
 571 - A function, by which each cons-cell, got as described above, is sent to
 572   this function. Two parameters are passed to the function, the connection
 573   object and this cons-cell. An empty string is the result.
 574Return a cons-cell, whose car is non-nil if the server succeeds, and cdr is the
 575result as specified in the description of MODE above."
 576  (mpd-conn-wakeup conn)
 577  (mpd-transact conn mode (concat cmd "\n"))
 578  (prog1
 579      (cons (_mpdgf)
 580	    ;; Declaration conn object structure dependent.
 581	    (aref conn (if (_mpdgf) (if (or (eq (_mpdgl) 't)
 582					    (functionp (_mpdgl))) 2 3) 4)))
 583    (_mpdsb "")))
 584
 585(defun mpd-simple-exec (conn cmd)
 586  "Execute mpd command CMD using CONN ignoring response.
 587Note that an OK/ACK message still has to come. Return non-nil iff the command
 588succeeds. `mpd-get-last-error' gives the server error message in case of
 589failure. See also `mpd-execute-command'."
 590  (if (not (listp (_mpdgs)))
 591      (car (mpd-execute-command conn cmd t))
 592    (_mpdss (cons cmd (_mpdgs))) t))
 593
 594(defun mpd-close-connection (conn)
 595  "Close the mpd server connection given by CONN."
 596  (mpd-assert-idle conn)
 597  (when (_mpdgo)
 598    (delete-process (_mpdgo))
 599    (_mpdso nil)))
 600
 601(defvar mpd-inter-conn
 602  (apply 'mpd-conn-new `(,@(mpd-connection-tidy
 603			    mpd-interactive-connection-parameters) nil))
 604  "The global mpd connection used for interactive queries.")
 605
 606(defsubst mpd-get-version (conn)
 607  "Get version information for the mpd server CONN is connected to.
 608Return a vector of three numbers, for the major, minor and patch levels."
 609  (mpd-assert-mpd-conn conn)
 610  (or (aref (_mpdgv) 0) (mpd-conn-wakeup conn))
 611  (_mpdgv))
 612(put 'mpd-get-version 'side-effect-free t)
 613
 614(defsubst mpd-get-last-error (conn)
 615  "Get the last server error message for mpd connection CONN.
 616Return nil in case of a successful last command."
 617  (mpd-assert-mpd-conn conn)
 618  (and (not (_mpdgf)) (_mpdgs)))
 619(put 'mpd-get-last-error 'side-effect-free t)
 620
 621(defsubst mpd-get-connection-timeout (conn)
 622  "Get the timeout of mpd connection CONN.
 623Return nil if CONN isn't a mpd connection object."
 624  (mpd-assert-mpd-conn conn)
 625  (_mpdgt))
 626(put 'mpd-get-connection-timeout 'side-effect-free t)
 627
 628;;;###autoload
 629(defun mpd-set-connection-timeout (conn timeout)
 630  "Set the timeout of mpd connection object CONN to TIMEOUT.
 631See also `mpd-new-connection'."
 632  (interactive
 633   (list mpd-inter-conn
 634	 (string-to-number
 635	  (read-string
 636	   "Connection timeout (None): "
 637	   (let ((timeout (mpd-get-connection-timeout mpd-inter-conn)))
 638	     (and timeout (number-to-string timeout)))))))
 639  (if (or (not (mpd-connp conn)) (and timeout (not (numberp timeout))))
 640      (error "Invalid parameters used to set connection timeout")
 641    (and (= timeout 0) (setq timeout nil))
 642    (_mpdst timeout)))
 643
 644(defsubst mpd-get-automatic-mode (conn)
 645  "Return the automatic mode value for mpd connection CONN.
 646See `mpd-set-automatic-mode' for a description of automatic mode. Return value
 647is like the MODE parameter of that function."
 648  (mpd-assert-mpd-conn conn)
 649  (_mpdga))
 650(put 'mpd-get-automatic-mode 'side-effect-free t)
 651
 652(defsubst mpd-set-automatic-mode (conn mode)
 653  "Set the automatic mode for mpd connection CONN.
 654Set automatic mode iff MODE is non-nil. Set to hooked automatic mode if the
 655non-nil MODE is a function.
 656
 657The connection is said to be in automatic mode if it connects on demand (usually
 658as a result of a server request using the connection) and takes care of
 659reconnecting whenever the connection gets broken. The automatic mode could be
 660hooked by specifying a function. In this case, the function is called with two
 661parameters, the connection object and the second parameter being the symbol 'pre
 662or 'post. The function is called with 'pre when reconnection is needed. If the
 663function returns a non-nil value, it is assumed that the function has done the
 664reconnection. Else, the library reconnects by itself, and the function is once
 665again called after that, with the parameter 'post (Return value is ignored)."
 666  (mpd-assert-mpd-conn conn)
 667  (_mpdsa mode))
 668
 669(make-obsolete 'mpd-get-reconnectible 'mpd-get-automatic-mode "2.1")
 670(defalias 'mpd-get-reconnectible 'mpd-get-automatic-mode
 671  "Return t if CONN reconnects when its mpd connection breaks.
 672This function is deprecated since version 2.1.
 673Use `mpd-get-automatic-mode' instead.")
 674
 675(make-obsolete 'mpd-make-reconnectible 'mpd-set-automatic-mode "2.1")
 676(defsubst mpd-make-reconnectible (conn &optional noreconn)
 677  "Make CONN reconnectible when its mpd connection breaks.
 678Unset the reconnectibility on non-nil prefix arg NORECONN.
 679This function is deprecated since version 2.1.
 680Use `mpd-set-automatic-mode' instead."
 681  (mpd-set-automatic-mode conn (not noreconn)))
 682
 683(defun mpd-force-accept-command (conn)
 684  "Force the mpd connection CONN to accept the next command.
 685*WARNING* DON'T use this unless you are really desperate. Shelf this off for
 686debugging purposes. Normally, the package should signal correctly whether it is
 687safe to receive the next command. Doing this could mean losing out on the
 688response of the current command, and worse, the response of the current command
 689could creep into the next one."
 690  (if (mpd-command-list-mode-p conn)
 691      (error "Command list mode has not ended")
 692    (_mpdsf nil)
 693    (_mpdss "")
 694    (_mpdsb "")))
 695
 696(defun mpd-command-list-begin (conn)
 697  "Start the command-list mode for CONN.
 698Only commands that can be used with `mpd-simple-exec' are allowed in
 699command-list mode. Commands can be issued as they are usually done.
 700Always return t, as the commands are queued up and not sent to the server.
 701`mpd-command-list-end' ends the command-list and executes the list built up."
 702  (mpd-assert-idle conn)
 703  (_mpdsf nil)				; FIXME: Why is this here ??
 704  (_mpdss nil))
 705
 706(defun mpd-command-list-mode-p (conn)
 707  "Return non-nil if mpd connection CONN is in command list mode."
 708  (mpd-assert-mpd-conn conn)
 709  (listp (_mpdgs)))
 710(put 'mpd-command-list-mode-p 'side-effect-free t)
 711
 712(defun mpd-command-list-end (conn)
 713  "End command-list mode for CONN.
 714This function needs to be preceded by a call to `mpd-command-list-begin'."
 715  (or (mpd-command-list-mode-p conn)
 716      (error "The connection is not in command-list mode"))
 717  (let (str)
 718    (setq str (concat "command_list_begin\n"
 719		      (mapconcat '(lambda (item) item) (nreverse (_mpdgs)) "\n")
 720		      "\ncommand_list_end"))
 721    (_mpdss "")
 722    (mpd-simple-exec conn str)))
 723
 724(defun mpd-connection-status (conn)
 725  "Get the status of mpd connection CONN.
 726Return one of 'busy for being in the midst of a request, 'ready for the ready
 727state, and 'command-list to indicate being in command-list mode."
 728  (mpd-assert-mpd-conn conn)
 729  (cond
 730   ((listp (_mpdgs)) 'command-list)
 731   ((stringp (_mpdgs)) 'ready)
 732   (t 'busy)))
 733(put 'mpd-connection-status 'side-effect-free t)
 734
 735(defmacro with-mpd-timeout-disabled (&rest args)
 736  `(progn
 737     (mpd-assert-mpd-conn conn)
 738     (let ((timeout (_mpdgt)))
 739       (unwind-protect
 740	   (progn
 741	     (_mpdst nil)
 742	     ,@args)
 743	 (_mpdst timeout)))))
 744(put 'with-mpd-timeout-disabled 'lisp-indent-function 0)
 745(def-edebug-spec with-mpd-timeout-disabled (body))
 746
 747;;; High level public interface helper functions.
 748
 749;;; Silence the compiler.
 750(defvar mpd-song-receiver)
 751(defvar foreach)
 752(defvar mpd-song-receiver-args)
 753
 754(defun mpd-song-receiver (conn cell)
 755  "Handle song data response from the mpd server.
 756See `mpd-execute-command' for a description of response handlers.
 757This is an internal function, do not use this in your code."
 758  (let ((key (car cell)))
 759    (unless (or (string-equal key "directory")
 760		(string-equal key "playlist"))
 761      (when (string-equal key "file")
 762	(when (plist-get mpd-song-receiver 'file)
 763	  (setq foreach (apply 'mpd-seq-add mpd-song-receiver
 764			       foreach mpd-song-receiver-args)))
 765	(setq mpd-song-receiver nil))
 766      (setq mpd-song-receiver
 767	    (plist-put mpd-song-receiver (intern key)
 768		       (if (member key '("Time" "Pos" "Id"))
 769			   (string-to-number (cdr cell)) (cdr cell)))))))
 770
 771(defun mpd-get-songs (conn cmd &optional foreach)
 772  "Get details of songs from the mpd server using connection CONN.
 773Use command CMD to get the songs. Call function FOREACH, if specified, for each
 774song, with the song provided as the argument. Return list of all songs if
 775FOREACH is not specified and FOREACH otherwise. When a list is returned, each
 776element of the list is a property list, some known keys being `file', `Pos' and
 777`Id'. The last two keys, along with `Time' if present, have integers as their
 778value. `Time' refers to the total length of the song. `Pos' and `Id' are present
 779only when the song retrieved is a part of a playlist. Other song tag data might
 780be present as well."
 781  (or (functionp foreach) (setq foreach nil))
 782  (let (mpd-song-receiver mpd-song-receiver-args)
 783    (mpd-execute-command conn cmd 'mpd-song-receiver)
 784    (and mpd-song-receiver
 785	 (setq foreach (mpd-seq-add mpd-song-receiver foreach)))
 786    (mpd-safe-nreverse foreach)))
 787
 788(defun mpd-make-cmd-concat (cmd arg &optional normal-nil)
 789  "Make mpd command string using command CMD and argument ARG.
 790ARG could be a string or a list of strings. If NORMAL-NIL is non-nil, do nothing
 791if ARG is nil, and return CMD for nil NORMAL-NIL and ARG. Use command list
 792mode implicitly for lists. Sanitize arguments before composition.
 793This is an internal function, do not use this in your code."
 794  (cond
 795   ((not (or arg normal-nil)) cmd)
 796   ((listp arg)
 797    (concat "command_list_begin\n"
 798	    (mapconcat '(lambda (item) (concat cmd " " (mpd-safe-string item)))
 799		       arg "\n") "\ncommand_list_end"))
 800   (t (concat cmd " " (mpd-safe-string arg)))))
 801(put 'mpd-make-cmd-concat 'side-effect-free t)
 802
 803(defun mpd-make-cmd-format (cmd validate arg1 &optional arg2)
 804  "Make mpd command string with format CMD and arguments ARG1, ARG2.
 805ARG1/ARG2 could be a list of arguments each. Use command list mode implicitly
 806for lists. Send each of the arguments pairs to VALIDATE before composition.
 807This is an internal function, do not use this in your code."
 808  (if (listp arg1)
 809      (let ((tail2 arg2))
 810	(and arg2 (or (= (length arg1) (length arg2))
 811		      (error "Argument lists are of unequal lengths")))
 812	(concat "command_list_begin\n"
 813		(mapconcat '(lambda (item)
 814			      (and validate (funcall validate item (car tail2)))
 815			      (prog1
 816				  (format cmd item (car tail2))
 817				(setq tail2 (cdr tail2)))) arg1 "\n")
 818		"\ncommand_list_end"))
 819    (and validate (funcall validate arg1 arg2))
 820    (format cmd arg1 arg2)))
 821
 822;;; Helper functions for interactive display.
 823
 824(defun mpd-line-to-buffer (str)
 825  "Insert STR as a line to the mpd display buffer."
 826  (with-current-buffer (get-buffer-create "*mpd-display*")
 827    (with-mpd-free-buffer
 828      (goto-char (point-max))
 829      (insert (concat str "\n"))
 830      (display-buffer (current-buffer)))))
 831
 832(defsubst mpd-separator-line (&optional num)
 833  "Make a separator line for insertion to the mpd display buffer.
 834The number of columns used is 80, unless specified using NUM."
 835  (propertize (concat (make-string (or num 80) ?*) "\n")
 836	      'face 'mpd-separator-face))
 837
 838(defun mpd-init-buffer (&optional str1 str2 str3)
 839  "Initialize the mpd display buffer using strings STR1, STR2, STR3.
 840Layout as follows:
 841	STR1 		if non-empty
 842	***...*** 	if STR1 is non-nil
 843	STR2 		if non-empty
 844	***...*** 	if STR2 is non-nil
 845	STR3		if non-empty"
 846  (let ((max (max (length str1) (length str2) (length str3))))
 847    (with-current-buffer (get-buffer-create "*mpd-display*")
 848      (erase-buffer)
 849      (insert
 850       (concat "\n" (and str1 (not (string-equal str1 ""))
 851			 (propertize (concat str1 "\n") 'face 'mpd-header-face))
 852	       (and str1 (mpd-separator-line max))
 853	       (and str2 (propertize (concat str2 "\n") 'face 'mpd-header-face))
 854	       (and str3 (mpd-separator-line max))
 855	       (and str3 (not (string-equal str3 ""))
 856		    (propertize (concat str3 "\n")
 857				'face 'mpd-header-face)) "\n")))))
 858
 859(defun mpd-render-field (desc val &optional nosep)
 860  "Format to a colorized line of form \"\\nDESC: VAL\".
 861Include the separating colon unless NOSEP is non-nil."
 862  (and val (not (string-equal val ""))
 863       (concat (propertize desc 'face 'mpd-first-field-face)
 864	       (and (not nosep) ": ")
 865	       (propertize val 'face 'mpd-second-field-face) "\n")))
 866(put 'mpd-render-field 'side-effect-free t)
 867
 868(defun mpd-render-plist (plist table)
 869  "Display property list PLIST as a pretty table in the display buffer.
 870TABLE is a list of entries is used to translate between keys and strings
 871displayed. Each entry of the table is a list of the key symbol, the
 872corresponding string to display, and optionally a function to call with the
 873value to get the string to display as the value. If the string to display is
 874nil, then those keys are ignored and not displayed at all."
 875  (when plist
 876    (let ((ptr plist) (str "") key value entry trans filter)
 877      (while ptr
 878	(setq key (car ptr))
 879	(setq value (cadr ptr))
 880	(when (and key value)
 881	  (setq entry (assq key table))
 882	  (setq trans (if entry (cadr entry) (symbol-name key)))
 883	  (setq filter (cadr (cdr entry)))
 884	  (when trans
 885	    (setq str (concat str (mpd-render-field
 886				   (format "%-13s" trans)
 887				   (format "%s" (if filter
 888						    (funcall filter value)
 889						  value)))))))
 890	(setq ptr (cddr ptr)))
 891      (mpd-line-to-buffer (concat str "\n" (mpd-separator-line))))))
 892
 893(defconst mpd-display-song-key-table
 894  '((Time "Length" (lambda (time) (format "%s seconds" time)))
 895    (file "Filename")
 896    (Pos)
 897    (Id)))
 898
 899(defsubst mpd-display-song (song)
 900  "Display mpd song data SONG in the mpd display buffer."
 901  (mpd-render-plist song mpd-display-song-key-table))
 902
 903(defun mpd-display-playlist-item (title num)
 904  "Display playlist item with TITLE and index NUM in mpd buffer."
 905  (mpd-line-to-buffer
 906   (concat (propertize (format "%4d  " (1+ num)) 'face 'mpd-first-field-face)
 907	   (propertize title 'face 'mpd-second-field-face))))
 908
 909(defun mpd-display-dir-info (item type)
 910  "Display mpd directory information to the mpd display buffer."
 911  (if (eq type 'file)
 912      (mpd-display-song item)
 913    (mpd-line-to-buffer
 914     (concat (mpd-render-field
 915	      (if (eq type 'playlist) "Playlist    " "Directory   ") item)
 916	     "\n" (mpd-separator-line)))))
 917
 918(defun mpd-display-dir-listing (item dir)
 919  "Display mpd directory listing to the mpd display buffer."
 920  (mpd-line-to-buffer
 921   (concat (mpd-render-field (if dir "Directory " "File      " ) item)
 922	   "\n" (mpd-separator-line))))
 923
 924(defsubst mpd-display-bullet (str)
 925  "Display a bulleted line to the mpd display buffer."
 926  (mpd-line-to-buffer (mpd-render-field " o " (format "%s" str) t)))
 927
 928(defconst mpd-display-output-key-table
 929  '((outputid "ID")
 930    (outputname "Name")
 931    (outputenabled "Enabled"
 932		   (lambda (enabled)
 933		     (cond
 934		      ((eq enabled t) "Yes")
 935		      ((eq enabled nil) "No")
 936		      (t enabled))))))
 937
 938(defsubst mpd-display-output (output)
 939  "Display mpd output OUTPUT in the mpd display buffer."
 940  (mpd-render-plist output mpd-display-output-key-table))
 941
 942(defun mpd-read-item (prompt &optional default zero allowneg)
 943  "Read a number from the minibuffer.
 944Display PROMPT as the prompt string prefix. Append the DEFAULT value,
 945if present, in brackets. Return the number read. Unless ZERO is non-nil,
 946add default value by one before operation, and decrement number read
 947by 1 before returning. If ALLOWNEG is non-nil, allow negative numbers."
 948  (let (num str)
 949    (and default (setq str (number-to-string (if zero default (1+ default)))))
 950    (setq
 951     num (mpd-string-to-number-strict
 952	  (read-string (concat prompt (if default (concat " (" str ")")) ": ")
 953		       nil nil str) allowneg))
 954    (if (and num (not zero)) (1- num) num)))
 955
 956;;;; High level public interface.
 957
 958;;;  These functions require response, and hence cannot be queued by using the
 959;;;  command-line mode.
 960
 961(defun mpd-status-receiver (lsym cell)
 962  "Handle response for the 'status' command to the mpd server.
 963See `mpd-execute-command' for a description of response handlers.
 964This is an internal function, do not use this in your code."
 965  (let ((sym (car cell)))
 966    (cond
 967     ((member sym '("volume" "repeat" "random" "playlist" "playlistlength"
 968		    "bitrate" "song" "songid" "xfade" "updating_db"))
 969      (put lsym (intern sym) (string-to-number (cdr cell))))
 970     ((string-equal sym "state")
 971      (and (member (cdr cell) '("play" "pause" "stop"))
 972	   (put lsym (intern sym) (intern (cdr cell)))))
 973     ((string-equal sym "time")
 974      (when (string-match "^\\([0-9]*\\):\\([0-9]*\\)$" (cdr cell))
 975	(put lsym 'time-elapsed (string-to-number (match-string 1 (cdr cell))))
 976	(put lsym 'time-total (string-to-number (match-string 2 (cdr cell))))))
 977     ((string-equal sym "audio")
 978      (when (string-match "^\\([0-9]*\\):\\([0-9]*\\):\\([0-9]*\\)$" (cdr cell))
 979	(put lsym 'sample-rate (string-to-number (match-string 1 (cdr cell))))
 980	(put lsym 'bits-per-sample
 981	     (string-to-number (match-string 2 (cdr cell))))
 982	(put lsym 'channels (string-to-number (match-string 3 (cdr cell))))))
 983     ;; currently only "error"
 984     (t (put lsym (intern sym) (cdr cell))))))
 985
 986(defun mpd-get-status (conn)
 987  "Get status of the mpd server, using connection CONN.
 988Return a property list, the known keys being `volume', `repeat', `random',
 989`playlist', `playlistlength', `bitrate', `song', `songid', `xfade', `state',
 990`time-elapsed', `time-total', `sample-rate', `bits-per-sample', `channels'
 991and `error'. Other keys might be present in the plist depending on the version
 992of MPD used. Some of the less obvious descriptions are:
 993
 994+-----------+------------------------------------------------------------------+
 995| playlist  |The playlist version number - a 'checksum' for the current        |
 996|           |playlist, guaranteed to change after a change in the playlist.    |
 997+-----------+------------------------------------------------------------------+
 998|  xfade    |The crossfade time in seconds between two songs occurs. This value|
 999|           |could be zero in case of no crossfade.                            |
1000+-----------+------------------------------------------------------------------+
1001|   song    |Position of the current song in the playlist.                     |
1002+-----------+------------------------------------------------------------------+
1003|  songid   |Song ID of the current song in the playlist.                      |
1004+-----------+------------------------------------------------------------------+
1005|  state    |Could be one of 'play, 'pause or 'stop                            |
1006+-----------+------------------------------------------------------------------+
1007|updating_db|The updating job id, displayed when there is a update taking      |
1008|           |place.                                                            |
1009+-----------+------------------------------------------------------------------+
1010|  error    |A description of the error, if one occurs. Could be nil, in case  |
1011|           |there is no error.                                                |
1012+-----------+------------------------------------------------------------------+
1013
1014All fields except error and state are whole numbers. `repeat' and `random' are
1015in addition, bi-state variables (0/1)"
1016  (setplist 'mpd-get-status-local-sym nil)
1017  (mpd-execute-command
1018   conn "status" '(lambda (conn cell)
1019		    (mpd-status-receiver 'mpd-get-status-local-sym cell)))
1020  (symbol-plist 'mpd-get-status-local-sym))
1021
1022(defun mpd-stats-receiver (lsym cell)
1023  "Handle response for the 'stats' command to the mpd server.
1024See `mpd-execute-command' for a description of response handlers.
1025This is an internal function, do not use this in your code."
1026  (let ((sym (car cell)))
1027    (cond
1028     ((member sym '("artists" "albums" "songs" "uptime"
1029		    "playtime" "db_playtime" "db_update"))
1030      (put lsym (intern sym) (string-to-number (cdr cell))))
1031     (t (put lsym (intern sym) (cdr cell))))))
1032
1033(defun mpd-get-stats (conn)
1034  "Get statistics for the mpd server connected using CONN.
1035Return a property list, the known keys being `artists', `albums', `songs',
1036`db_playtime' corresponding to the number of artists, albums and songs and the
1037total time in seconds of all songs in the database respectively; `db_update',
1038the time stamp of the last update to the database; `playtime', the total time
1039for which music has been played in seconds and `uptime', the server uptime,
1040in seconds as well. Other keys might be present in the plist depending on the
1041version of MPD used."
1042  (setplist 'mpd-get-stats-local-sym nil)
1043  (mpd-execute-command
1044   conn "stats" '(lambda (conn cell)
1045		   (mpd-stats-receiver 'mpd-get-stats-local-sym cell)))
1046  (symbol-plist 'mpd-get-stats-local-sym))
1047
1048;;;###autoload
1049(defun mpd-get-playlist (conn &optional foreach)
1050  "Get all songs in the current playlist managed by the mpd server.
1051CONN and FOREACH are as in `mpd-get-songs'."
1052  (interactive (progn (mpd-init-buffer "" "Current MPD Playlist" "")
1053		      (list mpd-inter-conn 'mpd-display-playlist-item)))
1054  (or (functionp foreach) (setq foreach nil))
1055  (mpd-execute-command
1056   conn "playlist" '(lambda (conn cell)
1057		      (setq foreach (mpd-elt-add
1058				     (cdr cell) foreach
1059				     (string-to-number (car cell))))))
1060  (mpd-safe-nreverse foreach))
1061
1062;;;###autoload
1063(defun mpd-get-playlist-entry (conn &optional item foreach use-id)
1064  "Get song data for entr(y/ies) ITEM in the current mpd playlist.
1065CONN, FOREACH and the return value are as in `mpd-get-songs'.
1066ITEM is the item position/id or a list of it. Note that ITEM as nil fetches
1067data for all entries in the current playlist rather than not doing anything.
1068Interpret ITEM as song id(s) iff USE-ID is non-nil."
1069  (interactive
1070   (let ((item (mpd-read-item
1071		"Enter item number"
1072		(plist-get (mpd-get-status mpd-inter-conn) 'song))))
1073     (mpd-init-buffer "" (format "MPD Playlist Item # %d" (1+ item)) "")
1074     (list mpd-inter-conn item 'mpd-display-song)))
1075  (setq use-id (if use-id "playlistid" "playlistinfo"))
1076  (mpd-get-songs
1077   conn (if item
1078	    (mpd-make-cmd-format
1079	     (concat use-id " %d")
1080	     '(lambda (item item2) (mpd-assert-wholenump item))
1081	     item) use-id) foreach))
1082
1083;;;###autoload
1084(defun mpd-get-current-song (conn &optional foreach)
1085  "Get song data for the current song in mpd.
1086CONN and FOREACH are as in `mpd-get-songs'. Return FOREACH if specified, and
1087the current song (see `mpd-get-songs' for how a song is represented) otherwise."
1088  (interactive (progn (mpd-init-buffer "" "Current MPD Song" "")
1089		      (list mpd-inter-conn 'mpd-display-song)))
1090  (setq foreach (mpd-get-songs conn "currentsong" foreach))
1091  (or (functionp foreach) (setq foreach (car foreach))) foreach)
1092
1093(defun mpd-get-playlist-changes (conn version &optional foreach nometa)
1094  "Get a list of changed song entries in the current mpd playlist.
1095CONN, FOREACH and the return value are as in `mpd-get-songs'.
1096Calculate the change between the current playlist and the playlist
1097with version number VERSION. Do not fetch complete metadata (only position and
1098song id is returned for each song) if NOMETA is non-nil."
1099  (mpd-assert-numberp version)
1100  (mpd-get-songs conn (format "%s %d" (if nometa "plchangesposid" "plchanges")
1101			      version) foreach))
1102
1103;;;###autoload
1104(defun mpd-get-directory-songs (conn &optional directory foreach)
1105  "Get all songs in a directory of the mpd database.
1106CONN, FOREACH and the return value are as in `mpd-get-songs'.
1107DIRECTORY is the relative directory path wrt the database root.
1108DIRECTORY could be a list as well, the action then corresponds to all songs
1109in all the directories. Note that the nil value for DIRECTORY corresponds
1110to the database toplevel rather than an empty list."
1111  (interactive
1112   (let ((str (read-string "Enter relative directory: ")))
1113     (progn (mpd-init-buffer "" (concat "Songs in directory " str) "")
1114	    (list mpd-inter-conn str 'mpd-display-song))))
1115  (mpd-get-songs
1116   conn (mpd-make-cmd-concat "listallinfo" directory) foreach))
1117
1118;;;###autoload
1119(defun mpd-get-directory-info (conn &optional directory foreach)
1120  "Get directory info for DIRECTORY in the mpd database.
1121Use CONN as the mpd connection for the purpose and call function FOREACH,
1122if specified, with each information field. The arguments passed to FOREACH is a
1123song object, directory string or playlist string and one of the symbols 'file,
1124'playlist or 'directory describing the data sent. DIRECTORY could be a list as
1125well, the action then corresponds information for all the directories. Note that
1126a nil for DIRECTORY corresponds to the database toplevel rather than an empty
1127list. Return FOREACH, if non-nil; else a vector of three elements: a list of
1128songs in the directory (see `mpd-get-songs' for how a song is represented),
1129a list of playlists and a list of subdirectories."
1130  (interactive
1131   (let ((str (read-string "Enter relative directory: ")))
1132     (progn (mpd-init-buffer "" (concat "Information on directory " str) "")
1133	    (list mpd-inter-conn str 'mpd-display-dir-info))))
1134  (or (functionp foreach) (setq foreach nil))
1135  (let (filemode (pl foreach) (dir foreach)
1136		 (mpd-song-receiver-args '(file)) mpd-song-receiver)
1137    (mpd-execute-command
1138     conn (mpd-make-cmd-concat "lsinfo" directory)
1139     '(lambda (conn cell)
1140	(if (string-equal (car cell) "directory")
1141	    (setq dir (mpd-elt-add (cdr cell) dir 'directory))
1142	  (if (string-equal (car cell) "playlist")
1143	      (setq pl (mpd-elt-add (cdr cell) pl 'playlist))
1144	    (mpd-song-receiver conn cell)))))
1145    (and mpd-song-receiver
1146	 (setq foreach (mpd-seq-add mpd-song-receiver foreach 'file)))
1147    (if (functionp foreach) foreach
1148      (vector (nreverse foreach) (nreverse pl) (nreverse dir)))))
1149
1150;;;###autoload
1151(defun mpd-list-directory-recursive (conn foreach &optional directory)
1152  "Get the file-directory hierarchy of a directory in the mpd database.
1153Use CONN for the connection and use function FOREACH to report each entry,
1154along with a non-nil second argument if the entry is a directory. DIRECTORY
1155could be a list as well, the action then corresponds to listing of all the
1156directories. Note that the nil value for DIRECTORY corresponds to the
1157database toplevel rather than an empty list."
1158  (interactive
1159   (let ((str (read-string "Enter relative directory: ")))
1160     (progn
1161       (mpd-init-buffer "" (concat "Recursive listing of directory " str) "")
1162       (list mpd-inter-conn 'mpd-display-dir-listing str))))
1163  (mpd-assert-type foreach functionp)
1164  (mpd-execute-command conn (mpd-make

Large files files are truncated, but you can click here to view the full file