1;;; emms-player-mpd.el --- MusicPD support for EMMS
2
3;; Copyright (C) 2005, 2006, 2007, 2008, 2009, 2014 Free Software Foundation, Inc.
4
5;; Author: Michael Olson <mwolson@gnu.org>, Jose Antonio Ortega Ruiz
6;; <jao@gnu.org>
7
8;; This file is part of EMMS.
9
10;; EMMS is free software; you can redistribute it and/or modify it
11;; under the terms of the GNU General Public License as published by
12;; the Free Software Foundation; either version 3, or (at your option)
13;; any later version.
14;;
15;; EMMS is distributed in the hope that it will be useful, but WITHOUT
16;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
17;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
18;; License for more details.
19;;
20;; You should have received a copy of the GNU General Public License
21;; along with EMMS; see the file COPYING.  If not, write to the
22;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23;; Boston, MA 02110-1301, USA.
24
25;;; Commentary:
26
27;;; Benefits of MusicPD
28
29;; MusicPD features crossfade, very little skipping, minor CPU usage,
30;; many clients, many supported output formats, fast manipulation via
31;; network processes, and good abstraction of client and server.
32
33;;; MusicPD setup
34
35;; If you want to set up a local MusicPD server, you'll need to have
36;; mpd installed.  If you want to use a remote server instance, no
37;; installation is needed.
38
39;; The website is at http://musicpd.org/.  Debian packages are
40;; available.  It is recommended to use mpd version 0.12.0 or higher.
41;;
42;; Copy the example configuration for mpd into ~/.mpdconf and edit it
43;; to your needs.  Use your top level music directory for
44;; music_directory.  If your playlists use absolute file names, be
45;; certain that music_directory has the leading directory part.
46;;
47;; Before you try to play anything, but after setting up the above,
48;; run `mkdir ~/.mpd && mpd --create-db' to create MusicPD's track
49;; database.
50;;
51;; Check to see if mpd is running.  It must be running as a daemon for
52;; you to be able to play anything.  Launch it by executing "mpd".  It
53;; can be killed later with "mpd --kill" (or just "killall mpd" if
54;; you're not using the latest development version).
55
56;;; EMMS setup
57
58;; Add the following to your config.
59;;
60;; (require 'emms-player-mpd)
61
62;; Adjust `emms-player-mpd-server-name' and
63;; `emms-player-mpd-server-port' to match the location and port of
64;; your MusicPD server.
65;;
66;;   (setq emms-player-mpd-server-name "localhost")
67;;   (setq emms-player-mpd-server-port "6600")
68
69;; If your MusicPD setup requires a password, you will need to do the
70;; following.
71;;
72;;   (setq emms-player-mpd-server-password "mypassword")
73
74;; To get track info from MusicPD, do the following.
75;;
76;;   (add-to-list 'emms-info-functions 'emms-info-mpd)
77
78;; To change the volume using MusicPD, do the following.
79;;
80;;   (setq emms-volume-change-function 'emms-volume-mpd-change)
81
82;; Add 'emms-player-mpd to the top of `emms-player-list'.
83;;
84;;   (add-to-list 'emms-player-list 'emms-player-mpd)
85
86;; If you use absolute file names in your m3u playlists (which is most
87;; likely), make sure you set `emms-player-mpd-music-directory' to the
88;; value of "music_directory" from your MusicPD config.  There are
89;; additional options available as well, but the defaults should be
90;; sufficient for most uses.
91
92;; You can set `emms-player-mpd-sync-playlist' to nil if your master
93;; EMMS playlist contains only stored playlists.
94
95;; If at any time you wish to replace the current EMMS playlist buffer
96;; with the contents of the MusicPD playlist, type
97;; M-x emms-player-mpd-connect.
98;;
99;; This will also run the relevant seek functions, so that if you use
100;; emms-playing-time, the displayed time will be accurate.
101
102;;; Contributors
103
104;; Adam Sjøgren implemented support for changing the volume.
105
106(require 'cl-lib)
107(require 'emms-player-simple)
108(require 'emms-source-playlist)  ; for emms-source-file-parse-playlist
109(require 'tq)
110(require 'emms-cache)
111(require 'emms-url)
112
113(eval-when-compile
114  (condition-case nil
115      (progn
116        (require 'url)           ; load if available
117        (require 'emms-url))
118    (error nil)))
119
120(defgroup emms-player-mpd nil
121  "EMMS player for MusicPD."
122  :group 'emms-player
123  :prefix "emms-player-mpd-")
124
125(defcustom emms-player-mpd (emms-player 'emms-player-mpd-start
126                                        'emms-player-mpd-stop
127                                        'emms-player-mpd-playable-p)
128 "*Parameters for the MusicPD player."
129 :type '(cons symbol alist)
130 :group 'emms-player-mpd)
131
132(defcustom emms-player-mpd-music-directory nil
133  "The value of 'music_directory' in your MusicPD configuration file.
134
135Unless your MusicPD is configured to use absolute file names, you must
136set this variable to the value of 'music_directory' in your MusicPD
137config."
138  ;; The :format part ensures that entering directories happens on the
139  ;; next line, where there is more space to work with
140  :type '(choice :format "%{%t%}:\n   %[Value Menu%] %v"
141                 (const nil)
142                 directory)
143  :group 'emms-player-mpd)
144
145(defun emms-player-mpd-get-supported-regexp ()
146  "Returns a regexp of file extensions that MusicPD supports,
147or nil if we cannot figure it out."
148  (let ((out (shell-command-to-string "mpd --version")))
149    ;; 0.17.x
150    (if (string-match "Decoders plugins:$" out)
151        (let* ((b (match-end 0))
152               (e (string-match "Output plugins:$" out))
153               (plugs (split-string (substring out b e) "\n" t))
154               (plugs (cl-mapcan (lambda (x)
155                                   (and (string-match " +\\[.*\\] +\\(.+\\)$" x)
156                                        (split-string (match-string 1 x) nil t)))
157                                 plugs))
158               (b (and (string-match "Protocols:$" out) (match-end 0)))
159               (prots (and b (substring out (+ 2 b) -1)))
160               (prots (split-string (or prots "") nil t)))
161          (concat "\\(\\.\\(m3u\\|pls\\|"
162                  (regexp-opt (delq nil plugs))
163                  "\\)\\'\\)\\|\\(\\`"
164                  (regexp-opt (delete "file://" prots)) "\\)"))
165      (let ((found-start nil)
166            (supported nil))
167        (if (string-match "Supported decoders:\\([^0]+?\\)Supported outputs:" out)
168            ;; 0.15.x
169            (setq supported (replace-regexp-in-string "\\[.+?\\]" ""
170                                                      (match-string 1 out)))
171          ;; < 0.15
172          (setq out (split-string out "\n"))
173          (while (car out)
174            (cond ((string= (car out) "Supported formats:")
175                   (setq found-start t))
176                  ((string= (car out) "")
177                   (setq found-start nil))
178                  (found-start
179                   (setq supported (concat supported (car out)))))
180            (setq out (cdr out))))
181        ;; Create regexp
182        (when (and (stringp supported)
183                   (not (string= supported "")))
184          (concat "\\`http://\\|\\.\\(m3u\\|pls\\|"
185                  (regexp-opt (delq nil (split-string supported)))
186                  "\\)\\'"))))))
187
188(defcustom emms-player-mpd-supported-regexp
189  ;; Use a sane default, just in case
190  (or (emms-player-mpd-get-supported-regexp)
191      (concat "\\`http://\\|"
192              (emms-player-simple-regexp
193               "m3u" "ogg" "flac" "mp3" "wav" "mod" "au" "aiff")))
194  "Formats supported by MusicPD."
195  :type 'regexp
196  :set (function
197        (lambda (sym value)
198          (set sym value)
199          (emms-player-set emms-player-mpd 'regex value)))
200  :group 'emms-player-mpd)
201
202(defcustom emms-player-mpd-connect-function 'open-network-stream
203  "Function used to initiate the connection to MusicPD.
204It should take same arguments as `open-network-stream' does."
205  :type 'function
206  :group 'emms-player-mpd)
207
208(defcustom emms-player-mpd-server-name (or (getenv "MPD_HOST") "localhost")
209  "The MusicPD server that we should connect to."
210  :type 'string
211  :group 'emms-player-mpd)
212
213(defcustom emms-player-mpd-server-port (or (getenv "MPD_PORT") "6600")
214  "The port of the MusicPD server that we should connect to."
215  :type '(choice number string)
216  :group 'emms-player-mpd)
217
218(defcustom emms-player-mpd-server-password nil
219  "The password for the MusicPD server that we should connect to."
220  :type '(choice (const :tag "None" nil)
221                 string)
222  :group 'emms-player-mpd)
223
224(defcustom emms-player-mpd-check-interval 1
225  "How often to check to see whether MusicPD has advanced to the
226next song.  This may be an integer, a floating point number, or
227nil.  If set to nil, this check will not be periodically
228performed.
229
230This variable is used only if `emms-player-mpd-sync-playlist' is
231non-nil."
232  :type '(choice (const :tag "Disable check" nil)
233                 number)
234  :group 'emms-player-mpd)
235
236(defcustom emms-player-mpd-verbose nil
237  "Whether to provide notifications for server connection events
238and errors."
239  :type 'boolean
240  :group 'emms-player-mpd)
241
242(defcustom emms-player-mpd-sync-playlist t
243  "Whether to synchronize the EMMS playlist with the MusicPD playlist.
244
245If your EMMS playlist contains music files rather than playlists,
246leave this set to non-nil.
247
248If your EMMS playlist contains stored playlists, set this to nil."
249  :type 'boolean
250  :group 'emms-player-mpd)
251
252(emms-player-set emms-player-mpd
253                 'regex
254                 emms-player-mpd-supported-regexp)
255
256(emms-player-set emms-player-mpd
257                 'pause
258                 'emms-player-mpd-pause)
259
260(emms-player-set emms-player-mpd
261                 'resume
262                 'emms-player-mpd-pause)
263
264(emms-player-set emms-player-mpd
265                 'seek
266                 'emms-player-mpd-seek)
267
268(emms-player-set emms-player-mpd
269                 'seek-to
270                 'emms-player-mpd-seek-to)
271
272;;; Dealing with the MusicPD network process
273
274(defvar emms-player-mpd-process nil)
275(defvar emms-player-mpd-queue nil)
276
277(defvar emms-player-mpd-playlist-id nil)
278(make-variable-buffer-local 'emms-player-mpd-playlist-id)
279
280(defvar emms-player-mpd-current-song nil)
281(defvar emms-player-mpd-last-state nil)
282(defvar emms-player-mpd-status-timer nil)
283
284(defvar emms-player-mpd-status-regexp
285  "^\\(OK\\( MPD \\)?\\|ACK \\[\\([0-9]+\\)@[0-9]+\\] \\(.+\\)\\)\n+\\'"
286  "Regexp that matches the valid status strings that MusicPD can
287return at the end of a request.")
288
289(defun emms-player-mpd-sentinel (proc event)
290  "The process sentinel for MusicPD."
291  (let ((status (process-status proc)))
292    (cond ((string-match "^deleted" event)
293           (when emms-player-mpd-verbose
294             (message "MusicPD process was deleted")))
295          ((memq status '(exit signal closed))
296           (emms-player-mpd-close-process t)
297           (when emms-player-mpd-verbose
298             (message "Closed MusicPD process")))
299          ((memq status '(run open))
300           (when emms-player-mpd-verbose
301             (message "MusicPD process started successfully")))
302          (t
303           (when emms-player-mpd-verbose
304             (message "Other MusicPD status change: %s, %s" status event))))))
305
306(defun emms-player-mpd-ensure-process ()
307  "Make sure that a MusicPD process is currently active."
308  (unless (and emms-player-mpd-process
309               (processp emms-player-mpd-process)
310               (memq (process-status emms-player-mpd-process) '(run open)))
311    (setq emms-player-mpd-process
312	  (if emms-player-mpd-server-port
313	      (funcall emms-player-mpd-connect-function "mpd"
314		     nil
315		     emms-player-mpd-server-name
316		     emms-player-mpd-server-port)
317	    (make-network-process :name "emms-mpd"
318				:service emms-player-mpd-server-name
319				:family 'local)))
320    (set-process-sentinel emms-player-mpd-process
321                          'emms-player-mpd-sentinel)
322    (setq emms-player-mpd-queue
323          (tq-create emms-player-mpd-process))
324    (if (fboundp 'set-process-query-on-exit-flag)
325        (set-process-query-on-exit-flag emms-player-mpd-process nil)
326      (set-process-query-on-exit-flag emms-player-mpd-process nil))
327    ;; send password
328    (when (stringp emms-player-mpd-server-password)
329      (tq-enqueue emms-player-mpd-queue
330                  (concat "password " emms-player-mpd-server-password "\n")
331                  emms-player-mpd-status-regexp nil #'ignore t))))
332
333(defun emms-player-mpd-close-process (&optional from-sentinel)
334  "Terminate the current MusicPD client process.
335FROM-SENTINEL indicates whether this was called by the process sentinel,
336in which case certain checks should not be made."
337  (when (or from-sentinel
338            (and (processp emms-player-mpd-process)
339                 (memq (process-status emms-player-mpd-process) '(run open))))
340    (tq-close emms-player-mpd-queue)
341    (setq emms-player-mpd-queue nil)
342    (setq emms-player-mpd-process nil)))
343
344(defun emms-player-mpd-send (question closure fn)
345  "Send the given QUESTION to the MusicPD server.
346When a reply comes, call FN with CLOSURE and the result."
347  (emms-player-mpd-ensure-process)
348  (unless (string= (substring question -1) "\n")
349    (setq question (concat question "\n")))
350  (tq-enqueue emms-player-mpd-queue question
351              emms-player-mpd-status-regexp
352              closure fn t))
353
354;;; Helper functions
355
356(defun emms-player-mpd-get-mpd-filename (file)
357  "Turn FILE into something that MusicPD can understand.
358
359This usually means removing a prefix."
360  (if (or (not emms-player-mpd-music-directory)
361          (not (eq (aref file 0) ?/))
362          (string-match "\\`http://" file))
363      file
364    (file-relative-name file emms-player-mpd-music-directory)))
365
366(defun emms-player-mpd-get-emms-filename (file)
367  "Turn FILE into something that EMMS can understand.
368
369This usually means adding a prefix."
370  (if (or (not emms-player-mpd-music-directory)
371          (eq (aref file 0) ?/)
372          (string-match "\\`http://" file))
373      file
374    (expand-file-name file emms-player-mpd-music-directory)))
375
376(defun emms-player-mpd-parse-response (response)
377  "Convert the given MusicPD response into a list.
378
379The car of the list is special:
380If an error has occurred, it will contain a cons cell whose car is
381an error number and whose cdr is the corresponding message.
382Otherwise, it will be nil."
383  (when (stringp response)
384    (save-match-data
385      (let* ((data (split-string response "\n"))
386             (cruft (last data 3))
387             (status (if (string= (cadr cruft) "")
388                         (car cruft)
389                       (cadr cruft))))
390        (setcdr cruft nil)
391        (when (and (stringp (car data))
392                   (string-match "^OK\\( MPD \\)?" (car data)))
393          (setq data (cdr data)))
394        (if (and (stringp status)
395                 (string-match "^ACK \\[\\([0-9]+\\)@[0-9]+\\] \\(.+\\)"
396                               status))
397            (cons (cons (match-string 1 status)
398                        (match-string 2 status))
399                  data)
400          (cons nil data))))))
401
402(defun emms-player-mpd-parse-line (line)
403  "Turn the given LINE from MusicPD into a cons cell.
404
405The format of the cell is (name . value)."
406  (when (string-match "\\`\\([^:\n]+\\):\\s-*\\(.+\\)" line)
407    (let ((name (match-string 1 line))
408          (value (match-string 2 line)))
409      (if (and name value)
410          (progn
411            (setq name (downcase name))
412            (cons name value))
413        nil))))
414
415(defun emms-player-mpd-get-alist (info)
416  "Turn the given parsed INFO from MusicPD into an alist."
417  (when (and info
418             (null (car info))          ; no error has occurred
419             (cdr info))                ; data exists
420    (let ((alist nil)
421          cell old-cell)
422      (dolist (line (cdr info))
423        (when (setq cell (emms-player-mpd-parse-line line))
424          (if (setq old-cell (assoc (car cell) alist))
425              (setcdr old-cell (cdr cell))
426            (setq alist (cons cell alist)))))
427      alist)))
428
429(defun emms-player-mpd-get-alists (info)
430  "Turn the given parsed INFO from MusicPD into an list of alists.
431
432The list will be in reverse order."
433  (when (and info
434             (null (car info))          ; no error has occurred
435             (cdr info))                ; data exists
436    (let ((alists nil)
437          (alist nil)
438          cell)
439      (dolist (line (cdr info))
440        (when (setq cell (emms-player-mpd-parse-line line))
441          (if (assoc (car cell) alist)
442              (setq alists (cons alist alists)
443                    alist (list cell))
444            (setq alist (cons cell alist)))))
445      (when alist
446        (setq alists (cons alist alists)))
447      alists)))
448
449(defun emms-player-mpd-get-tracks-1 (closure response)
450  (let ((songs (emms-player-mpd-get-alists
451                (emms-player-mpd-parse-response response)))
452        (tracks nil))
453    (when songs
454      (dolist (song-info songs)
455        (let ((file (cdr (assoc "file" song-info))))
456          (when file
457            (setq file (emms-player-mpd-get-emms-filename file))
458            (let* ((type (if (string-match "\\`http://" file)
459                            'url
460                           'file))
461                   (track (emms-track type file)))
462              (emms-info-mpd track song-info)
463              (run-hook-with-args 'emms-track-info-filters track)
464              (setq tracks (cons track tracks)))))))
465    (funcall (car closure) (cdr closure) tracks)))
466
467(defun emms-player-mpd-get-tracks (closure callback)
468  "Get the current playlist from MusicPD in the form of a list of
469EMMS tracks.
470Call CALLBACK with CLOSURE and result when the request is complete."
471  (emms-player-mpd-send "playlistinfo" (cons callback closure)
472                        #'emms-player-mpd-get-tracks-1))
473
474(defun emms-player-mpd-get-status-1 (closure response)
475  (funcall (car closure)
476           (cdr closure)
477           (emms-player-mpd-get-alist
478            (emms-player-mpd-parse-response response))))
479
480(defun emms-player-mpd-get-status (closure callback)
481  "Get status information from MusicPD.
482It will be returned in the form of an alist by calling CALLBACK
483with CLOSURE as its first argument, and the status as the
484second."
485  (emms-player-mpd-send "status" (cons callback closure)
486                        #'emms-player-mpd-get-status-1))
487
488(defun emms-player-mpd-get-status-part (closure callback item &optional info)
489  "Get ITEM from the current MusicPD status.
490Call CALLBACK with CLOSURE and result when the request is complete.
491If INFO is specified, use that instead of acquiring the necessary
492info from MusicPD."
493  (if info
494      (funcall callback closure (cdr (assoc item info)))
495    (emms-player-mpd-get-status
496     (cons callback (cons closure item))
497     (lambda (closure info)
498       (let ((fn (car closure))
499             (close (cadr closure))
500             (item (cddr closure)))
501         (funcall fn close (cdr (assoc item info))))))))
502
503(defun emms-player-mpd-get-playlist-id (closure callback &optional info)
504  "Get the current playlist ID from MusicPD.
505Call CALLBACK with CLOSURE and result when the request is complete.
506If INFO is specified, use that instead of acquiring the necessary
507info from MusicPD."
508  (when info
509    (setq callback (lambda (closure id) id)))
510  (emms-player-mpd-get-status-part closure callback "playlist" info))
511
512(defun emms-player-mpd-get-volume (closure callback &optional info)
513  "Get the current volume from MusicPD.
514Call CALLBACK with CLOSURE and result when the request is complete.
515If INFO is specified, use that instead of acquiring the necessary
516info from MusicPD."
517  (when info
518    (setq callback (lambda (closure volume) volume)))
519  (emms-player-mpd-get-status-part closure callback "volume" info))
520
521(defun emms-player-mpd-get-current-song (closure callback &optional info)
522  "Get the current song from MusicPD.
523This is in the form of a number that indicates the position of
524the song on the current playlist.
525
526Call CALLBACK with CLOSURE and result when the request is complete.
527If INFO is specified, use that instead of acquiring the necessary
528info from MusicPD."
529  (when info
530    (setq callback (lambda (closure id) id)))
531  (emms-player-mpd-get-status-part closure callback "song" info))
532
533(defun emms-player-mpd-get-mpd-state (closure callback &optional info)
534  "Get the current state of the MusicPD server.
535This is either \"play\", \"stop\", or \"pause\".
536
537Call CALLBACK with CLOSURE and result when the request is complete.
538If INFO is specified, use that instead of acquiring the necessary
539info from MusicPD."
540  (when info
541    (setq callback (lambda (closure id) id)))
542  (emms-player-mpd-get-status-part closure callback "state" info))
543
544(defun emms-player-mpd-get-playing-time (closure callback &optional info)
545  "Get the number of seconds that the current song has been playing,
546or nil if we cannot obtain this information.
547
548Call CALLBACK with CLOSURE and result when the request is complete.
549If INFO is specified, use that instead of acquiring the necessary
550info from MusicPD."
551  (if info
552      (emms-player-mpd-get-status-part
553       nil
554       (lambda (closure time)
555         (and time
556              (string-match "\\`\\([0-9]+\\):" time)
557              (string-to-number (match-string 1 time))))
558       "time" info)
559    (emms-player-mpd-get-status-part
560     (cons callback closure)
561     (lambda (closure time)
562       (funcall (car closure)
563                (cdr closure)
564                (and time
565                     (string-match "\\`\\([0-9]+\\):" time)
566                     (string-to-number (match-string 1 time)))))
567     "time" info)))
568
569(defun emms-player-mpd-select-song (prev-song new-song)
570  "Move to the given song position.
571
572The amount to move is the number difference between PREV-SONG and
573NEW-SONG.  NEW-SONG should be a string containing a number.
574PREV-SONG may be either a string containing a number or nil,
575which indicates that we should start from the beginning of the
576buffer and move to NEW-SONG."
577  (with-current-emms-playlist
578    ;; move to current track
579    (goto-char (if (and (stringp prev-song)
580                        emms-playlist-selected-marker
581                        (marker-position emms-playlist-selected-marker))
582                   emms-playlist-selected-marker
583                 (point-min)))
584    ;; seek forward or backward
585    (let ((diff (if (stringp prev-song)
586                    (- (string-to-number new-song)
587                       (string-to-number prev-song))
588                  (string-to-number new-song))))
589      (condition-case nil
590          (progn
591            ;; skip to first track if not on one
592            (when (and (> diff 0)
593                       (not (emms-playlist-track-at (point))))
594              (emms-playlist-next))
595            ;; move to new track
596            (while (> diff 0)
597              (emms-playlist-next)
598              (setq diff (- diff 1)))
599            (while (< diff 0)
600              (emms-playlist-previous)
601              (setq diff (+ diff 1)))
602            ;; select track at point
603            (unless (emms-playlist-selected-track-at-p)
604              (emms-playlist-select (point))))
605        (error (concat "Could not move to position " new-song))))))
606
607(defun emms-player-mpd-sync-from-emms-1 (closure)
608  (emms-player-mpd-get-playlist-id
609   closure
610   (lambda (closure id)
611     (let ((buffer (car closure))
612           (fn (cdr closure)))
613       (when (functionp fn)
614         (funcall fn buffer id))))))
615
616(defun emms-player-mpd-sync-from-emms (&optional callback)
617  "Synchronize the MusicPD playlist with the contents of the
618current EMMS playlist.
619
620If CALLBACK is provided, call it with the current EMMS playlist
621buffer and MusicPD playlist ID when we are done, if there were no
622errors."
623  (emms-player-mpd-clear)
624  (with-current-emms-playlist
625    (let (tracks)
626      (save-excursion
627        (setq tracks (nreverse
628                      (emms-playlist-tracks-in-region
629                       (point-min) (point-max)))))
630      (emms-player-mpd-add-several-tracks
631       tracks
632       (cons (current-buffer) callback)
633       #'emms-player-mpd-sync-from-emms-1))))
634
635(defun emms-player-mpd-sync-from-mpd-2 (closure info)
636  (let ((buffer (car closure))
637        (fn (cadr closure))
638        (close (cddr closure))
639        (id (emms-player-mpd-get-playlist-id nil #'ignore info))
640        (song (emms-player-mpd-get-current-song nil #'ignore info)))
641    (when (buffer-live-p buffer)
642      (let ((emms-playlist-buffer buffer))
643        (with-current-emms-playlist
644          (setq emms-player-mpd-playlist-id id)
645          (set-buffer-modified-p nil)
646          (if song
647              (emms-player-mpd-select-song nil song)
648            (goto-char (point-min)))))
649      (when (functionp fn)
650        (funcall fn close info)))))
651
652(defun emms-player-mpd-sync-from-mpd-1 (closure tracks)
653  (let ((buffer (car closure)))
654    (when (and tracks
655               (buffer-live-p buffer))
656      (let ((emms-playlist-buffer buffer))
657        (with-current-emms-playlist
658          (emms-playlist-clear)
659          (mapc #'emms-playlist-insert-track tracks)))
660      (emms-player-mpd-get-status closure
661                                  #'emms-player-mpd-sync-from-mpd-2))))
662
663(defun emms-player-mpd-sync-from-mpd (&optional closure callback)
664  "Synchronize the EMMS playlist with the contents of the current
665MusicPD playlist.  Namely, clear the EMMS playlist buffer and add
666tracks to it that are present in the MusicPD playlist.
667
668If the current buffer is an EMMS playlist buffer, make it the
669main EMMS playlist buffer."
670  (when (and emms-playlist-buffer-p
671             (not (eq (current-buffer) emms-playlist-buffer)))
672    (emms-playlist-set-playlist-buffer (current-buffer)))
673  (with-current-emms-playlist
674    (emms-player-mpd-get-tracks
675     (cons emms-playlist-buffer (cons callback closure))
676     #'emms-player-mpd-sync-from-mpd-1)))
677
678(defun emms-player-mpd-detect-song-change-2 (state info)
679  "Perform post-sync tasks after returning from a stop."
680  (setq emms-player-mpd-current-song nil)
681  (setq emms-player-playing-p 'emms-player-mpd)
682  (when (string= state "pause")
683    (setq emms-player-paused-p t))
684  (emms-player-mpd-detect-song-change info))
685
686(defun emms-player-mpd-detect-song-change-1 (closure info)
687  (let ((song (emms-player-mpd-get-current-song nil #'ignore info))
688        (state (emms-player-mpd-get-mpd-state nil #'ignore info))
689        (time (emms-player-mpd-get-playing-time nil #'ignore info))
690        (err-msg (cdr (assoc "error" info))))
691    (if (stringp err-msg)
692        (progn
693          (message "MusicPD error: %s" err-msg)
694          (emms-player-mpd-send
695           "clearerror"
696           nil #'ignore))
697      (cond ((string= state "stop")
698             (if song
699                 ;; a track remains: the user probably stopped MusicPD
700                 ;; manually, so we'll stop EMMS completely
701                 (let ((emms-player-stopped-p t))
702                   (setq emms-player-mpd-last-state "stop")
703                   (emms-player-stopped))
704               ;; no more tracks are left: we probably ran out of things
705               ;; to play, so let EMMS do something further if it wants
706               (unless (string= emms-player-mpd-last-state "stop")
707                 (setq emms-player-mpd-last-state "stop")
708                 (emms-player-stopped))))
709            ((and emms-player-mpd-last-state
710                  (string= emms-player-mpd-last-state "stop"))
711             ;; resume from a stop that occurred outside of EMMS
712             (setq emms-player-mpd-last-state nil)
713             (emms-player-mpd-sync-from-mpd
714              state
715              #'emms-player-mpd-detect-song-change-2))
716            ((string= state "pause")
717             nil)
718            ((string= state "play")
719             (setq emms-player-mpd-last-state "play")
720             (unless (or (null song)
721                         (and (stringp emms-player-mpd-current-song)
722                              (string= song emms-player-mpd-current-song)))
723               (let ((emms-player-stopped-p t))
724                 (emms-player-stopped))
725               (emms-player-mpd-select-song emms-player-mpd-current-song song)
726               (setq emms-player-mpd-current-song song)
727               (emms-player-started 'emms-player-mpd)
728               (when time
729                 (run-hook-with-args 'emms-player-time-set-functions
730                                     time))))))))
731
732(defun emms-player-mpd-detect-song-change (&optional info)
733  "Detect whether a song change has occurred.
734This is usually called by a timer.
735
736If INFO is specified, use that instead of acquiring the necessary
737info from MusicPD."
738  (if info
739      (emms-player-mpd-detect-song-change-1 nil info)
740    (emms-player-mpd-get-status nil #'emms-player-mpd-detect-song-change-1)))
741
742(defun emms-player-mpd-quote-file (file)
743  "Escape special characters in FILE and surround in double-quotes."
744  (concat "\""
745          (emms-replace-regexp-in-string
746           "\"" "\\\\\""
747           (emms-replace-regexp-in-string "\\\\" "\\\\\\\\" file))
748          "\""))
749
750;;;###autoload
751(defun emms-player-mpd-clear ()
752  "Clear the MusicPD playlist."
753  (interactive)
754  (when emms-player-mpd-status-timer
755    (emms-cancel-timer emms-player-mpd-status-timer)
756    (setq emms-player-mpd-status-timer nil))
757  (setq emms-player-mpd-last-state nil)
758  (emms-player-mpd-send "clear" nil #'ignore)
759  (let ((emms-player-stopped-p t))
760    (emms-player-stopped)))
761
762;;; Adding to the MusicPD playlist
763
764(defun emms-player-mpd-add-file (file closure callback)
765  "Add FILE to the current MusicPD playlist.
766Execute CALLBACK with CLOSURE as its first argument when done.
767
768If an error occurs, display a relevant message."
769  (setq file (emms-player-mpd-get-mpd-filename file))
770  (emms-player-mpd-send
771   (concat "add " (emms-player-mpd-quote-file file))
772   (cons file (cons callback closure))
773   (lambda (closure response)
774     (let ((output (emms-player-mpd-parse-response response))
775           (file (car closure))
776           (callback (cadr closure))
777           (close (cddr closure)))
778       (if (car output)
779           (message "MusicPD error: %s: %s" file (cdar output))
780         (when (functionp callback)
781           (funcall callback close)))))))
782
783(defun emms-player-mpd-add-buffer-contents (buffer closure callback)
784  "Load contents of BUFFER into MusicPD by adding each line.
785Execute CALLBACK with CLOSURE as its first argument when done.
786
787This handles both m3u and pls type playlists."
788  (with-current-buffer buffer
789    (goto-char (point-min))
790    (let ((format (emms-source-playlist-determine-format)))
791      (when format
792        (emms-player-mpd-add-several-files
793         (emms-source-playlist-files format)
794         closure callback)))))
795
796(defun emms-player-mpd-add-playlist (playlist closure callback)
797  "Load contents of PLAYLIST into MusicPD by adding each line.
798Execute CALLBACK with CLOSURE as its first argument when done.
799
800This handles both m3u and pls type playlists."
801  ;; This is useful for playlists of playlists
802  (with-temp-buffer
803    (emms-insert-file-contents playlist)
804    (emms-player-mpd-add-buffer-contents (current-buffer) closure callback)))
805
806(defun emms-player-mpd-add-streamlist (url closure callback)
807  "Download contents of URL and then add its feeds into MusicPD.
808Execute CALLBACK with CLOSURE as its first argument when done."
809  ;; This is useful with emms-streams.el
810  (if (fboundp 'url-insert-file-contents)
811      (progn
812        (require 'emms-url)
813        (with-temp-buffer
814          (url-insert-file-contents (emms-url-quote-entire url))
815          (emms-http-decode-buffer (current-buffer))
816          (emms-player-mpd-add-buffer-contents (current-buffer)
817                                               closure callback)))
818    (error (message (concat "You need to install url.el so that"
819                            " Emms can retrieve this stream")))))
820
821(defun emms-player-mpd-add (track closure callback)
822  "Add TRACK to the MusicPD playlist.
823Execute CALLBACK with CLOSURE as its first argument when done."
824  (let ((name (emms-track-get track 'name))
825        (type (emms-track-get track 'type)))
826    (cond ((eq type 'url)
827           (emms-player-mpd-add-file name closure callback))
828          ((eq type 'streamlist)
829           (emms-player-mpd-add-streamlist name closure callback))
830          ((or (eq type 'playlist)
831               (string-match "\\.\\(m3u\\|pls\\)\\'" name))
832           (emms-player-mpd-add-playlist name closure callback))
833          ((and (eq type 'file)
834                (string-match emms-player-mpd-supported-regexp name))
835           (emms-player-mpd-add-file name closure callback)))))
836
837(defun emms-player-mpd-add-several-tracks (tracks closure callback)
838  "Add TRACKS to the MusicPD playlist.
839Execute CALLBACK with CLOSURE as its first argument when done."
840  (when (consp tracks)
841    (while (cdr tracks)
842      (emms-player-mpd-add (car tracks) nil #'ignore)
843      (setq tracks (cdr tracks)))
844    ;; only execute callback on last track
845    (emms-player-mpd-add (car tracks) closure callback)))
846
847(defun emms-player-mpd-add-several-files (files closure callback)
848  "Add FILES to the MusicPD playlist.
849Execute CALLBACK with CLOSURE as its first argument when done."
850  (when (consp files)
851    (while (cdr files)
852      (emms-player-mpd-add-file (car files) nil #'ignore)
853      (setq files (cdr files)))
854    ;; only execute callback on last file
855    (emms-player-mpd-add-file (car files) closure callback)))
856
857;;; EMMS API
858
859(defun emms-player-mpd-playable-p (track)
860  "Return non-nil when we can play this track."
861  (and (memq (emms-track-type track) '(file url playlist streamlist))
862       (string-match (emms-player-get emms-player-mpd 'regex)
863                     (emms-track-name track))
864       (condition-case nil
865           (progn (emms-player-mpd-ensure-process)
866                  t)
867         (error nil))))
868
869(defun emms-player-mpd-play (&optional id)
870  "Play whatever is in the current MusicPD playlist.
871If ID is specified, play the song at that position in the MusicPD
872playlist."
873  (interactive)
874  (if id
875      (progn
876        (unless (stringp id)
877          (setq id (number-to-string id)))
878        (emms-player-mpd-send
879         (concat "play " id)
880         nil
881         (lambda (closure response)
882           (setq emms-player-mpd-current-song nil)
883           (if emms-player-mpd-check-interval
884               (setq emms-player-mpd-status-timer
885                     (run-at-time t emms-player-mpd-check-interval
886                                  'emms-player-mpd-detect-song-change))
887             (emms-player-mpd-detect-song-change)))))
888    ;; we only want to play one track, so don't start the timer
889    (emms-player-mpd-send
890     "play"
891     nil
892     (lambda (closure response)
893       (emms-player-started 'emms-player-mpd)))))
894
895(defun emms-player-mpd-start-and-sync-2 (buffer id)
896  (when (buffer-live-p buffer)
897    (let ((emms-playlist-buffer buffer))
898      (with-current-emms-playlist
899        (setq emms-player-mpd-playlist-id id)
900        (set-buffer-modified-p nil)
901        (let ((track-cnt 0))
902          (save-excursion
903            (goto-char
904             (if (and emms-playlist-selected-marker
905                      (marker-position emms-playlist-selected-marker))
906                 emms-playlist-selected-marker
907               (point-min)))
908            (condition-case nil
909                (while t
910                  (emms-playlist-previous)
911                  (setq track-cnt (1+ track-cnt)))
912              (error nil)))
913          (emms-player-mpd-play track-cnt))))))
914
915(defun emms-player-mpd-start-and-sync-1 (closure id)
916  (let ((buf-id (with-current-emms-playlist
917                  emms-player-mpd-playlist-id)))
918    (if (and (not (buffer-modified-p emms-playlist-buffer))
919             (stringp buf-id)
920             (string= buf-id id))
921        (emms-player-mpd-start-and-sync-2 emms-playlist-buffer id)
922      (emms-player-mpd-sync-from-emms
923       #'emms-player-mpd-start-and-sync-2))))
924
925(defun emms-player-mpd-start-and-sync ()
926  "Ensure that MusicPD's playlist is up-to-date with EMMS's
927playlist, and then play the current track.
928
929This is called if `emms-player-mpd-sync-playlist' is non-nil."
930  (when emms-player-mpd-status-timer
931    (emms-cancel-timer emms-player-mpd-status-timer)
932    (setq emms-player-mpd-status-timer nil))
933  (emms-player-mpd-send
934   "clearerror"
935   nil
936   (lambda (closure response)
937     (emms-player-mpd-get-playlist-id
938      nil
939      #'emms-player-mpd-start-and-sync-1))))
940
941(defun emms-player-mpd-connect-1 (closure info)
942  (setq emms-player-mpd-current-song nil)
943  (let* ((state (emms-player-mpd-get-mpd-state nil #'ignore info)))
944    (unless (string= state "stop")
945      (setq emms-player-playing-p 'emms-player-mpd))
946    (when (string= state "pause")
947      (setq emms-player-paused-p t))
948    (unless (string= state "stop")
949      (emms-player-mpd-detect-song-change info)
950      (when emms-player-mpd-check-interval
951        (setq emms-player-mpd-status-timer
952              (run-at-time t emms-player-mpd-check-interval
953                           'emms-player-mpd-detect-song-change))))))
954
955;;;###autoload
956(defun emms-player-mpd-connect ()
957  "Connect to MusicPD and retrieve its current playlist.
958
959Afterward, the status of MusicPD will be tracked.
960
961This also has the effect of changing the current EMMS playlist to
962be the same as the current MusicPD playlist.  Thus, this
963function is useful to call if the contents of the EMMS playlist
964buffer get out-of-sync for some reason."
965  (interactive)
966  (when emms-player-mpd-status-timer
967    (emms-cancel-timer emms-player-mpd-status-timer)
968    (setq emms-player-mpd-status-timer nil))
969  (emms-player-mpd-sync-from-mpd
970   nil #'emms-player-mpd-connect-1))
971
972(defun emms-player-mpd-start (track)
973  "Starts a process playing TRACK."
974  (interactive)
975  (if (and emms-player-mpd-sync-playlist
976           (not (memq (emms-track-get track 'type) '(streamlist playlist))))
977      (emms-player-mpd-start-and-sync)
978    (emms-player-mpd-clear)
979    ;; if we have loaded the item successfully, play it
980    (emms-player-mpd-add track nil #'emms-player-mpd-play)))
981
982(defun emms-player-mpd-disconnect (&optional no-stop)
983  "Terminate the MusicPD client process and disconnect from MusicPD.
984
985If NO-STOP is non-nil, do not indicate to EMMS that we are
986stopped.  This argument is meant to be used when calling this
987from other functions."
988  (interactive)
989  (emms-cancel-timer emms-player-mpd-status-timer)
990  (setq emms-player-mpd-status-timer nil)
991  (setq emms-player-mpd-current-song nil)
992  (setq emms-player-mpd-last-state nil)
993  (emms-player-mpd-close-process)
994  (unless no-stop
995    (let ((emms-player-stopped-p t))
996      (emms-player-stopped))))
997
998(defun emms-player-mpd-stop ()
999  "Stop the currently playing song."
1000  (interactive)
1001  (condition-case nil
1002      (emms-player-mpd-send "stop" nil #'ignore)
1003    (error nil))
1004  (emms-player-mpd-disconnect t)
1005  (let ((emms-player-stopped-p t))
1006    (emms-player-stopped)))
1007
1008(defun emms-player-mpd-pause ()
1009  "Pause the currently playing song."
1010  (interactive)
1011  (emms-player-mpd-send "pause" nil #'ignore))
1012
1013(defun emms-player-mpd-seek (amount)
1014  "Seek backward or forward by AMOUNT seconds, depending on sign of AMOUNT."
1015  (interactive)
1016  (emms-player-mpd-get-status
1017   amount
1018   (lambda (amount info)
1019     (let ((song (emms-player-mpd-get-current-song nil #'ignore info))
1020           (secs (emms-player-mpd-get-playing-time nil #'ignore info)))
1021       (when (and song secs)
1022         (emms-player-mpd-send
1023          (concat "seek " song " " (number-to-string (round (+ secs amount))))
1024          nil #'ignore))))))
1025
1026(defun emms-player-mpd-seek-to (pos)
1027  "Seek to POS seconds from the start of the current track."
1028  (interactive)
1029  (emms-player-mpd-get-current-song
1030   pos
1031   (lambda (pos song)
1032     (when (and song pos)
1033       (emms-player-mpd-send
1034        (concat "seek " song " " (number-to-string (round pos)))
1035        nil #'ignore)))))
1036
1037(defun emms-player-mpd-next ()
1038  "Move forward by one track in MusicPD's internal playlist."
1039  (interactive)
1040  (emms-player-mpd-send "next" nil #'ignore))
1041
1042(defun emms-player-mpd-previous ()
1043  "Move backward by one track in MusicPD's internal playlist."
1044  (interactive)
1045  (emms-player-mpd-send "previous" nil #'ignore))
1046
1047;;; Volume
1048
1049(defun emms-volume-mpd-change (amount)
1050  "Change volume up or down by AMOUNT, depending on whether it is
1051positive or negative."
1052  (interactive "MVolume change amount (+ increase, - decrease): ")
1053  (emms-player-mpd-get-volume
1054   amount
1055   (lambda (change volume)
1056     (let ((new-volume (+ (string-to-number volume) change)))
1057       (emms-player-mpd-send
1058        (concat "setvol \"" (number-to-string new-volume) "\"")
1059        nil #'ignore)))))
1060
1061;;; Now playing
1062
1063(defun emms-player-mpd-show-1 (closure response)
1064  (let* ((info (emms-player-mpd-get-alist
1065                (emms-player-mpd-parse-response response)))
1066         (insertp (car closure))
1067         (callback (cadr closure))
1068         (buffer (cddr closure))
1069         (name (cdr (assoc "name" info))) ; radio feeds sometimes set this
1070         (file (cdr (assoc "file" info)))
1071         (desc nil))
1072    ;; if we are playing lastfm radio, use its show function instead
1073    (if (and (boundp 'emms-lastfm-radio-stream-url)
1074             (stringp emms-lastfm-radio-stream-url)
1075             (string= emms-lastfm-radio-stream-url file))
1076        (with-current-buffer buffer
1077          (and (fboundp 'emms-lastfm-np)
1078               (emms-lastfm-np insertp callback)))
1079      ;; otherwise build and show the description
1080      (when info
1081        (when name
1082          (setq desc name))
1083        (when file
1084          (let ((track (emms-dictionary '*track*))
1085                track-desc)
1086            (if (string-match "\\`http://" file)
1087                (emms-track-set track 'type 'url)
1088              (emms-track-set track 'type 'file))
1089            (emms-track-set track 'name file)
1090            (emms-info-mpd track info)
1091            (run-hook-with-args 'emms-track-info-filters track)
1092            (setq track-desc (emms-track-description track))
1093            (when (and (stringp track-desc) (not (string= track-desc "")))
1094              (setq desc (if desc
1095                             (concat desc ": " track-desc)
1096                           track-desc))))))
1097      (if (not desc)
1098          (unless (functionp callback)
1099            (message "Nothing playing right now"))
1100        (setq desc (format emms-show-format desc))
1101        (cond ((functionp callback)
1102               (funcall callback buffer desc))
1103              (insertp
1104               (when (buffer-live-p buffer)
1105                 (with-current-buffer buffer
1106                   (insert desc))))
1107              (t
1108               (message "%s" desc)))))))
1109
1110;;;###autoload
1111(defun emms-player-mpd-show (&optional insertp callback)
1112  "Describe the current EMMS track in the minibuffer.
1113
1114If INSERTP is non-nil, insert the description into the current
1115buffer instead.
1116
1117If CALLBACK is a function, call it with the current buffer and
1118description as arguments instead of displaying the description or
1119inserting it.
1120
1121This function uses `emms-show-format' to format the current track.
1122It differs from `emms-show' in that it asks MusicPD for the current track,
1123rather than EMMS."
1124  (interactive "P")
1125  (emms-player-mpd-send "currentsong"
1126                        (cons insertp (cons callback (current-buffer)))
1127                        #'emms-player-mpd-show-1))
1128
1129;;; Track info
1130
1131(defun emms-info-mpd-process (track info)
1132  (dolist (data info)
1133    (let ((name (car data))
1134          (value (cdr data)))
1135      (setq name (cond ((string= name "artist") 'info-artist)
1136                       ((string= name "composer") 'info-composer)
1137                       ((string= name "performer") 'info-performer)
1138                       ((string= name "title") 'info-title)
1139                       ((string= name "album") 'info-album)
1140                       ((string= name "track") 'info-tracknumber)
1141                       ((string= name "date") 'info-year)
1142                       ((string= name "genre") 'info-genre)
1143                       ((string= name "time")
1144                        (setq value (string-to-number value))
1145                        'info-playing-time)
1146                       (t nil)))
1147      (when name
1148        (emms-track-set track name value)))))
1149
1150(defun emms-info-mpd-1 (track response)
1151  (let ((info (emms-player-mpd-get-alist
1152               (emms-player-mpd-parse-response response))))
1153    (when info
1154      (emms-info-mpd-process track info)
1155      (emms-track-updated track))))
1156
1157(defun emms-info-mpd (track &optional info)
1158  "Add track information to TRACK.
1159If INFO is specified, use that instead of acquiring the necessary
1160info from MusicPD.
1161
1162This is a useful addition to `emms-info-functions'."
1163  (if info
1164      (emms-info-mpd-process track info)
1165    (when (and (eq 'file (emms-track-type track))
1166               (not (string-match "\\`http://" (emms-track-name track))))
1167      (let ((file (emms-player-mpd-get-mpd-filename (emms-track-name track))))
1168        (when (or emms-player-mpd-music-directory
1169                  (and file
1170                       (string-match emms-player-mpd-supported-regexp file)))
1171          (condition-case nil
1172              (emms-player-mpd-send
1173               (concat "find filename "
1174                       (emms-player-mpd-quote-file file))
1175               track
1176               #'emms-info-mpd-1)
1177            (error nil)))))))
1178
1179;;; Caching
1180
1181(defun emms-cache-set-from-mpd-track (track-info)
1182  "Dump TRACK-INFO into the EMMS cache.
1183
1184The track should be an alist as per `emms-player-mpd-get-alist'."
1185  (when emms-cache-set-function
1186    (let ((track (emms-dictionary '*track*))
1187          (name (cdr (assoc "file" track-info))))
1188      (when name
1189        (setq name (emms-player-mpd-get-emms-filename name))
1190        (emms-track-set track 'type 'file)
1191        (emms-track-set track 'name name)
1192        (emms-info-mpd-process track track-info)
1193        (funcall emms-cache-set-function 'file name track)))))
1194
1195(defun emms-cache--info-cleanup (info)
1196  (let ((xs (mapcar (lambda (x)
1197                      (and (stringp x)
1198                           (not (string-match-p "\\`\\(Last-\\|direct\\)" x))
1199                           x))
1200                    info)))
1201    (cons nil (delq nil xs))))
1202
1203(defun emms-cache-set-from-mpd-directory (dir)
1204  "Dump all MusicPD data from DIR into the EMMS cache.
1205
1206This is useful to do when you have recently acquired new music."
1207  (interactive
1208   (list (if emms-player-mpd-music-directory
1209             (emms-read-directory-name "Directory: "
1210                                       emms-player-mpd-music-directory)
1211           (read-string "Directory: "))))
1212  (unless (string= dir "")
1213    (setq dir (emms-player-mpd-get-mpd-filename dir)))
1214  (if emms-cache-set-function
1215      (progn
1216        (message "Dumping MusicPD data to cache...")
1217        (emms-player-mpd-send
1218         (concat "listallinfo " dir)
1219         nil
1220         (lambda (closure response)
1221           (message "Dumping MusicPD data to cache...processing")
1222           (let ((info (emms-player-mpd-parse-response response)))
1223             (when (null (car info))
1224               (let* ((info (emms-cache--info-cleanup info))
1225                      (info (emms-player-mpd-get-alists info))
1226                      (track 1)
1227                      (total (length info)))
1228                 (dolist (track-info info)
1229                   (message "Dumping MusicPD data to cache...%d/%d" track total)
1230                   (emms-cache-set-from-mpd-track track-info)
1231                   (setq track (+ 1 track)))
1232                 (message "Dumping MusicPD data to cache... %d tracks processed"
1233                          total)))))))
1234    (error "Caching is not enabled")))
1235
1236(defun emms-cache-set-from-mpd-all ()
1237  "Dump all MusicPD data into the EMMS cache.
1238
1239This is useful to do once, just before using emms-browser.el, in
1240order to prime the cache."
1241  (interactive)
1242  (emms-cache-set-from-mpd-directory ""))
1243
1244;;; Updating tracks
1245
1246(defun emms-player-mpd-update-directory (dir)
1247  "Cause the tracks in DIR to be updated in the MusicPD database."
1248  (interactive
1249   (list (if emms-player-mpd-music-directory
1250             (emms-read-directory-name "Directory: "
1251                                       emms-player-mpd-music-directory)
1252           (read-string "Directory: "))))
1253  (unless (string= dir "")
1254    (setq dir (emms-player-mpd-get-mpd-filename dir)))
1255  (emms-player-mpd-send
1256   (concat "update " (emms-player-mpd-quote-file dir)) nil
1257   (lambda (closure response)
1258     (let ((id (cdr (assoc "updating_db"
1259                           (emms-player-mpd-get-alist
1260                            (emms-player-mpd-parse-response response))))))
1261       (if id
1262           (message "Updating DB with ID %s" id)
1263         (message "Could not update the DB"))))))
1264
1265(defun emms-player-mpd-update-all ()
1266  "Cause all tracks in the MusicPD music directory to be updated in
1267the MusicPD database."
1268  (interactive)
1269  (emms-player-mpd-update-directory ""))
1270
1271(defvar emms-player-mpd-waiting-for-update-timer nil
1272  "Timer object when waiting for MPD update to finish.")
1273
1274(defun emms-player-mpd-update-all-reset-cache ()
1275  "Update all tracks in the MusicPD music directory.
1276When update finishes, clear the EMMS cache and call
1277`emms-cache-set-from-mpd-all' to dump the MusicPD data into the
1278cache."
1279  (interactive)
1280  (if emms-player-mpd-waiting-for-update-timer
1281      (message "Already waiting for an update to finish.")
1282    (emms-player-mpd-send
1283     "update" nil
1284     'emms-player-mpd-wait-for-update)))
1285
1286(defun emms-player-mpd-wait-for-update (&optional closure response)
1287  "Wait for a currently running mpd update to finish.
1288Afterwards, clear the EMMS cache and call
1289`emms-cache-set-from-mpd-all'."
1290  (if response
1291      ;; This is the first call after the update command
1292      (let ((id (cdr (assoc "updating_db"
1293			    (emms-player-mpd-get-alist
1294			     (emms-player-mpd-parse-response response))))))
1295	(if id
1296	    (progn
1297	      (message "Updating DB with ID %s.  Waiting for the update to finish..." id)
1298	      (setq emms-player-mpd-waiting-for-update-timer
1299		    (run-at-time 1 nil 'emms-player-mpd-wait-for-update)))
1300	  (message "Could not update the DB")))
1301    ;; Otherwise, check if update is still in progress
1302    (emms-player-mpd-get-status-part
1303     nil
1304     (lambda (closure updating)
1305       (if updating
1306	   ;; MPD update still in progress, so wait another second
1307	   (run-at-time 1 nil 'emms-player-mpd-wait-for-update)
1308	 ;; MPD update finished
1309	 (setq  emms-player-mpd-waiting-for-update-timer nil)
1310	 (message "MPD update finished.")
1311	 (sit-for 1)
1312	 (clrhash emms-cache-db)
1313	 (emms-cache-set-from-mpd-all)))
1314     "updating_db")))
1315
1316
1317(provide 'emms-player-mpd)
1318
1319;;; emms-player-mpd.el ends here
1320