diff --git a/pisi/archive.py b/pisi/archive.py index 089abcf9..17e16c35 100644 --- a/pisi/archive.py +++ b/pisi/archive.py @@ -30,11 +30,105 @@ import pisi import pisi.util as util import pisi.context as ctx + class UnknownArchiveType(Exception): pass -class LzmaRuntimeError(Exception): - pass + +# Proxy class inspired from tarfile._BZ2Proxy +class _LZMAProxy(object): + + blocksize = 16 * 1024 + + def __init__(self, fileobj, mode): + self.fileobj = fileobj + self.mode = mode + self.name = getattr(self.fileobj, "name", None) + self.init() + + def init(self): + import lzma + self.pos = 0 + if self.mode == "r": + self.lzmaobj = lzma.LZMADecompressor() + if hasattr(self.fileobj, "seek"): + self.fileobj.seek(0) + self.buf = "" + else: + self.lzmaobj = lzma.LZMACompressor() + + def read(self, size): + b = [self.buf] + x = len(self.buf) + while x < size: + raw = self.fileobj.read(self.blocksize) + if not raw: + break + try: + data = self.lzmaobj.decompress(raw) + except EOFError: + break + b.append(data) + x += len(data) + self.buf = "".join(b) + + buf = self.buf[:size] + self.buf = self.buf[size:] + self.pos += len(buf) + return buf + + def seek(self, pos): + if pos < self.pos: + self.init() + self.read(pos - self.pos) + + def tell(self): + return self.pos + + def write(self, data): + self.pos += len(data) + raw = self.lzmaobj.compress(data) + self.fileobj.write(raw) + + def close(self): + if self.mode == "w": + raw = self.lzmaobj.flush() + self.fileobj.write(raw) + + +class TarFile(tarfile.TarFile): + + @classmethod + def lzmaopen(cls, name=None, mode="r", fileobj=None, + compresslevel=9, **kwargs): + """Open lzma compressed tar archive name for reading or writing. + Appending is not allowed. + """ + if len(mode) > 1 or mode not in "rw": + raise ValueError("mode must be 'r' or 'w'.") + + try: + import lzma + except ImportError: + raise tarfile.CompressionError("lzma module is not available") + + if fileobj is not None: + fileobj = _LZMAProxy(fileobj, mode) + else: + options = { + "format": "alone", + "level": compresslevel, + #"extreme": compresslevel == 9 + } + fileobj = lzma.LZMAFile(name, mode, options=options) + + try: + t = cls.taropen(name, mode, fileobj, **kwargs) + except IOError: + raise ReadError("not a lzma file") + t._extfileobj = False + return t + class ArchiveBase(object): """Base class for Archive classes.""" @@ -67,6 +161,7 @@ class ArchiveBinary(ArchiveBase): target_file = os.path.join(target_dir, os.path.basename(self.file_path)) shutil.copyfile(self.file_path, target_file) + class ArchiveBzip2(ArchiveBase): """ArchiveBzip2 handles Bzip2 archive files""" def __init__(self, file_path, arch_type = "bz2"): @@ -89,6 +184,7 @@ class ArchiveBzip2(ArchiveBase): os.chdir(oldwd) + class ArchiveGzip(ArchiveBase): """ArchiveGzip handles Gzip archive files""" def __init__(self, file_path, arch_type = "gz"): @@ -111,6 +207,7 @@ class ArchiveGzip(ArchiveBase): os.chdir(oldwd) + class ArchiveTar(ArchiveBase): """ArchiveTar handles tar archives depending on the compression type. Provides access to tar, tar.gz and tar.bz2 files. @@ -129,6 +226,7 @@ class ArchiveTar(ArchiveBase): def unpack_dir(self, target_dir): rmode = "" + self.tar = None if self.type == 'tar': rmode = 'r:' elif self.type == 'targz': @@ -136,11 +234,7 @@ class ArchiveTar(ArchiveBase): elif self.type == 'tarbz2': rmode = 'r:bz2' elif self.type == 'tarlzma': - rmode = 'r:' - self.file_path = self.file_path.rstrip(ctx.const.lzma_suffix) - ret, out, err = util.run_batch("lzma -k -f -d %s%s" % (self.file_path,ctx.const.lzma_suffix)) - if ret != 0: - raise LzmaRuntimeError(err) + self.tar = TarFile.lzmaopen(self.file_path) elif self.type == 'tarZ': rmode = 'r:' self.file_path = self.file_path.rstrip('.Z') @@ -150,7 +244,9 @@ class ArchiveTar(ArchiveBase): else: raise UnknownArchiveType - self.tar = tarfile.open(self.file_path, rmode) + if self.tar is None: + self.tar = tarfile.open(self.file_path, rmode) + oldwd = None try: # Don't fail if CWD doesn't exist (#6748) @@ -173,7 +269,7 @@ class ArchiveTar(ArchiveBase): # # Also, tar.extract() doesn't write on symlinks... Not any # more :). - if self.file_path == install_tar_path: + if self.file_path.startswith(install_tar_path): if os.path.isfile(tarinfo.name) or os.path.islink(tarinfo.name): try: os.unlink(tarinfo.name) @@ -202,7 +298,7 @@ class ArchiveTar(ArchiveBase): ctx.ui.notify(pisi.ui.desktopfile, desktopfile=tarinfo.name) # Bug #10680 and addition for tarZ files - if self.type == 'tarlzma' or self.type == 'tarZ': + if self.type == 'tarZ': os.unlink(self.file_path) try: @@ -223,31 +319,23 @@ class ArchiveTar(ArchiveBase): elif self.type == 'tarbz2': wmode = 'w:bz2' elif self.type == 'tarlzma': - wmode = 'w:' - self.file_path = self.file_path.rstrip(ctx.const.lzma_suffix) + level = int(ctx.config.values.build.compressionlevel) + self.tar = TarFile.lzmaopen(self.file_path, "w", + compresslevel=level) elif self.type == 'tarZ': wmode = 'w:' self.file_path = self.file_path.rstrip(".Z") else: raise UnknownArchiveType - self.tar = tarfile.open(self.file_path, wmode) + + if self.tar is None: + self.tar = tarfile.open(self.file_path, wmode) self.tar.add(file_name, arc_name) def close(self): self.tar.close() - if 'w' in str(self.tar.mode) and self.type == 'tarlzma': - batch = None - if ctx.config.values.build.compressionlevel: - batch = "lzma -%s -z %s" % (ctx.config.values.build.compressionlevel, self.file_path) - else: - batch = "lzma -z %s" % self.file_path - - ret, out, err = util.run_batch(batch) - if ret != 0: - raise LzmaRuntimeError(err) - class ArchiveZip(ArchiveBase): """ArchiveZip handles zip archives.