1 from xml
.dom
import Node
, minidom
4 import rox
, os
, sys
, urlparse
, tempfile
, shutil
, time
, urllib
5 from rox
import g
, tasks
8 from zeroinstall
.injector
import model
9 from zeroinstall
.zerostore
import unpack
, manifest
, NotStored
11 from logging
import warn
13 from xmltools
import *
16 dotted_ints
= '[0-9]+(.[0-9]+)*'
17 version_regexp
= '[^a-zA-Z0-9](%s)(-(pre|rc|post|)%s)*' % (dotted_ints
, dotted_ints
)
19 def get_combo_value(combo
):
20 i
= combo
.get_active()
24 watch
= gtk
.gdk
.Cursor(gtk
.gdk
.WATCH
)
26 def autopackage_get_details(package
):
28 type = 'application/x-bzip-compressed-tar'
29 for line
in file(package
):
30 if line
.startswith('export dataSize=') or line
.startswith('export data_size='):
31 size
= os
.path
.getsize(package
) - int(line
.split('"', 2)[1])
32 elif line
.startswith('compression=') and 'lzma' in line
:
33 type = 'application/x-lzma-compressed-tar'
34 if line
.startswith('## END OF STUB'): break
36 raise Exception("Can't find payload in autopackage (missing 'dataSize')")
39 def try_parse_version(version_str
):
41 return model
.parse_version(version_str
)
42 except SafeException
, ex
:
43 warn("Bad version", ex
)
47 def __init__(self
, feed_editor
, local_archive
= None):
48 self
.feed_editor
= feed_editor
50 self
.mime_type
= self
.start_offset
= None
52 widgets
= gtk
.glade
.XML(main
.gladefile
, 'add_archive')
54 tree
= widgets
.get_widget('extract_list')
55 model
= g
.TreeStore(str)
57 selection
= tree
.get_selection()
58 selection
.set_mode(g
.SELECTION_BROWSE
)
60 cell
= g
.CellRendererText()
61 col
= g
.TreeViewColumn('Extract', cell
)
62 col
.add_attribute(cell
, 'text', 0)
63 tree
.append_column(col
)
65 dialog
= widgets
.get_widget('add_archive')
67 mime_type
= widgets
.get_widget('mime_type')
68 mime_type
.set_active(0)
70 def local_archive_changed(chooser
):
72 path
= chooser
.get_filename()
73 widgets
.get_widget('subdirectory_frame').set_sensitive(False)
77 if mime_type
.get_active() == 0:
80 type = mime_type
.get_active_text()
82 archive_url
= widgets
.get_widget('archive_url')
83 url
= archive_url
.get_text()
85 url
= 'http://SITE/' + os
.path
.basename(path
)
86 archive_url
.set_text(url
)
90 if url
.endswith('.package'):
93 type = unpack
.type_from_url(url
)
95 if type == 'Autopackage':
96 # Autopackage isn't a real type. Examine the .package file
97 # and find out what it really is.
98 start_offset
, type = autopackage_get_details(path
)
100 self
.tmpdir
= tempfile
.mkdtemp('-0publish-gui')
102 # Must be readable to helper process running as 'zeroinst'...
103 old_umask
= os
.umask(0022)
105 unpack_dir
= os
.path
.join(self
.tmpdir
, 'unpacked')
108 dialog
.window
.set_cursor(watch
)
111 unpack
.unpack_archive(url
, file(path
), unpack_dir
,
112 type = type, start_offset
= start_offset
)
114 dialog
.window
.set_cursor(None)
118 chooser
.unselect_filename(path
)
121 iter = model
.append(None, ['Everything'])
122 items
= os
.listdir(unpack_dir
)
124 model
.append(iter, [f
])
126 # Choose a sensible default
127 iter = model
.get_iter_root()
128 if len(items
) == 1 and \
129 os
.path
.isdir(os
.path
.join(unpack_dir
, items
[0])) and \
130 items
[0] not in ('usr', 'opt', 'bin', 'etc', 'sbin', 'doc', 'var'):
131 iter = model
.iter_children(iter)
132 selection
.select_iter(iter)
134 self
.mime_type
= type
135 self
.start_offset
= start_offset
136 widgets
.get_widget('subdirectory_frame').set_sensitive(True)
138 local_archive_button
= widgets
.get_widget('local_archive')
139 local_archive_button
.connect('selection-changed', local_archive_changed
)
140 widgets
.get_widget('subdirectory_frame').set_sensitive(False)
142 def download(button
):
143 url
= widgets
.get_widget('archive_url').get_text()
145 raise Exception("Enter a URL to download from!")
147 chooser
= g
.FileChooserDialog('Save archive as...', dialog
, g
.FILE_CHOOSER_ACTION_SAVE
)
148 chooser
.set_current_name(os
.path
.basename(url
))
149 chooser
.add_button(g
.STOCK_CANCEL
, g
.RESPONSE_CANCEL
)
150 chooser
.add_button(g
.STOCK_SAVE
, g
.RESPONSE_OK
)
151 chooser
.set_default_response(g
.RESPONSE_OK
)
153 filename
= chooser
.get_filename()
155 if resp
!= g
.RESPONSE_OK
:
158 DownloadBox(url
, filename
, local_archive_button
)
160 widgets
.get_widget('download').connect('clicked', download
)
163 if r
== g
.RESPONSE_OK
:
164 unpack_dir
= os
.path
.join(self
.tmpdir
, 'unpacked')
166 url
= widgets
.get_widget('archive_url').get_text()
167 if urlparse
.urlparse(url
)[1] == '':
168 raise Exception('Missing host name in URL "%s"' % url
)
169 if urlparse
.urlparse(url
)[2] == '':
170 raise Exception('Missing resource part in URL "%s"' % url
)
171 local_archive
= widgets
.get_widget('local_archive').get_filename()
172 if not local_archive
:
173 raise Exception('Please select a local file')
174 if selection
.iter_is_selected(model
.get_iter_root()):
178 _
, iter = selection
.get_selected()
179 extract
= model
[iter][0]
180 root
= os
.path
.join(unpack_dir
, extract
)
182 size
= os
.path
.getsize(local_archive
)
183 if self
.start_offset
:
184 size
-= self
.start_offset
185 self
.create_archive_element(url
, self
.mime_type
, root
, extract
, size
,
190 dialog
.connect('response', resp
)
193 local_archive_button
.set_filename(local_archive
)
194 initial_url
= 'http://SITE/' + os
.path
.basename(local_archive
)
195 widgets
.get_widget('archive_url').set_text(initial_url
)
197 def destroy_tmp(self
):
199 shutil
.rmtree(self
.tmpdir
)
202 def create_archive_element(self
, url
, mime_type
, root
, extract
, size
, start_offset
):
203 alg
= manifest
.get_algorithm('sha1new')
204 digest
= alg
.new_digest()
205 for line
in alg
.generate_manifest(root
):
206 digest
.update(line
+ '\n')
207 id = alg
.getID(digest
)
209 # Add it to the cache if missing
210 # Helps with setting 'main' attribute later
212 main
.stores
.lookup(id)
214 main
.stores
.add_dir_to_cache(id, root
)
216 # Do we already have an implementation with this digest?
217 impl_element
= self
.feed_editor
.find_implementation(id)
219 if impl_element
is None:
220 # No. Create a new implementation. Guess the details...
222 leaf
= url
.split('/')[-1]
224 for m
in re
.finditer(version_regexp
, leaf
):
225 match
= m
.group()[1:]
226 if version
is None or len(version
) < len(match
):
229 existing_versions
= self
.feed_editor
.list_versions()
231 if existing_versions
and version
:
232 parsed_version
= try_parse_version(version
)
234 older_versions
= [(v
, elem
) for v
, elem
in existing_versions
if v
< parsed_version
]
236 impl_element
= self
.feed_editor
.doc
.createElementNS(XMLNS_INTERFACE
, 'implementation')
239 # Try to add it just after the previous version's element in the XML document
240 insert_after(impl_element
, max(older_versions
)[1])
241 elif existing_versions
:
242 # Else add it before the first
243 insert_before(impl_element
, min(existing_versions
)[1])
246 insert_element(impl_element
, self
.feed_editor
.doc
.documentElement
)
248 impl_element
.setAttribute('id', id)
249 impl_element
.setAttribute('released', time
.strftime('%Y-%m-%d'))
250 if version
: impl_element
.setAttribute('version', version
)
255 archive_element
= create_element(impl_element
, 'archive')
256 archive_element
.setAttribute('size', str(size
))
257 archive_element
.setAttribute('href', url
)
258 if extract
: archive_element
.setAttribute('extract', extract
)
259 if mime_type
: archive_element
.setAttribute('type', mime_type
)
260 if start_offset
: archive_element
.setAttribute('start-offset', str(start_offset
))
262 self
.feed_editor
.update_version_model()
265 self
.feed_editor
.edit_properties(element
= impl_element
)
268 def __init__(self
, url
, path
, archive_button
):
269 widgets
= gtk
.glade
.XML(main
.gladefile
, 'download')
272 output
= file(path
, 'w')
274 dialog
= widgets
.get_widget('download')
275 progress
= widgets
.get_widget('progress')
277 cancelled
= tasks
.Blocker()
280 dialog
.connect('response', resp
)
293 # (urllib2 is buggy; no fileno)
294 stream
= urllib
.urlopen(url
)
295 size
= float(stream
.info().get('Content-Length', None))
299 yield signing
.InputBlocker(stream
), cancelled
300 if cancelled
.happened
:
301 raise Exception("Download cancelled at user's request")
302 data
= os
.read(stream
.fileno(), 1024)
308 progress
.set_fraction(got
/ size
)
312 # No finally in python 2.4
317 archive_button
.set_filename(path
)
319 tasks
.Task(download())