Can now handle rar files (Denis Manente).
[rox-archive.git] / Archive.py
blob6d007d0eb56498ae3dc34a92902eab2114acf3bc
1 from string import rfind
2 import os.path
3 import re
4 import stat
5 import os
6 import signal
7 import fcntl
9 from gtk import *
10 from GDK import *
12 from rox.SaveBox import SaveBox
13 from support import *
14 from rox import choices
15 import string
17 child_pid = None
19 archive_formats = [
20 # Extension, extract, create
21 ('.tar', "tar xf '%s'", "tar cf '%(dst)s' '%(src)s'"),
22 ('.zip', "unzip '%s'", "zip -r '%(dst)s' '%(src)s'"),
23 ('.deb', "ar x '%s'", None),
24 ('.rar', "rar x '%s'", "rar a '%(dst)s' '%(src)s'"),
26 ('.tgz', "gunzip -c '%s' | tar xf -",
27 "tar cf - '%(src)s' | gzip > '%(dst)s'"),
28 ('.tar.gz', "gunzip -c '%s' | tar xf -",
29 "tar cf - '%(src)s' | gzip > '%(dst)s'"),
31 ('.tbz', "bunzip2 -c '%s' | tar xf -",
32 "tar cf - '%(src)s' | bzip2 > '%(dst)s'"),
33 ('.tar.bz', "bunzip2 -c '%s' | tar xf -",
34 "tar cf - '%(src)s' | bzip2 > '%(dst)s'"),
35 ('.tbz2', "bunzip2 -c '%s' | tar xf -",
36 "tar cf - '%(src)s' | bzip2 > '%(dst)s'"),
37 ('.tar.bz2', "bunzip2 -c '%s' | tar xf -",
38 "tar cf - '%(src)s' | bzip2 > '%(dst)s'")
41 compressed_formats = [
42 # Ext, extract, compress, type
43 ('.gz', "gunzip -c '%s'", "gzip -c '%s'", 'x-gzip'),
44 ('.bz', "bunzip2 -ck '%s'", "bzip2 -c '%s'", 'x-bzip'),
45 ('.bz2', "bunzip2 -ck '%s'", "bzip2 -c '%s'", 'x-bzip')
48 def get_format(path, formats):
49 for f in formats:
50 ext = f[0]
51 if ext == path[-len(ext):]:
52 return f
53 return None
55 def esc(text):
56 """Return text with \ and ' escaped"""
57 return re.sub("'", "'\"'\"'", text)
59 def bg_system(command, out = None):
60 "system(command), but still process GUI events while waiting..."
61 "If 'out' is set then the child's stdout goes to that FD. 'out' is "
62 "closed in the parent process."
63 global child_pid
65 (r, w) = os.pipe()
67 child_pid = fork()
68 if child_pid == -1:
69 os.close(r)
70 os.close(w)
71 if out != None:
72 os.close(out)
73 report_error("fork() failed!")
74 return 127
75 if child_pid == 0:
76 # Child
77 try:
78 if out != None:
79 os.dup2(out, 1)
81 # Collect stderr...
82 if w != 2:
83 os.dup2(w, 2)
84 os.close(w)
86 os.setpgid(0, 0) # Start a new process group
88 os.close(r)
90 error = os.system(command) != 0
91 os._exit(error)
92 except:
93 pass
94 os._exit(127)
96 if out != None:
97 os.close(out)
98 os.close(w)
100 done = [""]
101 def cb(src, cond, done = done):
102 data = os.read(src, 100)
103 if data:
104 done[0] = done[0] + data
105 else:
106 done.append(1)
107 tag = input_add(r, INPUT_READ, cb)
109 while len(done) < 2:
110 mainiteration()
112 input_remove(tag)
114 os.close(r)
115 (pid, status) = waitpid(child_pid, 0)
116 child_pid = None
118 str = string.strip(done[0])
119 if status or str:
120 if str:
121 report_error("Error: " + str)
122 else:
123 report_error("Operation failed")
125 return status
127 def make_archiver(path):
128 while path[-1:] == '/':
129 path = path[:-1]
130 if os.path.isdir(path):
131 window = ArchiveDir(path)
132 else:
133 window = None
134 f = get_format(path, archive_formats)
135 if f:
136 window = ExtractDir(path, f)
137 else:
138 f = get_format(path, compressed_formats)
139 if f:
140 window = ExtractFile(path, f)
141 else:
142 window = ArchiveFile(path)
144 window.connect('destroy', mainquit)
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 for file in os.listdir(subdir):
158 bg_system("mv '%s' ." % esc(os.path.join(subdir, file)))
159 os.rmdir(subdir)
161 def report_known(formats):
162 txt = "Allowed extensions are:\n"
163 for f in formats:
164 if f[2]:
165 txt = txt + f[0] + '\n'
166 report_error(txt)
168 class Archive(SaveBox):
169 def __init__(self, win, media, sub):
170 SaveBox.__init__(self, win, media, sub);
171 self.connect('destroy', self.destroyed)
173 def destroyed(self, widget):
174 global child_pid
176 if child_pid:
177 os.kill(-child_pid, signal.SIGTERM)
179 def save_as(self, path):
180 self.set_sensitive(FALSE);
181 success = FALSE
182 try:
183 success = self.do_save(path)
184 except:
185 success = FALSE
186 report_exception()
187 self.set_sensitive(TRUE);
188 return success
190 def set_uri(self, uri):
191 path = get_local_path(uri)
192 if path:
193 child('rox', '-x', path)
194 pass
196 # save_as(path) - write data to file, TRUE on success
197 # set_uri(uri) - data is safely saved to this location
199 class ExtractDir(Archive):
200 def __init__(self, path, format):
201 self.path = path
202 ext = format[0]
203 self.extract = format[1]
204 self.uri = path[:-len(ext)]
205 Archive.__init__(self, self, 'special', 'directory')
207 def do_save(self, path):
208 os.mkdir(path)
209 os.chdir(path)
210 if bg_system(self.extract % esc(self.path)):
211 return FALSE
212 else:
213 pull_up(path)
214 return TRUE
216 class ExtractFile(Archive):
217 def __init__(self, path, format):
218 self.path = path
219 ext = format[0]
220 self.extract = format[1]
221 self.uri = path[:-len(ext)]
222 Archive.__init__(self, self, 'text', 'plain')
224 def do_save(self, path):
225 if os.path.exists(path):
226 report_error("File `%s' already exists!" % path)
227 return FALSE
228 stats = os.stat(self.path)
229 mode = stat.S_IMODE(stats[stat.ST_MODE]) & 0x1ff;
230 out = os.open(path, os.O_WRONLY | os.O_CREAT, mode)
231 return bg_system(self.extract % esc(self.path), out = out) == 0
233 def send_raw(self, selection_data):
234 (r, w) = os.pipe()
235 self.data = ""
237 def cb(src, cond, self = self):
238 self.data = self.data + os.read(src, 1024)
239 input_add(r, INPUT_READ, cb)
241 bg_system(self.extract % esc(self.path), out = w)
243 while 1:
244 new = os.read(r, 1024)
245 if not new:
246 break
247 self.data = self.data + new
248 os.close(r)
249 selection_data.set(selection_data.target, 8, self.data)
250 self.data = ""
252 class ArchiveFile(Archive):
253 def __init__(self, path):
254 self.path = path
255 self.uri = path + '.gz'
256 Archive.__init__(self, self, 'application', 'x-gzip')
258 def do_save(self, path):
259 format = get_format(path, compressed_formats)
260 if not format or not format[2]:
261 report_known(compressed_formats)
262 return FALSE
264 if os.path.exists(path):
265 report_error("File `%s' already exists!" % path)
266 return FALSE
267 stats = os.stat(self.path)
268 mode = stat.S_IMODE(stats[stat.ST_MODE]) & 0x1ff;
269 out = os.open(path, os.O_WRONLY | os.O_CREAT, mode)
270 return bg_system(format[2] % esc(self.path), out = out) == 0
272 class ArchiveDir(Archive):
273 def __init__(self, path):
274 self.path = path
275 self.uri = path + '.tgz'
276 Archive.__init__(self, self, 'application', 'x-compressed-tar')
278 def do_save(self, path):
279 format = get_format(path, archive_formats)
280 if not format or not format[2]:
281 report_known(archive_formats)
282 return FALSE
284 os.chdir(os.path.dirname(self.uri))
285 retval = bg_system(format[2] % {
286 'src' : esc(os.path.basename(self.path)),
287 'dst' : esc(path)
289 return retval == 0