Typo in the regular expression...
[rox-archive.git] / Archive.py
blob2a18c8a3d144265bcbea581bf025c5fda3c1e139
1 import os.path
2 import re
3 import stat
4 import os
5 import signal
6 import fcntl
8 from gtk import *
9 from GDK import *
11 from rox.SaveBox import SaveBox
12 from support import *
13 from rox import choices
14 import string
16 child_pid = None
18 archive_formats = [
19 # Extension, extract, create
20 ('.tar', "tar xf '%s'", "tar cf '%(dst)s' '%(src)s'"),
21 ('.zip', "unzip '%s'", "zip -r '%(dst)s' '%(src)s'"),
22 ('.deb', "ar x '%s'", None),
23 ('.rar', "rar x '%s'", "rar a '%(dst)s' '%(src)s'"),
25 ('.tgz', "gunzip -c '%s' | tar xf -",
26 "tar cf - '%(src)s' | gzip > '%(dst)s'"),
27 ('.tar.gz', "gunzip -c '%s' | tar xf -",
28 "tar cf - '%(src)s' | gzip > '%(dst)s'"),
30 ('.tbz', "bunzip2 -c '%s' | tar xf -",
31 "tar cf - '%(src)s' | bzip2 > '%(dst)s'"),
32 ('.tar.bz', "bunzip2 -c '%s' | tar xf -",
33 "tar cf - '%(src)s' | bzip2 > '%(dst)s'"),
34 ('.tbz2', "bunzip2 -c '%s' | tar xf -",
35 "tar cf - '%(src)s' | bzip2 > '%(dst)s'"),
36 ('.tar.bz2', "bunzip2 -c '%s' | tar xf -",
37 "tar cf - '%(src)s' | bzip2 > '%(dst)s'")
40 compressed_formats = [
41 # Ext, extract, compress, type
42 ('.gz', "gunzip -c '%s'", "gzip -c '%s'", 'x-gzip'),
43 ('.bz', "bunzip2 -ck '%s'", "bzip2 -c '%s'", 'x-bzip'),
44 ('.bz2', "bunzip2 -ck '%s'", "bzip2 -c '%s'", 'x-bzip')
47 def get_format(path, formats):
48 for f in formats:
49 ext = f[0]
50 if ext == string.lower(path[-len(ext):]):
51 return f
52 return None
54 def esc(text):
55 """Return text with \ and ' escaped"""
56 return re.sub("'", "'\"'\"'", text)
58 def bg_system(command, out = None):
59 "system(command), but still process GUI events while waiting..."
60 "If 'out' is set then the child's stdout goes to that FD. 'out' is "
61 "closed in the parent process."
62 global child_pid
64 (r, w) = os.pipe()
66 child_pid = fork()
67 if child_pid == -1:
68 os.close(r)
69 os.close(w)
70 if out != None:
71 os.close(out)
72 report_error("fork() failed!")
73 return 127
74 if child_pid == 0:
75 # Child
76 try:
77 if out != None:
78 os.dup2(out, 1)
80 # Collect stderr...
81 if w != 2:
82 os.dup2(w, 2)
83 os.close(w)
85 os.setpgid(0, 0) # Start a new process group
87 os.close(r)
89 error = os.system(command) != 0
90 os._exit(error)
91 except:
92 pass
93 os._exit(127)
95 if out != None:
96 os.close(out)
97 os.close(w)
99 done = [""]
100 def cb(src, cond, done = done):
101 data = os.read(src, 100)
102 if data:
103 done[0] = done[0] + data
104 else:
105 done.append(1)
106 tag = input_add(r, INPUT_READ, cb)
108 while len(done) < 2:
109 mainiteration()
111 input_remove(tag)
113 os.close(r)
114 (pid, status) = waitpid(child_pid, 0)
115 child_pid = None
117 str = string.strip(done[0])
118 if status or str:
119 if str:
120 report_error("Error: " + str)
121 else:
122 report_error("Operation failed")
124 return status
126 def make_archiver(path):
127 while path[-1:] == '/':
128 path = path[:-1]
129 if os.path.isdir(path):
130 window = ArchiveDir(path)
131 else:
132 window = None
133 f = get_format(path, archive_formats)
134 if f:
135 window = ExtractDir(path, f)
136 else:
137 f = get_format(path, compressed_formats)
138 if f:
139 window = ExtractFile(path, f)
140 else:
141 window = ArchiveFile(path)
143 window.connect('destroy', lambda w: rox_toplevel_unref())
144 rox_toplevel_ref()
145 window.show()
147 def pull_up(dir):
148 "If dir contains only one subdir, move its contents into dir."
149 list = os.listdir(dir)
150 if len(list) != 1:
151 return
153 subdir = os.path.join(dir, list[0])
154 if not os.path.isdir(subdir):
155 return
157 tmp = '<Unknown>'
158 try:
159 tmp = os.tempnam(dir)
160 os.rename(subdir, tmp)
161 subdir = tmp
162 except:
163 print "Warning: failed to rename(%s, %s)\n" % (subdir, tmp)
165 for file in os.listdir(subdir):
166 bg_system("mv '%s' ." % esc(os.path.join(subdir, file)))
167 os.rmdir(subdir)
169 def report_known(formats):
170 txt = "Allowed extensions are:\n"
171 for f in formats:
172 if f[2]:
173 txt = txt + f[0] + '\n'
174 report_error(txt)
176 class Archive(SaveBox):
177 def __init__(self, win, type):
178 SaveBox.__init__(self, self, self.uri, type);
179 self.connect('destroy', self.destroyed)
181 def destroyed(self, widget):
182 global child_pid
184 if child_pid:
185 os.kill(-child_pid, signal.SIGTERM)
187 def save_as_file(self, path):
188 self.set_sensitive(FALSE)
189 success = FALSE
190 try:
191 success = self.do_save(path)
192 except:
193 success = FALSE
194 report_exception()
195 self.set_sensitive(TRUE);
196 return success
198 def set_uri(self, uri):
199 path = get_local_path(uri)
200 if path:
201 child('rox', '-x', path)
202 pass
204 # save_as(path) - write data to file, TRUE on success
205 # set_uri(uri) - data is safely saved to this location
207 class ExtractDir(Archive):
208 def __init__(self, path, format):
209 self.path = path
210 ext = format[0]
211 self.extract = format[1]
212 uri = path[:-len(ext)]
213 import re
214 found = re.match(r'^(.*)-(\d+\.)*\d+$', uri)
215 if found:
216 self.uri = found.group(1)
217 else:
218 self.uri = uri
219 Archive.__init__(self, self, 'special/directory')
221 def do_save(self, path):
222 os.mkdir(path)
223 os.chdir(path)
224 if bg_system(self.extract % esc(self.path)):
225 return FALSE
226 else:
227 pull_up(path)
228 return TRUE
230 class ExtractFile(Archive):
231 def __init__(self, path, format):
232 self.path = path
233 ext = format[0]
234 self.extract = format[1]
235 self.uri = path[:-len(ext)]
236 Archive.__init__(self, self, 'text/plain')
238 def do_save(self, path):
239 if os.path.exists(path):
240 report_error("File `%s' already exists!" % path)
241 return FALSE
242 stats = os.stat(self.path)
243 mode = stat.S_IMODE(stats[stat.ST_MODE]) & 0x1ff;
244 out = os.open(path, os.O_WRONLY | os.O_CREAT, mode)
245 return bg_system(self.extract % esc(self.path), out = out) == 0
247 def save_as_selection(self, selection_data):
248 (r, w) = os.pipe()
249 self.data = ""
251 def cb(src, cond, self = self):
252 self.data = self.data + os.read(src, 1024)
253 input_add(r, INPUT_READ, cb)
255 bg_system(self.extract % esc(self.path), out = w)
257 while 1:
258 new = os.read(r, 1024)
259 if not new:
260 break
261 self.data = self.data + new
262 os.close(r)
263 selection_data.set(selection_data.target, 8, self.data)
264 self.data = ""
266 class ArchiveFile(Archive):
267 def __init__(self, path):
268 self.path = path
269 self.uri = path + '.gz'
270 Archive.__init__(self, self, 'application/x-gzip')
272 def do_save(self, path):
273 format = get_format(path, compressed_formats)
274 if not format or not format[2]:
275 report_known(compressed_formats)
276 return FALSE
278 if os.path.exists(path):
279 report_error("File `%s' already exists!" % path)
280 return FALSE
281 stats = os.stat(self.path)
282 mode = stat.S_IMODE(stats[stat.ST_MODE]) & 0x1ff;
283 out = os.open(path, os.O_WRONLY | os.O_CREAT, mode)
284 return bg_system(format[2] % esc(self.path), out = out) == 0
286 class ArchiveDir(Archive):
287 def __init__(self, path):
288 self.path = path
289 self.uri = path + '.tgz'
290 Archive.__init__(self, self, 'application/x-compressed-tar')
292 def do_save(self, path):
293 format = get_format(path, archive_formats)
294 if not format or not format[2]:
295 report_known(archive_formats)
296 return FALSE
298 os.chdir(os.path.dirname(self.uri))
299 retval = bg_system(format[2] % {
300 'src' : esc(os.path.basename(self.path)),
301 'dst' : esc(path)
303 return retval == 0