Highlight name when changing format.
[rox-archive.git] / v1 / Archive.py
blob19fb829cd812a9cb91512c9127e605b865eeb545
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 rox import support
13 from rox 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 = input_add(r, INPUT_READ, cb)
74 while len(done) < 2:
75 mainiteration()
77 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.connect('destroy', lambda w: support.rox_toplevel_unref())
110 support.rox_toplevel_ref()
111 window.show()
113 def pull_up(dir):
114 "If dir contains only one subdir, move its contents into dir."
115 list = os.listdir(dir)
116 if len(list) != 1:
117 return
119 subdir = os.path.join(dir, list[0])
120 if not os.path.isdir(subdir):
121 return
123 tmp = '<Unknown>'
124 try:
125 tmp = os.tempnam(dir)
126 os.rename(subdir, tmp)
127 subdir = tmp
128 except:
129 print "Warning: failed to rename(%s, %s)\n" % (subdir, tmp)
131 for file in os.listdir(subdir):
132 bg_system("mv '%s' ." % esc(os.path.join(subdir, file)))
133 os.rmdir(subdir)
135 def report_known(formats):
136 txt = "Allowed extensions are:\n"
137 for f in formats:
138 if f[2]:
139 txt = txt + f[0] + '\n'
140 support.report_error(txt)
142 from os import _exit, fork, execvp, dup2
144 def child(*argv):
145 """Spawn a new process. Connect stderr to stdout."""
146 child = fork()
147 if child == 0:
148 # We are the child
149 try:
150 dup2(1, 2)
151 execvp(argv[0], argv)
152 except:
153 pass
154 print "Warning: exec('%s') failed!" % argv[0]
155 _exit(1)
156 elif child == -1:
157 print "Error: fork() failed!"
159 class Archive(SaveBox):
160 def __init__(self, win, type):
161 SaveBox.__init__(self, self, self.uri, type);
162 self.connect('destroy', self.destroyed)
164 if not hasattr(self, 'changed'):
165 return
166 if hasattr(self, 'set_type'):
167 self.save_area.entry.connect('changed', self.changed)
168 else:
169 print "(a newer version of ROX-Lib would be nice)"
171 def destroyed(self, widget):
172 global child_pid
174 if child_pid:
175 os.kill(-child_pid, signal.SIGTERM)
177 def save_as_file(self, path):
178 self.set_sensitive(FALSE)
179 success = FALSE
180 try:
181 success = self.do_save(path)
182 except:
183 success = FALSE
184 support.report_exception()
185 self.set_sensitive(TRUE);
186 return success
188 def set_uri(self, uri):
189 path = support.get_local_path(uri)
190 if path:
191 child('rox', '-x', path)
192 pass
194 # save_as(path) - write data to file, TRUE on success
195 # set_uri(uri) - data is safely saved to this location
197 class ExtractDir(Archive):
198 def __init__(self, path, format):
199 self.path = path
200 ext = format[0]
201 self.extract = format[1]
202 uri = path[:-len(ext)]
203 import re
204 found = re.match(r'^(.*)-(\d+\.)*\d+$', uri)
205 if found:
206 self.uri = found.group(1)
207 else:
208 self.uri = uri
209 Archive.__init__(self, self, 'special/directory')
211 def do_save(self, path):
212 os.mkdir(path)
213 os.chdir(path)
214 if bg_system(self.extract % esc(self.path)):
215 return FALSE
216 else:
217 pull_up(path)
218 return TRUE
220 class ExtractFile(Archive):
221 def __init__(self, path, format):
222 self.path = path
223 ext = format[0]
224 self.extract = format[1]
225 self.uri = path[:-len(ext)]
226 Archive.__init__(self, self, 'text/plain')
228 def do_save(self, path):
229 if os.path.exists(path):
230 support.report_error("File `%s' already exists!" % path)
231 return FALSE
232 stats = os.stat(self.path)
233 mode = stat.S_IMODE(stats[stat.ST_MODE]) & 0x1ff;
234 out = os.open(path, os.O_WRONLY | os.O_CREAT, mode)
235 return bg_system(self.extract % esc(self.path), out = out) == 0
237 def save_as_selection(self, selection_data):
238 (r, w) = os.pipe()
239 self.data = ""
241 def cb(src, cond, self = self):
242 self.data = self.data + os.read(src, 1024)
243 input_add(r, INPUT_READ, cb)
245 bg_system(self.extract % esc(self.path), out = w)
247 while 1:
248 new = os.read(r, 1024)
249 if not new:
250 break
251 self.data = self.data + new
252 os.close(r)
253 selection_data.set(selection_data.target, 8, self.data)
254 self.data = ""
256 class ArchiveFile(Archive):
257 def __init__(self, path):
258 self.path = path
259 self.uri = path + '.gz'
260 Archive.__init__(self, self, 'application/x-gzip')
262 def do_save(self, path):
263 format = get_format(path, compressed_formats)
264 if not format or not format[2]:
265 report_known(compressed_formats)
266 return FALSE
268 if os.path.exists(path):
269 support.report_error("File `%s' already exists!" % path)
270 return FALSE
271 stats = os.stat(self.path)
272 mode = stat.S_IMODE(stats[stat.ST_MODE]) & 0x1ff;
273 out = os.open(path, os.O_WRONLY | os.O_CREAT, mode)
274 return bg_system(format[2] % esc(self.path), out = out) == 0
276 def changed(self, entry):
277 path = entry.get_text()
278 format = get_format(path, compressed_formats)
279 if not format:
280 self.set_type('error/unknown')
281 return
282 self.set_type(format[3])
284 class ArchiveDir(Archive):
285 def __init__(self, path):
286 self.path = path
287 self.uri = path + '.tgz'
288 Archive.__init__(self, self, 'application/x-compressed-tar')
290 def do_save(self, path):
291 format = get_format(path, archive_formats)
292 if not format or not format[2]:
293 report_known(archive_formats)
294 return FALSE
296 os.chdir(os.path.dirname(self.uri))
297 retval = bg_system(format[2] % {
298 'src' : esc(os.path.basename(self.path)),
299 'dst' : esc(path)
301 return retval == 0
303 def changed(self, entry):
304 path = entry.get_text()
305 format = get_format(path, archive_formats)
306 if (not format) or not format[2]:
307 self.set_type('error/unknown')
308 return
309 self.set_type(format[3])