#!/usr/bin/env python from threading import Thread from Queue import Queue import os, re, subprocess from shutil import move, rmtree MSG_QUEUE = Queue() CMD_QUEUE = Queue() # samplefn = [ "hello.gz", "hello.tar.gz", "hello.tar", "hello.bz2", "hello.tar.bz2", "hello.rar", "hello.r01" ] class Archiver(Thread): def __init__(self, name, types, opts, mapping, filename): Thread.__init__(self) self.name = name # types is a list of regexpr self.types = types # generic options to take self.opts = opts self.program_name = None self.mapping = mapping self.filename = filename self.destdir = False self.optparse() def run(self): if not self.list and self.destdir: self._d_preprocess() cmdstr = self.cmdstr() if self.verbose: self.put('running >>%s<<' % (cmdstr)) process = subprocess.Popen(cmdstr, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) stdin, stdout = process.stdin, process.stdout while not stdout.closed: line = stdout.readline() if not line: stdout.close() stdin.close() continue try: CMD_QUEUE.get(False) os.kill(process.pid, 9) stdout.close() stdin.close() continue except: pass line = line.strip('\n') self.processLine(line) if self.destdir: self._d_postprocess() #(stdin, stdout, stderr) = os.popen3() def put(self, obj): MSG_QUEUE.put(obj) def optstring(self): # map the generic opts to app specific options optstr = self.default_opts for option, value in self.opts.items(): if self.mapping.has_key(option): optstr += ' ' + str(mapping[option]) else: pass return optstr def cmdstr(self): return self.name + " " + self.optstring() + ' "%s"' % (self.filename) def optparse(self): self.chdir = False self.destdir = False self.mode = False self.list = False self.verbose = False self.flat = False for option, value in self.opts.items(): if option == 'destdir': self.destdir = value elif option == 'mode': self.mode = value elif option == 'list': self.list = value elif option == 'verbose': self.verbose = value elif option == 'flat': self.flat = value def support(self, filename): for expr in self.types: if expr.match(filename): return True return False def flatten(self, directory): if self.verbose: "walking >>%s<<" % (directory) for dirpath, dirnames, filenames in os.walk(directory): for f in filenames: #print "<< %s/%s" % (dirpath, f) move('%s/%s' % (dirpath, f), '%s/%s' % (directory, f)) for dirpath, dirnames, filenames in os.walk(directory): for d in dirnames: #print "XX %s/%s" % (dirpath, d) rmtree('%s/%s' % (directory, d)) # make a cmd string safe to run def escape(self, cmdstr): cmdstr = cmdstr.replace('"', '\"') return cmdstr # these are to be re-implemented per wrapper # certain archive formats are able to do more # than others, so they might be more or less # custom glue involved def _d_preprocess(self): dd = self.destdir.replace('%n', self.filebase) self.filename = "../" + self.filename try: os.mkdir(dd, 0755) except OSError: # File exists:.. pass os.chdir(dd) self.chdir = True def _d_postprocess(self): if self.chdir: os.chdir('..') if self.flat: dd = self.destdir.replace('%n', self.filebase) self.flatten(dd) def processLine(self, line): if not self.verbose or self.list: return self.put(line) class Bzip2(Archiver): def __init__(self, opts, filename): global re_bz2 map = { } # decompress & force self.default_opts = '-df' self.filebase = self.escape("".join(filename.split('.')[:-1])) Archiver.__init__(self, 'bzip2', [re_bz2], opts, map, filename) def cmdstr(self): if self.list: self.put('Warning: archive type "bzip2" does not support multiple files.') return '/bin/true' else: return Archiver.cmdstr(self) class Gzip(Archiver): def __init__(self, opts, filename): global re_gz, re_com map = { } # decompress & force self.default_opts = '-df' self.filebase = self.escape("".join(filename.split('.')[:-1])) Archiver.__init__(self, 'gzip', [re_gz, re_com], opts, map, filename) def cmdstr(self): if self.list: self.put('Warning: archive type "gzip" does not support multiple files.') return '/bin/true' else: return Archiver.cmdstr(self) class Tar(Archiver): def __init__(self, opts, filename): global re_tar, re_tbz, re_tgz map = { } self.default_opts = '' self.filebase = self.escape("".join(filename.split('.')[:-1])) if re_tbz.match(filename): self.default_opts = '-j' self.filebase = self.escape("".join(filename.split('.')[:-2])) if re_tgz.match(filename): self.default_opts = '-z' self.filebase = self.escape("".join(filename.split('.')[:-1])) self.default_opts += '-xvf' Archiver.__init__(self, 'tar', [re_tar, re_tbz, re_tgz], opts, map, filename) def _get_info(self): pass def cmdstr(self): if self.list: return self.name + ' -tf "%s"' % (self.filename) else: return Archiver.cmdstr(self) def processLine(self, line): if not self.verbose and not self.list: return if self.list: # tar -t gives us basically what we want self.put(self.filename + '/' + line.strip()) return line = line.strip() self.put('unarchiving: ' + line) class Rar(Archiver): def __init__(self, opts, filename): global re_zip map = { } if inpath('unrar'): self.binmode = 'rar' elif inpath('rar'): self.binmode = 'rar' else: self.put("Error: neither 'unrar' or 'rar' in path.") self.re_data = re.compile("^Extracting .*") self.re_list_data = re.compile("^ \S+.*") self.filebase = self.escape("".join(filename.split('.')[:-1])) self.default_opts = '-y -idp' # respond 'yes' to every question Archiver.__init__(self, 'unrar', [re_rar], opts, map, self.escape(filename)) def _get_info(self): self.cmd = 'unrar v "%s"' % (self.filename) def cmdstr(self): if self.list: return self.name + ' v "%s"' % (self.filename) else: self.default_opts += ' x' return Archiver.cmdstr(self) def processLine(self, line): if not self.verbose and not self.list: return if self.list: if not self.re_list_data.match(line): return # do some processing first but.. self.put(self.filename + '/' + line.strip()) return if not self.re_data.match(line): return line = line.strip() filename = line.split()[1:-1] self.put('unarchiving: ' + "".join(filename)) class Zip(Archiver): def __init__(self, opts, filename): global re_zip map = { } #FIXME: make this work so that the program works with zip or unzip if inpath('unzip'): self.binmode = 'unzip' elif inpath('zip'): self.binmode = 'zip' else: self.put("Error: neither 'unzip' or 'zip' in path.") self.re_archive = re.compile(r"^Archive:.*$") self.re_summary = re.compile(r"[0-9]* file[s]?, .*$") self.filebase = self.escape("".join(filename.split('.')[:-1])) self.default_opts = '-o' # overwrite files without prompting Archiver.__init__(self, self.binmode, [re_zip], opts, map, self.escape(filename)) def _get_info(self): self.cmd = 'unzip -Zt "%s"' % (self.filename) def cmdstr(self): if self.list: return self.name + ' -Z "%s"' % (self.filename) else: # NOTE: this only works because Archiver is described in this # module's global scope return Archiver.cmdstr(self) def processLine(self, line): if not self.verbose and not self.list: return if self.re_archive.match(line): return if self.list: if self.re_summary.match(line): # someday we might want to do something with the summary info return f = line.split()[-1] self.put('%s/%s' % (self.filename, f)) return line = line.strip() line = 'unarchiving: ' + "".join(line.split(': ')[1:]) self.put(line) # zip allows destdir with -d switch def _d_preprocess(self): dd = self.destdir.replace('%n', self.filebase) self.default_opts += ' -d "%s"' % (dd) #self.put(self.default_opts) # just using the regular _d_postprocess re_gz = re.compile(r".*?(?