Typo.
[rox-archive.git] / AppRun
blob50ae7f2b4b36d0ad42d01a150d2c616316f6e16d
1 #!/usr/bin/env python
3 import findrox
4 import sys, os, formats
6 import rox
7 from rox import g, TRUE, FALSE, saving
9 if len(sys.argv) != 2:
10 rox.info("Drag a file or directory onto Archive to archive it. "
11 "Drag an archive onto it to extract.")
12 sys.exit(0)
14 def escape(text):
15 """Return text with \ and ' escaped"""
16 return text.replace("\\", "\\\\").replace("'", "\\'")
18 assert escape(''' a test ''') == ' a test '
19 assert escape(''' "a's test" ''') == ''' "a\\'s test" '''
20 assert escape(''' "a\\'s test" ''') == ''' "a\\\\\\'s test" '''
22 def Tmp():
23 # Protect against DoS attacks
24 import random
25 name = `random.randint(1, 1000000)` + '-archive'
27 import tempfile
28 return tempfile.TemporaryFile(suffix = name)
30 def pipe_through_command(command, src, dst):
31 """Execute 'command' with src as stdin (if any) and writing to
32 stream dst. src must be a fileno() stream."""
34 if dst:
35 if hasattr(dst, 'fileno'):
36 tmp_stream = dst
37 else:
38 tmp_stream = Tmp()
39 fd = tmp_stream.fileno()
40 else:
41 fd = -1
43 import os
44 child = os.fork()
45 if child == 0:
46 try:
47 if src:
48 os.dup2(src.fileno(), 0)
49 if fd != -1:
50 os.dup2(fd, 1)
51 os.system(command)
52 finally:
53 os._exit(0)
54 assert 0
55 pid, status = os.waitpid(child, 0)
56 if status != 0:
57 rox.alert('Command returned an error!')
58 return 0
59 if dst and tmp_stream is not dst:
60 print "(loading tmp)"
61 tmp_stream.seek(0)
62 data = tmp_stream.read()
63 print len(data)
64 tmp_stream.seek(0)
65 dst.write(tmp_stream.read())
66 return 1
68 # Show the savebox, so at least the user knows something is happening..
69 class FileData(saving.Saveable):
70 "A file on the local filesystem."
71 def __init__(self, path, stream):
72 self.path = path
73 self.start = stream.read(300)
74 try:
75 stream.seek(0)
76 self.stream = stream
77 except:
78 print "(unseekable)"
79 # Input is not a regular, local, seekable file, so copy it
80 # to a local temp file.
81 import shutil
82 tmp = Tmp()
83 tmp.write(self.start)
84 shutil.copyfileobj(stream, tmp)
85 self.stream = tmp
87 create_arc = ()
89 create_stream = (
90 ('gz', "gzip -c -", 'application/x-gzip'),
91 ('bz2', "bzip2 -c -", 'application/x-bzip')
94 extract_arc = (
95 # Archives. Executed from new directory.
96 ('tgz', "gunzip -c - | tar xf -", 'inode/directory'),
97 ('tar.gz', "gunzip -c - | tar xf -", 'inode/directory'),
98 ('tar.bz', "bunzip2 -c - | tar xf -", 'inode/directory'),
99 ('tar.bz2', "bunzip2 -c - | tar xf -", 'inode/directory'),
100 ('zip', "unzip -", None),
101 ('rar', "rar x -", None),
102 ('tar', "tar xf -", None)
105 extract_stream = (
106 # Compressed streams
107 ('gz', "gunzip -c -", None),
108 ('bz', "bunzip2 -ck -", None),
109 ('bz2', "bunzip2 -ck -", None)
112 def need_dir(self, command):
113 return command in [c for e,c,t in self.extract_arc]
115 def save_to_file(self, file):
116 command = self.op
117 if self.need_dir(command):
118 os.mkdir(file)
119 os.chdir(file)
120 return pipe_through_command(command, self.stream, None)
121 else:
122 return Saveable.save_to_file(self, file)
124 def save_to_stream(self, stream):
125 command = self.op
126 if self.need_dir(command):
127 raise Exception('Sorry, archives can only be extracted into '
128 'a local directory.')
129 self.stream.seek(0)
130 pipe_through_command(command, self.stream, stream)
132 def set_op(self, op):
133 self.op = op
135 class DirData(saving.Saveable):
136 def __init__(self, source):
137 self.path = source
138 self.start = None
140 create_arc = (
141 ('tgz', "tar cf - '%s' | gzip", 'application/x-compressed-tar'),
142 ('tar.gz', "tar cf - '%s' | gzip", 'application/x-compressed-tar'),
143 ('tar.bz', "tar cf - '%s' | bzip2", 'application/x-bzip-compressed-tar'),
144 ('tar.bz2', "tar cf - '%s' | bzip2", 'application/x-bzip-compressed-tar'),
145 ('zip', "zip -r - '%s'", 'application/zip'),
146 ('rar', "rar a - '%s'", 'application/x-rar'),
147 ('tar', "tar cf - '%s'", 'application/x-tar')
150 create_stream = ()
151 extract_arc = ()
152 extract_stream = ()
154 def save_to_stream(self, stream):
155 command = self.op % escape(self.path)
156 pipe_through_command(command, None, stream)
158 def set_op(self, op):
159 self.op = op
161 class ArchiveBox(saving.SaveBox):
162 def build_main_area(self):
163 self.vbox.add(self.save_area)
165 self.operation = g.OptionMenu()
166 self.vbox.pack_start(self.operation, FALSE, TRUE, 0)
167 self.operation.show()
168 self.operation.set_border_width(5)
169 self.updating = 0
171 self.ops_menu = g.Menu()
172 self.operation.set_menu(self.ops_menu)
174 def add_ops(self):
175 doc = self.save_area.document
176 self.i_to_op = []
177 for op, command, type in doc.create_arc:
178 item = g.MenuItem('Create .%s archive' % op)
179 item.show()
180 self.ops_menu.append(item)
181 self.i_to_op.append((op, command, type))
182 for op, command, type in doc.create_stream:
183 item = g.MenuItem('Compress as .%s' % op)
184 item.show()
185 self.ops_menu.append(item)
186 self.i_to_op.append((op, command, type))
188 if doc.start:
189 guess = formats.guess_format(doc.start)
190 else:
191 guess = None
193 if guess in ['gz', 'bz2', 'bz']:
194 if doc.path.endswith('.tar.' + guess):
195 guess = 'tar.' + guess
197 init = 0
198 for op, command, type in doc.extract_arc:
199 item = g.MenuItem('Extract from a .%s' % op)
200 item.show()
201 self.ops_menu.append(item)
202 if op == guess:
203 init = len(self.i_to_op)
204 self.i_to_op.append((op, command, type))
205 for op, command, type in doc.extract_stream:
206 item = g.MenuItem('Uncompress .%s' % op)
207 item.show()
208 self.ops_menu.append(item)
209 if op == guess:
210 init = len(self.i_to_op)
211 self.i_to_op.append((op, command, type))
213 name = doc.path
214 if guess and name.endswith('.' + guess):
215 name = name[:-len(guess)-1]
216 self.save_area.entry.set_text(name)
218 self.operation.connect('changed', self.op_changed)
219 self.save_area.entry.connect('changed', self.name_changed)
220 self.operation.set_history(init)
222 def name_changed(self, entry):
223 if self.updating:
224 return
225 self.updating = 1
227 name = entry.get_text()
228 doc = self.save_area.document
229 i = 0
230 for o, command, type in doc.create_arc + doc.create_stream:
231 if name.endswith('.' + o):
232 self.operation.set_history(i)
233 break
234 i += 1
236 self.updating = 0
238 def op_changed(self, operation):
239 i = operation.get_history()
240 doc = self.save_area.document
241 op = self.i_to_op[i]
242 doc.set_op(op[1])
243 if op[2]:
244 print op[2]
245 self.set_type(op[2])
246 else:
247 self.set_type('text/plain')
249 if self.updating:
250 return
251 self.updating = 1
253 name = self.save_area.entry.get_text()
254 if not name:
255 name = doc.path
256 for o, command, type in doc.create_arc + doc.create_stream:
257 if name.endswith('.' + o):
258 name = name[:-len(o)-1]
259 break
260 if i < len(doc.create_arc):
261 name += '.' + doc.create_arc[i][0]
262 else:
263 i -= len(doc.create_arc)
264 if i < len(doc.create_stream):
265 name += '.' + doc.create_stream[i][0]
266 self.save_area.entry.set_text(name)
268 self.updating = 0
270 # Check that our input is a regular local file.
271 # If not, fetch the data and put it in /tmp.
273 path = sys.argv[1]
275 source = None
277 if path == '-':
278 source = sys.stdin
279 else:
280 path = rox.get_local_path(path)
281 if not path:
282 rox.croak('Sorry, I can only extract/archive local files.')
283 if not os.path.isdir(path):
284 try:
285 source = file(path)
286 except:
287 rox.report_exception()
288 sys.exit(1)
290 if source:
291 data = FileData(path, source)
292 else:
293 data = DirData(path)
295 savebox = ArchiveBox(data, '', 'text/plain')
296 savebox.show()
297 g.gdk.flush()
299 savebox.add_ops()
301 rox.mainloop()