X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/a4243347741f0f7079858e03bca8fc32990f525c..ac9049029faddf79272076e79c3f4ab33da778a6:/python/disorder.py.in diff --git a/python/disorder.py.in b/python/disorder.py.in index ef33106..fe054a9 100644 --- a/python/disorder.py.in +++ b/python/disorder.py.in @@ -1,20 +1,18 @@ # # Copyright (C) 2004, 2005, 2007, 2008 Richard Kettlewell # -# This program is free software; you can redistribute it and/or modify +# 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 -# the Free Software Foundation; either version 2 of the License, or +# 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, 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. -# +# This program 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. +# # You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -# USA +# along with this program. If not, see . # """Python support for DisOrder @@ -52,7 +50,7 @@ import os import pwd import socket import binascii -import sha +import hashlib import sys import locale @@ -68,6 +66,18 @@ _unquoted = re.compile("[^\"' \\t\\n\\r][^ \t\n\r]*") _response = re.compile("([0-9]{3}) ?(.*)") +# hashes +_hashes = { + "sha1": hashlib.sha1, + "SHA1": hashlib.sha1, + "sha256": hashlib.sha256, + "SHA256": hashlib.sha256, + "sha384": hashlib.sha384, + "SHA384": hashlib.sha384, + "sha512": hashlib.sha512, + "SHA512": hashlib.sha512, +}; + version = "_version_" ######################################################################## @@ -115,8 +125,8 @@ class operationError(Error): self.cmd_ = cmd self.details_ = details def __str__(self): - """Return the complete response string from the server, with the command - if available. + """Return the complete response string from the server, with the + command if available. Excludes the final newline. """ @@ -389,8 +399,7 @@ class client: password = self.config['password'] else: password = self.password - # TODO support algorithms other than SHA-1 - h = sha.sha() + h = _hashes[algo]() h.update(password) h.update(binascii.unhexlify(challenge)) self._simple("user", user, h.hexdigest()) @@ -424,12 +433,22 @@ class client: Returns the ID of the new queue entry. - Note that queue IDs are unicode strings (because all track information - values are unicode strings). + Note that queue IDs are unicode strings (because all track + information values are unicode strings). """ res, details = self._simple("play", track) return unicode(details) # because it's unicode in queue() output + def playafter(self, target, tracks): + """Insert tracks into a specific point in the queue. + + Arguments: + target -- target ID or None to insert at start of queue + tracks -- a list of tracks to play""" + if target is None: + target = '' + self._simple("playafter", target, *tracks) + def remove(self, track): """Remove a track from the queue. @@ -530,8 +549,8 @@ class client: The return value is a list of dictionaries corresponding to recently played tracks. The next track to be played comes first. - See disorder_protocol(5) for the meanings of the keys. All keys are - plain strings but the values will be unicode strings.""" + See disorder_protocol(5) for the meanings of the keys. + All keys are plain strings but the values will be unicode strings.""" return self._somequeue("queue") def _somedir(self, command, dir, re): @@ -766,7 +785,8 @@ class client: The callback should return True to continue or False to stop (don't forget this, or your program will mysteriously misbehave). Once you - stop reading the log the connection is useless and should be deleted. + stop reading the log the connection is useless and should be + deleted. It is suggested that you use the disorder.monitor class instead of calling this method directly, but this is not mandatory. @@ -883,6 +903,83 @@ class client: """Confirm a user registration""" res, details = self._simple("confirm", confirmation) + def schedule_list(self): + """Get a list of scheduled events """ + self._simple("schedule-list") + return self._body() + + def schedule_del(self, event): + """Delete a scheduled event""" + self._simple("schedule-del", event) + + def schedule_get(self, event): + """Get the details for an event as a dict (returns None if + event not found)""" + res, details = self._simple("schedule-get", event) + if res == 555: + return None + d = {} + for line in self._body(): + bits = _split(line) + d[bits[0]] = bits[1] + return d + + def schedule_add(self, when, priority, action, *rest): + """Add a scheduled event""" + self._simple("schedule-add", str(when), priority, action, *rest) + + def adopt(self, id): + """Adopt a randomly picked track""" + self._simple("adopt", id) + + def playlist_delete(self, playlist): + """Delete a playlist""" + res, details = self._simple("playlist-delete", playlist) + if res == 555: + raise operationError(res, details, "playlist-delete") + + def playlist_get(self, playlist): + """Get the contents of a playlist + + The return value is an array of track names, or None if there is no + such playlist.""" + res, details = self._simple("playlist-get", playlist) + if res == 555: + return None + return self._body() + + def playlist_lock(self, playlist): + """Lock a playlist. Playlists can only be modified when locked.""" + self._simple("playlist-lock", playlist) + + def playlist_unlock(self): + """Unlock the locked playlist.""" + self._simple("playlist-unlock") + + def playlist_set(self, playlist, tracks): + """Set the contents of a playlist. The playlist must be locked. + + Arguments: + playlist -- Playlist to set + tracks -- Array of tracks""" + self._simple_body(tracks, "playlist-set", playlist) + + def playlist_set_share(self, playlist, share): + """Set the sharing status of a playlist""" + self._simple("playlist-set-share", playlist, share) + + def playlist_get_share(self, playlist): + """Returns the sharing status of a playlist""" + res, details = self._simple("playlist-get-share", playlist) + if res == 555: + return None + return _split(details)[0] + + def playlists(self): + """Returns the list of visible playlists""" + self._simple("playlists") + return self._body() + ######################################################################## # I/O infrastructure @@ -912,8 +1009,8 @@ class client: else: raise protocolError(self.who, "invalid response %s") - def _send(self, *command): - # Quote and send a command + def _send(self, body, *command): + # Quote and send a command and optional body # # Returns the encoded command. quoted = _quote(command) @@ -922,6 +1019,13 @@ class client: try: self.w.write(encoded) self.w.write("\n") + if body != None: + for l in body: + if l[0] == ".": + self.w.write(".") + self.w.write(l) + self.w.write("\n") + self.w.write(".\n") self.w.flush() return encoded except IOError, e: @@ -932,7 +1036,7 @@ class client: self._disconnect() raise - def _simple(self, *command): + def _simple(self, *command): # Issue a simple command, throw an exception on error # # If an I/O error occurs, disconnect from the server. @@ -940,10 +1044,20 @@ class client: # On success or 'normal' errors returns response as a (code, details) tuple # # On error raise operationError + return self._simple_body(None, *command) + + def _simple_body(self, body, *command): + # Issue a simple command with optional body, throw an exception on error + # + # If an I/O error occurs, disconnect from the server. + # + # On success or 'normal' errors returns response as a (code, details) tuple + # + # On error raise operationError if self.state == 'disconnected': self.connect() if command: - cmd = self._send(*command) + cmd = self._send(body, *command) else: cmd = None res, details = self._response() @@ -1024,8 +1138,8 @@ class client: class monitor: """DisOrder event log monitor class - Intended to be subclassed with methods corresponding to event log messages - the implementor cares about over-ridden.""" + Intended to be subclassed with methods corresponding to event log + messages the implementor cares about over-ridden.""" def __init__(self, c=None): """Constructor for the monitor class @@ -1041,8 +1155,8 @@ class monitor: def run(self): """Start monitoring logs. Continues monitoring until one of the - message-specific methods returns False. Can be called more than once - (but not recursively!)""" + message-specific methods returns False. Can be called more than + once (but not recursively!)""" self.c.log(self._callback) def when(self):