Added feed-for.
[0publish-gui.git] / archive.py
blobb516f6b7a4f3b4b195962df895f290bc667d8b68
1 from xml.dom import Node, minidom
2 import re
4 import rox, os, sys, urlparse, tempfile, shutil, time, urllib
5 from rox import g, tasks
6 import gtk.glade
8 from zeroinstall.zerostore import unpack, manifest, NotStored
10 import signing
11 from xmltools import *
12 import main
14 RESPONSE_SAVE = 0
15 RESPONSE_SAVE_AND_TEST = 1
17 dotted_ints = '[0-9]+(.[0-9]+)*'
18 version_regexp = '[^a-zA-Z0-9](%s)(-(pre|rc|post|)%s)*' % (dotted_ints, dotted_ints)
20 def get_combo_value(combo):
21 i = combo.get_active()
22 m = combo.get_model()
23 return m[i][0]
25 watch = gtk.gdk.Cursor(gtk.gdk.WATCH)
27 class AddArchiveBox:
28 def __init__(self, feed_editor, local_archive = None):
29 self.feed_editor = feed_editor
30 self.tmpdir = None
32 widgets = gtk.glade.XML(main.gladefile, 'add_archive')
34 tree = widgets.get_widget('extract_list')
35 model = g.TreeStore(str)
36 tree.set_model(model)
37 selection = tree.get_selection()
38 selection.set_mode(g.SELECTION_BROWSE)
40 cell = g.CellRendererText()
41 col = g.TreeViewColumn('Extract', cell)
42 col.add_attribute(cell, 'text', 0)
43 tree.append_column(col)
45 dialog = widgets.get_widget('add_archive')
47 widgets.get_widget('mime_type').set_active(0)
49 def local_archive_changed(chooser):
50 model.clear()
51 path = chooser.get_filename()
52 widgets.get_widget('subdirectory_frame').set_sensitive(False)
53 self.destroy_tmp()
54 if not path: return
56 self.tmpdir = tempfile.mkdtemp('-0publish-gui')
57 url = widgets.get_widget('archive_url').get_text()
58 try:
59 dialog.window.set_cursor(watch)
60 gtk.gdk.flush()
61 try:
62 unpack.unpack_archive(url, file(path), self.tmpdir)
63 finally:
64 dialog.window.set_cursor(None)
65 except:
66 chooser.unselect_filename(path)
67 self.destroy_tmp()
68 raise
69 iter = model.append(None, ['Everything'])
70 items = os.listdir(self.tmpdir)
71 for f in items:
72 model.append(iter, [f])
73 tree.expand_all()
74 # Choose a sensible default
75 iter = model.get_iter_root()
76 if len(items) == 1 and \
77 os.path.isdir(os.path.join(self.tmpdir, items[0])) and \
78 items[0] not in ('usr', 'opt', 'bin', 'etc', 'sbin', 'doc', 'var'):
79 iter = model.iter_children(iter)
80 selection.select_iter(iter)
82 widgets.get_widget('subdirectory_frame').set_sensitive(True)
84 local_archive_button = widgets.get_widget('local_archive')
85 local_archive_button.connect('selection-changed', local_archive_changed)
86 widgets.get_widget('subdirectory_frame').set_sensitive(False)
88 def download(button):
89 url = widgets.get_widget('archive_url').get_text()
90 if not url:
91 raise Exception("Enter a URL to download from!")
93 chooser = g.FileChooserDialog('Save archive as...', dialog, g.FILE_CHOOSER_ACTION_SAVE)
94 chooser.set_current_name(os.path.basename(url))
95 chooser.add_button(g.STOCK_CANCEL, g.RESPONSE_CANCEL)
96 chooser.add_button(g.STOCK_SAVE, g.RESPONSE_OK)
97 chooser.set_default_response(g.RESPONSE_OK)
98 resp = chooser.run()
99 filename = chooser.get_filename()
100 chooser.destroy()
101 if resp != g.RESPONSE_OK:
102 return
104 DownloadBox(url, filename, local_archive_button)
106 widgets.get_widget('download').connect('clicked', download)
108 def resp(dialog, r):
109 if r == g.RESPONSE_OK:
110 url = widgets.get_widget('archive_url').get_text()
111 if urlparse.urlparse(url)[1] == '':
112 raise Exception('Missing host name in URL "%s"' % url)
113 if urlparse.urlparse(url)[2] == '':
114 raise Exception('Missing resource part in URL "%s"' % url)
115 local_archive = widgets.get_widget('local_archive').get_filename()
116 if not local_archive:
117 raise Exception('Please select a local file')
118 mime_type = get_combo_value(widgets.get_widget('mime_type'))
119 if selection.iter_is_selected(model.get_iter_root()):
120 root = self.tmpdir
121 extract = None
122 else:
123 _, iter = selection.get_selected()
124 extract = model[iter][0]
125 root = os.path.join(self.tmpdir, extract)
127 size = os.path.getsize(local_archive)
128 self.create_archive_element(url, mime_type, root, extract, size)
129 self.destroy_tmp()
130 dialog.destroy()
132 dialog.connect('response', resp)
134 if local_archive:
135 local_archive_button.set_filename(local_archive)
136 initial_url = 'http://SITE/' + os.path.basename(local_archive)
137 widgets.get_widget('archive_url').set_text(initial_url)
139 def destroy_tmp(self):
140 if self.tmpdir:
141 shutil.rmtree(self.tmpdir)
142 self.tmpdir = None
144 def create_archive_element(self, url, mime_type, root, extract, size):
145 alg = manifest.get_algorithm('sha1new')
146 digest = alg.new_digest()
147 for line in alg.generate_manifest(root):
148 digest.update(line + '\n')
149 id = alg.getID(digest)
151 # Add it to the cache if missing
152 # Helps with setting 'main' attribute later
153 try:
154 main.stores.lookup(id)
155 except NotStored:
156 main.stores.add_dir_to_cache(id, root)
158 # Do we already have an implementation with this digest?
159 impl_element = self.feed_editor.find_implementation(id)
161 if impl_element is None:
162 # No. Create a new implementation. Guess the details...
164 leaf = url.split('/')[-1]
165 version = None
166 for m in re.finditer(version_regexp, leaf):
167 match = m.group()[1:]
168 if version is None or len(best) < len(match):
169 version = match
171 impl_element = create_element(self.feed_editor.doc.documentElement, 'implementation')
172 impl_element.setAttribute('id', id)
173 impl_element.setAttribute('released', time.strftime('%Y-%m-%d'))
174 if version: impl_element.setAttribute('version', version)
175 created_impl = True
176 else:
177 created_impl = False
179 archive_element = create_element(impl_element, 'archive')
180 archive_element.setAttribute('size', str(size))
181 archive_element.setAttribute('href', url)
182 if extract: archive_element.setAttribute('extract', extract)
184 self.feed_editor.update_version_model()
186 if created_impl:
187 self.feed_editor.edit_properties(element = impl_element)
189 class DownloadBox:
190 def __init__(self, url, path, archive_button):
191 widgets = gtk.glade.XML(main.gladefile, 'download')
192 gtk.gdk.flush()
194 output = file(path, 'w')
196 dialog = widgets.get_widget('download')
197 progress = widgets.get_widget('progress')
199 cancelled = tasks.Blocker()
200 def resp(box, resp):
201 cancelled.trigger()
202 dialog.connect('response', resp)
204 def download():
205 stream = None
207 def cleanup():
208 dialog.destroy()
209 if output:
210 output.close()
211 if stream:
212 stream.close()
214 try:
215 # (urllib2 is buggy; no fileno)
216 stream = urllib.urlopen(url)
217 size = float(stream.info().get('Content-Length', None))
218 got = 0
220 while True:
221 yield signing.InputBlocker(stream), cancelled
222 if cancelled.happened:
223 raise Exception("Download cancelled at user's request")
224 data = os.read(stream.fileno(), 1024)
225 if not data: break
226 output.write(data)
227 got += len(data)
229 if size:
230 progress.set_fraction(got / size)
231 else:
232 progress.pulse()
233 except:
234 # No finally in python 2.4
235 cleanup()
236 raise
237 else:
238 cleanup()
239 archive_button.set_filename(path)
241 tasks.Task(download())