#
-# Copyright (C) 2004, 2005, 2007 Richard Kettlewell
+# Copyright (C) 2004, 2005, 2007, 2008 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
# various regexps we'll use
_ws = re.compile(r"^[ \t\n\r]+")
-_squote = re.compile("'(([^\\\\']|\\\\[\\\\\"'n])+)'")
-_dquote = re.compile("\"(([^\\\\\"]|\\\\[\\\\\"'n])+)\"")
+_squote = re.compile("'(([^\\\\']|\\\\[\\\\\"'n])*)'")
+_dquote = re.compile("\"(([^\\\\\"]|\\\\[\\\\\"'n])*)\"")
_unquoted = re.compile("[^\"' \\t\\n\\r][^ \t\n\r]*")
_response = re.compile("([0-9]{3}) ?(.*)")
debug_proto = 0x0001
debug_body = 0x0002
- def __init__(self):
+ def __init__(self, user=None, password=None):
"""Constructor for DisOrder client class.
The constructor reads the configuration file, but does not connect
self.config = { 'collections': [],
'username': pw.pw_name,
'home': _dbhome }
+ self.user = user
+ self.password = password
home = os.getenv("HOME")
if not home:
home = pw.pw_dir
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:
s.connect(self.who)
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())
+ (res, details) = self._simple()
+ (protocol, algo, challenge) = _split(details)
+ if protocol != '2':
+ raise communicationError(self.who,
+ "unknown protocol version %s" % protocol)
+ if cookie is None:
+ if self.user is None:
+ user = self.config['username']
+ else:
+ user = self.user
+ if self.password is None:
+ password = self.config['password']
+ else:
+ password = self.password
+ # TODO support algorithms other than SHA-1
+ h = sha.sha()
+ h.update(password)
+ h.update(binascii.unhexlify(challenge))
+ self._simple("user", user, h.hexdigest())
+ else:
+ self._simple("cookie", cookie)
self.state = 'connected'
except socket.error, e:
self._disconnect()
########################################################################
# Operations
- def become(self, who):
- """Become another user.
-
- Arguments:
- who -- the user to become.
-
- Only trusted users can perform this operation.
- """
- self._simple("become", who)
-
def play(self, track):
"""Play a track.
"""
self._simple("reconfigure")
- def rescan(self, pattern):
+ def rescan(self, *flags):
"""Rescan one or more collections.
- Arguments:
- pattern -- glob pattern matching collections to rescan.
-
Only trusted users can perform this operation.
"""
- self._simple("rescan", pattern)
+ self._simple("rescan", *flags)
def version(self):
"""Return the server's version number."""
- return self._simple("version")[1]
+ return _split(self._simple("version")[1])[0]
def playing(self):
"""Return the currently playing track.
if ret == 555:
return None
else:
- return details
+ return _split(details)[0]
def prefs(self, track):
"""Get all the preferences for a track.
second the line from the event log.
The callback should return True to continue or False to stop (don't
- forget this, or your program will mysteriously misbehave).
+ forget this, or your program will mysteriously misbehave). Once you
+ 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.
l = l[1:]
if not callback(self, l):
break
- # tell the server to stop sending, eat the remains of the body,
- # eat the response
- self._send("version")
- self._body()
- self._response()
def pause(self):
"""Pause the current track."""
The return value is the preference
"""
ret, details = self._simple("part", track, context, part)
- return details
+ return _split(details)[0]
def setglobal(self, key, value):
"""Set a global preference value.
if ret == 555:
return None
else:
- return details
+ return _split(details)[0]
+
+ def make_cookie(self):
+ """Create a login cookie"""
+ ret, details = self._simple("make-cookie")
+ return _split(details)[0]
+
+ def revoke(self):
+ """Revoke a login cookie"""
+ self._simple("revoke")
+
+ def adduser(self, user, password):
+ """Create a user"""
+ self._simple("adduser", user, password)
+
+ def deluser(self, user):
+ """Delete a user"""
+ self._simple("deluser", user)
+
+ def userinfo(self, user, key):
+ """Get user information"""
+ res, details = self._simple("userinfo", user, key)
+ if res == 555:
+ return None
+ return _split(details)[0]
+
+ def edituser(self, user, key, value):
+ """Set user information"""
+ self._simple("edituser", user, key, value)
+
+ def users(self):
+ """List all users
+
+ The return value is a list of all users."""
+ self._simple("users")
+ return self._body()
+
+ def register(self, username, password, email):
+ """Register a user"""
+ res, details = self._simple("register", username, password, email)
+ return _split(details)[0]
+
+ def confirm(self, confirmation):
+ """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)
########################################################################
# I/O infrastructure
elif keyword == 'scratched':
if len(bits) == 2:
return self.scratched(bits[0], bits[1])
+ elif keyword == 'rescanned':
+ return self.rescanned()
return self.invalid(line)
def completed(self, track):
line -- line that could not be understood"""
return True
+ def rescanned(self):
+ """Called when a rescan completes"""
+ return True
+
# Local Variables:
# mode:python
# py-indent-offset:2