Update Chinese Translations (Babyfai Cheung)
[rox-archive.git] / formats.py
blob4ba7dac78b4132f80a9c6f0fdbbab9f1e08c7a3b
1 if __name__ == '__main__':
2 import findrox; findrox.version(1, 99, 11)
3 import os, sys
4 from support import shell_escape, Tmp
5 import rox
6 from rox.processes import PipeThroughCommand
8 current_command = None
10 def pipe_through_command(command, src, dst):
11 global current_command
12 assert not current_command
13 try:
14 src.seek(0)
15 except:
16 pass
17 current_command = PipeThroughCommand(command, src, dst)
18 try:
19 current_command.wait()
20 finally:
21 current_command = None
23 operations = []
24 class Operation:
25 add_extension = False
27 def __init__(self, extension):
28 operations.append(self)
29 self.extension = extension
31 def can_handle(self, data):
32 return isinstance(data, FileData)
34 def save_to_stream(self, data, stream):
35 pipe_through_command(self.command, data.source, stream)
37 class Compress(Operation):
38 "Compress a stream into another stream."
39 add_extension = True
41 def __init__(self, extension, command, type):
42 Operation.__init__(self, extension)
43 self.command = command
44 self.type = type
46 def __str__(self):
47 return _('Compress as .%s') % self.extension
49 class Decompress(Operation):
50 "Decompress a stream into another stream."
51 type = 'text/plain'
53 def __init__(self, extension, command):
54 Operation.__init__(self, extension)
55 self.command = command
57 def __str__(self):
58 return _('Decompress .%s') % self.extension
60 class Extract(Operation):
61 "Extract an archive to a directory."
62 type = 'inode/directory'
64 def __init__(self, extension, command):
65 "If command has a %s then the source path is inserted, else uses stdin."
66 Operation.__init__(self, extension)
67 self.command = command
69 def __str__(self):
70 return _('Extract from a .%s') % self.extension
72 def save_to_stream(self, data, stream):
73 raise Exception(_('This operation creates a directory, so you have '
74 'to drag to a filer window on the local machine'))
76 def save_to_file(self, data, path):
77 if os.path.exists(path):
78 if not os.path.isdir(path):
79 raise Exception(_("'%s' already exists and is not a directory!") %
80 path)
81 if not os.path.exists(path):
82 os.mkdir(path)
83 os.chdir(path)
84 command = self.command
85 source = data.source
86 if command.find("'%s'") != -1:
87 command = command % shell_escape(source.name)
88 source = None
89 try:
90 pipe_through_command(command, source, None)
91 finally:
92 try:
93 os.rmdir(path) # Will only succeed if it's empty
94 except:
95 pass
96 if os.path.exists(path):
97 self.pull_up(path)
99 def pull_up(self, path):
100 # If we created only a single subdirectory, move it up.
101 dirs = os.listdir(path)
102 if len(dirs) != 1:
103 return
104 dir = dirs[0]
105 unneeded_path = os.path.join(path, dir)
106 if not os.path.isdir(unneeded_path):
107 return
108 import random
109 tmp_path = os.path.join(path, 'tmp-' + `random.randint(0, 100000)`)
110 os.rename(unneeded_path, tmp_path)
111 for file in os.listdir(tmp_path):
112 os.rename(os.path.join(tmp_path, file), os.path.join(path, file))
113 os.rmdir(tmp_path)
115 class Archive(Operation):
116 "Create an archive from a directory."
117 add_extension = True
119 def __init__(self, extension, command, type):
120 assert command.find("'%s'") != -1
122 Operation.__init__(self, extension)
123 self.command = command
124 self.type = type
126 def __str__(self):
127 return _('Create .%s archive') % self.extension
129 def can_handle(self, data):
130 return isinstance(data, DirData)
132 def save_to_stream(self, data, stream):
133 os.chdir(os.path.dirname(data.path))
134 command = self.command % shell_escape(os.path.basename(data.path))
135 pipe_through_command(command, None, stream)
137 tgz = Extract('tgz', "gunzip -c - | tar xf -")
138 tbz = Extract('tar.bz2', "bunzip2 -c - | tar xf -")
139 tarz = Extract('tar.Z', "uncompress -c - | tar xf -")
140 rar = Extract('rar', "rar x '%s'")
141 ace = Extract('ace', "unace x '%s'")
142 tar = Extract('tar', "tar xf -")
143 rpm = Extract('rpm', "rpm2cpio - | cpio -id --quiet")
144 cpio = Extract('cpio', "cpio -id --quiet")
145 deb = Extract('deb', "ar x '%s'")
146 zip = Extract('zip', "unzip -q '%s'")
147 jar = Extract('jar', "unzip -q '%s'")
149 make_tgz = Archive('tgz', "tar cf - '%s' | gzip", 'application/x-compressed-tar')
150 Archive('tar.gz', "tar cf - '%s' | gzip", 'application/x-compressed-tar')
151 Archive('tar.bz2', "tar cf - '%s' | bzip2", 'application/x-bzip-compressed-tar')
152 Archive('zip', "zip -qr - '%s'", 'application/zip'),
153 Archive('jar', "zip -qr - '%s'", 'application/x-jar')
154 Archive('tar', "tar cf - '%s'", 'application/x-tar')
156 # Note: these go afterwards so that .tar.gz matches before .gz
157 make_gz = Compress('gz', "gzip -c -", 'application/x-gzip')
158 Compress('bz2', "bzip2 -c -", 'application/x-bzip')
159 Compress('uue', "uuencode /dev/stdout", 'application/x-uuencoded')
161 gz = Decompress('gz', "gunzip -c -")
162 bz2 = Decompress('bz2', "bunzip2 -ck -")
163 uue = Decompress('uue', "uudecode -o /dev/stdout")
164 z = Decompress('Z', "uncompress -c -")
167 # Can bzip2 read bzip files?
169 aliases = {
170 'tar.gz': 'tgz',
171 'tar.bz': 'tar.bz2',
172 'tbz': 'tar.bz2',
173 'bz': 'bz2'
176 known_extensions = {}
177 for x in operations:
178 try:
179 known_extensions[x.extension] = None
180 except AttributeError:
181 pass
183 class FileData:
184 "A file on the local filesystem."
185 mode = None
186 def __init__(self, path):
187 self.path = path
189 if path == '-':
190 source = sys.stdin
191 else:
192 try:
193 source = file(path)
194 self.mode = os.stat(path).st_mode
195 except:
196 rox.report_exception()
197 sys.exit(1)
199 self.path = path
200 start = source.read(300)
201 try:
202 if source is sys.stdin:
203 raise Exception("Always copy stdin!")
204 source.seek(0)
205 self.source = source
206 except:
207 # Input is not a regular, local, seekable file, so copy it
208 # to a local temp file.
209 import shutil
210 tmp = Tmp()
211 tmp.write(start)
212 tmp.flush()
213 shutil.copyfileobj(source, tmp)
214 tmp.seek(0)
215 tmp.flush()
216 self.source = tmp
217 self.default = self.guess_format(start)
219 if path == '-':
220 name = 'Data'
221 else:
222 name = path
223 for ext in known_extensions:
224 if path.endswith('.' + ext):
225 new = path[:-len(ext)-1]
226 if len(new) < len(name):
227 name = new
228 if self.default.add_extension:
229 name += '.' + self.default.extension
231 if name == path:
232 # Default name is same as input. Change it somehow...
233 if '.' in os.path.basename(name):
234 name = name[:name.rindex('.')]
235 else:
236 name += '.unpacked'
238 self.default_name = name
240 def guess_format(self, data):
241 "Return a good default Operation, judging by the first 300 bytes or so."
242 l = len(data)
243 def string(offset, match):
244 return data[offset:offset + len(match)] == match
245 def short(offset, match):
246 if l > offset + 1:
247 a = data[offset]
248 b = data[offset + 1]
249 return ((a == match & 0xff) and (b == (match >> 8))) or \
250 (b == match & 0xff) and (a == (match >> 8))
251 return 0
253 # Archives
254 if string(257, 'ustar\0') or string(257, 'ustar\040\040\0'):
255 return tar
256 if short(0, 070707) or short(0, 0143561) or string(0, '070707') or \
257 string(0, '070701') or string(0, '070702'):
258 return cpio
259 if string(0, '!<arch>') or string(0, '\\<ar>') or string(0, '<ar>'):
260 if string(7, '\ndebian'):
261 return deb
262 if string(0, 'Rar!'): return rar
263 if string(7, '**ACE**'): return ace
264 if string(0, 'PK\003\004'): return zip
265 if string(0, 'PK00'): return zip
266 if string(0, '\xed\xab\xee\xdb'): return rpm
268 # Compressed streams
269 if string(0, '\037\213'):
270 if self.path.endswith('.tar.gz') or self.path.endswith('.tgz'):
271 return tgz
272 return gz
273 if string(0, 'BZh') or string(0, 'BZ'):
274 if self.path.endswith('.tar.bz') or self.path.endswith('.tar.bz2') or \
275 self.path.endswith('.tbz') or self.path.endswith('.tbz2'):
276 return tbz
277 return bz2
278 if string(0, 'begin '):
279 return uue
280 if string(0, '\037\235'):
281 if self.path.endswith('.tar.Z'):
282 return tarz
283 return z
285 return make_gz
287 class DirData:
288 mode = None
289 def __init__(self, path):
290 self.path = path
291 self.default = make_tgz
292 self.default_name = path + '.' + self.default.extension