#
-# Copyright (C) 2004, 2005 Richard Kettlewell
+# Copyright (C) 2004, 2005, 2007 Richard Kettlewell
#
# 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
for path in sys.argv[1:]:
d.play(path)
+See disorder_protocol(5) for details of the communication protocol.
+
+NB that this code only supports servers configured to use SHA1-based
+authentication. If the server demands another hash then it will not be
+possible to use this module.
"""
import re
while True:
k = i.next()
v = i.next()
- d[k] = v
+ d[str(k)] = v
except StopIteration:
pass
return d
home = pw.pw_dir
privconf = _configfile + "." + pw.pw_name
passfile = home + os.sep + ".disorder" + os.sep + "passwd"
- self._readfile(_configfile)
+ if os.path.exists(_configfile):
+ self._readfile(_configfile)
if os.path.exists(privconf):
self._readfile(privconf)
if os.path.exists(passfile) and _userconf:
sys.stderr.write("\n")
sys.stderr.flush()
- def connect(self):
- """Connect to the DisOrder server and authenticate.
+ def connect(self, cookie=None):
+ """c.connect(cookie=None)
+
+ Connect to the DisOrder server and authenticate.
Raises communicationError if connection fails and operationError if
authentication fails (in which case disconnection is automatic).
Other operations automatically connect if we're not already
connected, so it is not strictly necessary to call this method.
+
+ If COOKIE is specified then that is used to log in instead of
+ the username/password.
"""
if self.state == 'disconnected':
try:
self.w = s.makefile("wb")
self.r = s.makefile("rb")
(res, challenge) = self._simple()
- h = sha.sha()
- h.update(self.config['password'])
- h.update(binascii.unhexlify(challenge))
- self._simple("user", self.config['username'], h.hexdigest())
+ if cookie is None:
+ h = sha.sha()
+ h.update(self.config['password'])
+ h.update(binascii.unhexlify(challenge))
+ self._simple("user", self.config['username'], h.hexdigest())
+ else:
+ self._simple("cookie", cookie)
self.state = 'connected'
except socket.error, e:
self._disconnect()
Arguments:
track -- the path of the track to play.
+
+ Returns the ID of the new queue entry.
+
+ Note that queue IDs are unicode strings (because all track information
+ values are unicode strings).
"""
- self._simple("play", track)
+ res, details = self._simple("play", track)
+ return unicode(details) # because it's unicode in queue() output
def remove(self, track):
"""Remove a track from the queue.
def playing(self):
"""Return the currently playing track.
- If a track is playing then it is returned as a dictionary.
+ If a track is playing then it is returned as a dictionary. See
+ disorder_protocol(5) for the meanings of the keys. All keys are
+ plain strings but the values will be unicode strings.
+
If no track is playing then None is returned."""
res, details = self._simple("playing")
if res % 10 != 9:
"""Return a list of recently played tracks.
The return value is a list of dictionaries corresponding to
- recently played tracks. The oldest track comes first."""
+ recently played tracks. The oldest track comes first.
+
+ 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("recent")
def queue(self):
"""Return the current queue.
The return value is a list of dictionaries corresponding to
- recently played tracks. The next track to be played comes first."""
+ 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."""
return self._somequeue("queue")
def _somedir(self, command, dir, re):
track -- the track to query
key -- the preference to remove
- The return value is the preference
+ The return value is the preference.
"""
ret, details = self._simple("get", track, key)
- return details
+ if ret == 555:
+ return None
+ else:
+ return details
def prefs(self, track):
"""Get all the preferences for a track.
self._simple("search", _quote(words))
return self._body()
+ def tags(self):
+ """List all tags
+
+ The return value is a list of all tags which apply to at least one
+ track."""
+ self._simple("tags")
+ return self._body()
+
def stats(self):
"""Get server statistics.
ret, details = self._simple("move", track, str(delta))
return int(details)
+ def moveafter(self, target, tracks):
+ """Move a track in the queue
+
+ Arguments:
+ target -- target ID or None
+ tracks -- a list of IDs to move
+
+ If target is '' or is not in the queue then the tracks are moved to
+ the head of the queue.
+
+ Otherwise the tracks are moved to just after the target."""
+ if target is None:
+ target = ''
+ self._simple("moveafter", target, *tracks)
+
def log(self, callback):
"""Read event log entries as they happen.
ret, details = self._simple("part", track, context, part)
return details
+ def setglobal(self, key, value):
+ """Set a global preference value.
+
+ Arguments:
+ key -- the preference name
+ value -- the new preference value
+ """
+ self._simple("set-global", key, value)
+
+ def unsetglobal(self, key):
+ """Unset a global preference value.
+
+ Arguments:
+ key -- the preference to remove
+ """
+ self._simple("set-global", key, value)
+
+ def getglobal(self, key):
+ """Get a global preference value.
+
+ Arguments:
+ key -- the preference to look up
+
+ The return value is the preference
+ """
+ ret, details = self._simple("get-global", key)
+ if ret == 555:
+ return None
+ else:
+ return details
+
+ def make_cookie(self):
+ """Create a login cookie"""
+ ret, details = self._simple("make-cookie")
+ return details
+
+ def revoke(self):
+ """Revoke a login cookie"""
+ self._simple("revoke")
+
########################################################################
# I/O infrastructure
#
# If an I/O error occurs, disconnect from the server.
#
- # On success returns response as a (code, details) tuple
+ # On success or 'normal' errors returns response as a (code, details) tuple
#
# On error raise operationError
if self.state == 'disconnected':
else:
cmd = None
res, details = self._response()
- if res / 100 == 2:
+ if res / 100 == 2 or res == 555:
return res, details
raise operationError(res, details, cmd)