6 #mpd.rb is the Ruby MPD Library
8 #Written for MPD 0.11.5 (see http://www.musicpd.org for MPD itself)
10 #The MPD class provides an interface for communicating with an MPD server (MPD = Music Player
11 #Daemon, a 'jukebox' server that plays various audio files like mp3, Ogg Vorbis, etc -- see
12 #www.musicpd.org for more about MPD itself). Method names largely correspond to the same command
13 #with the MPD protocol itself, and other MPD tools, like mpc. Some convenience methods for
14 #writing clients are included as well.
18 #The default host is 'localhost'. The default port is 6600.
19 #If the user has environment variables MPD_HOST or MPD_PORT set, these
20 #will override the default settings.
22 #mpd.rb makes no attempt to keep the socket alive. If it dies it just opens a new socket.
24 #If your MPD server requires a password, you will need to use MPD#password= or MPD#password(pass)
25 #before you can use any other server command. Once you set a password with an instance it will
26 #persist, even if your session is disconnected.
28 #Unfortunately there is no way to do callbacks from the server. For example, if you want to do
29 #something special when a new song begins, the best you can do is monitor MPD#currentsong.dbid for a
30 #new ID number and then do that something when you notice a change. But given latency you are
31 #unlikely to be able to stop the next song from starting. What I'd like to see is a feature added to
32 #MPD where when each song finishes it loads the next song and then waits for a "continue" signal
33 #before beginning playback. In the meantime the only way to do this would be to constantly maintain
34 #a single song playlist, swapping out the finished song for a new song each time.
40 # m = MPD.new('some_host')
44 # m.currentsong.title => 'Ruby Tuesday'
45 # m.strf('%a - %t') => 'The Beatles - Ruby Tuesday'
49 #mpd.rb is Copyright (c) 2004, Michael C. Libby (mcl@andsoforth.com)
51 #mpd.rb homepage is: http://www.andsoforth.com/geek/MPD.html
53 #report mpd.rb bugs to mcl@andsoforth.com
55 #Translated and adapted from MPD.pm by Tue Abrahamsen.
59 #This program is free software; you can redistribute it and/or modify
60 #it under the terms of the GNU General Public License as published by
61 #the Free Software Foundation; either version 2 of the License, or
62 #(at your option) any later version.
64 #This program is distributed in the hope that it will be useful,
65 #but WITHOUT ANY WARRANTY; without even the implied warranty of
66 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
67 #GNU General Public License for more details.
69 #You should have received a copy of the GNU General Public License
70 #along with this program; if not, write to the Free Software
71 #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
73 #See file COPYING for details.
76 MPD_VERSION = '0.11.5' #Version of MPD this version of mpd.rb was tested against
78 DEFAULT_MPD_HOST = 'localhost'
79 DEFAULT_MPD_PORT = 6600
81 # MPD::SongInfo elements are:
83 # +file+ :: full pathname of file as seen by server
84 # +album+ :: name of the album
85 # +artist+ :: name of the artist
86 # +dbid+ :: mpd db id for track
87 # +pos+ :: playlist array index (starting at 0)
88 # +time+ :: time of track in seconds
89 # +title+ :: track title
90 # +track+ :: track number within album
92 SongInfo = Struct.new("SongInfo", "file", "album", "artist", "dbid", "pos", "time", "title", "track")
94 # MPD::Error elements are:
96 # +number+ :: ID number of the error as Integer
97 # +index+ :: Line number of the error (0 if not in a command list) as Integer
98 # +command+ :: Command name that caused the error
99 # +description+ :: Human readable description of the error
101 Error = Struct.new("Error", "number", "index", "command", "description")
103 #common regexps precompiled for speed and clarity
106 'ACK_MESSAGE' => Regexp.new(/^ACK \[(\d+)\@(\d+)\] \{(.+)\} (.+)$/),
107 'DIGITS_ONLY' => Regexp.new(/^\d+$/),
108 'OK_MPD_VERSION' => Regexp.new(/^OK MPD (.+)$/),
109 'NON_DIGITS' => Regexp.new(/^\D+$/),
110 'LISTALL' => Regexp.new(/^file:\s/),
111 'PING' => Regexp.new(/^OK/),
112 'PLAYLIST' => Regexp.new(/^(\d+?):(.+)$/),
113 'PLAYLISTINFO' => Regexp.new(/^(.+?):\s(.+)$/),
114 'STATS' => Regexp.new(/^(.+?):\s(.+)$/),
115 'STATUS' => Regexp.new(/^(.+?):\s(.+)$/),
118 # If the user has environment variables MPD_HOST or MPD_PORT set, these will override the default
119 # settings. Setting host or port in MPD.new will override both the default and the user settings.
120 # Defaults are defined in class constants MPD::DEFAULT_MPD_HOST and MPD::DEFAULT_MPD_PORT.
122 def initialize(mpd_host = nil, mpd_port = nil)
124 @overwrite_playlist = true
125 @allow_toggle_states = true
126 @debug_socket = false
129 @mpd_host = ENV['MPD_HOST'] if @mpd_host.nil?
130 @mpd_host = DEFAULT_MPD_HOST if @mpd_host.nil?
133 @mpd_port = ENV['MPD_PORT'] if @mpd_port.nil?
134 @mpd_port = DEFAULT_MPD_PORT if @mpd_port.nil?
142 # Add song at <i>path</i> to the playlist. <i>path</i> is the relative path as seen by the server,
143 # not the actual path name of the file on the filesystem.
146 socket_puts("add \"#{path}\"")
149 # Clear the playlist of all entries. Consider MPD#save first.
155 # Clear the error element in status info.
156 # Rare that you will need or want to do this. Most error info is cleared automatically anytime a
157 # valid play type command is issued or continues to function.
161 socket_puts("clearerror")
164 # Close the connection to the server.
167 return nil unless is_connected?
172 # Private method for creating command lists.
174 def command_list_begin
175 @command_list = ["command_list_begin"]
178 # Wish this would take a block, but haven't quite figured out to get that to work
179 # For now just put commands in the list.
185 # Closes and executes a command list.
188 @command_list << "command_list_end"
189 sp = @command_list.flatten.join("\n")
194 # Activate a closed connection. Will automatically send password if one has been set.
197 unless is_connected? then
198 warn "connecting to socket" if @debug_socket
199 @socket = TCPSocket.new(@mpd_host, @mpd_port)
200 if md = @@re['OK_MPD_VERSION'].match(@socket.readline) then
202 if @mpd_version > MPD_VERSION then
203 warn "MPD server version newer than mpd.rb version - expect the unexpected"
205 unless @password.nil? then
206 warn "connect sending password" if @debug_socket
207 @socket.puts("password #{@password}")
211 warn "Connection error (Invalid Version Response)"
217 # Clear every entry from the playlist but the current song.
220 # this really ought to just generate a list and send that to delete()
222 (playlistlength.to_i - 1).downto(currentsong.pos + 1) do |i|
223 command( "delete #{i}" )
225 (currentsong.pos - 1).downto(0) do |i|
226 command( "delete #{i}" )
231 # Sets the crossfade value (in seconds)
233 def crossfade(fade_value)
234 socket_puts("crossfade #{fade_value}")
238 # Returns an instance of Struct MPD::SongInfo.
241 response_to_songinfo(@@re['PLAYLISTINFO'],
242 socket_puts("currentsong")
246 # Turns off socket command debugging.
249 @debug_socket = false
252 # Turns on socket command debugging (prints each socket command to STDERR as well as the socket)
258 # <i>song</i> is one of:
259 # * a song's playlist number,
260 # * a song's MPD database ID (if <i>from_id</i> is set to true),
261 # * any object that implements a <i>collect</i> function that ultimately boils down to a set of integers. :)
264 # <tt>MPD#delete(1) # delete second song (remember playlist starts at index 0)</tt>
265 # <tt>MPD#delete(0..4) # delete first five songs</tt>
266 # <tt>MPD#delete(['1', '2', '3']) # delete songs two, three, and four</tt>
267 # <tt>MPD#delete(1..3, 45..48, '99') # delete songs two thru four, forty-six thru forty-nine, and one hundred
269 # When <i>from_id</i> is true, the argument(s) will be treated as MPD database IDs.
270 # It is not recommended to use ranges with IDs since they are unlikely to be consecutive.
271 # An array of IDs, however, would be handy. And don't worry about using indexes in a long list.
272 # The function will convert all references to IDs before deleting (as well as removing duplicates).
273 def delete(song, from_id = false)
274 cmd = from_id ? 'deleteid' : 'delete'
275 slist = expand_list(song).flatten.uniq
277 if slist.length == 1 then
278 return nil unless @@re['DIGITS_ONLY'].match(slist[0].to_s)
279 return socket_puts("#{cmd} #{slist[0]}")
282 # convert to ID for list commands, otherwise as soon as first delete happens
283 # the rest of the indexes won't be accurate
284 slist = slist.map{|x| playlistinfo(x).dbid }
288 next unless @@re['DIGITS_ONLY'].match(slist[0].to_s)
289 command("deleteid #{x}")
291 return command_list_end
295 # Returns a Struct MPD::Error,
301 # Alias for MPD#delete(song_id, true)
302 def deleteid(song_id)
303 delete(song_id, true)
306 # Takes and prepares any <i>collect</i>able list to be flattened and uniq'ed.
307 # That is, it converts <tt>[0..2, '3', [4, 5]]</tt> into <tt>[0, 1, 2, '3', [4, 5]]</tt>.
308 # Essentially it expands Range objects and the like.
311 if d.respond_to?("collect") then
312 if d.collect == d then
313 return d.collect{|x| expand_list(x)}
316 if dc.length > 1 then
317 return d.collect{|x| expand_list(x)}
327 # Finds exact matches of <i>find_string</i> in the MPD database.
328 # <i>find_type</i> is limited to 'album', 'artist', and 'title'.
330 # Returns an array containing an instance of MPD::SongInfo (Struct) for every song in the current
333 # Results from MPD#find() do not have valid information for dbid or pos
335 def find(find_type, find_string)
336 response_to_songinfo(@@re['PLAYLISTINFO'],
337 socket_puts("find #{find_type} \"#{find_string}\"")
341 # Runs MPD#find using the given parameters and automatically adds each result
342 # to the playlist. Returns an Array of MPD::SongInfo structs.
344 def find_add(find_type, find_string)
345 flist = find(find_type, find_string)
348 command("add #{x.file}")
354 # Private method for handling the messages the server sends.
356 def get_server_response
358 while line = @socket.readline.chomp do
359 # Did we cause an error? Save the data!
360 if md = @@re['ACK_MESSAGE'].match(line) then
361 @error = Error.new(md[1].to_i, md[2].to_i, md[3], md[4])
362 raise "MPD Error #{md[1]}: #{md[4]}"
364 return response if @@re['PING'].match(line)
371 # Internal method for converting results from currentsong, playlistinfo, playlistid to
372 # MPD::SongInfo structs
374 def hash_to_songinfo(h)
375 SongInfo.new(h['file'],
378 h['Id'].nil? ? nil : h['Id'].to_i,
379 h['Pos'].nil? ? nil : h['Pos'].to_i,
386 # Pings the server and returns true or false depending on whether a response was receieved.
389 return false if @socket.nil? || @socket.closed?
390 warn "is_connected to socket: ping" if @debug_socket
392 if @@re['PING'].match(@socket.readline) then
400 # Kill the MPD server.
401 # No way exists to restart it from here, so be careful.
405 rescue #kill always causes a readline error in get_server_response
409 # Gets a list of Artist names or Album names from the MPD database (not the current playlist).
410 # <i>type</i> is either 'artist' (default) or 'album'. The <i>artist</i> parameter is
411 # used with <i>type</i>='album' to limit results to just the albums by that artist.
413 def list(type = 'artist', artist = '')
414 response = socket_puts(type == 'album' ? "list album \"#{artist}\"" : "list artist")
417 if md = /^(?:Artist|Album):\s(.+)$/.match(f) then
424 # Returns a list of all filenames in <i>path</i> (recursively) according to the MPD database.
425 # If <i>path</i> is omitted, lists every file in the database.
427 def listall(path = '')
428 resp = socket_puts("listall \"#{path}\"").grep(@@re['LISTALL']).map{|x| x.sub(@@re['LISTALL'], '')}
432 # Returns an Array containing MPD::SongInfo for each file in <i>path</i> (recursively) according
433 # to the MPD database.
434 # If <i>path</i> is omitted, lists every file in the datbase.
435 def listallinfo(path = '')
438 response_to_songinfo(@@re['PLAYLISTINFO'],
439 socket_puts("listallinfo \"#{path}\"")
443 # Load a playlist from the MPD playlist directory.
446 socket_puts("load \"#{playlist}\"")
450 # Returns Array of strings containing a list of directories, files or playlists in <i>path</i> (as
451 # seen by the MPD database).
452 # If <i>path</i> is omitted, uses the root directory.
453 def lsinfo(path = '')
456 socket_puts("lsinfo \"#{path}\"").each do |f|
457 if md = /^(.[^:]+):\s(.+)$/.match(f)
458 if ['file', 'playlist', 'directory'].grep(md[1]).length > 0 then
467 # Returns an Array of playlist paths (as seen by the MPD database).
470 lsinfo.grep(/^playlist:\s/).map{|x| x.sub(/^playlist:\s/, '')}.compact
473 # Move song at <i>curr_pos</i> to <i>new_pos</i> in the playlist.
475 def move(curr_pos, new_pos)
476 socket_puts("move #{curr_pos} #{new_pos}")
479 # Move song with MPD database ID <i>song_id</i> to <i>new_pos</i> in the playlist.
481 def moveid(song_id, new_pos)
482 socket_puts("moveid #{song_id} #{new_pos}")
485 # Return the version string returned by the MPD server
491 # Play next song in the playlist. See note about shuffling in MPD#set_random
492 # Returns songid as Integer.
499 # Send the password <i>pass</i> to the server and sets it for this MPD instance.
500 # If <i>pass</i> is omitted, uses any previously set password (see MPD#password=).
501 # Once a password is set by either method MPD#connect can automatically send the password if
504 def password(pass = @password)
506 socket_puts("password #{pass}")
509 # Set the password to <i>pass</i>.
514 # Pause playback on the server
515 # Returns ('pause'|'play'|'stop').
517 def pause(value = nil)
518 cstatus = status['state']
519 return cstatus if cstatus == 'stop'
521 if value.nil? && @allow_toggle_states then
522 value = cstatus == 'pause' ? '0' : '1'
524 socket_puts("pause #{value}")
528 # Send a ping to the server and keep the connection alive.
534 # Start playback of songs in the playlist with song at index
535 # <i>number</i> in the playlist.
536 # Empty <i>number</i> starts playing from current spot or beginning.
537 # Returns current song as MPD::SongInfo.
539 def play(number = '')
540 socket_puts("play #{number}")
544 # Start playback of songs in the playlist with song having
545 # mpd database ID <i>number</i>.
546 # Empty <i>number</i> starts playing from current spot or beginning.
547 # Returns songid as Integer.
549 def playid(number = '')
550 socket_puts("playid #{number}")
554 # <b>Deprecated</b> Use MPD#playlistinfo or MPD#playlistid instead
555 # Returns an Array containing paths for each song in the current playlist
558 warn "MPD#playlist is deprecated. Use MPD#playlistinfo or MPD#playlistid instead."
560 socket_puts("playlist").each do |f|
561 if md = @@re['PLAYLIST'].match(f) then
568 # Returns an array containing an instance of MPD::SongInfo (Struct) for every song in the current
569 # playlist or a single instance of MPD::SongInfo (if <i>snum</i> is specified).
571 # <i>snum</i> is the song's index in the playlist.
572 # If <i>snum</i> == '' then the whole playlist is returned.
573 def playlistinfo(snum = '', from_id = false)
574 plist = response_to_songinfo(@@re['PLAYLISTINFO'],
575 socket_puts("playlist#{from_id ? 'id' : 'info'} #{snum}")
577 return snum == '' ? plist : plist[0]
580 # An alias for MPD#playlistinfo with <i>from_id</i> = true.
581 # Looks up song <i>sid</i> is the song's MPD ID (<i>dbid</i> in an MPD::SongInfo
583 # Returns an Array of Hashes.
585 def playlistid(sid = '')
586 playlistinfo(sid, true)
589 # Get the length of the playlist from the server.
593 status['playlistlength'].to_i
596 # Returns an Array of MPD#SongInfo. The songs listed are either those added since previous
597 # playlist version, <i>playlist_num</i>, <b>or</b>, if a song was deleted, the new playlist that
598 # resulted. Cumbersome. Eventually methods will be written that help track adds/deletes better.
600 def plchanges(playlist_num = '-1')
601 response_to_songinfo(@@re['PLAYLISTINFO'],
602 socket_puts("plchanges #{playlist_num}")
606 # Play previous song in the playlist. See note about shuffling in MPD#set_random.
607 # Return songid as Integer
610 socket_puts("previous")
615 # Sets random mode on the server, either directly, or by toggling (if
616 # no argument given and @allow_toggle_states = true). Mode "0" = not
617 # random; Mode "1" = random. Random affects playback order, but not playlist
618 # order. When random is on the playlist is shuffled and then used instead
619 # of the actual playlist. Previous and next in random go to the previous
620 # and next songs in the shuffled playlist. Calling MPD#next and then
621 # MPD#prev would start playback at the beginning of the current song.
623 def random(mode = nil)
624 return nil if mode.nil? && !@allow_toggle_states
625 return nil unless /^(0|1)$/.match(mode) || @allow_toggle_states
627 mode = status['random'] == '1' ? '0' : '1'
629 socket_puts("random #{mode}")
633 # Sets repeat mode on the server, either directly, or by toggling (if
634 # no argument given and @allow_toggle_states = true). Mode "0" = not
635 # repeat; Mode "1" = repeat. Repeat means that server will play song 1
636 # when it reaches the end of the playlist.
638 def repeat(mode = nil)
639 return nil if mode.nil? && !@allow_toggle_states
640 return nil unless /^(0|1)$/.match(mode) || @allow_toggle_states
642 mode = status['repeat'] == '1' ? '0' : '1'
644 socket_puts("repeat #{mode}")
648 # Private method to convert playlistinfo style server output into MPD#SongInfo list
649 # <i>re</i> is the Regexp to use to match "<element type>: <element>".
650 # <i>response</i> is the output from MPD#socket_puts.
651 def response_to_songinfo(re, response)
655 if md = re.match(f) then
656 if md[1] == 'file' then
658 list << nil unless list == []
660 list << hash_to_songinfo(hash)
668 list << nil unless list == []
670 list << hash_to_songinfo(hash)
675 # Deletes the playlist file <i>playlist</i>.m3u from the playlist directory on the server.
678 socket_puts("rm \"#{playlist}\"")
681 # Save the current playlist as <i>playlist</i>.m3u in the playlist directory on the server.
682 # If <i>force</i> is true, any existing playlist with the same name will be deleted before saving.
684 def save(playlist, force = @overwrite_playlist)
685 socket_puts("save \"#{playlist}\"")
687 if error.number == 56 && force then
689 return socket_puts("save \"#{playlist}\"")
694 # Similar to MPD#find, only search is not strict. It will match <i>search_type</i> of 'artist',
695 # 'album', 'title', or 'filename' against <i>search_string</i>.
696 # Returns an Array of MPD#SongInfo.
698 def search(search_type, search_string)
699 response_to_songinfo(@@re['PLAYLISTINFO'],
700 socket_puts("search #{search_type} \"#{search_string}\"")
704 # Conducts a search of <i>search_type</i> for <i>search_string</i> and adds the results to the
705 # current playlist. Returns the results of the search.
707 def search_add(search_type, search_string)
708 results = search(search_type, search_string)
709 unless results == [] then
712 command( "add \"#{s.file}\"")
719 # Seek to <i>position</i> seconds within song number <i>song</i> in the playlist. If no
720 # <i>song</i> is given, uses current song.
722 def seek(position, song = currentsong.pos)
723 socket_puts("seek #{song} #{position}")
726 # Seek to <i>position</i> seconds within song ID <i>song</i>. If no <i>song</i> is given, uses
729 def seekid(position, song_id = currentsong.dbid)
730 socket_puts("seekid #{song_id} #{position}")
733 # Set the volume to <i>volume</i>. Range is limited to 0-100. MPD#set_volume
734 # will adjust any value passed less than 0 or greater than 100.
737 vol = 0 if vol.to_i < 0
738 vol = 100 if vol.to_i > 100
739 socket_puts("setvol #{vol}")
743 # Shuffles the current playlist and increments playlist version by 1.
744 # This will rearrange your actual playlist with no way to resort it
745 # (other than saving it before shuffling and then reloading it).
746 # If you just want random playback use MPD#random.
749 socket_puts("shuffle")
752 # Sends a command to the MPD server and optionally to STDOUT if
753 # MPD#debug_on has been used to turn debugging on
756 connect unless is_connected?
757 warn "socket_puts to socket: #{cmd}" if @debug_socket
759 return get_server_response
762 # Returns a hash containing various server stats:
764 # +albums+ :: number of albums in mpd database
765 # +artists+ :: number of artists in mpd database
766 # +db_playtime+ :: sum of all song times in in mpd database
767 # +db_update+ :: last mpd database update in UNIX time
768 # +playtime+ :: time length of music played during uptime
769 # +songs+ :: number of songs in mpd database
770 # +uptime+ :: mpd server uptime in seconds
774 socket_puts("stats").each do |f|
775 if md = @@re['STATS'].match(f);
782 # Returns a hash containing various status elements:
784 # +audio+ :: '<sampleRate>:<bits>:<channels>' describes audio stream
785 # +bitrate+ :: bitrate of audio stream in kbps
786 # +error+ :: if there is an error, returns message here
787 # +playlist+ :: the playlist version number as String
788 # +playlistlength+ :: number indicating the length of the playlist as String
789 # +repeat+ :: '0' or '1'
790 # +song+ :: playlist index number of current song (stopped on or playing)
791 # +songid+ :: song ID number of current song (stopped on or playing)
792 # +state+ :: 'pause'|'play'|'stop'
793 # +time+ :: '<elapsed>:<total>' (both in seconds) of current playing/paused song
794 # +updating_db+ :: '<job id>' if currently updating db
795 # +volume+ :: '0' to '100'
796 # +xfade+ :: crossfade in seconds
800 socket_puts("status").each do |f|
801 if md = @@re['STATUS'].match(f) then
809 # Returns ('pause'|'play'|'stop').
816 # Pass a format string (like strftime) and get back a string of MPD information.
818 # Format string elements are:
819 # <tt>%f</tt> :: filename
820 # <tt>%a</tt> :: artist
821 # <tt>%A</tt> :: album
822 # <tt>%i</tt> :: MPD database ID
823 # <tt>%p</tt> :: playlist position
824 # <tt>%t</tt> :: title
825 # <tt>%T</tt> :: track time (in seconds)
826 # <tt>%n</tt> :: track number
827 # <tt>%e</tt> :: elapsed playtime (MM:SS form)
828 # <tt>%l</tt> :: track length (MM:SS form)
830 # <i>song_info</i> can either be an existing MPD::SongInfo object (such as the one returned by
831 # MPD#currentsong) or the MPD database ID for a song. If no <i>song_info</i> is given, all
832 # song-related elements will come from the current song.
834 def strf(format_string, song_info = currentsong)
835 unless song_info.class == Struct::SongInfo
836 if @@re['DIGITS_ONLY'].match(song_info.to_s) then
837 song_info = playlistid(song_info)
842 format_string.scan(/%[EO]?.|./o) do |x|
845 s << song_info.file.to_s
848 s << song_info.artist.to_s
851 s << song_info.album.to_s
854 s << song_info.dbid.to_s
857 s << song_info.pos.to_s
860 s << song_info.title.to_s
863 s << song_info.time.to_s
866 s << song_info.track.to_s
869 t = status['time'].split(/:/)[0].to_f
870 s << sprintf( "%d:%02d", t / 60, t % 60 )
873 t = status['time'].split(/:/)[1].to_f
874 s << sprintf( "%d:%02d", t / 60, t % 60 )
884 # Swap two songs in the playlist, either based on playlist indexes or song IDs (when <i>from_id</i> is true).
886 def swap(song_from, song_to, from_id = false)
887 if @@re['DIGITS_ONLY'].match(song_from.to_s) && @@re['DIGITS_ONLY'].match(song_to.to_s) then
888 return socket_puts("#{from_id ? 'swapid' : 'swap'} #{song_from} #{song_to}")
890 raise "invalid input for swap"
894 # Alias for MPD#swap(song_id_from, song_id_to, true)
896 def swap_id(song_id_from, song_id_to)
897 swap(song_id_from, song_id_to, true)
900 # Searches MP3 directory for new music and removes old music from the MPD database.
901 # <i>path</i> is an optional argument that specifies a particular directory or
902 # song/file to update. <i>path</i> can also be a list of paths to update.
903 # If <i>path</i> is omitted, the entire database will be updated using the server's
904 # base MP3 directory.
906 def update(path = '')
907 ulist = expand_list(path).flatten.uniq
908 if ulist.length == 1 then
909 return socket_puts("update #{ulist[0]}")
913 command("update #{x}")
915 return command_list_end
919 # Returns the types of URLs that can be handled by the server.
923 socket_puts("urlhandlers").each do |f|
924 handlers << f if /^handler: (.+)$/.match(f)
929 # <b>Deprecated</b> Use MPD#setvol instead.
930 # Increase or decrease volume (depending on whether <i>vol_change</i> is positive or
931 # negative. Volume is limited to the range of 0-100 (server ensures that change
932 # does not take volume out of range).
935 def volume(vol_change)
936 warn "MPD#volume is deprecated. Use MPD#setvol instead."
937 socket_puts("volume #{vol_change}")
941 private :command, :command_list_begin, :command_list_end, :expand_list
942 private :connect, :get_server_response, :socket_puts
943 private :hash_to_songinfo, :response_to_songinfo