Use users.db. trackdb* moves to lib/, as it's now used by client.c to
[disorder] / python / disorder.py.in
index 7582979..6478981 100644 (file)
@@ -1,5 +1,5 @@
 #
 #
-# 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
 #
 # 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
@@ -39,6 +39,11 @@ Example 2:
   for path in sys.argv[1:]:
     d.play(path)
 
   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
 """
 
 import re
@@ -271,7 +276,7 @@ class client:
   debug_proto = 0x0001
   debug_body = 0x0002
 
   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
     """Constructor for DisOrder client class.
 
     The constructor reads the configuration file, but does not connect
@@ -289,6 +294,8 @@ class client:
     self.config = { 'collections': [],
                     'username': pw.pw_name,
                     'home': _dbhome }
     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
     home = os.getenv("HOME")
     if not home:
       home = pw.pw_dir
@@ -322,8 +329,10 @@ class client:
       sys.stderr.write("\n")
       sys.stderr.flush()
 
       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).
 
     Raises communicationError if connection fails and operationError if
     authentication fails (in which case disconnection is automatic).
@@ -334,6 +343,9 @@ class client:
 
     Other operations automatically connect if we're not already
     connected, so it is not strictly necessary to call this method.
 
     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:
     """
     if self.state == 'disconnected':
       try:
@@ -364,10 +376,21 @@ class client:
         self.w = s.makefile("wb")
         self.r = s.makefile("rb")
         (res, challenge) = self._simple()
         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:
+          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
+          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()
         self.state = 'connected'
       except socket.error, e:
         self._disconnect()
@@ -405,6 +428,9 @@ class client:
     track -- the path of the track to play.
 
     Returns the ID of the new queue entry.
     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).
     """
     res, details = self._simple("play", track)
     return unicode(details)             # because it's unicode in queue() output
     """
     res, details = self._simple("play", track)
     return unicode(details)             # because it's unicode in queue() output
@@ -475,7 +501,10 @@ class client:
   def playing(self):
     """Return the currently playing track.
 
   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:
     If no track is playing then None is returned."""
     res, details = self._simple("playing")
     if res % 10 != 9:
@@ -497,14 +526,20 @@ class client:
     """Return a list of recently played tracks.
 
     The return value is a list of dictionaries corresponding to
     """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
     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):
     return self._somequeue("queue")
 
   def _somedir(self, command, dir, re):
@@ -586,7 +621,7 @@ class client:
     track -- the track to query
     key -- the preference to remove
 
     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)
     if ret == 555:
     """
     ret, details = self._simple("get", track, key)
     if ret == 555:
@@ -816,6 +851,23 @@ class client:
     else:
       return details
 
     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")
+
+  def adduser(self, user, password):
+    """Create a user"""
+    self._simple("adduser", user, password)
+
+  def deluser(self, user):
+    """Delete a user"""
+    self._simple("deluser", user)
+
   ########################################################################
   # I/O infrastructure
 
   ########################################################################
   # I/O infrastructure