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