Convert to ROX-Lib2.
[rox-archive.git] / Archive.py
blob2cd3c899518a4d73673d01ae4c1c01dfbe4768dd
1 import os.path
2 import re
3 import stat
4 import os
5 import signal
6 import fcntl
8 from rox.saving import SaveBox
9 from rox import g, choices
11 child_pid = None
13 from formats import *
15 def esc(text):
16 """Return text with \ and ' escaped"""
17 return re.sub("'", "'\"'\"'", text)
19 def bg_system(command, out = None):
20 "system(command), but still process GUI events while waiting..."
21 "If 'out' is set then the child's stdout goes to that FD. 'out' is "
22 "closed in the parent process."
23 global child_pid
25 (r, w) = os.pipe()
27 child_pid = fork()
28 if child_pid == -1:
29 os.close(r)
30 os.close(w)
31 if out != None:
32 os.close(out)
33 support.alert("fork() failed!")
34 return 127
35 if child_pid == 0:
36 # Child
37 try:
38 if out != None:
39 os.dup2(out, 1)
41 # Collect stderr...
42 if w != 2:
43 os.dup2(w, 2)
44 os.close(w)
46 os.setpgid(0, 0) # Start a new process group
48 os.close(r)
50 error = os.system(command) != 0
51 os._exit(error)
52 except:
53 pass
54 os._exit(127)
56 if out != None:
57 os.close(out)
58 os.close(w)
60 done = [""]
61 def cb(src, cond, done = done):
62 data = os.read(src, 100)
63 if data:
64 done[0] = done[0] + data
65 else:
66 done.append(1)
67 tag = gtk.input_add_full(r, gtk.gdk.INPUT_READ, cb)
69 while len(done) < 2:
70 gtk.mainiteration()
72 gtk.input_remove(tag)
74 os.close(r)
75 (pid, status) = os.waitpid(child_pid, 0)
76 child_pid = None
78 str = string.strip(done[0])
79 if status or str:
80 if str:
81 support.alert(str)
82 else:
83 support.alert("Operation failed")
85 return status
87 def make_archiver(path):
88 while path[-1:] == '/':
89 path = path[:-1]
90 if os.path.isdir(path):
91 window = ArchiveDir(path)
92 else:
93 window = None
94 f = get_format(path, archive_formats)
95 if f:
96 window = ExtractDir(path, f)
97 else:
98 f = get_format(path, compressed_formats)
99 if f:
100 window = ExtractFile(path, f)
101 else:
102 window = ArchiveFile(path)
104 window.show()
106 def pull_up(dir):
107 "If dir contains only one subdir, move its contents into dir."
108 list = os.listdir(dir)
109 if len(list) != 1:
110 return
112 subdir = os.path.join(dir, list[0])
113 if not os.path.isdir(subdir):
114 return
116 tmp = '<Unknown>'
117 try:
118 tmp = os.tempnam(dir)
119 os.rename(subdir, tmp)
120 subdir = tmp
121 except:
122 print "Warning: failed to rename(%s, %s)\n" % (subdir, tmp)
124 for file in os.listdir(subdir):
125 bg_system("mv '%s' ." % esc(os.path.join(subdir, file)))
126 os.rmdir(subdir)
128 def report_known(formats):
129 txt = "Allowed extensions are:\n"
130 for f in formats:
131 if f[2]:
132 txt = txt + f[0] + '\n'
133 support.alert(txt)
135 from os import _exit, fork, execvp, dup2
137 def child(*argv):
138 """Spawn a new process. Connect stderr to stdout."""
139 child = fork()
140 if child == 0:
141 # We are the child
142 try:
143 dup2(1, 2)
144 execvp(argv[0], argv)
145 except:
146 pass
147 print "Warning: exec('%s') failed!" % argv[0]
148 _exit(1)
149 elif child == -1:
150 print "Error: fork() failed!"
152 class Archive(SaveBox):
153 def __init__(self, win, type):
154 SaveBox.__init__(self, self, self.uri, type);
155 self.connect('destroy', self.destroyed)
157 if not hasattr(self, 'changed'):
158 return
159 self.save_area.entry.connect('changed', self.changed)
161 def destroyed(self, widget):
162 global child_pid
164 if child_pid:
165 os.kill(-child_pid, signal.SIGTERM)
167 def save_as_file(self, path):
168 self.set_sensitive(FALSE)
169 success = FALSE
170 try:
171 success = self.do_save(path)
172 except:
173 success = FALSE
174 support.report_exception()
175 self.set_sensitive(TRUE);
176 return success
178 def set_uri(self, uri):
179 path = support.get_local_path(uri)
180 if path:
181 child('rox', '-x', path)
182 pass
184 # save_as(path) - write data to file, TRUE on success
185 # set_uri(uri) - data is safely saved to this location
187 class ExtractDir(Archive):
188 def __init__(self, path, format):
189 self.path = path
190 ext = format[0]
191 self.extract = format[1]
192 uri = path[:-len(ext)]
193 import re
194 found = re.match(r'^(.*)-(\d+\.)*\d+$', uri)
195 if found:
196 self.uri = found.group(1)
197 else:
198 self.uri = uri
199 Archive.__init__(self, self, 'special/directory')
201 def do_save(self, path):
202 os.mkdir(path)
203 os.chdir(path)
204 if bg_system(self.extract % esc(self.path)):
205 return FALSE
206 else:
207 pull_up(path)
208 return TRUE
210 class ExtractFile(Archive):
211 def __init__(self, path, format):
212 self.path = path
213 ext = format[0]
214 self.extract = format[1]
215 self.uri = path[:-len(ext)]
216 Archive.__init__(self, self, 'text/plain')
218 def do_save(self, path):
219 if os.path.exists(path):
220 support.alert("File `%s' already exists!" % path)
221 return FALSE
222 stats = os.stat(self.path)
223 mode = stat.S_IMODE(stats[stat.ST_MODE]) & 0x1ff;
224 out = os.open(path, os.O_WRONLY | os.O_CREAT, mode)
225 return bg_system(self.extract % esc(self.path), out = out) == 0
227 def save_as_selection(self, selection_data):
228 (r, w) = os.pipe()
229 self.data = ""
231 def cb(src, cond, self = self):
232 self.data = self.data + os.read(src, 1024)
233 gtk.input_add_full(r, gtk.gdk.INPUT_READ, cb)
235 bg_system(self.extract % esc(self.path), out = w)
237 while 1:
238 new = os.read(r, 1024)
239 if not new:
240 break
241 self.data = self.data + new
242 os.close(r)
243 selection_data.set(selection_data.target, 8, self.data)
244 self.data = ""
246 class ArchiveFile(Archive):
247 def __init__(self, path):
248 self.path = path
249 self.uri = path + '.gz'
250 Archive.__init__(self, self, 'application/x-gzip')
252 def do_save(self, path):
253 format = get_format(path, compressed_formats)
254 if not format or not format[2]:
255 report_known(compressed_formats)
256 return FALSE
258 if os.path.exists(path):
259 support.alert("File `%s' already exists!" % path)
260 return FALSE
261 stats = os.stat(self.path)
262 mode = stat.S_IMODE(stats[stat.ST_MODE]) & 0x1ff;
263 out = os.open(path, os.O_WRONLY | os.O_CREAT, mode)
264 return bg_system(format[2] % esc(self.path), out = out) == 0
266 def changed(self, entry):
267 path = entry.get_text()
268 format = get_format(path, compressed_formats)
269 if not format:
270 self.set_type('error/unknown')
271 return
272 self.set_type(format[3])
274 class ArchiveDir(Archive):
275 def __init__(self, path):
276 self.path = path
277 self.uri = path + '.tgz'
278 Archive.__init__(self, self, 'application/x-compressed-tar')
280 def do_save(self, path):
281 format = get_format(path, archive_formats)
282 if not format or not format[2]:
283 report_known(archive_formats)
284 return FALSE
286 os.chdir(os.path.dirname(self.uri))
287 retval = bg_system(format[2] % {
288 'src' : esc(os.path.basename(self.path)),
289 'dst' : esc(path)
291 return retval == 0
293 def changed(self, entry):
294 path = entry.get_text()
295 format = get_format(path, archive_formats)
296 if (not format) or not format[2]:
297 self.set_type('error/unknown')
298 return
299 self.set_type(format[3])