X-Git-Url: https://code.delx.au/bg-scripts/blobdiff_plain/f71c2fbef0d1f0f7b4957ebc68f5639a533fec1a..7fef5e45b01eb71bba28aefb7a50be3fcca9345c:/bin/randombg.py diff --git a/bin/randombg.py b/bin/randombg.py index ce67906..dad637d 100755 --- a/bin/randombg.py +++ b/bin/randombg.py @@ -4,57 +4,233 @@ VERSION = "2.0" import asyncore, asynchat, socket -import os, os.path, sys, time +import os, os.path, random, sys, time from optparse import OptionParser import logging from logging import debug, info, warning, error, critical logging.basicConfig(format="%(levelname)s: %(message)s") +try: + import cPickle as pickle +except ImportError: + import pickle try: # Required libraries import asyncsched - from FileLists import * - import WallChanger + import wallchanger except ImportError, e: critical("Missing libraries! Exiting...") sys.exit(1) -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) + + +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 + +class BaseFileList(object): + """Base file list implementation""" + def scan_paths(self): + raise NotImplementedError() + + def add_path(self, path): + raise NotImplementedError() + + def store_cache(self, path): + pass + + def load_cache(self, filename, rescanPaths = False): + pass + + def get_next_image(self): + raise NotImplementedError() + + def get_prev_image(self): + raise NotImplementedError() + + def get_current_image(self): + raise NotImplementedError() + + def is_empty(self): + return True + + +class RandomFileList(BaseFileList): + def __init__(self): + self.list = [] + self.paths = [] + 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) + 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] + debug("Picked file '%s' from list" % self.last_image) + return self.last_image - debug("Initilizing RandomBG") - self.randombg = WallChanger.RandomBG(filelist, options.background_colour, options.permanent) + def is_empty(self): + return len(self.list) == 0 + + +class AllRandomFileList(BaseFileList): + def __init__(self): + self.list = None + self.paths = [] + self.imagePointer = 0 + + # Scan the input directory, and then randomize the file list + def scan_paths(self): + debug("Scanning paths") + + self.list = [] + for path in self.paths: + debug('Scanning "%s"' % path) + for dirpath, dirsnames, filenames in os.walk(path): + for filename in filter_images(filenames): + 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) + debug('Added path "%s" to the list' % path) + + def store_cache(self, filename): + try: + fd = open(filename, 'wb') + pickle.dump(obj = self, file = fd, protocol = 2) + debug("Cache successfully stored") + except Exception, e: + warning("Storing cache: %s" % e) + + def load_cache(self, filename, rescanPaths = False): + debug('Attempting to load cache from "%s"' % filename) + self.paths.sort() + try: + fd = open(filename, 'rb') + tmp = pickle.load(fd) + if self.paths == tmp.paths: + debug("Path lists match, copying properties") + # Overwrite this object with the other + for attr in ('list', 'imagePointer'): + setattr(self, attr, getattr(tmp, attr)) + else: + debug("Ignoring cache, path lists do not match") + except Exception, e: + warning("Loading cache: %s" % e) + + 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] + 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] + 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): + self.directories = {} + + def scan_paths(self): + pass + + def add_path(self, path): + debug('Added path "%s" to the list' % path) + for dirpath, dirs, filenames in os.walk(path): + 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 + debug('Adding "%s" to "%s"' % (filenames, dirpath)) + else: + debug("No images found in '%s'" % dirpath) + + def get_next_image(self): + directory = random.choice(self.directories.keys()) + debug('directory: "%s"' % directory) + filename = random.choice(self.directories[directory]) + debug('filename: "%s"' % filename) + return os.path.join(directory, filename) + + def is_empty(self): + return len(self.directories.values()) == 0 + + +class Cycler(object): + def init(self, options, paths): self.cycle_time = options.cycle_time + self.history_filename = options.history_filename - self.task = None - self.cmd_next() + debug("Initialising wallchanger") + wallchanger.init(options.background_colour, options.permanent) - def find_files(self, options, paths): + debug("Initialising file list") if options.all_random: - filelist = AllRandomFileList() + self.filelist = AllRandomFileList() elif options.folder_random: - filelist = FolderRandomFileList() + self.filelist = FolderRandomFileList() else: - filelist = RandomFileList() + self.filelist = RandomFileList() for path in paths: - filelist.doAddPath(path) + self.filelist.add_path(path) - if filelist.attemptCacheLoad(options.history_filename): + if self.filelist.load_cache(self.history_filename): debug("Loaded cache successfully") else: debug("Could not load cache") - filelist.doScanPaths() + self.filelist.scan_paths() + + if self.filelist.is_empty(): + error("No images were found. Exiting...") + sys.exit(1) + + self.task = None + self.cmd_next() + + def finish(self): + self.filelist.store_cache(self.history_filename) + + def find_files(self, options, paths): return filelist def cmd_reset(self): def next(): - self.randombg.cycleNext() + image = self.filelist.get_next_image() + wallchanger.set_image(image) self.task = None self.cmd_reset() @@ -64,25 +240,31 @@ class Cycler(object): debug("Reset timer for %s seconds" % self.cycle_time) def cmd_reload(self): - self.randombg.cycleReload() + image = self.filelist.get_current_image() + wallchanger.set_image(image) self.cmd_reset() def cmd_next(self): - self.randombg.cycleNext() + image = self.filelist.get_next_image() + wallchanger.set_image(image) self.cmd_reset() def cmd_prev(self): - self.randombg.cyclePrev() + image = self.filelist.get_prev_image() + wallchanger.set_image(image) self.cmd_reset() def cmd_rescan(self): - self.randombg.filelist.doScanPaths() + self.filelist.scan_paths() self.cmd_next() def cmd_pause(self): if self.task is not None: self.task.cancel() self.task = None + + def cmd_exit(self): + asyncsched.exit() class Server(asynchat.async_chat): def __init__(self, cycler, conn, addr): @@ -110,25 +292,32 @@ class Server(asynchat.async_chat): class Listener(asyncore.dispatcher): - def __init__(self, socket_filename, randombg): + def __init__(self, socket_filename, cycler): asyncore.dispatcher.__init__(self) - self.randombg = randombg + 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): conn, addr = self.accept() - Server(self.randombg, conn, addr) + Server(self.cycler, conn, addr) + + def writable(self): + return False def do_server(options, paths): try: - cycler = Cycler(options, paths) + cycler = Cycler() listener = Listener(options.socket_filename, cycler) - asyncsched.loop() - except KeyboardInterrupt: - print + # 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: @@ -148,6 +337,9 @@ def do_client(options, args): time.sleep(options.cycle_time) sock.close() +def do_oneshot(options, paths): + cycler = Cycler() + cycler.init(options, paths) def build_parser(): parser = OptionParser(version="%prog " + VERSION, @@ -169,14 +361,20 @@ def build_parser(): 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('~/tmp/tmp_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'), @@ -187,7 +385,13 @@ def main(): parser = build_parser() options, args = parser.parse_args(sys.argv[1:]) - logging.basicConfig(level=10*options.verbose) + 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) if os.path.exists(options.socket_filename): do_client(options, args)