4 import sys
, os
, formats
7 from rox
import g
, TRUE
, FALSE
, saving
10 rox
.info("Drag a file or directory onto Archive to archive it. "
11 "Drag an archive onto it to extract.")
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" '''
23 # Protect against DoS attacks
25 name
= `random
.randint(1, 1000000)`
+ '-archive'
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."""
35 if hasattr(dst
, 'fileno'):
39 fd
= tmp_stream
.fileno()
48 os
.dup2(src
.fileno(), 0)
55 pid
, status
= os
.waitpid(child
, 0)
57 rox
.alert('Command returned an error!')
59 if dst
and tmp_stream
is not dst
:
62 data
= tmp_stream
.read()
65 dst
.write(tmp_stream
.read())
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
):
73 self
.start
= stream
.read(300)
79 # Input is not a regular, local, seekable file, so copy it
80 # to a local temp file.
84 shutil
.copyfileobj(stream
, tmp
)
90 ('gz', "gzip -c -", 'application/x-gzip'),
91 ('bz2', "bzip2 -c -", 'application/x-bzip')
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)
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):
117 if self
.need_dir(command
):
120 return pipe_through_command(command
, self
.stream
, None)
122 return Saveable
.save_to_file(self
, file)
124 def save_to_stream(self
, stream
):
126 if self
.need_dir(command
):
127 raise Exception('Sorry, archives can only be extracted into '
128 'a local directory.')
130 pipe_through_command(command
, self
.stream
, stream
)
132 def set_op(self
, op
):
135 class DirData(saving
.Saveable
):
136 def __init__(self
, source
):
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')
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
):
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)
171 self
.ops_menu
= g
.Menu()
172 self
.operation
.set_menu(self
.ops_menu
)
175 doc
= self
.save_area
.document
177 for op
, command
, type in doc
.create_arc
:
178 item
= g
.MenuItem('Create .%s archive' % op
)
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
)
185 self
.ops_menu
.append(item
)
186 self
.i_to_op
.append((op
, command
, type))
189 guess
= formats
.guess_format(doc
.start
)
193 if guess
in ['gz', 'bz2', 'bz']:
194 if doc
.path
.endswith('.tar.' + guess
):
195 guess
= 'tar.' + guess
198 for op
, command
, type in doc
.extract_arc
:
199 item
= g
.MenuItem('Extract from a .%s' % op
)
201 self
.ops_menu
.append(item
)
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
)
208 self
.ops_menu
.append(item
)
210 init
= len(self
.i_to_op
)
211 self
.i_to_op
.append((op
, command
, type))
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
):
227 name
= entry
.get_text()
228 doc
= self
.save_area
.document
230 for o
, command
, type in doc
.create_arc
+ doc
.create_stream
:
231 if name
.endswith('.' + o
):
232 self
.operation
.set_history(i
)
238 def op_changed(self
, operation
):
239 i
= operation
.get_history()
240 doc
= self
.save_area
.document
247 self
.set_type('text/plain')
253 name
= self
.save_area
.entry
.get_text()
256 for o
, command
, type in doc
.create_arc
+ doc
.create_stream
:
257 if name
.endswith('.' + o
):
258 name
= name
[:-len(o
)-1]
260 if i
< len(doc
.create_arc
):
261 name
+= '.' + doc
.create_arc
[i
][0]
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
)
270 # Check that our input is a regular local file.
271 # If not, fetch the data and put it in /tmp.
280 path
= rox
.get_local_path(path
)
282 rox
.croak('Sorry, I can only extract/archive local files.')
283 if not os
.path
.isdir(path
):
287 rox
.report_exception()
291 data
= FileData(path
, source
)
295 savebox
= ArchiveBox(data
, '', 'text/plain')