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