blob: d7ce14de625e2d095fb7f1a8a958af4cd6b2fdfd [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import bz2
17import stat
18import tarfile
19import zlib
20import StringIO
21
22from import_ext import ImportExternal
23from error import ImportError
24
25class ImportTar(ImportExternal):
26 """Streams a (optionally compressed) tar file from the network
27 directly into a Project's Git repository.
28 """
29 @classmethod
30 def CanAccept(cls, url):
31 """Can this importer read and unpack the data stored at url?
32 """
33 if url.endswith('.tar.gz') or url.endswith('.tgz'):
34 return True
35 if url.endswith('.tar.bz2'):
36 return True
37 if url.endswith('.tar'):
38 return True
39 return False
40
41 def _UnpackFiles(self):
42 url_fd, url = self._OpenUrl()
43 try:
44 if url.endswith('.tar.gz') or url.endswith('.tgz'):
45 tar_fd = _Gzip(url_fd)
46 elif url.endswith('.tar.bz2'):
47 tar_fd = _Bzip2(url_fd)
48 elif url.endswith('.tar'):
49 tar_fd = _Raw(url_fd)
50 else:
51 raise ImportError('non-tar file extension: %s' % url)
52
53 try:
54 tar = tarfile.TarFile(name = url,
55 mode = 'r',
56 fileobj = tar_fd)
57 try:
58 for entry in tar:
59 mode = entry.mode
60
61 if (mode & 0170000) == 0:
62 if entry.isdir():
63 mode |= stat.S_IFDIR
64 elif entry.isfile() or entry.islnk(): # hard links as files
65 mode |= stat.S_IFREG
66 elif entry.issym():
67 mode |= stat.S_IFLNK
68
69 if stat.S_ISLNK(mode): # symlink
70 data_fd = StringIO.StringIO(entry.linkname)
71 data_sz = len(entry.linkname)
72 elif stat.S_ISDIR(mode): # directory
73 data_fd = StringIO.StringIO('')
74 data_sz = 0
75 else:
76 data_fd = tar.extractfile(entry)
77 data_sz = entry.size
78
79 self._UnpackOneFile(mode, data_sz, entry.name, data_fd)
80 finally:
81 tar.close()
82 finally:
83 tar_fd.close()
84 finally:
85 url_fd.close()
86
87
88
89class _DecompressStream(object):
90 """file like object to decompress a tar stream
91 """
92 def __init__(self, fd):
93 self._fd = fd
94 self._pos = 0
95 self._buf = None
96
97 def tell(self):
98 return self._pos
99
100 def seek(self, offset):
101 d = offset - self._pos
102 if d > 0:
103 self.read(d)
104 elif d == 0:
105 pass
106 else:
107 raise NotImplementedError, 'seek backwards'
108
109 def close(self):
110 self._fd = None
111
112 def read(self, size = -1):
113 if not self._fd:
114 raise EOFError, 'Reached EOF'
115
116 r = []
117 try:
118 if size >= 0:
119 self._ReadChunk(r, size)
120 else:
121 while True:
122 self._ReadChunk(r, 2048)
123 except EOFError:
124 pass
125
126 if len(r) == 1:
127 r = r[0]
128 else:
129 r = ''.join(r)
130 self._pos += len(r)
131 return r
132
133 def _ReadChunk(self, r, size):
134 b = self._buf
135 try:
136 while size > 0:
137 if b is None or len(b) == 0:
138 b = self._Decompress(self._fd.read(2048))
139 continue
140
141 use = min(size, len(b))
142 r.append(b[:use])
143 b = b[use:]
144 size -= use
145 finally:
146 self._buf = b
147
148 def _Decompress(self, b):
149 raise NotImplementedError, '_Decompress'
150
151
152class _Raw(_DecompressStream):
153 """file like object for an uncompressed stream
154 """
155 def __init__(self, fd):
156 _DecompressStream.__init__(self, fd)
157
158 def _Decompress(self, b):
159 return b
160
161
162class _Bzip2(_DecompressStream):
163 """file like object to decompress a .bz2 stream
164 """
165 def __init__(self, fd):
166 _DecompressStream.__init__(self, fd)
167 self._bz = bz2.BZ2Decompressor()
168
169 def _Decompress(self, b):
170 return self._bz.decompress(b)
171
172
173_FHCRC, _FEXTRA, _FNAME, _FCOMMENT = 2, 4, 8, 16
174class _Gzip(_DecompressStream):
175 """file like object to decompress a .gz stream
176 """
177 def __init__(self, fd):
178 _DecompressStream.__init__(self, fd)
179 self._z = zlib.decompressobj(-zlib.MAX_WBITS)
180
181 magic = fd.read(2)
182 if magic != '\037\213':
183 raise IOError, 'Not a gzipped file'
184
185 method = ord(fd.read(1))
186 if method != 8:
187 raise IOError, 'Unknown compression method'
188
189 flag = ord(fd.read(1))
190 fd.read(6)
191
192 if flag & _FEXTRA:
193 xlen = ord(fd.read(1))
194 xlen += 256 * ord(fd.read(1))
195 fd.read(xlen)
196 if flag & _FNAME:
197 while fd.read(1) != '\0':
198 pass
199 if flag & _FCOMMENT:
200 while fd.read(1) != '\0':
201 pass
202 if flag & _FHCRC:
203 fd.read(2)
204
205 def _Decompress(self, b):
206 return self._z.decompress(b)