From: Greg Darke Date: Tue, 6 Nov 2007 08:00:40 +0000 (+1100) Subject: Initial import X-Git-Url: https://code.delx.au/bg-scripts/commitdiff_plain/437cc3ef42a0038caa22c4676c4e25831697f258?ds=sidebyside Initial import --- diff --git a/bin/findsame_file.py b/bin/findsame_file.py new file mode 100755 index 0000000..1a5ce8d --- /dev/null +++ b/bin/findsame_file.py @@ -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 index 0000000..293b9fb --- /dev/null +++ b/bin/findsame_file_new.py @@ -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 index 0000000..b2fd661 --- /dev/null +++ b/bin/kde_randombg.py @@ -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 (, ) + 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 index 0000000..b9314ea --- /dev/null +++ b/bin/ncat.py @@ -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 index 0000000..3ee536b --- /dev/null +++ b/bin/new_wget_image_board.py @@ -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 index 0000000..664a1df --- /dev/null +++ b/bin/pyfing @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# Copyright 2007 James Bunton +# Modified by Greg Darke (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 index 0000000..5bbc844 --- /dev/null +++ b/bin/randombg2 @@ -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 index 0000000..c756411 --- /dev/null +++ b/bin/randombg2.py @@ -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 index 0000000..b18b672 --- /dev/null +++ b/bin/randombg2_ipc.py @@ -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 index 0000000..76f10f1 --- /dev/null +++ b/lib/AsyncSocket.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python +# Copyright 2007 Greg Darke +# 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 index 0000000..6bb2d3d --- /dev/null +++ b/lib/Enum.py @@ -0,0 +1,54 @@ +#! python +# Copyright 2007 Greg Darke +# 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 index 0000000..6c769f6 --- /dev/null +++ b/lib/FileLists.py @@ -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 index 0000000..c431317 --- /dev/null +++ b/lib/GregDebug.py @@ -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 index 0000000..9b494d1 --- /dev/null +++ b/lib/SigHandler.py @@ -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 index 0000000..1fb3e82 --- /dev/null +++ b/lib/WallChanger.py @@ -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 index 0000000..f90a2a5 --- /dev/null +++ b/lib/commands_async.py @@ -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 index 0000000..f996c1b --- /dev/null +++ b/lib/priv_options.py @@ -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 index 0000000..28951e1 --- /dev/null +++ b/lib/python24_adapter.py @@ -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 index 0000000..aca0d55 --- /dev/null +++ b/lib/twisted_wget.py @@ -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 index 0000000..ce79d86 --- /dev/null +++ b/lib/wget_lib.py @@ -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 index 0000000..b479132 --- /dev/null +++ b/lib/x11_helpers.py @@ -0,0 +1,38 @@ +#! python +# Copyright 2007 Greg Darke +# 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)