From: James Bunton Date: Tue, 1 Jul 2008 15:01:13 +0000 (+1000) Subject: Major updates to randombg - stdlib is good :) X-Git-Url: https://code.delx.au/bg-scripts/commitdiff_plain/f71c2fbef0d1f0f7b4957ebc68f5639a533fec1a Major updates to randombg - stdlib is good :) * Converted to use asyncore. * Use logging. * Use do_something instead of doSomething to be consistent with most of stdlib. * Client mode now accepts parameters. --- diff --git a/bin/randombg.py b/bin/randombg.py index 7a94fc0..ce67906 100755 --- a/bin/randombg.py +++ b/bin/randombg.py @@ -1,42 +1,165 @@ #!/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 index 0000000..c021fc1 --- /dev/null +++ b/lib/asyncsched.py @@ -0,0 +1,46 @@ +# Copyright 2008 James Bunton +# 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) +