]> code.delx.au - bg-scripts/commitdiff
Initial import
authorGreg Darke <greg@tsukasa.net.au>
Tue, 6 Nov 2007 08:00:40 +0000 (19:00 +1100)
committerGreg Darke <greg@tsukasa.net.au>
Tue, 6 Nov 2007 08:00:40 +0000 (19:00 +1100)
21 files changed:
bin/findsame_file.py [new file with mode: 0755]
bin/findsame_file_new.py [new file with mode: 0755]
bin/kde_randombg.py [new file with mode: 0755]
bin/ncat.py [new file with mode: 0755]
bin/new_wget_image_board.py [new file with mode: 0755]
bin/pyfing [new file with mode: 0755]
bin/randombg2 [new file with mode: 0755]
bin/randombg2.py [new file with mode: 0644]
bin/randombg2_ipc.py [new file with mode: 0755]
lib/AsyncSocket.py [new file with mode: 0644]
lib/Enum.py [new file with mode: 0644]
lib/FileLists.py [new file with mode: 0644]
lib/GregDebug.py [new file with mode: 0644]
lib/SigHandler.py [new file with mode: 0644]
lib/WallChanger.py [new file with mode: 0644]
lib/commands_async.py [new file with mode: 0644]
lib/priv_options.py [new file with mode: 0644]
lib/python24_adapter.py [new file with mode: 0644]
lib/twisted_wget.py [new file with mode: 0755]
lib/wget_lib.py [new file with mode: 0644]
lib/x11_helpers.py [new file with mode: 0644]

diff --git a/bin/findsame_file.py b/bin/findsame_file.py
new file mode 100755 (executable)
index 0000000..1a5ce8d
--- /dev/null
@@ -0,0 +1,175 @@
+#!/usr/bin/env python2.5
+
+MINFILE_SIZE = 1024
+FILEBUFFER_SIZE = 1024**2
+
+import os, sys, bisect
+
+from copy import copy
+from base64 import standard_b64encode as b64encode
+from collections import defaultdict
+import cPickle
+try:
+       import hashlib
+       def _getSha1(filename):
+               return hashlib.sha1()
+except ImportError:
+       import sha
+       def _getSha1(filename):
+               return sha.new()
+def getSha1(filename):
+       sha1 = _getSha1(filename)
+       f = file(filename, 'r')
+       data = f.read(FILEBUFFER_SIZE)
+       while data:
+               sha1.update(data)
+               data = f.read(FILEBUFFER_SIZE)
+       return b64encode(sha1.digest())
+
+try:
+       import psyco
+       psyco.full()
+except ImportError:
+       print >>sys.stderr, "WARNING: Could not load psyco"
+
+class DiskObject(object):
+       def __repr__(self):
+               return repr(self.getFullPath())
+       def __str__(self):
+               return self.getFullPath()
+       def __lt__(self, other):
+               if not hasattr(other, 'getFullPath'):
+                       raise NotImplemented()
+               return self.getFullPath() < other.getFullPath()
+       def __eq__(self, other):
+               if not hasattr(other, 'getFullPath'):
+                       raise NotImplemented()
+               return self.getFullPath() == other.getFullPath()
+       def __hash__(self):
+               return hash(self.getFullPath())
+
+class Folder(DiskObject):
+       def __init__(self, name, parent = None):
+               if name.find(os.path.sep) >= 0 and name != os.path.sep:
+                       print name
+                       parent_name, name = os.path.split(name)
+                       parent = Folder(parent_name, parent)
+
+               self.name = name
+               self.parent = parent
+               if parent:
+                       parent.addChild(self)
+               self.children = {}
+       def getFullPath(self):
+               folderStack = []
+               f = self
+               while f:
+                       folderStack.append(f.name)
+                       f = f.parent
+               return os.path.sep.join(reversed(folderStack))
+       def addChild(self, child):
+               self.children[child.name] = child
+
+def findDirectory(rootDir, dirName, createNonExistant = False):
+       dir = dirName.split(os.path.sep)[1:]
+       if dir == ['']:
+               dir = []
+       
+       ret = rootDir
+       for folderName in dir:
+               try:
+                       ret = ret.children[folderName]
+               except KeyError, e:
+                       if not createNonExistant:
+                               raise e
+                       ret = Folder(folderName, ret)
+
+       return ret
+
+class FileObject(DiskObject):
+       def __init__(self, name, folder):
+               self.name = name
+               self.folder = folder
+               statinfo = os.stat(self.getFullPath())
+               self.mtime_size = (statinfo.st_mtime, statinfo.st_size)
+       def getDiskID(self):
+               statinfo = os.stat(self.getFullPath())
+               return (statinfo.st_dev, statinfo.st_ino) # Identify the file
+       def get_mtime_size(self):
+               return self.mtime_size
+       def getFullPath(self):
+               return '%(folder)s/%(file)s' % { 'folder': self.folder.getFullPath(), 'file': self.name }
+
+class GlobalFileInfo(object):
+       def __init__(self):
+               self.files = defaultdict(list)
+               self.filelist = {}
+               self.root = Folder('')
+
+       def _scanDirUpdateFile(self, dirObject, dirPath, filename):
+               def printPath(word):
+                       print '%s "%s"' % (word, filename[-80:])
+               fullpath = os.path.join(dirPath, filename)
+               if os.path.islink(fullpath) or not os.path.isfile(fullpath):
+                       printPath('Skipping')
+                       return
+               try:
+                       file = FileObject(filename, dirObject)
+                       new_mtime_size = file.get_mtime_size()
+
+                       if file in self.filelist:
+                               if file.get_mtime_size() == self.filelist[file].get_mtime_size():
+                                       printPath('Skipping')
+                                       return
+                               old_sha1 = self.filelist[file].sha1
+                               del self.filelist[file]
+                               self.files[old_sha1].remove(file)
+
+                       if file.get_mtime_size()[1] < MINFILE_SIZE:
+                               printPath('Skipping')
+                               return
+                       printPath('Scanning')
+
+                       file.sha1 = getSha1(fullpath)
+                       self.files[file.sha1].append(file)
+                       self.filelist[file] = file
+               except IOError:
+                       print >>sys.stderr, 'WARNING: Could not get sha1 of "%s"\n' % (fullpath)
+
+       def scanDir(self, dirName):
+               root = findDirectory(self.root, dirName, createNonExistant = True)
+
+               for dirPath, dirs, files in os.walk(dirName):
+                       print 'Scanning directory "%s"\n' % dirPath
+                       folder = findDirectory(self.root, dirPath, createNonExistant = True)
+                       # Add the children Directories
+                       if '.svn' in dirs:
+                               dirs.remove('.svn')
+                       for d in dirs:
+                               Folder(d, folder) # As a side effect, this is added to the parent correctly
+
+                       for f in files:
+                               sys.stdout.write("\033[A\033[300D\033[2K")
+                               self._scanDirUpdateFile(folder, dirPath, f)
+                       sys.stdout.write("\033[A\033[100D\033[2K")
+       def findDuplicates(self):
+               return [(sha1, list(filenames)) for sha1, filenames in self.files.items() if len(filenames) > 1]
+
+def main():
+       try:
+               files = cPickle.load(open(sys.argv[1]))
+       except IOError:
+               files = GlobalFileInfo()
+
+       for dir in sys.argv[2:]:
+               if dir[-1] == '/':
+                       dir = dir[:-1]
+               files.scanDir(dir)
+       
+       cPickle.dump(files, open(sys.argv[1], 'wb'), 2)
+       print "Done"
+
+###    print files.files
+
+if __name__ == "__main__":
+       main()
diff --git a/bin/findsame_file_new.py b/bin/findsame_file_new.py
new file mode 100755 (executable)
index 0000000..293b9fb
--- /dev/null
@@ -0,0 +1,170 @@
+#!/usr/bin/env python2.5
+
+MINFILE_SIZE = 1024
+FILEBUFFER_SIZE = 1024**2
+APPLICATION_VERSION = '0.2'
+
+import os, sys, bisect
+
+import python24_adapter
+from copy import copy
+from base64 import standard_b64encode as b64encode
+from collections import defaultdict
+import cPickle
+
+try:
+       import hashlib
+       def _getSha1(filename):
+               return hashlib.sha1()
+except ImportError:
+       import sha
+       def _getSha1(filename):
+               return sha.new()
+def getSha1(filename):
+       if _sha1_cache.has_key(filename):
+               return b64encode(_sha1_cache[filename])
+
+       sha1 = _getSha1(filename)
+       f = file(filename, 'r')
+       data = f.read(FILEBUFFER_SIZE)
+       while data:
+               sha1.update(data)
+               data = f.read(FILEBUFFER_SIZE)
+
+       ret = sha1.digest()
+       _sha1_cache[filename] = ret
+       return b64encode(ret)
+
+try:
+       import psyco
+       psyco.full()
+except ImportError:
+       print >>sys.stderr, "WARNING: Could not load psyco"
+
+def __versionUpgrade0_1(input):
+       import base64
+       return '0.2', dict((filename, base64.b64decode(sha1hash)) for filename, sha1hash in input)
+
+def loadCache(filename = os.path.expanduser('~/.sha1_cache'), version = APPLICATION_VERSION):
+       global _sha1_cache
+       try:
+               cache_version, cache = cPickle.load(open(filename, 'rb'))
+               if cache_version == '0.1':
+                       cache_version, cache = __versionUpgrade0_1(cache)
+
+               if cache_version != version:
+                       raise Exception("Invalid Version")
+               print 'WARNING: Using the cache file "%s", sha1 hash may be old' % filename
+       except:
+               cache = {}
+       _sha1_cache = cache
+
+def storeCache(filename = os.path.expanduser('~/.sha1_cache'), version = APPLICATION_VERSION):
+       fd = open(filename, 'wb')
+       try:
+               cPickle.dump((version, _sha1_cache), fd)
+       finally:
+               fd.close()
+               
+class GlobalFileInfo(object):
+       def __init__(self):
+               self.files = defaultdict(lambda : defaultdict(list))
+
+       def _scanDirUpdateFile(self, dirPath, filename):
+               def printPath(word):
+                       print '%s "%s"' % (word, filename[-80:])
+               fullpath = os.path.abspath(os.path.join(dirPath, filename))
+               if os.path.islink(fullpath) or not os.path.isfile(fullpath):
+                       printPath('Skipping')
+                       return
+               try:
+                       statInfo = os.stat(fullpath)
+
+                       if statInfo.st_size < MINFILE_SIZE:
+                               printPath('Skipping')
+                               return
+                       printPath('Scanning')
+
+                       fileHash = getSha1(fullpath)
+                       self.files[(fileHash, statInfo.st_size)][(statInfo.st_dev, statInfo.st_ino)].append(fullpath)
+               except IOError:
+                       print >>sys.stderr, 'WARNING: Could not get sha1 of "%s"\n' % (fullpath)
+
+       def scanDir(self, dirName):
+               for dirPath, dirs, files in os.walk(dirName):
+                       print 'Scanning directory "%s"\n' % dirPath
+                       # Add the children Directories
+                       if '.svn' in dirs:
+                               dirs.remove('.svn')
+
+                       for f in files:
+                               sys.stdout.write("\033[A\033[300D\033[2K")
+                               self._scanDirUpdateFile(dirPath, f)
+                       sys.stdout.write("\033[A\033[100D\033[2K")
+       def findDuplicates(self):
+               return [(key, inodes) for key, inodes in self.files.items() if len(inodes) > 1]
+
+def prettyFormatDups(dups):
+       return '\n'.join( \
+                       '%s\n\t%s' % (key, \
+                               '\n\t'.join('%s: %s' % (inode_key, ', '.join(files)) for inode_key, files in inodes.items()) \
+                               ) for key, inodes in dups \
+                       )
+
+       ret = []
+       for key, inodes in dups:
+               section = []
+               for inode_key, files in inodes.items():
+                       section.append('%s: %s' % (inode_key, ', '.join(files)))
+               ret.append('%s\n\t%s' % (key, '\n\t'.join(section)))
+       
+       return '\n'.join(ret)
+
+       
+def makeBashScript(dups, fd):
+       spaceSaved = 0
+       print >>fd, "#!/bin/bash"
+       print >>fd, '# This script was created automatically by "%s"' % __file__
+       # Print out a helper function
+       print >>fd
+       print >>fd, 'function doLink() {'
+       print >>fd, '\tINPUT_FILE="${1}"'
+       print >>fd, '\tshift'
+       print >>fd, '\tfor file in "$@" ; do'
+       print >>fd, '\t\tln "${INPUT_FILE}" "${file}"'
+       print >>fd, '\tdone'
+       print >>fd, '}'
+
+       for dup_key, inodes in dups:
+               print >>fd
+               print >>fd, '# Handling %s' % str(dup_key)
+               inodes_data = inodes.items()
+               inodes_data.sort(key = lambda x: len(x[1]), reverse = True)
+               for inode_key, files in inodes_data[1:]:
+                       print >>fd, '# Removing files connected to inode %d on device %d' % (inode_key[1], inode_key[0])
+                       print >>fd, 'rm -f "%s"' % '" "'.join(file for file in files)
+               fileToLink = inodes_data[0][1][0] # Get the first filename of the largest group of (already) linked files
+               print >>fd, '# Now link all the files together'
+               print >>fd, 'doLink "%s" "%s"' % (fileToLink, '" "'.join('" "'.join(files) for inode_key, files in inodes_data[1:]))
+               spaceSaved += sum(len(files) for inode_key, files in inodes_data[1:]) * dup_key[1]
+       
+       print >>fd
+       print >>fd, '# Total space saved: %d B (%dK B) (%d MB)' % (spaceSaved, spaceSaved / 1024, spaceSaved / 1024**2)
+
+def main():
+       loadCache()
+       files = GlobalFileInfo()
+
+       for dir in sys.argv[2:]:
+               files.scanDir(dir)
+
+       storeCache()
+       print "Done."
+       try:
+               fd = open(sys.argv[1], 'wb')
+               makeBashScript(files.findDuplicates(), fd)
+       finally:
+               fd.close()
+       
+if __name__ == "__main__":
+       main()
diff --git a/bin/kde_randombg.py b/bin/kde_randombg.py
new file mode 100755 (executable)
index 0000000..b2fd661
--- /dev/null
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+# coding:utf-8
+# This is an example of a DCOP enabled application written in Python, using
+# PyKDE and the dcopexport module. Taken from server.py example in kde-bindings
+# which was written by Torben Weis and Julian Rockey
+
+import sys
+import randombg2
+from kdecore import KApplication, KCmdLineArgs, KAboutData
+from dcopexport import DCOPExObj
+from qt import QString, QStringList
+
+# the class which will expose methods to DCOP - the methods do NOT
+# need to be a member of this class.
+class RandomBGIface (DCOPExObj):
+       def __init__ (self, id = 'RandomBGIface'):
+               DCOPExObj.__init__ (self, id)
+
+               # the methods available from this app via DCOP
+               #         addMethod (<signature>, <Python method>)
+               self.addMethod('QString getCurrentWallpaper()', self.getCurrent)
+               self.addMethod('void setCurrentWallpaper(QString)', self.changeTo)
+               self.addMethod('void cycleNextWallpaper()', self.cycleNext)
+               self.addMethod('void cyclePrevWallpaper()', self.cyclePrev)
+               self.addMethod('bool writeCache()', self.writeCache)
+               
+               self.filelist = randombg2.AllRandomFileList()
+               self.filelist.doAddPaths([
+                                         '/home/greg/images_sfw/Futakoi',
+                                         '/home/greg/images_sfw/Moon Phase',
+                                         '/home/greg/images_sfw/Ouran High',
+                                         '/home/greg/images_sfw/Paniponi',
+                                         '/home/greg/images_sfw/Popotan',
+                                         '/home/greg/images_sfw/Rozen Maiden',
+                                         '/home/greg/images_sfw/Yotsuba',
+                                         '/home/greg/images_sfw/chobits',
+                                         '/home/greg/images_sfw/ichigo Mashimaro',
+                                         '/home/greg/images_sfw/カードキャプターさくら',
+                                         '/home/greg/images_sfw/涼宮ハルヒの憂鬱',
+                                         '/home/greg/images_sfw/灼眼のシャナ',
+                                         '/home/greg/images_sfw/舞-乙HiME',
+                                         '/home/greg/images_sfw/舞HiME',
+                                         '/home/greg/images_sfw/魔法先生ネギま',
+                                         '/home/greg/images_sfw/魔法少女リリカルなのは'])
+               #for path in ['/home/greg/images_sfw/Air','/home/greg/images_sfw/Azumanga Daioh','/home/greg/images_sfw/Futakoi','/home/greg/images_sfw/Karin','/home/greg/images_sfw/Love Hina','/home/greg/images_sfw/Moon Phase','/home/greg/images_sfw/Neon Genesis','/home/greg/images_sfw/Ouran High','/home/greg/images_sfw/Paniponi','/home/greg/images_sfw/Popotan','/home/greg/images_sfw/Rozen Maiden','/home/greg/images_sfw/Yotsuba','/home/greg/images_sfw/chobits','/home/greg/images_sfw/dot Hack','/home/greg/images_sfw/ichigo Mashimaro','/home/greg/images_sfw/kasimasi','/home/greg/images_sfw/カードキャププターさくら','/home/greg/images_sfw/涼宮ハルヒの憂鬱','/home/greg/images_sfw/灼眼のシャナ','/home/greg/images_sfw/舞-乙HiME','/home/greg/images_sfw/舞HiME','/home/greg/images_sfw/魔法先生ネギま','/home/greg/images_sfw/魔法少女リリカルなのは']:
+               #       self.filelist.doAddPath(path)
+               if not  self.filelist.attemptCacheLoad(randombg2.CACHE_LOCATION):
+                       self.filelist.doScanPaths()
+               self.randombg = randombg2.RandomBG(self.filelist)
+
+       def getCurrent(self):
+               return self.filelist.getLastImage()
+
+       def changeTo(self, wallpaper):
+               self.randombg._randombg(wallpaper)
+
+       def cycleNext(self):
+               self.randombg.cycleNext()
+
+       def cyclePrev(self):
+               self.randombg.cyclePrev()
+       
+       def writeCache(self):
+               if self.filelist.doStoreCache(randombg2.CACHE_LOCATION):
+                       return True
+               else:
+                       return False
+
+description = "A basic application template"
+version                = "1.0"
+aboutData      = KAboutData ("testdcopexport", "randombg",\
+       version, description, KAboutData.License_GPL,\
+       "(C) 2006 Greg Darke")
+
+aboutData.addAuthor ("James Bunton", "Origional Version", "jbun5313@usyd.edu.au")
+aboutData.addAuthor ("Greg Darke", "Coded the WMaker/KDE backends, the persistent random lists, and general bug fixes", "gdar9540@usyd.edu.au")
+
+KCmdLineArgs.init (sys.argv, aboutData)
+
+KCmdLineArgs.addCmdLineOptions ([("+paths", "Paths to scan for images")])
+
+MYAPPID = 'randombg'
+app   = KApplication()
+dcop  = app.dcopClient()
+appid = str(dcop.registerAs(MYAPPID, False))
+if appid != MYAPPID:
+       print >>sys.stderr, 'Failed to get the applicationid of "%s", this means this program is already running' % MYAPPID
+       print 'Got "%s"' % appid
+       sys.exit(1)
+
+print "DCOP Application: %s starting" % appid
+
+randombgIface = RandomBGIface()
+
+app.exec_loop()
+
+randombgIface.filelist.doStoreCache(randombg2.CACHE_LOCATION)
diff --git a/bin/ncat.py b/bin/ncat.py
new file mode 100755 (executable)
index 0000000..b9314ea
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+
+import sys, os, os.path, socket 
+from optparse import OptionParser, Values
+
+VERSION = '''$Revision$'''
+
+try:
+       # These are my libraries...
+       import GregDebug, AsyncSocket
+
+       from GregDebug import debug, setDebugLevel, DEBUG_LEVEL_DEBUG, DEBUG_LEVEL_LOW, DEBUG_LEVEL_MEDIUM, DEBUG_LEVEL_HIGH, DEBUG_INCREMENT
+except ImportError, e:
+       print >>sys.stderr, "Missing libraries!\nExiting..."
+       sys.exit(1)
+
+
+class NetClient(object):
+       def __init__(self):
+               self.async_handler = AsyncSocket.AsyncSocketOwner()
+
+       def buildparser(self):
+               parser = OptionParser(version="%prog " + VERSION, 
+                       description = "Connects to a specific server",
+                       usage = "%prog [options] server [port]")
+               parser.add_option("-U", "--domain-socket",
+                       action="store_true", dest="isUnixDomainSocket", default="",
+                       help="Make the connection over a unix domain socket")
+               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)")
+               return parser
+
+       def createUnixSocket(self, domainSocketName):
+               sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+               sock.connect(domainSocketName)
+               return sock
+
+       def __handleStdin(self, fd):
+               data = fd.read()
+               if not data:
+                       debug('Seems that stdin has been closed (got no data from in on the last read) - exiting main loop')
+                       self.async_handler.exit() #XXX: I think this is the correct name
+               self.remoteSock.send(data) # Write the data to the socket
+               # TODO: Make sure that the data written to the socket has been completly written - Since
+               # self.sock is in non-blocking mode it may not have send everything
+
+       def __handleSock(self, fd):
+               data = fd.read()
+               if not data:
+                       debug('Seems that the socket has been closed (got no data from in on the last read) - exiting main loop')
+                       self.async_handler.doExit() #XXX: I think this is the correct name
+               sys.stdout.write(data) # Write the data to the screen
+               sys.stdout.flush()
+               # TODO: Turn sys.stdout into a non-blocking socket, since this call could block, and make things
+               # non-responsive
+
+       def main(self):
+               parser = self.buildparser()
+               useroptions, params = 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)
+
+               if useroptions.isUnixDomainSocket:
+                       self.remoteSock = self.createUnixSocket(params[0])
+               else:
+                       print >>sys.stderr, "No connection type specified"
+                       sys.exit(1)
+
+               self.async_handler.addFD(sys.stdin, self.__handleStdin)
+               self.async_handler.addSocket(self.remoteSock, self.__handleSock)
+               self.async_handler.mainLoop()
+
+if __name__ == "__main__":
+       NetClient().main()
diff --git a/bin/new_wget_image_board.py b/bin/new_wget_image_board.py
new file mode 100755 (executable)
index 0000000..3ee536b
--- /dev/null
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+
+import sys, os, re, itertools
+from wget_lib import *
+
+import twisted_wget
+from twisted_wget import reactor
+from Enum import enum
+
+DEBUG = True
+
+URL_TYPE = enum('ImageBoard', 'HtmlPage', 'Image', 'Other')
+
+def addtolist(list, *regexStrs):
+       def decorator(func):
+               for regexStr in regexStrs:
+                       regex = re.compile(regexStr)
+                       list.append( (regex, func) )
+               return func
+       return decorator
+
+class Downloader(object):
+       htmlParsers = []
+       class ParserException(Exception):
+               pass
+
+       def __init__(self, url):
+               self.url = url
+               self.deferred = None
+
+       def downloadFiles(self):
+               # XXX: This is a major hack and needs to be cleaned
+               def commonCallback(downloadObject):
+                       self.workingUrls.remove(downloadObject)
+                       self.activeHosts.remove(downloadObject.host)
+                       self.__scheduleDownloadLater()
+               def successCallback(downloadObject, data): 
+                       print 'Downloaded %s' % downloadObject.url
+                       commonCallback(downloadObject)
+                       downloadObject.data = data
+                       downloadObject.callback(downloadObject)
+                       self.doneUrls.add(downloadObject)
+               def errorCallback(downloadObject, data):
+                       commonCallback(downloadObject)
+                       print 'Error: %s' % data
+               def doDownload(file):
+                       print 'About to download "%s"' % file.url
+                       twisted_wget.downloadURL(file.url, 
+                                                                        successBack = lambda data: successCallback(file, data),
+                                                                        errorBack =   lambda data: errorCallback(file, data)
+                                                                       )
+                       self.waitingUrls.remove(file)   
+                       self.workingUrls.add(file)
+                       self.activeHosts.add(file.host)
+
+
+               self.deferred = None
+               for file in list(self.waitingUrls):
+                       if file.host not in self.activeHosts:
+                               doDownload(file)
+
+       # Notes:
+       #  - image_object.data is a string containing all of the data
+       #  - image_object.url is a string containing the url where the data was downloaded from
+       def _parseImageBoard(self, image_object):
+               assert(image_object.data != None)
+               assert(image_object.url != None)
+
+               for parser_regex, parser in self.htmlParsers:
+                       if parser_regex.search(image_object.url):
+                               return parser(image_object)
+               raise DownloadManager.ParserException('Could not find the correct parser')
+
+       @addtolist(htmlParsers, '\.4chan\.org')
+       def _parseImageBoard_4chan(self, image_object):
+               import htmldata
+               def __extractImageUrlsFromList(urllist):
+                       for url_elem in urllist:
+                               if url_elem.tag_name.upper() == 'A' and isImageURL(url_elem.url):
+                                       yield url_elem.url
+
+               # TODO: Extract a better filename from the list
+               urllist = __extractImageUrlsFromList( htmldata.urlextract(image_object.data, image_object.url) )
+               urllist = xRemoveDups(urllist)
+               urllist = itertools.ifilter(
+                                            lambda elem: elem.find('/thumb/') == -1,
+                                            itertools.ifilter(lambda elem: elem.find('/src.cgi/') == -1, urllist)
+                                           )
+
+               if DEBUG:
+                       urllist, urllist_dup = itertools.tee(urllist)
+                       print >>sys.stderr, 'Got the following urls: \n\t%s' % '\n\t'.join(urllist_dup)
+
+               for url in urllist:
+                       self.downloadImage(url, referer = image_object.url)
+
+def main(output_directory):
+       dm = DownloadManager()
+       for url in sys.argv[1:]:
+               dm.recursiveDownloadImages(url)
+
+       reactor.run()
+
+if __name__ == "__main__":
+       output_directory = os.environ.get('WGET_IMAGEBOARD_DIRECTORY', 
+                                         os.path.join(os.environ['HOME'], 'Images_old', 'wget'))
+       main(output_directory)
diff --git a/bin/pyfing b/bin/pyfing
new file mode 100755 (executable)
index 0000000..664a1df
--- /dev/null
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+# Copyright 2007 James Bunton <jamesbunton@fastmail.fm>
+# Modified by Greg Darke <gdar9540@usyd.edu.au> (2007)
+# Licensed for distribution under the GPL version 2, check COPYING for details
+# Check to see if people are online...
+
+import commands_async, pwd, socket, sys
+
+def matchNames(names):
+       def getFullName(gecos_entry):
+               return gecos_entry[: entry.pw_gecos.find(',')]
+       def parsePWDentry(entry):
+               return (entry.pw_name.lower(), getFullName(entry.pw_gecos).lower())
+
+       pwall = [parsePWDentry(entry) for entry in pwd.getpwall()]
+
+       out = []
+       for name in names:
+               found = False
+               name = name.lower()
+               for entry in pwall:
+                       username, realname = entry
+                       if username.find(name) >= 0 or realname.find(name) >= 0:
+                               found = True
+                               out.append((username, realname))
+               if not found:
+                       print "User '%s' not found in /etc/passwd, assuming you gave a username and you are not on the IT machines..." % name
+                       out.append((name, "[Not Found in /etc/passwd]"))
+       return out
+
+def getSmbStatus():
+       def halfparse(data):
+               return data.split('\n')[4:]
+
+       sshcmd = "ssh %s -q -o StrictHostKeyChecking=no -o BatchMode=true '/usr/samba/bin/smbstatus -b'"
+
+       cmd_async = commands_async.CommandRunner()
+       cmd_async.executeCommand(sshcmd % "ugitsmb.ug.it.usyd.edu.au")
+       cmd_async.executeCommand(sshcmd % "itsmb.ug.it.usyd.edu.au")
+       cmd_async.waitForCompletion()
+
+       data = []
+       for cmd, output in cmd_async.getOutputs().items():
+               data += halfparse(output)
+
+       out = []
+       for line in data:
+               line_split = line.strip().split()
+               if not line_split or len(line_split) != 5:
+                       continue
+
+               pid, username, group, _, ip = line_split
+               host = socket.gethostbyaddr(ip[1:-1])[0]
+               out.append((username, host))
+       return out
+
+def getLastStatus():
+       hosts = ["mono"]
+       hosts += ['congo%d' % i for i in range(1,5)]
+       hosts += ['nlp%d' % i for i in range(0,9)]
+       #hosts += ['userf%d' % i for i in range(1,6)]
+
+       sshcmd = "ssh %s -q -o StrictHostKeyChecking=no -o BatchMode=true 'last -a -t $(date +%%Y%%m%%d%%H%%M%%S)|grep \"still logged in\"'"
+###    sshcmd = "rsh -n %s 'last -a -t $(date +%%Y%%m%%d%%H%%M%%S)|grep \"still logged in\"'"
+
+       cmd_async = commands_async.CommandRunner()
+       for host in hosts:
+               cmd_async.executeCommand(sshcmd % host)
+
+       cmd_async.waitForCompletion()
+       data = "".join(output for cmd,output in cmd_async.getOutputs().items())
+       
+       out = []
+       for line in data.split('\n'):
+               if not line.strip():
+                       continue
+               try:
+                       chunk = line.strip().split()
+                       username = chunk[0]
+                       ip = chunk[-1]
+               except Exception, e:
+                       print "Error:", line, e
+                       return []
+               if ip == 'in': # From 'still logged in'
+                       host = "unknown"
+               else:
+                       try:
+                               host = socket.gethostbyaddr(ip)[0]
+                       except:
+                               host = "unknown"
+               out.append((username, host))
+       return out
+
+
+def printLocation((username, fullname), smbStatus):
+       # Since we only want to know if they are at a location, and now how many times they are at
+       # the location, we store it in a set
+       locationList = set(ip for username2, ip in smbStatus if username == username2)
+       if locationList:
+               print "Username %s:\n  Full name: '%s'\n  %s\n" % \
+                       (username, fullname, '\n  '.join('Location: %s' % ip for ip in locationList))
+
+def main():
+       names =  matchNames(sys.argv[1:])
+       smbStatus = getSmbStatus()
+       lastStatus = getLastStatus()
+       status = smbStatus + lastStatus
+       
+       for name in names:
+               printLocation(name, status)
+
+if __name__ == "__main__":
+       main()
diff --git a/bin/randombg2 b/bin/randombg2
new file mode 100755 (executable)
index 0000000..5bbc844
--- /dev/null
@@ -0,0 +1,240 @@
+#!/usr/bin/env python
+
+import commands, sys, os, os.path, random, socket, subprocess
+import cPickle, datetime, time
+from optparse import OptionParser, Values
+
+VERSION = "1.1"
+CACHE_LOCATION = os.path.expanduser('~/.randombg2_filelist_cache')
+
+try:
+       import GregDebug
+       from SigHandler import HUPInterrupt
+       from GregDebug import debug, setDebugLevel, DEBUG_LEVEL_DEBUG, DEBUG_LEVEL_LOW, DEBUG_LEVEL_MEDIUM, DEBUG_LEVEL_HIGH, DEBUG_INCREMENT
+       from FileLists import *
+except ImportError:
+       print >>sys.stderr, "Missing libraries!\nExiting..."
+       sys.exit(1)
+
+try:
+       from collections import defaultdict
+       def magicdict():
+               return defaultdict(dict)
+except ImportError:
+       class magicdict(dict):
+               def __getitem__(self, key):
+                       if not self.has_key(key):
+                               self[key] = {}
+                       return dict.__getitem__(self, key)
+
+class RandomBG(object):
+       KDE_CONFIG = os.path.expanduser('~/.kde/share/config/kdesktoprc')
+       def __init__(self, filelist, backgroundColour='black', permanent=False):
+               windowManager = self._determineWindowManager()
+               debug('Determined the window manager is "%s"' % windowManager, DEBUG_LEVEL_MEDIUM)
+               self.backgroundColour = backgroundColour
+               self.permanent = permanent
+               self.filelist = filelist
+
+               if windowManager == "WMAKER":
+                       self._randombg = self._randombgWMAKER
+               elif windowManager == "KDE":
+                       self._randombg = self._randombgKDE
+               elif windowManager == "OSX":
+                       self._randombg = self._randombgOSX
+               else:
+                       raise Exception("Unknown window manager")
+
+       def _determineWindowManager(self):
+               """Searches for a some specified windows within the current X session to see 
+               what window manager we are running under"""
+               
+               debug("Testing for OSX (NonX)", DEBUG_LEVEL_LOW)
+               if commands.getstatusoutput("ps ax -o command -c|grep -q WindowServer")[0] == 0:
+                       return "OSX"
+               debug("Testing for KDE", DEBUG_LEVEL_LOW)
+               if commands.getstatusoutput("xwininfo -name 'KDE Desktop'")[0] == 0:
+                       return "KDE"
+               debug("Testing for WMaker", DEBUG_LEVEL_LOW)
+               if commands.getstatusoutput("xlsclients | grep -qi wmaker")[0] == 0:
+                       return "WMAKER"
+               
+               return None
+
+       def _randombgWMAKER(self, file):
+               cmd = ["wmsetbg", 
+                       "-b", self.backgroundColour, # Sets the background colour to be what the user specified
+                       "-S", # 'Smooth' (WTF?)
+                       "-e", # Center the image on the screen (only affects when the image in no the in the correct aspect ratio
+                       "-d", # dither
+                       "-a"] # scale the image, keeping the aspect ratio
+               if self.permanent:
+                       cmd += ["-u"] # update the wmaker database
+               cmd += [file]
+               return subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, stdin=open('/dev/null', 'r')).wait()
+       
+       def _randombgOSX(self, file):
+               cmd = """osascript -e 'tell application "finder" to set desktop picture to posix file "%s"'""" % file
+               debug(cmd, DEBUG_LEVEL_DEBUG)
+               return commands.getstatusoutput(cmd)[0]
+
+       def _parseKDEConfig(self, filename = KDE_CONFIG):
+               fd = open(filename, 'r')
+               result = magicdict()
+               section = None
+               for line in fd:
+                       line = line.strip()
+                       if not line or line.startswith('#'):
+                               continue
+
+                       if line.startswith('[') and line.endswith(']'):
+                               section = line[1:-1]
+                               result[section] = {}
+                               continue
+                       elif not section:
+                               raise Exception('Invalid kdesktoprc file')
+
+                       unpack = line.split('=', 1)
+                       if len(unpack) == 2:
+                               key, val = unpack
+                       else:
+                               key, val = unpack[0], None
+                       result[section][key] = val
+                       
+               fd.close()
+               return result
+
+       def _writeKDEConfig(self, config, filename = KDE_CONFIG):
+               fd = open(filename, 'w')
+               for section, values in config.items():
+                       print >>fd, '[%s]' % section
+                       for k, v in values.items():
+                               if v != None:
+                                       print >>fd, '%s=%s' % (k,v)
+                               else:
+                                       print >>fd, k
+                       print >>fd
+               fd.close()
+       
+       def _randombgKDE(self, file):
+               kdeconfig = self._parseKDEConfig()
+               #kdeconfig['Background Common']['DrawBackgroundPerScreen_0']='true'
+               for section in ('Desktop0', 'Desktop0Screen0'):
+                       kdeconfig[section]['Wallpaper'] = file
+                       kdeconfig[section]['UseSHM'] = 'true'
+                       kdeconfig[section]['WallpaperMode'] = 'ScaleAndCrop'
+                       # Ensure that random mode is disabled...
+                       if 'MultiWallpaperMode' in kdeconfig[section]:
+                               del kdeconfig[section]['MultiWallpaperMode']
+
+               self._writeKDEConfig(kdeconfig)
+
+               return subprocess.Popen(['dcop', 'kdesktop', 'KBackgroundIface', 'configure'], 
+                          stdout=sys.stdout, stderr=sys.stderr, stdin=open('/dev/null', 'r')).wait()
+
+       def __call__(self):
+               self.cycleNext()
+
+       def cycleNext(self):
+               file = self.filelist.getNextRandomImage()
+               return self._randombg(file)
+       
+       def cyclePrev(self):
+               file = self.filelist.getPrevRandomImage()
+               return self._randombg(file)
+
+def buildparser():
+       def addfilestolist(optclass, opt, value, parser, fileList):
+               fo = open(value)
+               for line in fo:
+                       fileList.list.append(line.strip())
+               fo.close()
+               fileList.allowAllRandom = False
+               
+       parser = OptionParser(version="%prog " + VERSION, 
+               description = "Picks a random background image",
+               usage = "%prog [options] dir [dir2 ...]")
+       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)")
+       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("--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",
+               help="Cause the image to cycle every X seconds")
+       return parser
+
+def main():
+       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)
+
+       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()
+       try:
+
+               if not filelist.hasImages():
+                       print >>sys.stderr, "No files!"
+                       parser.print_help()
+                       sys.exit(1)
+               
+               ret = None
+               debug("Initilizing RandomBG", DEBUG_LEVEL_DEBUG)
+               randombg = RandomBG(filelist, useroptions.background_colour, useroptions.permanent)
+               if useroptions.cycle_time > 0:
+                       while True:
+                               try:
+                                       debug("Cycling wallpaper", DEBUG_LEVEL_LOW)
+                                       ret = randombg()
+                                       if ret:
+                                               debug('Could not set wallpaper. Returned "%s" % ret')
+                                               break
+                                       debug('About to sleep for "%d" seconds' % useroptions.cycle_time, DEBUG_LEVEL_LOW)
+                                       time.sleep(useroptions.cycle_time)
+                               except KeyboardInterrupt, e:
+                                       break
+                                       debug("Caught KeyboardInterrupt", DEBUG_LEVEL_LOW)
+                               except HUPInterrupt, e:
+                                       # Force a new image to be displayed before the timeout
+                                       debug("Caught SIGHUP: Loading new image")
+               else:
+                       ret = randombg()
+               
+       finally:
+               filelist.doStoreCache(CACHE_LOCATION)
+       
+       sys.exit(ret)
+
+if __name__ == "__main__":
+       main()
diff --git a/bin/randombg2.py b/bin/randombg2.py
new file mode 100644 (file)
index 0000000..c756411
--- /dev/null
@@ -0,0 +1 @@
+randombg2
\ No newline at end of file
diff --git a/bin/randombg2_ipc.py b/bin/randombg2_ipc.py
new file mode 100755 (executable)
index 0000000..b18b672
--- /dev/null
@@ -0,0 +1,216 @@
+#!/usr/bin/env python
+
+import sys, os, os.path, socket 
+from optparse import OptionParser, Values
+
+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
+
+       from FileLists import *
+except ImportError, e:
+       print >>sys.stderr, "Missing libraries!\nExiting..."
+       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
+               
+       parser = OptionParser(version="%prog " + VERSION, 
+               description = "Picks a random background image",
+               usage = "%prog [options] dir [dir2 ...]")
+       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)")
+       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("--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",
+               help="Cause the image to cycle every X seconds")
+       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)
+
+               # Store some of the other useful options
+               self.cycle_time = useroptions.cycle_time
+
+       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
+
+       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)
+               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)
+               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 _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/AsyncSocket.py b/lib/AsyncSocket.py
new file mode 100644 (file)
index 0000000..76f10f1
--- /dev/null
@@ -0,0 +1,160 @@
+#!/usr/bin/env python
+# Copyright 2007 Greg Darke <gdar9540@usyd.edu.au>
+# Licensed for distribution under the GPL version 2, check COPYING for details
+# A async framework for sockets and fds (fds only supported under unix operating systems)
+# NOTE: Orig version submitted for NETS3603 assignment 1 (Semester 1 - 2007)
+
+
+from __future__ import division
+import os, sys, select, socket, bisect, fcntl
+from time import time
+
+class Callback(object):
+       __slots__ = ['callback', 'callback_time']
+       def __init__(self, callback_time, callback):
+               self.callback_time = callback_time
+               self.callback = callback
+       def __call__(self):
+               return self.callback()
+       def __lt__(self, other):
+               if hasattr(other, 'callback_time'):
+                       return self.callback_time < other.callback_time
+               else:
+                       return NotImplemented
+
+class AsyncSocketOwner(object):
+       """This is the object contains the 'main loop' of the application"""
+       def __init__(self):
+               self.sockets_input = []
+               self.socket_callbacks = {}
+               self.timer_callbacks = []
+               self._exit = False
+               self.state = {}
+
+       def print_state(self):
+###            sys.stdout.write('\033[H\033[2J')
+               print "\n".join(['%s: %s' % v for v in self.state.items()])
+               self.addCallback(1.0, self.print_state)
+
+       def _check_timers_callbacks(self):
+               now = time()
+               i = bisect.bisect(self.timer_callbacks, Callback(now, None))
+               self.state['Processing Callbacks'] = '%d of %d' % (i, 
+                               len(self.timer_callbacks))
+               needCall = self.timer_callbacks[0:i]
+               self.timer_callbacks = self.timer_callbacks[i:]
+
+               for callback in needCall:
+                       callback()
+
+       def exit(self):
+               self._exit = True
+
+       def mainLoop(self):
+               try:
+                       while not self._exit:
+                               if len(self.timer_callbacks) > 0:
+                                       timeout = max(self.timer_callbacks[0].callback_time - time(), 0)
+                                       # Wait until the next timer expires for input
+                                       inputready, outputready, exceptready = \
+                                                       select.select(self.sockets_input, [], [], timeout)
+                               else:
+                                       # Wait forever for input
+                                       inputready, outputready, exceptready = \
+                                                       select.select(self.sockets_input, [], [])
+
+                               # Handle any data received
+                               self.state['Waiting sockets'] = len(inputready)
+                               self.state['Socket count'] = len(self.sockets_input)
+                               for s in inputready:
+                                       self.socket_callbacks[s](s)
+
+                               # Handle timers:
+                               if len(self.timer_callbacks) > 0 and \
+                                               self.timer_callbacks[0].callback_time < time():
+                                       self._check_timers_callbacks()
+               except KeyboardInterrupt:
+                       pass
+       
+       def _addFDCallback(self, fd, callback):
+               """Add a callback for a file descriptor, also add it to the select call"""
+               self.sockets_input.append(fd)
+               self.socket_callbacks[fd] = callback
+
+       def removeSocket(self, fd):
+               """Removes the sepecified fd from the event loop"""
+               self.sockets_input.remove(fd)
+               del self.socket_callbacks[fd]
+
+       def addFD(self, fd, callback):
+               """Adds a file descriptor to the event loop"""
+               # Turn blocking off
+               flags = fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK
+               fcntl.fcntl(fd, fcntl.F_SETFL, flags)
+               self._addFDCallback(fd, callback)
+
+       def addSocket(self, s, callback):
+               """Adds a socket to the event loop"""
+               # Disable blocking - So now we have an async socket
+               s.setblocking(False) 
+               self._addFDCallback(s, callback)
+
+       def addLineBufferedSocket(self, s, callback):
+               sockWrapper = LineBufferedAsyncClientConnection(s, callback, self)
+               s.setblocking(False) 
+               self._addFDCallback(s, sockWrapper._dataArrived)
+
+       def addCallback(self, seconds, callback):
+               """Add a timer callback"""
+               # Keep the list of callbacks sorted to keep things more efficient (Note: This would be better with a heap)
+               cb = Callback(time() + seconds, callback)
+               bisect.insort(self.timer_callbacks, cb)
+               return cb
+
+       def removeCallback(self, callback_object):
+               """Remove a callback from the list. NB: If the time has fired/is in the list to be
+               fired, the outcome is undefined (currently it will be called - but this may change)"""
+               if callback_object in self.timer_callbacks:
+                       self.timer_callbacks.remove(callback_object)
+
+class LineBufferedAsyncClientConnection(object):
+       __slots__ = ['sock', 'callback', 'delim', 'eventLoop', 'linesBuffer', 'lineBuffer', 'closed']
+       def __init__(self, sock, callback, eventLoop, delim = '\n'):
+               self.sock = sock
+               self.callback = callback
+               self.delim = delim
+               self.eventLoop = eventLoop
+               self.linesBuffer = []
+               self.lineBuffer = ''
+
+       def _dataArrived(self, *args, **kwargs):
+               data = self.sock.recv(65535)
+               if not data:
+                       self.closed = True
+                       self.eventLoop.removeSocket(self.sock)
+                       return
+
+               self.lineBuffer += data
+               newLinePos = self.lineBuffer.rfind(self.delim)
+               if newLinePos >= 0:
+                       self.linesBuffer += self.lineBuffer[:newLinePos].split(self.delim)
+                       self.lineBuffer = self.lineBuffer[newLinePos+1:]
+                       self.callback(self)
+
+       def fileno(self):
+               """Return the encapsulated socket's fileno (used for select.select)"""
+               return self.sock.fileno()
+
+       def readline(self):
+               if not self.hasLine():
+                       raise Exception('No data in buffer')
+               ret = self.linesBuffer[0]
+               del self.linesBuffer[0]
+               return ret
+
+       def write(self, data):
+               self.sock.write(data)
+       send = write
+       
+       def hasLine(self):
+               return len(self.linesBuffer) > 0
diff --git a/lib/Enum.py b/lib/Enum.py
new file mode 100644 (file)
index 0000000..6bb2d3d
--- /dev/null
@@ -0,0 +1,54 @@
+#! python
+# Copyright 2007 Greg Darke <gdar9540@usyd.edu.au>
+# Licensed for distribution under the GPL version 2, check COPYING for details
+# An enum like interface
+
+"""
+An enum like interface
+"""
+
+__all__ = ('enum', )
+
+import itertools
+
+def enum(*args):
+       return EnumContainer(*args)
+
+class EnumElement(object):
+       __slots__ = ('enumName', 'enumContainer')
+       def __init__(self, enumName, enumContainer):
+               self.enumName = enumName
+               self.enumContainer = enumContainer
+       def __repr__(self):
+               return '%s(%s)' % (self.__class__.__name__, self.enumName)
+       def __str__(self):
+               return self.enumName
+       def __eq__(self, other):
+               if not isinstance(other, self.__class__): return NotImplemented
+               return other is self
+       def __hash__(self):
+               return hash(self.enumName) ^ hash(self.enumContainer)
+
+class EnumContainer(object):
+       def __init__(self, *enums):
+               self.enumList = tuple( EnumElement(enumName, self) for enumName in enums)
+               for enumName, enumElement in itertools.izip(enums, self.enumList):
+                       setattr(self, enumName, enumElement)
+
+       def __contains__(self, enum):
+               return enum in self.enumList
+
+       def __repr__(self):
+               return '%s(%s)' % (self.__class__.__name__, ', '.join(self.enumList))
+
+       def explode(self):
+               """Places contents of this enum into the callers global namespace"""
+               import inspect
+               frameObject, _, _, _, _ = inspect.stack[1] # The stackframe of who called us
+               global_dict = frameObject.f_globals
+               del frameObject
+               for enum in self.enumList:
+                       if str(enum) in global_dict:
+                               raise ValueError, '%s is already in your global dict' % enum
+               for enum in self.enumList:
+                       global_dict[str(enum)] = enum
diff --git a/lib/FileLists.py b/lib/FileLists.py
new file mode 100644 (file)
index 0000000..6c769f6
--- /dev/null
@@ -0,0 +1,195 @@
+#! python
+
+import sys, os, os.path, random
+import cPickle
+
+try:
+       import GregDebug
+       from GregDebug import debug, DEBUG_LEVEL_DEBUG, DEBUG_LEVEL_LOW, DEBUG_LEVEL_MEDIUM, DEBUG_LEVEL_HIGH
+       if __name__ == "__main__":
+               GregDebug.DEBUG_LEVEL = -10
+except ImportError:
+       print >>sys.stderr, "WARNING: debugging disabled as GregDebug could not be found"
+       DEBUG_LEVEL_DEBUG = DEBUG_LEVEL_LOW = DEBUG_LEVEL_MEDIUM = DEBUG_LEVEL_HIGH = None
+       def debug(message, level=None, indent_level = None):
+               pass
+
+class FileListNotImplemented(Exception):
+       pass
+
+def filterImageFiles(imageList):
+       IMAGE_EXT_LIST = ('.jpg', '.jpe', '.jpeg', '.png', '.gif', '.bmp')
+       def isImageFile(fname):
+               filebase, fileext = os.path.splitext(fname)
+               fileext = fileext.lower()
+               return fileext in IMAGE_EXT_LIST
+       return [fname for fname in imageList if isImageFile(fname)]
+       
+
+class BaseFileList(object):
+       """Base file list implementation"""
+       def doScanPaths(self):
+               raise FileListNotImplemented()
+       def doAddPaths(self, paths):
+               for path in paths:
+                       self.doAddPath(path)
+       def doAddPath(self, path):
+               raise FileListNotImplemented()
+       def doStoreCache(self, path):
+               return False
+       def getNextRandomImage(self):
+               raise FileListNotImplemented()
+       def getPrevRandomImage(self):
+               raise FileListNotImplemented()
+       def getCurrentImage(self):
+               raise FileListNotImplemented()
+       def attemptCacheLoad(self, filename, rescanPaths = False):
+               return False
+       def hasImages(self):
+               return False
+
+class RandomFileList(BaseFileList):
+       def __init__(self):
+               self.list = []
+               self.paths = []
+               self.lastImage = None
+
+       def doScanPaths(self):
+               for path in self.paths:
+                       for dirpath, dirsnames, filenames in os.walk(path):
+                               for filename in filterImageFiles(filenames):
+                                       self.list.append(os.path.join(dirpath, filename))
+
+       def doAddPath(self, path):
+               self.paths.append(path)
+               debug('Added path "%s" to the list' % path, DEBUG_LEVEL_DEBUG)
+
+       def doStoreCache(self, filename):
+               pass
+
+       def getLastImage(self):
+               return self.lastImage
+
+       def getNextRandomImage(self):
+               n = random.randint(0, len(self.list)-1)
+               self.lastImage = self.list[n]
+               debug("Picked file '%s' from list" % self.lastImage)
+               return self.lastImage
+       
+       def attemptCacheLoad(self, filename, rescanPaths = False):
+               return False
+       
+       def hasImages(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 doScanPaths(self):
+               debug("Scanning paths", DEBUG_LEVEL_DEBUG)
+
+               self.list = []
+               for path in self.paths:
+                       debug('Scanning "%s"' % path, DEBUG_LEVEL_DEBUG)
+                       print os.path.exists(path)
+                       for dirpath, dirsnames, filenames in os.walk(path):
+                               for filename in filterImageFiles(filenames):
+                                       debug('Adding file "%s"' % filename, DEBUG_LEVEL_DEBUG - 2*GregDebug.DEBUG_INCREMENT)
+                                       self.list.append(os.path.join(dirpath, filename))
+
+               random.shuffle(self.list)
+
+       def doAddPath(self, path):
+               self.paths.append(path)
+               debug('Added path "%s" to the list' % path, DEBUG_LEVEL_DEBUG)
+
+       def doStoreCache(self, filename):
+               fd = open(filename, 'wb')
+               cPickle.dump(obj = self, file = fd, protocol = 2)
+               debug("Cache successfully stored", DEBUG_LEVEL_LOW)
+               return True
+
+       def getCurrentImage(self):
+               return self.list[self.imagePointer]
+       
+       def __incrementInRange(self, n, amount = 1, rangeMax = None, rangeMin = 0):
+               if rangeMax == None: rangeMax = len(self.list)
+               assert rangeMax > 0
+               return (n + amount) % rangeMax
+
+       def getNextRandomImage(self):
+               self.imagePointer = self.__incrementInRange(self.imagePointer)
+               imageName = self.list[self.imagePointer]
+               debug("Picked file '%s' (pointer=%d) from list" % (imageName, self.imagePointer))
+               return imageName
+
+       def getPrevRandomImage(self):
+               self.imagePointer = self.__incrementInRange(self.imagePointer, amount=-1)
+               imageName = self.list[self.imagePointer]
+               debug("Picked file '%s' (pointer=%d) from list" % (imageName, self.imagePointer))
+               return imageName
+
+       def attemptCacheLoad(self, filename, rescanPaths = False):
+               debug('Attempting to load cache from "%s"' % filename, DEBUG_LEVEL_DEBUG)
+               self.paths.sort()
+               try:
+                       return self._attemptCacheLoad(filename)
+               except Exception, e:
+                       debug("Exception while loading cache: '%s'" % e)
+
+       def _attemptCacheLoad(self, filename):
+               try:
+                       fd = open(filename, 'rb')
+                       tmp = cPickle.load(fd)
+                       if self.paths == tmp.paths:
+                               debug("Path lists match, copying properties", DEBUG_LEVEL_DEBUG)
+                               # Overwrite this object with the other
+                               for attr in ('list', 'imagePointer'):
+                                       setattr(self, attr, getattr(tmp, attr))
+                               return True
+                       else:
+                               debug("Ignoring cache, path lists do not match", DEBUG_LEVEL_LOW)
+                               return False
+               except IOError, e:
+                       debug("Exception raised while trying to load cache: '%s'" % e)
+                       return False
+
+       def hasImages(self):
+               return self.list
+
+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 doScanPaths(self):
+               return True # Since it is already done
+       
+       def doAddPath(self, path):
+               debug('Added path "%s" to the list' % path, DEBUG_LEVEL_DEBUG)
+               for dirpath, dirs, filenames in os.walk(path):
+                       debug('Scanning "%s" for images' % dirpath)
+                       if self.directories.has_key(dirpath):
+                               continue
+                       filenames = filterImageFiles(filenames)
+                       if len(filenames):
+                               self.directories[dirpath] = filenames
+                               debug('Adding "%s" to "%s"' % (filenames, dirpath))
+                       else:
+                               debug("No images found in '%s'" % dirpath)
+       
+       def getNextRandomImage(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 hasImages(self):
+               return len(self.directories.values())
diff --git a/lib/GregDebug.py b/lib/GregDebug.py
new file mode 100644 (file)
index 0000000..c431317
--- /dev/null
@@ -0,0 +1,70 @@
+#! python
+
+import sys
+import cgitb
+import inspect
+
+DEBUG_INCREMENT    = 5
+DEBUG_LEVEL_DEBUG  = DEBUG_INCREMENT * -2
+DEBUG_LEVEL_LOW    = DEBUG_INCREMENT * -1
+DEBUG_LEVEL_MEDIUM = DEBUG_INCREMENT *  0
+DEBUG_LEVEL_HIGH   = DEBUG_INCREMENT *  1
+DEBUG_LEVEL = DEBUG_LEVEL_MEDIUM
+
+__stackTraceEnabled = True
+
+def stackTraceEnabled(value):
+       global __stackTraceEnabled
+       __stackTraceEnabled = value
+
+def setDebugLevel(level):
+       global DEBUG_LEVEL
+       DEBUG_LEVEL = level
+
+def isBoundMethod(stackFrame):
+       """Checks to see if the method that is running in the specified stackFrame is 
+a bound method.
+Returns a 2-tuple containing if it is a bound method, and the object that it is
+bound to if it is bound."""
+       def errout():
+               return (False, None)
+
+       if stackFrame.f_code.co_argcount < 1:
+               return errout()
+       firstVarName = stackFrame.f_code.co_varnames[0]
+       firstVar = stackFrame.f_locals[firstVarName]
+       if not hasattr(firstVar, stackFrame.f_code.co_name):
+               return errout()
+       if not hasattr(getattr(firstVar, stackFrame.f_code.co_name), 'func_code'):
+               return errout()
+       if getattr(getattr(firstVar, stackFrame.f_code.co_name), 'func_code') == stackFrame.f_code:
+               return (True, firstVar)
+       else:
+               return errout()
+
+def createStackTrace(stackList):
+       if not __stackTraceEnabled:
+               return ''
+       strStackList = []
+       for stackItem in stackList:
+               stackItemRepr = ""
+               bm = isBoundMethod(stackItem[0]) # stackframe
+               if bm[0]:
+                       stackItemRepr = '%s.' % bm[1].__class__.__name__
+               stackItemRepr += stackItem[3] # Function Name
+               del bm # Help remove circular dependencies (reduces memory useage)
+               strStackList.append(stackItemRepr)
+
+       return '=>'.join(strStackList)
+
+def debug(message, level=DEBUG_LEVEL_MEDIUM, indent_level = None):
+       if level >= DEBUG_LEVEL:
+               stack = inspect.stack()[1:-1] # Ignore this method
+               stack.reverse()
+               if indent_level == None:
+                       indent_level = len(stack)
+               for line in message.split('\n'):
+                       print >>sys.stderr, '%s %s [%s]' %('>' * indent_level, line, createStackTrace(stack))
+
+def tracebackHook(etype, evalue, etb):
+       print cgitb.text( (etype, evalue, etb), context = 5)
diff --git a/lib/SigHandler.py b/lib/SigHandler.py
new file mode 100644 (file)
index 0000000..9b494d1
--- /dev/null
@@ -0,0 +1,18 @@
+#! python
+
+from signal import signal, SIGHUP, SIGTERM
+
+class HUPInterrupt(Exception):
+       pass
+class TERMInterrupt(Exception):
+       pass
+
+def HUPHandler(signal, stackFrame):
+       raise HUPInterrupt
+
+def TERMHandler(signal, stackFrame):
+       raise TERMInterrupt
+
+# Install the handlers
+signal(SIGHUP, HUPHandler)
+signal(SIGTERM, TERMHandler)
diff --git a/lib/WallChanger.py b/lib/WallChanger.py
new file mode 100644 (file)
index 0000000..1fb3e82
--- /dev/null
@@ -0,0 +1,201 @@
+#! python
+
+import commands, sys, os, os.path, subprocess, time
+from GregDebug import debug, setDebugLevel, DEBUG_LEVEL_DEBUG, DEBUG_LEVEL_LOW, DEBUG_LEVEL_MEDIUM, DEBUG_LEVEL_HIGH, DEBUG_INCREMENT
+
+import python24_adapter # NB: Must be imported before collections
+import collections
+
+"""This is a cross platform/cross window manager way to change your current
+desktop image."""
+
+__all__ = ('RandomBG')
+
+KDE_CONFIG = os.path.expanduser('~/.kde/share/config/kdesktoprc')
+
+def RandomBG(*args, **kwargs):
+       """Desktop Changer factory"""
+
+       ret = None
+
+       debug("Testing for OSX (NonX11)", DEBUG_LEVEL_LOW)
+       if commands.getstatusoutput("ps ax -o command -c|grep -q WindowServer")[0] == 0:
+               ret = __OSXChanger(*args, **kwargs)
+
+       if 'DISPLAY' not in os.environ:
+               # X11 is not running
+               return ret
+       else:
+               if os.uname()[0] == 'Darwin':
+                       # Try to detect if the X11 server is running on OSX
+                       if commands.getstatusoutput("ps ax -o command|grep -q '/.*X11 .* %s'" % os.environ['DISPLAY'])[0] != 0:
+                               # X11 is not running for this display
+                               return ret
+
+       debug("Testing for KDE", DEBUG_LEVEL_LOW)
+       if commands.getstatusoutput("xwininfo -name 'KDE Desktop'")[0] == 0:
+               if ret is not None:
+                       ret.nextChanger = __KDEChanger(*args, **kwargs)
+               else:
+                       ret = __KDEChanger(*args, **kwargs)
+
+       debug("Testing for WMaker", DEBUG_LEVEL_LOW)
+       if commands.getstatusoutput("xlsclients | grep -qi wmaker")[0] == 0:
+               if ret is not None:
+                       ret.nextChanger = __WMakerChanger(*args, **kwargs)
+               else:
+                       ret = __WMakerChanger(*args, **kwargs)
+       
+       if ret is None:
+               raise Exception("Unknown window manager")
+       else:
+               return ret
+
+class __BaseChanger(object):
+       def __init__(self, filelist, backgroundColour='black', permanent=False):
+               debug('Determined the window manager is "%s"' % self.__class__.__name__, DEBUG_LEVEL_MEDIUM)
+               self.backgroundColour = backgroundColour
+               self.permanent = permanent
+               self.filelist = filelist
+               # Used to 'chain' background changers
+               self.nextChanger = None
+
+       def callChained(self, filename):
+               if self.nextChanger is None:
+                       return True
+               else:
+                       return self.nextChanger.changeTo(filename)
+
+       def cycleNext(self):
+               file = self.filelist.getNextRandomImage()
+               return self.changeTo(file) and self.callChained(file)
+       
+       def cyclePrev(self):
+               file = self.filelist.getPrevRandomImage()
+               return self.changeTo(file) and self.callChained(file)
+       
+       def cycleReload(self):
+               file = self.filelist.getCurrentImage()
+               return self.changeTo(file) and self.callChained(file)
+
+
+class __WMakerChanger(__BaseChanger):
+       _ConvertedWallpaperLocation = '/tmp/wallpapers_wmaker/'
+       def _removeOldImageCache(self):
+               """Cleans up any old temp images"""
+               if not os.path.isdir(self._ConvertedWallpaperLocation):
+                       os.mkdir(self._ConvertedWallpaperLocation)
+               for fullpath, filenames, dirnames in os.walk(self._ConvertedWallpaperLocation, topdown=False):
+                       for filename in filenames:
+                               os.unlink(os.path.join(fullpath, filename))
+                       for dirname in dirnames:
+                               os.unlink(os.path.join(fullpath, dirname))
+
+       def _convertImageFormat(self, file):
+               """Convert the image to a png, and store it in a local place"""
+               self._removeOldImageCache()
+               output_name = os.path.join(self._ConvertedWallpaperLocation, '%s.png' % time.time())
+               cmd = ["convert", '-resize', '1280', '-gravity', 'Center', '-crop', '1280x800+0+0', file, output_name]
+               debug("""Convert command: '"%s"'""" % '" "'.join(cmd), DEBUG_LEVEL_DEBUG)
+               return output_name, subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, stdin=None).wait()
+       def changeTo(self, file):
+               file, convert_status = self._convertImageFormat(file)
+               if convert_status:
+                       debug('Convert failed')
+               cmd = ["wmsetbg", 
+                       "-b", self.backgroundColour, # Sets the background colour to be what the user specified
+                       "-S", # 'Smooth' (WTF?)
+                       "-e", # Center the image on the screen (only affects when the image in no the in the correct aspect ratio
+###                    "-a", # scale the image, keeping the aspect ratio
+                       "-u", # Force this to be the default background
+                       "-d"  # dither
+                       ]
+               if self.permanent:
+                       cmd += ["-u"] # update the wmaker database
+               cmd += [file]
+               debug('''WMaker bgset command: "'%s'"''' % "' '".join(cmd), DEBUG_LEVEL_DEBUG)
+               return not subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, stdin=None).wait()
+
+class __OSXChanger(__BaseChanger):
+       _ConvertedWallpaperLocation = '/tmp/wallpapers/'
+       def _removeOldImageCache(self):
+               """Cleans up any old temp images"""
+               if not os.path.isdir(self._ConvertedWallpaperLocation):
+                       os.mkdir(self._ConvertedWallpaperLocation)
+               for fullpath, filenames, dirnames in os.walk(self._ConvertedWallpaperLocation, topdown=False):
+                       for filename in filenames:
+                               os.unlink(os.path.join(fullpath, filename))
+                       for dirname in dirnames:
+                               os.unlink(os.path.join(fullpath, dirname))
+
+       def _convertImageFormat(self, file):
+               """Convert the image to a png, and store it in a local place"""
+               self._removeOldImageCache()
+               output_name = os.path.join(self._ConvertedWallpaperLocation, '%s.png' % time.time())
+               cmd = ["convert", file, output_name]
+               debug("""Convert command: '"%s"'""" % '" "'.join(cmd), DEBUG_LEVEL_DEBUG)
+               return output_name, subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, stdin=None).wait()
+
+       def changeTo(self, file):
+               output_name, ret = self._convertImageFormat(file)
+               if ret: # Since 0 indicates success
+                       debug("Convert failed %s" % ret)
+                       return False
+               cmd = """osascript -e 'tell application "finder" to set desktop picture to posix file "%s"'""" % output_name
+               debug(cmd, DEBUG_LEVEL_DEBUG)
+               return not commands.getstatusoutput(cmd)[0]
+
+class __KDEChanger(__BaseChanger):
+       def _parseKDEConfig(self, filename = KDE_CONFIG):
+               fd = open(filename, 'r')
+               result = collection.defaultdict(dict)
+               section = None
+               for line in fd:
+                       line = line.strip()
+                       if not line or line.startswith('#'):
+                               continue
+
+                       if line.startswith('[') and line.endswith(']'):
+                               section = line[1:-1]
+                               result[section] = {}
+                               continue
+                       elif not section:
+                               raise Exception('Invalid kdesktoprc file')
+
+                       unpack = line.split('=', 1)
+                       if len(unpack) == 2:
+                               key, val = unpack
+                       else:
+                               key, val = unpack[0], None
+                       result[section][key] = val
+                       
+               fd.close()
+               return result
+
+       def _writeKDEConfig(self, config, filename = KDE_CONFIG):
+               fd = open(filename, 'w')
+               for section, values in config.items():
+                       print >>fd, '[%s]' % section
+                       for k, v in values.items():
+                               if v != None:
+                                       print >>fd, '%s=%s' % (k,v)
+                               else:
+                                       print >>fd, k
+                       print >>fd
+               fd.close()
+       
+       def changeTo(self, file):
+               kdeconfig = self._parseKDEConfig()
+               #kdeconfig['Background Common']['DrawBackgroundPerScreen_0']='true'
+               for section in ('Desktop0', 'Desktop0Screen0'):
+                       kdeconfig[section]['Wallpaper'] = file
+                       kdeconfig[section]['UseSHM'] = 'true'
+                       kdeconfig[section]['WallpaperMode'] = 'ScaleAndCrop'
+                       # Ensure that random mode is disabled...
+                       if 'MultiWallpaperMode' in kdeconfig[section]:
+                               del kdeconfig[section]['MultiWallpaperMode']
+
+               self._writeKDEConfig(kdeconfig)
+
+               return not subprocess.Popen(['dcop', 'kdesktop', 'KBackgroundIface', 'configure'], 
+                          stdout=sys.stdout, stderr=sys.stderr, stdin=open('/dev/null', 'r')).wait()
diff --git a/lib/commands_async.py b/lib/commands_async.py
new file mode 100644 (file)
index 0000000..f90a2a5
--- /dev/null
@@ -0,0 +1,67 @@
+#! python
+
+"""
+A small utility that provides similar functionality to the commands module, but 
+allows you to get the output from multiple processes at the same time
+"""
+
+__author__ = "Greg Darke"
+
+import subprocess, fcntl, os
+from select import select
+try:
+       import cStringIO as _StringIO
+except ImportError:
+       import StringIO as _StringIO
+
+class CommandRunner(object):
+       def __init__(self):
+               self._outputs = {}
+               self._processes = {}
+               self._fds = []
+
+       def _executeCommand(self, cmd):
+               """Execute the command"""
+               output = _StringIO.StringIO()
+               isShell = isinstance(cmd, str)
+               process = subprocess.Popen(args = cmd, shell = isShell, 
+                               stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
+
+               # Turn blocking off
+               flags = fcntl.fcntl(process.stdout, fcntl.F_GETFL) | os.O_NONBLOCK
+               fcntl.fcntl(process.stdout, fcntl.F_SETFL, flags)
+               
+               return (output, process)
+
+       def executeCommand(self, cmd):
+               """Executes a command, but does not return anything"""
+               output, process = self._executeCommand(cmd)
+               self._outputs[process.stdout] = output
+               self._processes[cmd] = process
+               self._fds.append(process.stdout)
+
+       def _waitLoop(self):
+               count = 0
+               while self._fds:
+                       # While there are still some processes left
+                       inputReady, outputReady, exceptReady = \
+                                       select(self._fds, [], self._fds)
+
+                       for fd in inputReady:
+                               data = fd.read()
+                               if not data:
+                                       self._fds.remove(fd)
+                                       continue
+                               self._outputs[fd].write(data)
+
+                       for fd in exceptReady:
+                               self._fds.remove(fd)
+
+       def waitForCompletion(self):
+               """Waits for all of the running processes to finish"""
+               self._waitLoop()
+
+       def getOutputs(self):
+               """Returns a dictionay containing the command as the key, and a string (of the output) as the value"""
+               outputs = dict((cmd, self._outputs[process.stdout].getvalue()) for cmd, process in self._processes.items())
+               return outputs
diff --git a/lib/priv_options.py b/lib/priv_options.py
new file mode 100644 (file)
index 0000000..f996c1b
--- /dev/null
@@ -0,0 +1,28 @@
+#! /usr/bin/env python
+
+import os
+
+__all__ = ['load_options']
+
+def load_options():
+       FILENAME = os.path.expanduser('~/priv/credentials.conf')
+       options = {}
+       try:
+               options_fd = open(FILENAME, "r")
+
+               for line in options_fd:
+                       if line.startswith('#'):
+                               continue
+                       line = line.strip().split('=')
+                       if  len(line) < 2:
+                               continue
+
+                       key = line[0]
+                       value = '='.join(line[1:])
+                       
+                       options[key] = value
+
+               options_fd.close()
+       except:
+               pass
+       return options
diff --git a/lib/python24_adapter.py b/lib/python24_adapter.py
new file mode 100644 (file)
index 0000000..28951e1
--- /dev/null
@@ -0,0 +1,20 @@
+#! python
+
+import collections
+
+class defaultdict(dict):
+       def __init__(self, default_factory):
+               self.default_factory = default_factory
+       def __missing__(self, key):
+               if self.default_factory is None:
+                       raise KeyError(key)
+               self[key] = value = self.default_factory()
+               return value
+       def __getitem__(self, key):
+               try:
+                       return dict.__getitem__(self, key)
+               except KeyError:
+                       return self.__missing__(key)
+
+if not hasattr(collections, 'defaultdict'):
+       collections.defaultdict = defaultdict
diff --git a/lib/twisted_wget.py b/lib/twisted_wget.py
new file mode 100755 (executable)
index 0000000..aca0d55
--- /dev/null
@@ -0,0 +1,145 @@
+#!/usr/bin/env python2.4
+
+import GregDebug, base64, os, sys, urlparse
+
+from twisted.internet import reactor, protocol
+from twisted.web.client import HTTPClientFactory
+from twisted.web.http import HTTPClient
+from twisted.web.client import _parse as parseURL
+
+__all__ = ('downloadURL', )
+
+def parseURL(url, defaultPort = None):
+       """Based on twisted.web.client._parse"""
+       parsed = urlparse.urlparse(url)
+       scheme = parsed[0]
+       path = urlparse.urlunparse(('','')+parsed[2:])
+       if defaultPort is None:
+               if scheme == 'https':
+                       defaultPort = 443
+               else:
+                       defaultPort = 80
+       host, port = parsed[1], defaultPort
+
+       if '@' in host:
+               authUser, host = host.split('@', 1)
+               auth = (authUser, )
+               if ':' in authUser:
+                       auth = tuple(authUser.split(':', 1))
+       else:
+               auth = None
+
+       if ':' in host:
+               host, port = host.rsplit(':', 1)
+               port = int(port)
+
+       return scheme, auth, host, port, path
+
+class HTTPProxyFactory(protocol.ClientFactory):
+       def __init__(self, realFactory, proxyServer, proxyMethod = 'GET', proxyPassword = None):
+               self.realFactory = realFactory
+               self.proxyHost, self.proxyPort = proxyServer
+               self.proxyMethod = proxyMethod
+               self.proxyPassword = proxyPassword
+
+       def buildProtocol(self, addr):
+               protocol = HTTPProxyProtocol(self, self.realFactory.buildProtocol(addr) )
+               return protocol
+
+       def __getattr__(self, key):
+               return getattr(self.realFactory, key)
+
+class HTTPProxyProtocol(protocol.Protocol):
+       def __init__(self, factory, proxied):
+               self.factory = factory
+               self.proxied = proxied
+               self.proxyPassword = factory.proxyPassword
+               if self.proxyPassword is not None:
+                       self.proxyPassword = base64.standard_b64encode('%s:%s' % self.proxyPassword)
+               if factory.proxyMethod == 'GET':
+                       self.__connectionMade = self.__connectionMade_GET
+               else:
+                       raise NotImplementedError
+
+       def __send(self, value):
+               self.transport.write(value)
+
+       def __getTransportWrites(self, function, *args, **kwargs):
+               temp = self.transport.write 
+               request = []
+               self.transport.write = lambda data: request.append(data)
+               function(*args, **kwargs)
+               self.proxied.connectionMade()
+               self.transport.write = temp
+               return request
+
+       def __connectionMade_GET(self):
+               self.factory.realFactory.path = self.factory.realFactory.url
+               self.proxied.makeConnection(self.transport)
+
+               self.__send('GET %s HTTP/1.0\r\n' % self.factory.realFactory.url)
+               if self.proxyPassword is not None:
+                       self.__send('Proxy-Authorization: Basic %s\r\n' % self.proxyPassword)
+
+               # Remove the real http client's get request
+               for line in self.__getTransportWrites(self.proxied.connectionMade)[1:]:
+                       self.__send(line)
+
+       def connectionMade(self):
+               self.proxied.transport = self.transport
+               self.__connectionMade()
+
+       def dataReceived(self, data):
+               self.proxied.dataReceived(data)
+
+       def connectionLost(self, reason):
+               self.proxied.connectionLost(reason)
+
+proxies = {}
+def downloadURL(url, method = 'GET', successBack = None, errorBack = None):
+       factory = HTTPClientFactory(url, method = method)
+       scheme, auth, host, port, path = parseURL(url)
+       if successBack is not None:
+               factory.deferred.addCallback(successBack)
+       if errorBack is not None:
+               factory.deferred.addErrback(errorBack)
+       if scheme in proxies:
+               (host, port), password, factory_type = proxies[scheme]
+               # Change the factory to the proxies one
+               factory = factory_type(realFactory = factory, proxyServer = (host, port), proxyMethod = method, proxyPassword = password) 
+       
+       reactor.connectTCP(host, port, factory)
+       return factory
+
+# Note: Does not currently honor the no-proxy variable
+def parseProxies():
+       for k,v in ( (k,v) for k,v in os.environ.items() if v and k.endswith('_proxy')):
+               proxy_type = k[:-len('_proxy')]
+               if proxy_type == 'http':
+                       _, auth, host, port, _ = parseURL(v)
+                       proxies[proxy_type] = (host, port), auth, HTTPProxyFactory
+
+def main(urls):
+       def summerise(string, summerisedLen = 100):
+               if len(string) <= summerisedLen:
+                       return string
+               else:
+                       summerisedLen -= 5
+                       start = summerisedLen // 2
+                       return '%s ... %s' % (string[:start], string[-(summerisedLen - start):])
+
+       def s(data):
+               print 'Success: "%r"' % summerise(data)
+###            print 'factory: (\n\t%s\n)' % '\n\t'.join('%s:%s' % (attr, getattr(factory, attr)) for attr in dir(factory))
+
+       def e(data):
+               print data
+
+       for url in urls:
+               factory = downloadURL(url, successBack = s, errorBack = e)
+       reactor.run()
+
+# Parse the environment variables for proxy servers
+parseProxies()
+if __name__ == "__main__":
+       main(sys.argv[1:])
diff --git a/lib/wget_lib.py b/lib/wget_lib.py
new file mode 100644 (file)
index 0000000..ce79d86
--- /dev/null
@@ -0,0 +1,42 @@
+#! python
+
+__all__ = ('parse_url', 'isImageURL', 'unique', 'removeDups', 'xRemoveDups')
+
+IMAGE_EXTENSIONS = ('PNG', 'JPG', 'JPEG', 'BMP', 'GIF', 'SWF', 'TIF', 'TIFF')
+
+def parse_url(url):
+       """Parses a url into a tuple of (hostname, directory, filename)."""
+       return ('hostname', 'directory', 'filename')
+
+def isImageURL(url):
+       """Checks if an filename is an image"""
+       try:
+               _, extension = url.rsplit('.', 1)
+       except ValueError:
+               # There was no '.' in the url
+               return False
+       else:
+               return extension.upper() in IMAGE_EXTENSIONS
+
+def unique(l):
+       list_iter = iter(l)
+       last_item = list_iter.next()
+       yield last_item
+       for item in list_iter:
+               if last_item != item:
+                       yield item
+                       last_item = item
+       
+def removeDups(l):
+       """Removes duplicates from the list (Note: The ordering of the list may change)"""
+       return list(unique(sorted(l)))
+
+def xRemoveDups(l):
+       """Removes duplicates from the list.
+          Requires O(n) memory, objects must be hashable"""
+       yielded = set()
+       for elem in l:
+               if elem in yielded:
+                       continue
+               yielded.add(elem)
+               yield elem
diff --git a/lib/x11_helpers.py b/lib/x11_helpers.py
new file mode 100644 (file)
index 0000000..b479132
--- /dev/null
@@ -0,0 +1,38 @@
+#! python
+# Copyright 2007 Greg Darke <starstuff@optusnet.com.au>
+# Licensed for distribution under the GPL version 2, check COPYING for details
+# Some little helper utils
+
+import subprocess, commands, itertools
+
+__all__ = ('getResolutions', )
+
+def _seperateGroups(lines):
+       ret = []
+       current_section = []
+       for line in lines:
+               if line.strip() == '':
+                       ret.append(current_section)
+                       current_section = []
+                       continue
+               current_section.append(line)
+       if current_section:
+               ret.append(current_section)
+       return ret
+
+def getResolutions():
+       xdpyinfo_status, xdpyinfo_output = commands.getstatusoutput('xdpyinfo')
+       lines = xdpyinfo_output.splitlines()
+       groups = _seperateGroups(xdpyinfo_output.splitlines())
+
+       screens = []
+       for screen_data in itertools.islice(groups, 1, None, None):
+               _, screen_number = screen_data[0].split()
+               # remove the leading and trailing characters
+               screen_number = screen_number[1:-1]
+
+               _, screen_resolution_str, _, _, _ = screen_data[1].strip().split()
+               screen_resolution = screen_resolution_str.split('x')
+
+               screens.append( (screen_number, tuple(int(val) for val in screen_resolution)))
+       return dict(screens)