]> code.delx.au - bg-scripts/commitdiff
Major updates to randombg - stdlib is good :)
authorJames Bunton <jamesbunton@delx.net.au>
Tue, 1 Jul 2008 15:01:13 +0000 (01:01 +1000)
committerJames Bunton <jamesbunton@delx.net.au>
Tue, 1 Jul 2008 15:01:13 +0000 (01:01 +1000)
 * Converted to use asyncore.
 * Use logging.
 * Use do_something instead of doSomething to be consistent with most of stdlib.
 * Client mode now accepts parameters.

bin/randombg.py
lib/asyncsched.py [new file with mode: 0644]

index 7a94fc0febd89e3d67b849739a4dafefb6418a4f..ce6790627c61687c6f7cf1b112f0ac0421ece016 100755 (executable)
 #!/usr/bin/env python
 
-import sys, os, os.path, socket 
-from optparse import OptionParser, Values
+VERSION = "2.0"
 
-VERSION = "1.1"
-CACHE_LOCATION = os.path.expanduser('~/.randombg2_filelist_cache')
-SOCKET_FILE = os.path.expanduser('~/tmp/tmp_socket')
 
-try:
-       # These are my libraries...
-       import GregDebug, AsyncSocket, WallChanger, SigHandler
-
-       from GregDebug import debug, setDebugLevel, DEBUG_LEVEL_DEBUG, DEBUG_LEVEL_LOW, DEBUG_LEVEL_MEDIUM, DEBUG_LEVEL_HIGH, DEBUG_INCREMENT
+import asyncore, asynchat, socket
+import os, os.path, sys, time
+from optparse import OptionParser
+import logging
+from logging import debug, info, warning, error, critical
+logging.basicConfig(format="%(levelname)s: %(message)s")
 
+try:
+       # Required libraries
+       import asyncsched
        from FileLists import *
+       import WallChanger
 except ImportError, e:
-       print >>sys.stderr, "Missing libraries!\nExiting..."
+       critical("Missing libraries! Exiting...")
        sys.exit(1)
 
-def buildparser():
-       def buildOptions():
-               pass
-       def addfilestolist(optclass, opt, value, parser, fileList):
-               fo = open(value)
-               for line in fo:
-                       fileList.list.append(line.strip())
-               fo.close()
-               fileList.allowAllRandom = False
+
+class Cycler(object):
+       def __init__(self, options, paths):
+               filelist = self.find_files(options, paths)
+               if not filelist.hasImages():
+                       error("No images were found. Exiting...")
+                       sys.exit(1)
+       
+               debug("Initilizing RandomBG")
+               self.randombg = WallChanger.RandomBG(filelist, options.background_colour, options.permanent)
+               self.cycle_time = options.cycle_time
+
+               self.task = None
+               self.cmd_next()
+
+       def find_files(self, options, paths):
+               if options.all_random:
+                       filelist = AllRandomFileList()
+               elif options.folder_random:
+                       filelist = FolderRandomFileList()
+               else:
+                       filelist = RandomFileList()
+
+               for path in paths:
+                       filelist.doAddPath(path)
+
+               if filelist.attemptCacheLoad(options.history_filename):
+                       debug("Loaded cache successfully")
+               else:
+                       debug("Could not load cache")
+                       filelist.doScanPaths()
+               return filelist
+
+       def cmd_reset(self):
+               def next():
+                       self.randombg.cycleNext()
+                       self.task = None
+                       self.cmd_reset()
+
+               if self.task is not None:
+                       self.task.cancel()
+               self.task = asyncsched.schedule(self.cycle_time, next)
+               debug("Reset timer for %s seconds" % self.cycle_time)
+       
+       def cmd_reload(self):
+               self.randombg.cycleReload()
+               self.cmd_reset()
+
+       def cmd_next(self):
+               self.randombg.cycleNext()
+               self.cmd_reset()
+       
+       def cmd_prev(self):
+               self.randombg.cyclePrev()
+               self.cmd_reset()
+       
+       def cmd_rescan(self):
+               self.randombg.filelist.doScanPaths()
+               self.cmd_next()
+       
+       def cmd_pause(self):
+               if self.task is not None:
+                       self.task.cancel()
+                       self.task = None
+
+class Server(asynchat.async_chat):
+       def __init__(self, cycler, conn, addr):
+               asynchat.async_chat.__init__(self, conn=conn)
+               self.cycler = cycler
+               self.ibuffer = []
+               self.set_terminator("\n")
+
+       def collect_incoming_data(self, data):
+               self.ibuffer.append(data)
+       
+       def found_terminator(self):
+               line = "".join(self.ibuffer).lower()
+               self.ibuffer = []
+               prefix, cmd = line.split(None, 1)
+               if prefix != "cmd":
+                       debug('Bad line received "%s"' % line)
+                       return
+               if hasattr(self.cycler, "cmd_" + cmd):
+                       debug('Executing command "%s"' % cmd)
+                       getattr(self.cycler, "cmd_" + cmd)()
+               else:
+                       debug('Unknown command received "%s"' % cmd)
+
+
+
+class Listener(asyncore.dispatcher):
+       def __init__(self, socket_filename, randombg):
+               asyncore.dispatcher.__init__(self)
+               self.randombg = randombg
+               self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
+               self.bind(socket_filename)
+               self.listen(2) # Backlog = 2
+       
+       def handle_accept(self):
+               conn, addr = self.accept()
+               Server(self.randombg, conn, addr)
                
+
+def do_server(options, paths):
+       try:
+               cycler = Cycler(options, paths)
+               listener = Listener(options.socket_filename, cycler)
+               asyncsched.loop()
+       except KeyboardInterrupt:
+               print
+       finally:
+               # Make sure that the socket is cleaned up
+               try:
+                       os.unlink(options.socket_filename)
+               except:
+                       pass
+
+def do_client(options, args):
+       if len(args) == 0:
+               args = ["next"]
+       sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+       sock.connect(options.socket_filename)
+       sock = sock.makefile()
+       for i, cmd in enumerate(args):
+               sock.write("cmd %s\n" % cmd)
+               if i+1 != len(args):
+                       time.sleep(options.cycle_time)
+       sock.close()
+
+
+def build_parser():
        parser = OptionParser(version="%prog " + VERSION, 
-               description = "Picks a random background image",
-               usage = "%prog [options] dir [dir2 ...]")
+               description = "Cycles through random background images.",
+               usage =
+                       "\n(server) %prog [options] dir [dir2 ...]"
+                       "\n(client) %prog [options] [next|prev|rescan|reload|pause] [...]"
+                       "\nThe first instance to be run will be the server.\n"
+               )
        parser.add_option("-p", "--permanent",
                action="store_true", dest="permanent", default=False,
                help="Make the background permanent. Note: This will cause all machines logged in with this account to simultaneously change background [Default: %default]")
-       parser.add_option("-q", "--quiet", "--silent",
-               action="count", dest="quiet", default=0,
-               help="Make the script quiet (good for running from a shell script)")
        parser.add_option("-v", '-d', "--verbose", "--debug",
                action="count", dest="verbose", default=0,
                help="Make the louder (good for debugging, or those who are curious)")
@@ -49,173 +172,29 @@ def buildparser():
        parser.add_option("--folder-random",
                action="store_true", dest="folder_random", default=False,
                help="Give each folder an equal chance of having an image selected from it")
-       #parser.add_option("--file-list",
-       #       action="callback", callback=addfilestolist, type="string", callback_args=(fileList,),
-       #       help="Adds the list of images from the external file")
-       parser.add_option("--cycle",
-               action="store", type="int", default=0, dest="cycle_time",
+       parser.add_option("--cycle-time",
+               action="store", type="int", default=1800, dest="cycle_time",
                help="Cause the image to cycle every X seconds")
+       parser.add_option("--socket",
+               action="store", type="string", dest="socket_filename", default=os.path.expanduser('~/tmp/tmp_socket'),
+               help="Location of the command/control socket.")
+       parser.add_option("--history-file",
+               action="store", type="string", dest="history_filename", default=os.path.expanduser('~/.randombg_historyfile'),
+               help="Stores the location of the last image to be loaded.")
        return parser
 
-
-def createIPCClient(domainSocketName):
-       sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-       sock.connect(domainSocketName)
-       sock_file = sock.makefile()
-       return sock_file
-
 def main():
-       if os.path.exists(SOCKET_FILE):
-               # We are the client
-               sock = createIPCClient(SOCKET_FILE)
-               print >>sock, "CMD NEXT"
-###            print >>sock, "CMD PREVIOUS"
-###            print >>sock, "CMD PAUSE"
-               sock.close()
-       else:
-               # We are the server
-               try:
-                       Server(SOCKET_FILE)()
-               finally:
-                       # Make sure that the socket is cleaned up
-                       os.unlink(SOCKET_FILE)
-
-class Server(object):
-       def __init__(self, domainSocketName):
-               self.socketHandler = self._createIPCServer(domainSocketName)
-               self.callbackObj = None
-
-               parser = buildparser()
-               useroptions, paths = parser.parse_args(sys.argv[1:])
-
-               setDebugLevel(DEBUG_INCREMENT * (useroptions.quiet - useroptions.verbose))
-               debug("Just set GregDebug.DEBUG_LEVEL to %d" % GregDebug.DEBUG_LEVEL, DEBUG_LEVEL_LOW)
-
-               self.filelist = self.__getFileList(useroptions, paths)
-
-               if not self.filelist.hasImages():
-                       print >>sys.stderr, "No files!"
-                       parser.print_help()
-                       sys.exit(1)
-       
-               debug("Initilizing RandomBG", DEBUG_LEVEL_DEBUG)
-               self.randombg = WallChanger.RandomBG(self.filelist, useroptions.background_colour, useroptions.permanent)
+       parser = build_parser()
+       options, args = parser.parse_args(sys.argv[1:])
 
-               # Store some of the other useful options
-               self.cycle_time = useroptions.cycle_time
+       logging.basicConfig(level=10*options.verbose)
 
-       def __getFileList(self, useroptions, paths):
-               if useroptions.all_random:
-                       filelist = AllRandomFileList()
-               elif useroptions.folder_random:
-                       filelist = FolderRandomFileList()
-               else:
-                       filelist = RandomFileList()
-
-               for path in paths:
-                       filelist.doAddPath(path)
-
-               if filelist.attemptCacheLoad(CACHE_LOCATION):
-                       debug("Loaded cache successfully", DEBUG_LEVEL_LOW)
-               else:
-                       debug("Could not load cache")
-                       filelist.doScanPaths()
-               return filelist
+       if os.path.exists(options.socket_filename):
+               do_client(options, args)
+       else:
+               do_server(options, args)
 
-       def cycle_reload(self):
-               debug("Reloading wallpaper", DEBUG_LEVEL_LOW)
-               ret = self.randombg.cycleReload()
-               if not ret:
-                       debug('Could not set wallpaper. Returned "%s"' % ret)
-               debug('About to sleep for "%d" seconds' % self.cycle_time, DEBUG_LEVEL_LOW)
-               self.callbackObj = self.socketHandler.addCallback(self.cycle_time, self.cycle_next)
-               return ret
-
-       def cycle_next(self):
-               debug("Cycling wallpaper", DEBUG_LEVEL_LOW)
-               ret = self.randombg.cycleNext()
-               if not ret:
-                       debug('Could not set wallpaper. Returned "%s"' % ret)
-               debug('About to sleep for "%d" seconds' % self.cycle_time, DEBUG_LEVEL_LOW)
-               self.callbackObj = self.socketHandler.addCallback(self.cycle_time, self.cycle_next)
-               self.filelist.doStoreCache(CACHE_LOCATION)
-               return ret
-
-       def cycle_prev(self):
-               debug("Cycling wallpaper", DEBUG_LEVEL_LOW)
-               ret = self.randombg.cyclePrev()
-               if not ret:
-                       debug('Could not set wallpaper. Returned "%s"' % ret)
-               debug('About to sleep for "%d" seconds' % self.cycle_time, DEBUG_LEVEL_LOW)
-               # Yes this is ment to be cycle_next
-               self.callbackObj = self.socketHandler.addCallback(self.cycle_time, self.cycle_next)
-               self.filelist.doStoreCache(CACHE_LOCATION)
-               return ret
-               
-       def _finished(self):
-               self.filelist.doStoreCache(CACHE_LOCATION)
-       
-       def __call__(self):
-               # Callback immediatly
-               self.socketHandler.addCallback(0.0, self.cycle_reload)
-               # Now go into the main loop
-               self.socketHandler.mainLoop()
-               # Clean up time
-               self._finished()
-
-       def _createIPCServer(self, domainSocketName):
-               """Create the Server socket, and start listening for clients"""
-
-               class Handler(object):
-                       def __init__(self, parent):
-                               self.parent = parent
-                       def _removeOldTimer(self):
-                               if self.parent.callbackObj:
-                                       self.parent.socketHandler.removeCallback(self.parent.callbackObj)
-                       def _cmd_PAUSE(self):
-                               debug("Pausing randombg")
-                               self._removeOldTimer()
-                       def _cmd_NEXT(self):
-                               self._removeOldTimer()
-                               self.parent.cycle_next()
-                       def _cmd_PREVIOUS(self):
-                               self._removeOldTimer()
-                               self.parent.cycle_prev()
-                       def _cmd_RESCAN(self):
-                               self.parent.filelist.doScanPaths()
-                               self._cmd_NEXT()
-                       def _cmd_RELOAD(self):
-                               self._removeOldTimer()
-                               self.parent.cycle_reload()
-                       def _processLine(self, line):
-                               prefix, cmd = line.split(None, 1)
-                               if prefix != 'CMD':
-                                       debug('Unknown command received "%s"' % line)
-                                       return
-                               if hasattr(self, '_cmd_%s' % cmd):
-                                       getattr(self, '_cmd_%s' % cmd)()
-                               else:
-                                       debug('Unknown command received "%s"' % cmd)
-                       def __call__(self, lineReader):
-                               try:
-                                       while lineReader.hasLine():
-                                               self._processLine(lineReader.readline())
-                               except Exception, e:
-                                       debug(str(e))
-
-               def handleClient(sock):
-                       conn, address = sock.accept()
-                       async_handler.addLineBufferedSocket(conn, Handler(self) )
-
-               async_handler = AsyncSocket.AsyncSocketOwner()
-
-               sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-               sock.bind(domainSocketName)
-               sock.listen(2) # Backlog = 2
-
-               async_handler.addSocket(sock, handleClient)
-
-               return async_handler
 
 if __name__ == "__main__":
        main()
+
diff --git a/lib/asyncsched.py b/lib/asyncsched.py
new file mode 100644 (file)
index 0000000..c021fc1
--- /dev/null
@@ -0,0 +1,46 @@
+# Copyright 2008 James Bunton <jamesbunton@fastmail.fm>
+# Licensed for distribution under the GPL version 2, check COPYING for details
+# asyncore.loop() with delayed function calls
+
+import asyncore
+import time
+import heapq
+
+tasks = []
+
+class Task(object):
+       def __init__(self, delay, func, args=[], kwargs={}):
+               self.time = time.time() + delay
+               self.func = lambda: func(*args, **kwargs)
+       
+       def __cmp__(self, other):
+               return cmp(self.time, other.time)
+
+       def __call__(self):
+               f = self.func
+               self.func = None
+               if f:
+                       return f()
+
+       def cancel(self):
+               assert self.func is not None
+               self.func = None
+
+def schedule(delay, func, args=[], kwargs={}):
+       task = Task(delay, func, args, kwargs)
+       heapq.heappush(tasks, task)
+       return task
+
+def loop(timeout=30.0):
+       while True:
+               now = time.time()
+               while tasks and tasks[0].time < now:
+                       task = heapq.heappop(tasks)
+                       task()
+
+               t = timeout
+               if tasks:
+                       t = max(min(t, tasks[0].time - now), 0)
+
+               asyncore.poll(timeout=t)
+