fordon_greeman / voidfiles

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