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