chiark / gitweb /
server side support for cookies, basic tests
[disorder] / python / disorder.py.in
index 95854383f6768912530fa5280f50d3992973994c..c501be5b8802002d3bb45b16b85d1f8f39dca23c 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
@@ -39,6 +39,11 @@ Example 2:
   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
@@ -322,8 +327,10 @@ class client:
       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).
@@ -334,6 +341,9 @@ class client:
 
     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:
@@ -364,10 +374,13 @@ class client:
         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()
@@ -403,8 +416,14 @@ class client:
 
     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.
@@ -472,7 +491,10 @@ class client:
   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:
@@ -494,14 +516,20 @@ class client:
     """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):
@@ -583,7 +611,7 @@ class client:
     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:
@@ -711,6 +739,21 @@ class client:
     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.
 
@@ -798,6 +841,15 @@ class client:
     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