# # Copyright (C) 2008 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import bz2 import stat import tarfile import zlib import StringIO from import_ext import ImportExternal from error import ImportError class ImportTar(ImportExternal): """Streams a (optionally compressed) tar file from the network directly into a Project's Git repository. """ @classmethod def CanAccept(cls, url): """Can this importer read and unpack the data stored at url? """ if url.endswith('.tar.gz') or url.endswith('.tgz'): return True if url.endswith('.tar.bz2'): return True if url.endswith('.tar'): return True return False def _UnpackFiles(self): url_fd, url = self._OpenUrl() try: if url.endswith('.tar.gz') or url.endswith('.tgz'): tar_fd = _Gzip(url_fd) elif url.endswith('.tar.bz2'): tar_fd = _Bzip2(url_fd) elif url.endswith('.tar'): tar_fd = _Raw(url_fd) else: raise ImportError('non-tar file extension: %s' % url) try: tar = tarfile.TarFile(name = url, mode = 'r', fileobj = tar_fd) try: for entry in tar: mode = entry.mode if (mode & 0170000) == 0: if entry.isdir(): mode |= stat.S_IFDIR elif entry.isfile() or entry.islnk(): # hard links as files mode |= stat.S_IFREG elif entry.issym(): mode |= stat.S_IFLNK if stat.S_ISLNK(mode): # symlink data_fd = StringIO.StringIO(entry.linkname) data_sz = len(entry.linkname) elif stat.S_ISDIR(mode): # directory data_fd = StringIO.StringIO('') data_sz = 0 else: data_fd = tar.extractfile(entry) data_sz = entry.size self._UnpackOneFile(mode, data_sz, entry.name, data_fd) finally: tar.close() finally: tar_fd.close() finally: url_fd.close() class _DecompressStream(object): """file like object to decompress a tar stream """ def __init__(self, fd): self._fd = fd self._pos = 0 self._buf = None def tell(self): return self._pos def seek(self, offset): d = offset - self._pos if d > 0: self.read(d) elif d == 0: pass else: raise NotImplementedError, 'seek backwards' def close(self): self._fd = None def read(self, size = -1): if not self._fd: raise EOFError, 'Reached EOF' r = [] try: if size >= 0: self._ReadChunk(r, size) else: while True: self._ReadChunk(r, 2048) except EOFError: pass if len(r) == 1: r = r[0] else: r = ''.join(r) self._pos += len(r) return r def _ReadChunk(self, r, size): b = self._buf try: while size > 0: if b is None or len(b) == 0: b = self._Decompress(self._fd.read(2048)) continue use = min(size, len(b)) r.append(b[:use]) b = b[use:] size -= use finally: self._buf = b def _Decompress(self, b): raise NotImplementedError, '_Decompress' class _Raw(_DecompressStream): """file like object for an uncompressed stream """ def __init__(self, fd): _DecompressStream.__init__(self, fd) def _Decompress(self, b): return b class _Bzip2(_DecompressStream): """file like object to decompress a .bz2 stream """ def __init__(self, fd): _DecompressStream.__init__(self, fd) self._bz = bz2.BZ2Decompressor() def _Decompress(self, b): return self._bz.decompress(b) _FHCRC, _FEXTRA, _FNAME, _FCOMMENT = 2, 4, 8, 16 class _Gzip(_DecompressStream): """file like object to decompress a .gz stream """ def __init__(self, fd): _DecompressStream.__init__(self, fd) self._z = zlib.decompressobj(-zlib.MAX_WBITS) magic = fd.read(2) if magic != '\037\213': raise IOError, 'Not a gzipped file' method = ord(fd.read(1)) if method != 8: raise IOError, 'Unknown compression method' flag = ord(fd.read(1)) fd.read(6) if flag & _FEXTRA: xlen = ord(fd.read(1)) xlen += 256 * ord(fd.read(1)) fd.read(xlen) if flag & _FNAME: while fd.read(1) != '\0': pass if flag & _FCOMMENT: while fd.read(1) != '\0': pass if flag & _FHCRC: fd.read(2) def _Decompress(self, b): return self._z.decompress(b)