Convert to ROX-Lib2.
[rox-archive.git] / AppRun
blob863cc7f0c915cebca1f154553e3e62432f96373d
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 -"),
91 ('bz2', "bzip2 -c -")
94 extract_arc = (
95 # Archives
96 ('tgz', "gunzip -c '%s' | tar xf -"),
97 ('tar.gz', "gunzip -c '%s' | tar xf -"),
98 ('tar.bz', "bunzip2 -c '%s' | tar xf -"),
99 ('tar.bz2', "bunzip2 -c '%s' | tar xf -"),
100 ('zip', "unzip '%s'"),
101 ('rar', "rar x '%s'"),
102 ('tar', "tar xf '%s'")
105 extract_stream = (
106 # Compressed streams
107 ('gz', "gunzip -c -"),
108 ('bz', "bunzip2 -ck -"),
109 ('bz2', "bunzip2 -ck -")
112 def save_to_file(self, file):
113 command = self.op
114 if command.find('%s') != -1:
115 return pipe_through_command(command % file,
116 self.stream, None)
117 else:
118 return Saveable.save_to_file(self, file)
120 def save_to_stream(self, stream):
121 command = self.op
122 if command.find('%s') != -1:
123 raise Exception('Sorry, archives can only be extracted into '
124 'a local directory.')
125 self.stream.seek(0)
126 pipe_through_command(command, self.stream, stream)
128 def set_op(self, op):
129 self.op = op
131 class DirData(saving.Saveable):
132 def __init__(self, source):
133 self.path = source
134 self.start = None
136 create_arc = (
137 ('tgz', "tar cf - '%s' | gzip"),
138 ('tar.gz', "tar cf - '%s' | gzip"),
139 ('tar.bz', "tar cf - '%s' | bzip2"),
140 ('tar.bz2', "tar cf - '%s' | bzip2"),
141 ('zip', "zip -r - '%s'"),
142 ('rar', "rar a - '%s'"),
143 ('tar', "tar cf - '%s'")
146 create_stream = ()
147 extract_arc = ()
148 extract_stream = ()
150 def save_to_stream(self, stream):
151 command = self.op % escape(self.path)
152 pipe_through_command(command, None, stream)
154 def set_op(self, op):
155 self.op = op
157 class ArchiveBox(saving.SaveBox):
158 def build_main_area(self):
159 self.vbox.add(self.save_area)
161 self.operation = g.OptionMenu()
162 self.vbox.pack_start(self.operation, FALSE, TRUE, 0)
163 self.operation.show()
164 self.operation.set_border_width(5)
165 self.updating = 0
167 self.ops_menu = g.Menu()
168 self.operation.set_menu(self.ops_menu)
170 def add_ops(self):
171 doc = self.save_area.document
172 self.i_to_op = []
173 for op, command in doc.create_arc:
174 item = g.MenuItem('Create .%s archive' % op)
175 item.show()
176 self.ops_menu.append(item)
177 self.i_to_op.append(command)
178 for op, command in doc.create_stream:
179 item = g.MenuItem('Compress as .%s' % op)
180 item.show()
181 self.ops_menu.append(item)
182 self.i_to_op.append(command)
184 if doc.start:
185 guess = formats.guess_format(doc.start)
186 else:
187 guess = None
189 if guess in ['gz', 'bz2', 'bz']:
190 if doc.path.endswith('.tar.' + guess):
191 guess = 'tar.' + guess
193 init = 0
194 for op, command in doc.extract_arc:
195 item = g.MenuItem('Extract from a .%s' % op)
196 item.show()
197 self.ops_menu.append(item)
198 if op == guess:
199 init = len(self.i_to_op)
200 self.i_to_op.append(command)
201 for op, command in doc.extract_stream:
202 item = g.MenuItem('Uncompress .%s' % op)
203 item.show()
204 self.ops_menu.append(item)
205 if op == guess:
206 init = len(self.i_to_op)
207 self.i_to_op.append(command)
209 name = doc.path
210 if name.endswith('.' + guess):
211 name = name[:-len(guess)-1]
212 self.save_area.entry.set_text(name)
214 self.operation.connect('changed', self.op_changed)
215 self.save_area.entry.connect('changed', self.name_changed)
216 self.operation.set_history(init)
218 def name_changed(self, entry):
219 if self.updating:
220 return
221 self.updating = 1
223 name = entry.get_text()
224 doc = self.save_area.document
225 i = 0
226 for o, command in doc.create_arc + doc.create_stream:
227 if name.endswith('.' + o):
228 self.operation.set_history(i)
229 break
230 i += 1
232 self.updating = 0
234 def op_changed(self, operation):
235 if self.updating:
236 return
237 self.updating = 1
239 i = operation.get_history()
240 doc = self.save_area.document
241 doc.set_op(self.i_to_op[i])
242 name = self.save_area.entry.get_text()
243 if not name:
244 name = doc.path
245 for o, command in doc.create_arc + doc.create_stream:
246 if name.endswith('.' + o):
247 name = name[:-len(o)-1]
248 break
249 if i < len(doc.create_arc):
250 name += '.' + doc.create_arc[i][0]
251 else:
252 i -= len(doc.create_arc)
253 if i < len(doc.create_stream):
254 name += '.' + doc.create_stream[i][0]
255 self.save_area.entry.set_text(name)
257 self.updating = 0
259 # Check that our input is a regular local file.
260 # If not, fetch the data and put it in /tmp.
262 path = sys.argv[1]
264 source = None
266 if path == '-':
267 source = sys.stdin
268 else:
269 path = rox.get_local_path(path)
270 if not path:
271 rox.croak('Sorry, I can only extract/archive local files.')
272 if not os.path.isdir(path):
273 try:
274 source = file(path)
275 except:
276 rox.report_exception()
277 sys.exit(1)
279 if source:
280 data = FileData(path, source)
281 else:
282 data = DirData(path)
284 savebox = ArchiveBox(data, '', 'text/plain')
285 savebox.show()
286 g.gdk.flush()
288 savebox.add_ops()
290 rox.mainloop()