chiark / gitweb /
Add 'adopt' command. This adopts a randomly picked track by changing
[disorder] / python / disorder.py.in
index 325599bfad7393e989c5b3370c55bf785591ad5f..e873e49c76387ba627cd7d5db5bb92748530996c 100644 (file)
@@ -1,20 +1,18 @@
 #
 #
-# 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
+# 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
 # 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.
 #
 # (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
 # 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 <http://www.gnu.org/licenses/>.
 #
 
 """Python support for DisOrder
 #
 
 """Python support for DisOrder
@@ -62,8 +60,8 @@ _userconf = True
 
 # various regexps we'll use
 _ws = re.compile(r"^[ \t\n\r]+")
 
 # 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}) ?(.*)")
 _unquoted = re.compile("[^\"' \\t\\n\\r][^ \t\n\r]*")
 
 _response = re.compile("([0-9]{3}) ?(.*)")
@@ -375,7 +373,11 @@ class client:
           s.connect(self.who)
         self.w = s.makefile("wb")
         self.r = s.makefile("rb")
           s.connect(self.who)
         self.w = s.makefile("wb")
         self.r = s.makefile("rb")
-        (res, challenge) = self._simple()
+        (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']
         if cookie is None:
           if self.user is None:
             user = self.config['username']
@@ -385,6 +387,7 @@ class client:
             password = self.config['password']
           else:
             password = self.password
             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))
           h = sha.sha()
           h.update(password)
           h.update(binascii.unhexlify(challenge))
@@ -411,16 +414,6 @@ class client:
   ########################################################################
   # Operations
 
   ########################################################################
   # 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.
 
   def play(self, track):
     """Play a track.
 
@@ -484,19 +477,16 @@ class client:
     """
     self._simple("reconfigure")
 
     """
     self._simple("reconfigure")
 
-  def rescan(self, pattern):
+  def rescan(self, *flags):
     """Rescan one or more collections.
 
     """Rescan one or more collections.
 
-    Arguments:
-    pattern -- glob pattern matching collections to rescan.
-
     Only trusted users can perform this operation.
     """
     Only trusted users can perform this operation.
     """
-    self._simple("rescan", pattern)
+    self._simple("rescan", *flags)
 
   def version(self):
     """Return the server's version number."""
 
   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.
 
   def playing(self):
     """Return the currently playing track.
@@ -627,7 +617,7 @@ class client:
     if ret == 555:
       return None
     else:
     if ret == 555:
       return None
     else:
-      return details
+      return _split(details)[0]
 
   def prefs(self, track):
     """Get all the preferences for a track.
 
   def prefs(self, track):
     """Get all the preferences for a track.
@@ -773,7 +763,8 @@ class client:
     second the line from the event log.
     
     The callback should return True to continue or False to stop (don't
     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.
 
     It is suggested that you use the disorder.monitor class instead of
     calling this method directly, but this is not mandatory.
@@ -793,11 +784,6 @@ class client:
         l = l[1:]
       if not callback(self, l):
         break
         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."""
 
   def pause(self):
     """Pause the current track."""
@@ -818,7 +804,7 @@ class client:
     The return value is the preference 
     """
     ret, details = self._simple("part", track, context, part)
     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.
 
   def setglobal(self, key, value):
     """Set a global preference value.
@@ -849,12 +835,12 @@ class client:
     if ret == 555:
       return None
     else:
     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")
 
   def make_cookie(self):
     """Create a login cookie"""
     ret, details = self._simple("make-cookie")
-    return details
+    return _split(details)[0]
   
   def revoke(self):
     """Revoke a login cookie"""
   
   def revoke(self):
     """Revoke a login cookie"""
@@ -879,6 +865,50 @@ class client:
     """Set user information"""
     self._simple("edituser", 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)
+
+  def adopt(self, id):
+    """Adopt a randomly picked track"""
+    self._simple("adopt", id)
+
   ########################################################################
   # I/O infrastructure
 
   ########################################################################
   # I/O infrastructure
 
@@ -1093,6 +1123,8 @@ class monitor:
     elif keyword == 'scratched':
       if len(bits) == 2:
         return self.scratched(bits[0], bits[1])
     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):
     return self.invalid(line)
 
   def completed(self, track):
@@ -1172,6 +1204,10 @@ class monitor:
     line -- line that could not be understood"""
     return True
 
     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
 # Local Variables:
 # mode:python
 # py-indent-offset:2