New files for Gtk+-2.0 version.
[rox-archive.git] / formats.py
blob9ad077423789a1c314d7f7663969e5b9574adf30
1 import os
2 from support import pipe_through_command
4 operations = []
5 class Operation:
6 add_extension = 0
8 def __init__(self, extension):
9 operations.append(self)
10 self.extension = extension
12 def can_handle(self, data):
13 return isinstance(data, FileData)
15 def save_to_stream(self, data, stream):
16 pipe_through_command(self.command, data.source, stream)
18 class Compress(Operation):
19 "Compress a stream into another stream."
20 add_extension = 1
22 def __init__(self, extension, command, type):
23 Operation.__init__(self, extension)
24 self.command = command
25 self.type = type
27 def __str__(self):
28 return 'Compress as .%s' % self.extension
30 class Decompress(Operation):
31 "Decompress a stream into another stream."
32 type = 'text/plain'
34 def __init__(self, extension, command):
35 Operation.__init__(self, extension)
36 self.command = command
38 def __str__(self):
39 return 'Decompress .%s' % self.extension
41 class Extract(Operation):
42 "Extract an archive to a directory."
43 type = 'inode/directory'
45 def __init__(self, extension, command):
46 Operation.__init__(self, extension)
47 self.command = command
49 def __str__(self):
50 return 'Extract from a .%s' % self.extension
52 def save_to_stream(self, data, stream):
53 raise Exception('This operation creates a directory, so you have '
54 'to drag to a filer window on the local machine')
56 def save_to_file(self, data, path):
57 if os.path.exists(path):
58 if not os.path.isdir(path):
59 raise Exception("'%s' already exists and is not a directory!" %
60 path)
61 if not rox.confirm('Directory already exists; extract into it?',
62 g.STOCK_YES):
63 raise Exception('Extraction aborted... try somewhere else')
64 if not os.path.exists(path):
65 os.mkdir(path)
66 os.chdir(path)
67 pipe_through_command(self.command, data.source, None)
69 class ExtractPath(Extract):
70 "Extract an archive (which must be a file) to a directory."
71 def save_to_file(self, data, path):
72 raise Exception('Not implemented yet')
74 class Archive(Operation):
75 "Create an archive from a directory."
76 add_extension = 1
78 def __init__(self, extension, command, type):
79 Operation.__init__(self, extension)
80 self.command = command
81 self.type = type
83 def __str__(self):
84 return 'Create .%s archive' % self.extension
86 def can_handle(self, data):
87 return isinstance(data, DirData)
89 tgz = Extract('tgz', "gunzip -c - | tar xf -")
90 tbz = Extract('tar.bz2', "bunzip2 -c - | tar xf -")
91 zip = Extract('zip', "unzip -")
92 jar = Extract('jar', "unzip -")
93 rar = Extract('rar', "rar x -")
94 tar = Extract('tar', "tar xf -")
95 rpm = Extract('rpm', "rpm2cpio - | cpio -id --quiet")
96 cpio = Extract('cpio', "cpio -id --quiet")
97 deb = ExtractPath('deb', "ar x '%s'")
99 make_tgz = Archive('tgz', "tar cf - '%s' | gzip", 'application/x-compressed-tar')
100 Archive('tar.gz', "tar cf - '%s' | gzip", 'application/x-compressed-tar')
101 Archive('tar.bz', "tar cf - '%s' | bzip2", 'application/x-bzip-compressed-tar')
102 Archive('tar.bz2', "tar cf - '%s' | bzip2", 'application/x-bzip-compressed-tar')
103 Archive('zip', "zip -r - '%s'", 'application/zip'),
104 Archive('jar', "zip -r - '%s'", 'application/x-jar')
105 Archive('rar', "rar a - '%s'", 'application/x-rar'),
106 Archive('tar', "tar cf - '%s'", 'application/x-tar')
108 # Note: these go afterwards so that .tar.gz matches before .gz
109 make_gz = Compress('gz', "gzip -c -", 'application/x-gzip')
110 Compress('bz2', "bzip2 -c -", 'application/x-bzip')
112 gz = Decompress('gz', "gunzip -c -")
113 bz2 = Decompress('bz2', "bunzip2 -ck -")
116 # Can bzip2 read bzip files?
118 aliases = {
119 'tar.gz': 'tgz',
120 'tar.bz': 'tar.bz2',
121 'bz': 'bz2'
124 known_extensions = {}
125 for x in operations:
126 try:
127 known_extensions[x.extension] = None
128 except AttributeError:
129 pass
131 def guess_format(data):
132 "Return a good default Operation, judging by the first 300 bytes or so."
133 l = len(data)
134 def string(offset, match):
135 return data[offset:offset + len(match)] == match
136 def short(offset, match):
137 if l > offset + 1:
138 a = data[offset]
139 b = data[offset + 1]
140 return ((a == match & 0xff) and (b == (match >> 8))) or \
141 (b == match & 0xff) and (a == (match >> 8))
142 return 0
144 # Archives
145 if string(257, 'ustar\0') or string(257, 'ustar\040\040\0'):
146 return tar
147 if short(0, 070707) or short(0, 0143561) or string(0, '070707') or \
148 string(0, '070701') or string(0, '070702'):
149 return cpio
150 if string(0, '!<arch>') or string(0, '\\<ar>') or string(0, '<ar>'):
151 if string(7, '\ndebian'):
152 return deb
153 if string(0, 'Rar!'): return rar
154 if string(0, 'PK\003\004'): return zip
156 # Compressed streams
157 if string(0, '\037\213'): return gz
158 if string(0, 'BZh'): return bz2
159 if string(0, 'BZ'): return bz2 # bzip, but maybe bzip2 can cope?
161 return make_gz
163 class FileData:
164 "A file on the local filesystem."
165 def __init__(self, path):
166 self.path = path
168 if path == '-':
169 source = sys.stdin
170 else:
171 try:
172 source = file(path)
173 except:
174 rox.report_exception()
175 sys.exit(1)
177 self.path = path
178 start = source.read(300)
179 try:
180 source.seek(0)
181 self.source = source
182 except:
183 print "(unseekable)"
184 # Input is not a regular, local, seekable file, so copy it
185 # to a local temp file.
186 import shutil
187 tmp = Tmp()
188 tmp.write(start)
189 shutil.copyfileobj(source, tmp)
190 self.source = tmp
191 self.default = guess_format(start)
193 if path == '-':
194 name = 'Data'
195 else:
196 name = path
197 for ext in known_extensions:
198 if path.endswith('.' + ext):
199 new = path[:-len(ext)-1]
200 if len(new) < len(name):
201 name = new
202 self.default_name = name
204 class DirData:
205 def __init__(self, path):
206 self.path = path
207 self.default = make_tgz
208 self.default_name = path + self.default.extension