X-Git-Url: https://code.delx.au/bg-scripts/blobdiff_plain/e82e47afd1d108241367327cad3f7637cd9b5d3c..fd176b77f01e67307c9899735ecac01939b2aebf:/randombg.py diff --git a/randombg.py b/randombg.py index 9a587bb..63e9460 100755 --- a/randombg.py +++ b/randombg.py @@ -1,456 +1,475 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 -VERSION = "2.0" +VERSION = "2.1" -import asyncore, asynchat, socket -import os, os.path, random, sys, time +import asyncore +import asynchat +import socket +import os +import random +import sys +import time from optparse import OptionParser import logging try: - logging.basicConfig(format="%(levelname)s: %(message)s") + logging.basicConfig(format="%(levelname)s: %(message)s") except TypeError: # Python 2.3's logging.basicConfig does not support parameters - logging.basicConfig() + logging.basicConfig() try: - import cPickle as pickle + import cPickle as pickle except ImportError: - import pickle + import pickle try: - # Required libraries - import asyncsched - import wallchanger + # Required libraries + import asyncsched + import wallchanger except ImportError, e: - logging.critical("Missing libraries! Exiting...", exc_info=1) - sys.exit(1) + logging.critical("Missing libraries! Exiting...", exc_info=1) + sys.exit(1) def filter_images(filenames): - extensions = ('.jpg', '.jpe', '.jpeg', '.png', '.gif', '.bmp') - for filename in filenames: - _, ext = os.path.splitext(filename) - if ext.lower() in extensions: - yield filename + extensions = ('.jpg', '.jpe', '.jpeg', '.png', '.gif', '.bmp') + for filename in filenames: + _, ext = os.path.splitext(filename) + if ext.lower() in extensions: + yield filename class BaseFileList(object): - """Base file list implementation""" - def __init__(self): - self.paths = [] - self.favourites = [] + """Base file list implementation""" + def __init__(self): + self.paths = [] + self.favourites = [] - def add_path(self, path): - self.paths.append(path) + def add_path(self, path): + self.paths.append(path) - def store_cache(self, filename): - try: - logging.debug("Attempting to store cache") - fd = open(filename, 'wb') - pickle.dump(self, fd, 2) - logging.debug("Cache successfully stored") - except Exception, e: - warning("Storing cache: %s" % e) + def store_cache(self, filename): + try: + logging.debug("Attempting to store cache") + fd = open(filename, 'wb') + pickle.dump(self, fd, 2) + logging.debug("Cache successfully stored") + except Exception, e: + warning("Storing cache: %s" % e) - def load_cache(self, filename): - try: - logging.debug("Attempting to load cache from: %s" % filename) - self.paths.sort() + def load_cache(self, filename): + try: + logging.debug("Attempting to load cache from: %s" % filename) + self.paths.sort() - fd = open(filename, 'rb') - tmp = pickle.load(fd) + fd = open(filename, 'rb') + tmp = pickle.load(fd) - if tmp.__class__ != self.__class__: - raise ValueError("Using different file list type") + if tmp.__class__ != self.__class__: + raise ValueError("Using different file list type") - tmp.paths.sort() - if self.paths != tmp.paths: - raise ValueError, "Path list changed" + tmp.paths.sort() + if self.paths != tmp.paths: + raise ValueError, "Path list changed" - # Overwrite this object with the other - for attr, value in tmp.__dict__.items(): - setattr(self, attr, value) + # Overwrite this object with the other + for attr, value in tmp.__dict__.items(): + setattr(self, attr, value) - return True + return True - except Exception, e: - logging.warning("Loading cache: %s" % e) - return False + except Exception, e: + logging.warning("Loading cache: %s" % e) + return False - def add_to_favourites(self): - '''Adds the current image to the list of favourites''' - self.favourites.append(self.get_current_image()) + def add_to_favourites(self): + '''Adds the current image to the list of favourites''' + self.favourites.append(self.get_current_image()) - def scan_paths(self): - raise NotImplementedError() + def scan_paths(self): + raise NotImplementedError() - def get_next_image(self): - raise NotImplementedError() + def get_next_image(self): + raise NotImplementedError() - def get_prev_image(self): - raise NotImplementedError() + def get_prev_image(self): + raise NotImplementedError() - def get_current_image(self): - raise NotImplementedError() + def get_current_image(self): + raise NotImplementedError() - def is_empty(self): - return True + def is_empty(self): + return True class RandomFileList(BaseFileList): - def __init__(self): - super(RandomFileList, self).__init__() - self.list = [] - self.last_image = None - - def scan_paths(self): - for path in self.paths: - for dirpath, dirsnames, filenames in os.walk(path): - for filename in filter_images(filenames): - self.list.append(os.path.join(dirpath, filename)) - - def add_path(self, path): - self.paths.append(path) - logging.debug('Added path "%s" to the list' % path) - - def get_next_image(self): - n = random.randint(0, len(self.list)-1) - self.last_image = self.list[n] - logging.debug("Picked file '%s' from list" % self.last_image) - return self.last_image - - def get_current_image(self): - if self.last_image: - return self.last_image - else: - return self.get_next_image() - - def is_empty(self): - return len(self.list) == 0 + def __init__(self): + super(RandomFileList, self).__init__() + self.list = [] + self.last_image = None + + def scan_paths(self): + for path in self.paths: + for dirpath, dirsnames, filenames in os.walk(path): + for filename in filter_images(filenames): + self.list.append(os.path.join(dirpath, filename)) + + def add_path(self, path): + self.paths.append(path) + logging.debug('Added path "%s" to the list' % path) + + def get_next_image(self): + n = random.randint(0, len(self.list)-1) + self.last_image = self.list[n] + logging.debug("Picked file '%s' from list" % self.last_image) + return self.last_image + + def get_current_image(self): + if self.last_image: + return self.last_image + else: + return self.get_next_image() + + def is_empty(self): + return len(self.list) == 0 class AllRandomFileList(BaseFileList): - def __init__(self): - super(AllRandomFileList, self).__init__() - self.list = None - self.imagePointer = 0 - - # Scan the input directory, and then randomize the file list - def scan_paths(self): - logging.debug("Scanning paths") - - self.list = [] - for path in self.paths: - logging.debug('Scanning "%s"' % path) - for dirpath, dirsnames, filenames in os.walk(path): - for filename in filter_images(filenames): - logging.debug('Adding file "%s"' % filename) - self.list.append(os.path.join(dirpath, filename)) - - random.shuffle(self.list) - - def add_path(self, path): - self.paths.append(path) - logging.debug('Added path "%s" to the list' % path) - - def store_cache(self, filename): - try: - fd = open(filename, 'wb') - pickle.dump(self, fd, 2) - logging.debug("Cache successfully stored") - except Exception, e: - logging.warning("Storing cache", exc_info=1) - - def get_current_image(self): - return self.list[self.imagePointer] - - def __inc_in_range(self, n, amount = 1, rangeMax = None, rangeMin = 0): - if rangeMax == None: rangeMax = len(self.list) - assert rangeMax > 0 - return (n + amount) % rangeMax - - def get_next_image(self): - self.imagePointer = self.__inc_in_range(self.imagePointer) - imageName = self.list[self.imagePointer] - logging.debug("Picked file '%s' (pointer=%d) from list" % (imageName, self.imagePointer)) - return imageName - - def get_prev_image(self): - self.imagePointer = self.__inc_in_range(self.imagePointer, amount=-1) - imageName = self.list[self.imagePointer] - logging.debug("Picked file '%s' (pointer=%d) from list" % (imageName, self.imagePointer)) - return imageName - - def is_empty(self): - return len(self.list) == 0 + def __init__(self): + super(AllRandomFileList, self).__init__() + self.list = None + self.imagePointer = 0 + + # Scan the input directory, and then randomize the file list + def scan_paths(self): + logging.debug("Scanning paths") + + self.list = [] + for path in self.paths: + logging.debug('Scanning "%s"' % path) + for dirpath, dirsnames, filenames in os.walk(path): + for filename in filter_images(filenames): + logging.debug('Adding file "%s"' % filename) + self.list.append(os.path.join(dirpath, filename)) + + random.shuffle(self.list) + + def add_path(self, path): + self.paths.append(path) + logging.debug('Added path "%s" to the list' % path) + + def store_cache(self, filename): + try: + fd = open(filename, 'wb') + pickle.dump(self, fd, 2) + logging.debug("Cache successfully stored") + except Exception, e: + logging.warning("Storing cache", exc_info=1) + + def get_current_image(self): + return self.list[self.imagePointer] + + def __inc_in_range(self, n, amount = 1, rangeMax = None, rangeMin = 0): + if rangeMax == None: rangeMax = len(self.list) + assert rangeMax > 0 + return (n + amount) % rangeMax + + def get_next_image(self): + self.imagePointer = self.__inc_in_range(self.imagePointer) + imageName = self.list[self.imagePointer] + logging.debug("Picked file '%s' (pointer=%d) from list" % (imageName, self.imagePointer)) + return imageName + + def get_prev_image(self): + self.imagePointer = self.__inc_in_range(self.imagePointer, amount=-1) + imageName = self.list[self.imagePointer] + logging.debug("Picked file '%s' (pointer=%d) from list" % (imageName, self.imagePointer)) + return imageName + + def is_empty(self): + return len(self.list) == 0 class FolderRandomFileList(BaseFileList): - """A file list that will pick a file randomly within a directory. Each - directory has the same chance of being chosen.""" - def __init__(self): - super(FolderRandomFileList, self).__init__() - self.directories = {} - self.last_image = None - - def scan_paths(self): - pass - - def add_path(self, path): - logging.debug('Added path "%s" to the list' % path) - for dirpath, dirs, filenames in os.walk(path): - logging.debug('Scanning "%s" for images' % dirpath) - if self.directories.has_key(dirpath): - continue - filenames = list(filter_images(filenames)) - if len(filenames): - self.directories[dirpath] = filenames - logging.debug('Adding "%s" to "%s"' % (filenames, dirpath)) - else: - logging.debug("No images found in '%s'" % dirpath) - - def get_next_image(self): - directory = random.choice(self.directories.keys()) - logging.debug('directory: "%s"' % directory) - filename = random.choice(self.directories[directory]) - logging.debug('filename: "%s"' % filename) - return os.path.join(directory, filename) - - def get_current_image(self): - if self.last_image: - return self.last_image - else: - return self.get_next_image() - - def is_empty(self): - return len(self.directories.values()) == 0 + """A file list that will pick a file randomly within a directory. Each + directory has the same chance of being chosen.""" + def __init__(self): + super(FolderRandomFileList, self).__init__() + self.directories = {} + self.last_image = None + + def scan_paths(self): + pass + + def add_path(self, path): + logging.debug('Added path "%s" to the list' % path) + for dirpath, dirs, filenames in os.walk(path): + logging.debug('Scanning "%s" for images' % dirpath) + if self.directories.has_key(dirpath): + continue + filenames = list(filter_images(filenames)) + if len(filenames): + self.directories[dirpath] = filenames + logging.debug('Adding "%s" to "%s"' % (filenames, dirpath)) + else: + logging.debug("No images found in '%s'" % dirpath) + + def get_next_image(self): + directory = random.choice(self.directories.keys()) + logging.debug('directory: "%s"' % directory) + filename = random.choice(self.directories[directory]) + logging.debug('filename: "%s"' % filename) + return os.path.join(directory, filename) + + def get_current_image(self): + if self.last_image: + return self.last_image + else: + return self.get_next_image() + + def is_empty(self): + return len(self.directories.values()) == 0 class Cycler(object): - def init(self, options, paths, oneshot=False): - self.cycle_time = options.cycle_time - self.history_filename = options.history_filename - - logging.debug("Initialising wallchanger") - wallchanger.init(options.background_colour, options.permanent, options.convert) - - logging.debug("Initialising file list") - if options.all_random: - self.filelist = AllRandomFileList() - elif options.folder_random: - self.filelist = FolderRandomFileList() - else: - self.filelist = RandomFileList() - - for path in paths: - self.filelist.add_path(path) - - if self.filelist.load_cache(self.history_filename): - logging.debug("Loaded cache successfully") - else: - logging.debug("Could not load cache") - self.filelist.scan_paths() - - if self.filelist.is_empty(): - logging.error("No images were found. Exiting...") - sys.exit(1) - - self.task = None - if oneshot: - self.cmd_next() - else: - self.cmd_reload() - - def finish(self): - self.filelist.store_cache(self.history_filename) - - def find_files(self, options, paths): - return filelist - - def cmd_reset(self): - def next(): - image = self.filelist.get_next_image() - wallchanger.set_image(image) - self.task = None - self.cmd_reset() - - if self.task is not None: - self.task.cancel() - self.task = asyncsched.schedule(self.cycle_time, next) - logging.debug("Reset timer for %s seconds" % self.cycle_time) - self.filelist.store_cache(self.history_filename) - - def cmd_reload(self): - image = self.filelist.get_current_image() - wallchanger.set_image(image) - self.cmd_reset() - - def cmd_next(self): - image = self.filelist.get_next_image() - wallchanger.set_image(image) - self.cmd_reset() - - def cmd_prev(self): - image = self.filelist.get_prev_image() - wallchanger.set_image(image) - self.cmd_reset() - - def cmd_rescan(self): - self.filelist.scan_paths() - - def cmd_pause(self): - if self.task is not None: - self.task.cancel() - self.task = None - - def cmd_exit(self): - asyncsched.exit() - - def cmd_favourite(self): - self.filelist.add_to_favourites() + def init(self, options, paths, oneshot=False): + self.cycle_time = options.cycle_time + self.cache_filename = options.cache_filename + + logging.debug("Initialising wallchanger") + wallchanger.init(options.background_colour, options.convert) + + logging.debug("Initialising file list") + if options.all_random: + self.filelist = AllRandomFileList() + elif options.folder_random: + self.filelist = FolderRandomFileList() + else: + self.filelist = RandomFileList() + + for path in paths: + self.filelist.add_path(path) + + if self.filelist.load_cache(self.cache_filename): + logging.debug("Loaded cache successfully") + else: + logging.debug("Could not load cache") + self.filelist.scan_paths() + + if self.filelist.is_empty(): + logging.error("No images were found. Exiting...") + sys.exit(1) + + self.task = None + if oneshot: + self.cmd_next() + else: + self.cmd_reload() + + def finish(self): + self.filelist.store_cache(self.cache_filename) + + def find_files(self, options, paths): + return filelist + + def cmd_reset(self): + def next(): + image = self.filelist.get_next_image() + wallchanger.set_image(image) + self.task = None + self.cmd_reset() + + if self.task is not None: + self.task.cancel() + self.task = asyncsched.schedule(self.cycle_time, next) + logging.debug("Reset timer for %s seconds" % self.cycle_time) + self.filelist.store_cache(self.cache_filename) + + def cmd_reload(self): + image = self.filelist.get_current_image() + wallchanger.set_image(image) + self.cmd_reset() + + def cmd_next(self): + image = self.filelist.get_next_image() + wallchanger.set_image(image) + self.cmd_reset() + + def cmd_prev(self): + image = self.filelist.get_prev_image() + wallchanger.set_image(image) + self.cmd_reset() + + def cmd_rescan(self): + self.filelist.scan_paths() + + def cmd_pause(self): + if self.task is not None: + self.task.cancel() + self.task = None + + def cmd_exit(self): + asyncsched.exit() + + def cmd_favourite(self): + self.filelist.add_to_favourites() class Server(asynchat.async_chat): - def __init__(self, cycler, sock): - asynchat.async_chat.__init__(self, sock) - 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": - logging.debug('Bad line received "%s"' % line) - return - if hasattr(self.cycler, "cmd_" + cmd): - logging.debug('Executing command "%s"' % cmd) - getattr(self.cycler, "cmd_" + cmd)() - else: - logging.debug('Unknown command received "%s"' % cmd) + def __init__(self, cycler, sock): + asynchat.async_chat.__init__(self, sock) + 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": + logging.debug('Bad line received "%s"' % line) + return + if hasattr(self.cycler, "cmd_" + cmd): + logging.debug('Executing command "%s"' % cmd) + getattr(self.cycler, "cmd_" + cmd)() + else: + logging.debug('Unknown command received "%s"' % cmd) class SockHackWrap(object): - def __init__(self, sock, addr): - self.__sock = sock - self.__addr = addr - def getpeername(self): - return self.__addr - def __getattr__(self, key): - return getattr(self.__sock, key) + def __init__(self, sock, addr): + self.__sock = sock + self.__addr = addr + def getpeername(self): + return self.__addr + def __getattr__(self, key): + return getattr(self.__sock, key) class Listener(asyncore.dispatcher): - def __init__(self, socket_filename, cycler): - asyncore.dispatcher.__init__(self) - self.cycler = cycler - self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) - self.bind(socket_filename) - self.listen(2) # Backlog = 2 - - def handle_accept(self): - sock, addr = self.accept() - Server(self.cycler, SockHackWrap(sock, addr)) - - def writable(self): - return False - + def __init__(self, socket_filename, cycler): + asyncore.dispatcher.__init__(self) + self.cycler = cycler + self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.bind(socket_filename) + self.listen(2) # Backlog = 2 + + def handle_accept(self): + sock, addr = self.accept() + Server(self.cycler, SockHackWrap(sock, addr)) + + def writable(self): + return False + def do_server(options, paths): - try: - cycler = Cycler() - listener = Listener(options.socket_filename, cycler) - # Initialisation of Cycler delayed so we grab the socket quickly - cycler.init(options, paths) - try: - asyncsched.loop() - except KeyboardInterrupt: - print - cycler.finish() - finally: - # Make sure that the socket is cleaned up - try: - os.unlink(options.socket_filename) - except: - pass + try: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(options.socket_filename) + print >>sys.stderr, "Server is already running! Sending exit command." + sock = sock.makefile() + sock.write("cmd exit\n") + sock.close() + except Exception, e: + pass + + try: + os.unlink(options.socket_filename) + except OSError: + pass + + cycler = Cycler() + listener = Listener(options.socket_filename, cycler) + # Initialisation of Cycler delayed so we grab the socket quickly + cycler.init(options, paths) + try: + asyncsched.loop() + except KeyboardInterrupt: + print + cycler.finish() 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 < len(args) - 1: - time.sleep(options.cycle_time) - sock.close() + 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 < len(args) - 1: + time.sleep(options.cycle_time) + sock.close() def do_oneshot(options, paths): - cycler = Cycler() - cycler.init(options, paths, oneshot=True) + cycler = Cycler() + cycler.init(options, paths, oneshot=True) def build_parser(): - parser = OptionParser(version="%prog " + VERSION, - 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("-v", '-d', "--verbose", "--debug", - action="count", dest="verbose", default=0, - help="Make the louder (good for debugging, or those who are curious)") - parser.add_option("-b", "--background-colour", - action="store", type="string", dest="background_colour", default="black", - help="Change the default background colour that is displayed if the image is not in the correct aspect ratio [Default: %default]") - parser.add_option("--all-random", - action="store_true", dest="all_random", default=False, - help="Make sure that all images have been displayed before repeating an image") - parser.add_option("-1", "--oneshot", - action="store_true", dest="oneshot", default=False, - help="Set one random image and terminate immediately.") - 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("--convert", - action="store_true", dest="convert", default=False, - help="Do conversions using ImageMagick or PIL, don't rely on the window manager") - 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('~/.randombg_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 + parser = OptionParser(version="%prog " + VERSION, + 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("-v", '-d', "--verbose", "--debug", + action="count", dest="verbose", default=0, + help="Make the louder (good for debugging, or those who are curious)") + parser.add_option("-b", "--background-colour", + action="store", type="string", dest="background_colour", default="black", + help="Change the default background colour that is displayed if the image is not in the correct aspect ratio [Default: %default]") + parser.add_option("--all-random", + action="store_true", dest="all_random", default=False, + help="Make sure that all images have been displayed before repeating an image") + parser.add_option("-1", "--oneshot", + action="store_true", dest="oneshot", default=False, + help="Set one random image and terminate immediately.") + 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("--convert", + action="store_true", dest="convert", default=False, + help="Do conversions using ImageMagick or PIL, don't rely on the window manager") + 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('~/.randombg_socket'), + help="Location of the command/control socket.") + parser.add_option("--cache-file", + action="store", type="string", dest="cache_filename", default=os.path.expanduser('~/.randombg_cache'), + help="Stores the location of the last image to be loaded.") + parser.add_option("--server", + action="store_true", dest="server", default=False, + help="Run in server mode to listen for clients.") + return parser def main(): - parser = build_parser() - options, args = parser.parse_args(sys.argv[1:]) - - if options.verbose == 1: - logging.getLogger().setLevel(logging.INFO) - elif options.verbose >= 2: - logging.getLogger().setLevel(logging.DEBUG) - - if options.oneshot: - do_oneshot(options, args) - else: - if os.path.exists(options.socket_filename): - do_client(options, args) - else: - do_server(options, args) + parser = build_parser() + options, args = parser.parse_args(sys.argv[1:]) + + if options.verbose == 1: + logging.getLogger().setLevel(logging.INFO) + elif options.verbose >= 2: + logging.getLogger().setLevel(logging.DEBUG) + + if options.server: + do_server(options, args) + return + + if options.oneshot: + do_oneshot(options, args) + return + + try: + do_client(options, args) + return + except Exception, e: + print >>sys.stderr, "Failed to connect to server:", e if __name__ == "__main__": - main() + main()