X-Git-Url: http://git.kaliko.me/?p=python-musicpd.git;a=blobdiff_plain;f=mpd.py;h=7305f7d94eba202796d147894c9ecb671cc1381a;hp=2875f95ced8c42c3511ce9d60c7868692e734755;hb=7299c4fd1a57ce290cbd4f9463deeebb02764e61;hpb=5c84adfc3853f683df1a79f4e4852386c4714508 diff --git a/mpd.py b/mpd.py index 2875f95..7305f7d 100644 --- a/mpd.py +++ b/mpd.py @@ -1,18 +1,18 @@ -# Python MPD client library -# Copyright (C) 2008 J. Alexander Treuman +# python-mpd: Python MPD client library +# Copyright (C) 2008-2010 J. Alexander Treuman # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by +# python-mpd is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # -# This program is distributed in the hope that it will be useful, +# python-mpd is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# You should have received a copy of the GNU Lesser General Public License +# along with python-mpd. If not, see . import socket @@ -41,6 +41,9 @@ class CommandListError(MPDError): class PendingCommandError(MPDError): pass +class IteratingError(MPDError): + pass + class _NotConnected(object): def __getattr__(self, attr): @@ -55,82 +58,95 @@ class MPDClient(object): self._reset() self._commands = { # Status Commands - "clearerror": self._fetch_nothing, - "currentsong": self._fetch_object, - "idle": self._fetch_list, - "noidle": None, - "status": self._fetch_object, - "stats": self._fetch_object, + "clearerror": self._fetch_nothing, + "currentsong": self._fetch_object, + "idle": self._fetch_list, + "noidle": None, + "status": self._fetch_object, + "stats": self._fetch_object, # Playback Option Commands - "consume": self._fetch_nothing, - "crossfade": self._fetch_nothing, - "random": self._fetch_nothing, - "repeat": self._fetch_nothing, - "setvol": self._fetch_nothing, - "single": self._fetch_nothing, - "volume": self._fetch_nothing, + "consume": self._fetch_nothing, + "crossfade": self._fetch_nothing, + "mixrampdb": self._fetch_nothing, + "mixrampdelay": self._fetch_nothing, + "random": self._fetch_nothing, + "repeat": self._fetch_nothing, + "setvol": self._fetch_nothing, + "single": self._fetch_nothing, + "replay_gain_mode": self._fetch_nothing, + "replay_gain_status": self._fetch_item, + "volume": self._fetch_nothing, # Playback Control Commands - "next": self._fetch_nothing, - "pause": self._fetch_nothing, - "play": self._fetch_nothing, - "playid": self._fetch_nothing, - "previous": self._fetch_nothing, - "seek": self._fetch_nothing, - "seekid": self._fetch_nothing, - "stop": self._fetch_nothing, + "next": self._fetch_nothing, + "pause": self._fetch_nothing, + "play": self._fetch_nothing, + "playid": self._fetch_nothing, + "previous": self._fetch_nothing, + "seek": self._fetch_nothing, + "seekid": self._fetch_nothing, + "stop": self._fetch_nothing, # Playlist Commands - "add": self._fetch_nothing, - "addid": self._fetch_item, - "clear": self._fetch_nothing, - "delete": self._fetch_nothing, - "deleteid": self._fetch_nothing, - "move": self._fetch_nothing, - "moveid": self._fetch_nothing, - "playlist": self._fetch_playlist, - "playlistfind": self._fetch_songs, - "playlistid": self._fetch_songs, - "playlistinfo": self._fetch_songs, - "playlistsearch": self._fetch_songs, - "plchanges": self._fetch_songs, - "plchangesposid": self._fetch_changes, - "shuffle": self._fetch_nothing, - "swap": self._fetch_nothing, - "swapid": self._fetch_nothing, + "add": self._fetch_nothing, + "addid": self._fetch_item, + "clear": self._fetch_nothing, + "delete": self._fetch_nothing, + "deleteid": self._fetch_nothing, + "move": self._fetch_nothing, + "moveid": self._fetch_nothing, + "playlist": self._fetch_playlist, + "playlistfind": self._fetch_songs, + "playlistid": self._fetch_songs, + "playlistinfo": self._fetch_songs, + "playlistsearch": self._fetch_songs, + "plchanges": self._fetch_songs, + "plchangesposid": self._fetch_changes, + "shuffle": self._fetch_nothing, + "swap": self._fetch_nothing, + "swapid": self._fetch_nothing, # Stored Playlist Commands - "listplaylist": self._fetch_list, - "listplaylistinfo": self._fetch_songs, - "listplaylists": self._fetch_playlists, - "load": self._fetch_nothing, - "playlistadd": self._fetch_nothing, - "playlistclear": self._fetch_nothing, - "playlistdelete": self._fetch_nothing, - "playlistmove": self._fetch_nothing, - "rename": self._fetch_nothing, - "rm": self._fetch_nothing, - "save": self._fetch_nothing, + "listplaylist": self._fetch_list, + "listplaylistinfo": self._fetch_songs, + "listplaylists": self._fetch_playlists, + "load": self._fetch_nothing, + "playlistadd": self._fetch_nothing, + "playlistclear": self._fetch_nothing, + "playlistdelete": self._fetch_nothing, + "playlistmove": self._fetch_nothing, + "rename": self._fetch_nothing, + "rm": self._fetch_nothing, + "save": self._fetch_nothing, # Database Commands - "count": self._fetch_object, - "find": self._fetch_songs, - "list": self._fetch_list, - "listall": self._fetch_database, - "listallinfo": self._fetch_database, - "lsinfo": self._fetch_database, - "search": self._fetch_songs, - "update": self._fetch_item, + "count": self._fetch_object, + "find": self._fetch_songs, + "findadd": self._fetch_nothing, + "list": self._fetch_list, + "listall": self._fetch_database, + "listallinfo": self._fetch_database, + "lsinfo": self._fetch_database, + "search": self._fetch_songs, + "update": self._fetch_item, + "rescan": self._fetch_item, + # Sticker Commands + "sticker get": self._fetch_item, + "sticker set": self._fetch_nothing, + "sticker delete": self._fetch_nothing, + "sticker list": self._fetch_list, + "sticker find": self._fetch_songs, # Connection Commands - "close": None, - "kill": None, - "password": self._fetch_nothing, - "ping": self._fetch_nothing, + "close": None, + "kill": None, + "password": self._fetch_nothing, + "ping": self._fetch_nothing, # Audio Output Commands - "disableoutput": self._fetch_nothing, - "enableoutput": self._fetch_nothing, - "outputs": self._fetch_outputs, + "disableoutput": self._fetch_nothing, + "enableoutput": self._fetch_nothing, + "outputs": self._fetch_outputs, # Reflection Commands - "commands": self._fetch_list, - "notcommands": self._fetch_list, - "tagtypes": self._fetch_list, - "urlhandlers": self._fetch_list, + "commands": self._fetch_list, + "notcommands": self._fetch_list, + "tagtypes": self._fetch_list, + "urlhandlers": self._fetch_list, + "decoders": self._fetch_plugins, } def __getattr__(self, attr): @@ -144,44 +160,58 @@ class MPDClient(object): command = attr wrapper = self._execute if command not in self._commands: - raise AttributeError("'%s' object has no attribute '%s'" % - (self.__class__.__name__, attr)) + command = command.replace("_", " ") + if command not in self._commands: + raise AttributeError("'%s' object has no attribute '%s'" % + (self.__class__.__name__, attr)) return lambda *args: wrapper(command, args) def _send(self, command, args): if self._command_list is not None: raise CommandListError("Cannot use send_%s in a command list" % - command) + command.replace(" ", "_")) self._write_command(command, args) - self._pending.append(command) + retval = self._commands[command] + if retval is not None: + self._pending.append(command) def _fetch(self, command, args=None): if self._command_list is not None: raise CommandListError("Cannot use fetch_%s in a command list" % - command) + command.replace(" ", "_")) + if self._iterating: + raise IteratingError("Cannot use fetch_%s while iterating" % + command.replace(" ", "_")) if not self._pending: raise PendingCommandError("No pending commands to fetch") if self._pending[0] != command: - raise PendingCommandError("%s is not the currently " + raise PendingCommandError("'%s' is not the currently " "pending command" % command) del self._pending[0] retval = self._commands[command] if callable(retval): return retval() + return retval def _execute(self, command, args): + if self._iterating: + raise IteratingError("Cannot execute '%s' while iterating" % + command) if self._pending: - raise PendingCommandError("Cannot execute %s with " + raise PendingCommandError("Cannot execute '%s' with " "pending commands" % command) retval = self._commands[command] - if self._command_list is not None and not callable(retval): - raise CommandListError("%s not allowed in command list" % command) - self._write_command(command, args) - if self._command_list is None: + if self._command_list is not None: + if not callable(retval): + raise CommandListError("'%s' not allowed in command list" % + command) + self._write_command(command, args) + self._command_list.append(retval) + else: + self._write_command(command, args) if callable(retval): return retval() return retval - self._command_list.append(retval) def _write_line(self, line): self._wfile.write("%s\n" % line) @@ -224,7 +254,6 @@ class MPDClient(object): while pair: yield pair pair = self._read_pair(separator) - raise StopIteration def _read_list(self): seen = None @@ -235,12 +264,10 @@ class MPDClient(object): (seen, key)) seen = key yield value - raise StopIteration def _read_playlist(self): for key, value in self._read_pairs(":"): yield value - raise StopIteration def _read_objects(self, delimiters=[]): obj = {} @@ -259,19 +286,27 @@ class MPDClient(object): obj[key] = value if obj: yield obj - raise StopIteration def _read_command_list(self): - for retval in self._command_list: - yield retval() - self._command_list = None + try: + for retval in self._command_list: + yield retval() + finally: + self._command_list = None self._fetch_nothing() - raise StopIteration + + def _iterator_wrapper(self, iterator): + try: + for item in iterator: + yield item + finally: + self._iterating = False def _wrap_iterator(self, iterator): if not self.iterate: return list(iterator) - return iterator + self._iterating = True + return self._iterator_wrapper(iterator) def _fetch_nothing(self): line = self._read_line() @@ -299,6 +334,9 @@ class MPDClient(object): def _fetch_objects(self, delimiters): return self._wrap_iterator(self._read_objects(delimiters)) + def _fetch_changes(self): + return self._fetch_objects(["cpos"]) + def _fetch_songs(self): return self._fetch_objects(["file"]) @@ -311,8 +349,8 @@ class MPDClient(object): def _fetch_outputs(self): return self._fetch_objects(["outputid"]) - def _fetch_changes(self): - return self._fetch_objects(["cpos"]) + def _fetch_plugins(self): + return self._fetch_objects(["plugin"]) def _fetch_command_list(self): return self._wrap_iterator(self._read_command_list()) @@ -328,6 +366,7 @@ class MPDClient(object): def _reset(self): self.mpd_version = None + self._iterating = False self._pending = [] self._command_list = None self._sock = None @@ -347,26 +386,26 @@ class MPDClient(object): flags = socket.AI_ADDRCONFIG except AttributeError: flags = 0 - msg = "getaddrinfo returns an empty list" + err = None for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP, flags): af, socktype, proto, canonname, sa = res + sock = None try: sock = socket.socket(af, socktype, proto) sock.connect(sa) - except socket.error, msg: - if sock: + return sock + except socket.error, err: + if sock is not None: sock.close() - sock = None - continue - break - if not sock: - raise socket.error(msg) - return sock + if err is not None: + raise err + else: + raise ConnectionError("getaddrinfo returns an empty list") def connect(self, host, port): - if self._sock: + if self._sock is not None: raise ConnectionError("Already connected") if host.startswith("/"): self._sock = self._connect_unix(host) @@ -386,9 +425,16 @@ class MPDClient(object): self._sock.close() self._reset() + def fileno(self): + if self._sock is None: + raise ConnectionError("Not connected") + return self._sock.fileno() + def command_list_ok_begin(self): if self._command_list is not None: raise CommandListError("Already in command list") + if self._iterating: + raise IteratingError("Cannot begin command list while iterating") if self._pending: raise PendingCommandError("Cannot begin command list " "with pending commands") @@ -398,6 +444,8 @@ class MPDClient(object): def command_list_end(self): if self._command_list is None: raise CommandListError("Not in command list") + if self._iterating: + raise IteratingError("Already iterating over a command list") self._write_command("command_list_end") return self._fetch_command_list()