| # |
| # 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) |