if __name__ == '__main__': import findrox; findrox.version(1, 99, 11) import os, sys from support import shell_escape, Tmp import rox from rox.processes import PipeThroughCommand current_command = None def pipe_through_command(command, src, dst): global current_command assert not current_command try: src.seek(0) except: pass current_command = PipeThroughCommand(command, src, dst) try: current_command.wait() finally: current_command = None operations = [] class Operation: add_extension = False def __init__(self, extension): operations.append(self) self.extension = extension def can_handle(self, data): return isinstance(data, FileData) def save_to_stream(self, data, stream): pipe_through_command(self.command, data.source, stream) class Compress(Operation): "Compress a stream into another stream." add_extension = True def __init__(self, extension, command, type): Operation.__init__(self, extension) self.command = command self.type = type def __str__(self): return _('Compress as .%s') % self.extension class Decompress(Operation): "Decompress a stream into another stream." type = 'text/plain' def __init__(self, extension, command): Operation.__init__(self, extension) self.command = command def __str__(self): return _('Decompress .%s') % self.extension class Extract(Operation): "Extract an archive to a directory." type = 'inode/directory' def __init__(self, extension, command): "If command has a %s then the source path is inserted, else uses stdin." Operation.__init__(self, extension) self.command = command def __str__(self): return _('Extract from a .%s') % self.extension def save_to_stream(self, data, stream): raise Exception(_('This operation creates a directory, so you have ' 'to drag to a filer window on the local machine')) def save_to_file(self, data, path): if os.path.exists(path): if not os.path.isdir(path): raise Exception(_("'%s' already exists and is not a directory!") % path) if not os.path.exists(path): os.mkdir(path) os.chdir(path) command = self.command source = data.source if command.find("'%s'") != -1: command = command % shell_escape(source.name) source = None try: pipe_through_command(command, source, None) finally: try: os.rmdir(path) # Will only succeed if it's empty except: pass if os.path.exists(path): self.pull_up(path) def pull_up(self, path): # If we created only a single subdirectory, move it up. dirs = os.listdir(path) if len(dirs) != 1: return dir = dirs[0] unneeded_path = os.path.join(path, dir) if not os.path.isdir(unneeded_path): return import random tmp_path = os.path.join(path, 'tmp-' + `random.randint(0, 100000)`) os.rename(unneeded_path, tmp_path) for file in os.listdir(tmp_path): os.rename(os.path.join(tmp_path, file), os.path.join(path, file)) os.rmdir(tmp_path) class Archive(Operation): "Create an archive from a directory." add_extension = True def __init__(self, extension, command, type): assert command.find("'%s'") != -1 Operation.__init__(self, extension) self.command = command self.type = type def __str__(self): return _('Create .%s archive') % self.extension def can_handle(self, data): return isinstance(data, DirData) def save_to_stream(self, data, stream): os.chdir(os.path.dirname(data.path)) command = self.command % shell_escape(os.path.basename(data.path)) pipe_through_command(command, None, stream) tgz = Extract('tgz', "gunzip -c - | tar xf -") tbz = Extract('tar.bz2', "bunzip2 -c - | tar xf -") tarz = Extract('tar.Z', "uncompress -c - | tar xf -") rar = Extract('rar', "rar x '%s'") ace = Extract('ace', "unace x '%s'") tar = Extract('tar', "tar xf -") rpm = Extract('rpm', "rpm2cpio - | cpio -id --quiet") cpio = Extract('cpio', "cpio -id --quiet") deb = Extract('deb', "ar x '%s'") zip = Extract('zip', "unzip -q '%s'") jar = Extract('jar', "unzip -q '%s'") make_tgz = Archive('tgz', "tar cf - '%s' | gzip", 'application/x-compressed-tar') Archive('tar.gz', "tar cf - '%s' | gzip", 'application/x-compressed-tar') Archive('tar.bz2', "tar cf - '%s' | bzip2", 'application/x-bzip-compressed-tar') Archive('zip', "zip -qr - '%s'", 'application/zip'), Archive('jar', "zip -qr - '%s'", 'application/x-jar') Archive('tar', "tar cf - '%s'", 'application/x-tar') # Note: these go afterwards so that .tar.gz matches before .gz make_gz = Compress('gz', "gzip -c -", 'application/x-gzip') Compress('bz2', "bzip2 -c -", 'application/x-bzip') Compress('uue', "uuencode /dev/stdout", 'application/x-uuencoded') gz = Decompress('gz', "gunzip -c -") bz2 = Decompress('bz2', "bunzip2 -ck -") uue = Decompress('uue', "uudecode -o /dev/stdout") z = Decompress('Z', "uncompress -c -") # Can bzip2 read bzip files? aliases = { 'tar.gz': 'tgz', 'tar.bz': 'tar.bz2', 'tbz': 'tar.bz2', 'bz': 'bz2' } known_extensions = {} for x in operations: try: known_extensions[x.extension] = None except AttributeError: pass class FileData: "A file on the local filesystem." mode = None def __init__(self, path): self.path = path if path == '-': source = sys.stdin else: try: source = file(path) self.mode = os.stat(path).st_mode except: rox.report_exception() sys.exit(1) self.path = path start = source.read(300) try: if source is sys.stdin: raise Exception("Always copy stdin!") source.seek(0) self.source = source except: # Input is not a regular, local, seekable file, so copy it # to a local temp file. import shutil tmp = Tmp() tmp.write(start) tmp.flush() shutil.copyfileobj(source, tmp) tmp.seek(0) tmp.flush() self.source = tmp self.default = self.guess_format(start) if path == '-': name = 'Data' else: name = path for ext in known_extensions: if path.endswith('.' + ext): new = path[:-len(ext)-1] if len(new) < len(name): name = new if self.default.add_extension: name += '.' + self.default.extension if name == path: # Default name is same as input. Change it somehow... if '.' in os.path.basename(name): name = name[:name.rindex('.')] else: name += '.unpacked' self.default_name = name def guess_format(self, data): "Return a good default Operation, judging by the first 300 bytes or so." l = len(data) def string(offset, match): return data[offset:offset + len(match)] == match def short(offset, match): if l > offset + 1: a = data[offset] b = data[offset + 1] return ((a == match & 0xff) and (b == (match >> 8))) or \ (b == match & 0xff) and (a == (match >> 8)) return 0 # Archives if string(257, 'ustar\0') or string(257, 'ustar\040\040\0'): return tar if short(0, 070707) or short(0, 0143561) or string(0, '070707') or \ string(0, '070701') or string(0, '070702'): return cpio if string(0, '!') or string(0, '\\') or string(0, ''): if string(7, '\ndebian'): return deb if string(0, 'Rar!'): return rar if string(7, '**ACE**'): return ace if string(0, 'PK\003\004'): return zip if string(0, 'PK00'): return zip if string(0, '\xed\xab\xee\xdb'): return rpm # Compressed streams if string(0, '\037\213'): if self.path.endswith('.tar.gz') or self.path.endswith('.tgz'): return tgz return gz if string(0, 'BZh') or string(0, 'BZ'): if self.path.endswith('.tar.bz') or self.path.endswith('.tar.bz2') or \ self.path.endswith('.tbz') or self.path.endswith('.tbz2'): return tbz return bz2 if string(0, 'begin '): return uue if string(0, '\037\235'): if self.path.endswith('.tar.Z'): return tarz return z return make_gz class DirData: mode = None def __init__(self, path): self.path = path self.default = make_tgz self.default_name = path + '.' + self.default.extension