2 from zeroinstall
.support
import tasks
3 from zeroinstall
.injector
.model
import *
4 from zeroinstall
.injector
.iface_cache
import iface_cache
5 from zeroinstall
.injector
import writer
, namespaces
, gpg
8 from logging
import warn
11 from dialog
import DialogResponse
12 from gui
import Template
13 from impl_list
import ImplementationList
18 _dialogs
= {} # Interface -> Properties
31 def format_para(para
):
32 lines
= [l
.strip() for l
in para
.split('\n')]
33 return ' '.join(lines
)
35 def open_in_browser(link
):
36 browser
= os
.environ
.get('BROWSER', 'firefox')
41 os
.spawnlp(os
.P_NOWAIT
, browser
, browser
, link
)
44 print >>sys
.stderr
, "Error", ex
48 def have_source_for(policy
, interface
):
49 # Note: we don't want to actually fetch the source interfaces at
50 # this point, so we check whether:
51 # - We have a feed of type 'src' (not fetched), or
52 # - We have a source implementation in a regular feed
54 for f
in interface
.feeds
:
55 if f
.machine
== 'src':
57 # Don't have any src feeds. Do we have a source implementation
58 # as part of a regular feed?
59 impls
= interface
.implementations
.values()
60 for f
in policy
.usable_feeds(interface
):
62 feed_iface
= iface_cache
.get_interface(f
.uri
)
63 if feed_iface
.implementations
:
64 impls
.extend(feed_iface
.implementations
.values())
65 except zeroinstall
.NeedDownload
:
66 pass # OK, will get called again later
68 warn("Failed to load feed '%s': %s", f
.uri
, str(ex
))
70 if x
.machine
== 'src':
75 def __init__(self
, widgets
):
76 description
= widgets
.get_widget('description')
77 description
.connect('button-press-event', self
.button_press
)
79 self
.buffer = description
.get_buffer()
80 self
.heading_style
= self
.buffer.create_tag(underline
= True, scale
= 1.2)
81 self
.link_style
= self
.buffer.create_tag(underline
= True, foreground
= 'blue')
82 description
.set_size_request(-1, 100)
84 def button_press(self
, tv
, bev
):
85 if bev
.type == gtk
.gdk
.BUTTON_PRESS
and bev
.button
== 1:
86 x
, y
= tv
.window_to_buffer_coords(tv
.get_window_type(bev
.window
),
87 int(bev
.x
), int(bev
.y
))
88 itr
= tv
.get_iter_at_location(x
, y
)
89 if itr
and self
.link_style
in itr
.get_tags():
90 if not itr
.begins_tag(self
.link_style
):
91 itr
.backward_to_tag_toggle(self
.link_style
)
93 end
.forward_to_tag_toggle(self
.link_style
)
94 target
= itr
.get_text(end
).strip()
95 open_in_browser(target
)
97 def set_details(self
, interface
):
99 heading_style
= self
.heading_style
101 buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
103 iter = buffer.get_start_iter()
105 buffer.insert_with_tags(iter,
106 '%s ' % interface
.get_name(), heading_style
)
107 buffer.insert(iter, '(%s)' % interface
.summary
)
109 buffer.insert(iter, '\n%s\n' % interface
.uri
)
111 # (converts to local time)
112 if interface
.last_modified
:
113 buffer.insert(iter, '\nLast upstream change: %s' % time
.ctime(interface
.last_modified
))
115 if interface
.last_checked
:
116 buffer.insert(iter, '\nLast checked: %s' % time
.ctime(interface
.last_checked
))
118 last_check_attempt
= iface_cache
.get_last_check_attempt(interface
.uri
)
119 if last_check_attempt
:
120 if interface
.last_checked
and interface
.last_checked
>= last_check_attempt
:
121 pass # Don't bother reporting successful attempts
123 buffer.insert(iter, '\nLast check attempt: %s (failed or in progress)' %
124 time
.ctime(last_check_attempt
))
126 buffer.insert_with_tags(iter, '\n\nDescription\n', heading_style
)
128 paragraphs
= [format_para(p
) for p
in (interface
.description
or "-").split('\n\n')]
130 buffer.insert(iter, '\n\n'.join(paragraphs
))
131 buffer.insert(iter, '\n')
134 for x
in interface
.get_metadata(namespaces
.XMLNS_IFACE
, 'homepage'):
136 buffer.insert(iter, '\n')
138 buffer.insert(iter, 'Homepage: ')
139 buffer.insert_with_tags(iter, '%s\n' % x
.content
, self
.link_style
)
141 buffer.insert_with_tags(iter, '\nSignatures\n', heading_style
)
142 sigs
= iface_cache
.get_cached_signatures(interface
.uri
)
145 if isinstance(sig
, gpg
.ValidSig
):
147 details
= sig
.get_details()
149 if item
[0] in ('pub', 'uid') and len(item
) > 9:
152 buffer.insert_with_tags(iter, 'Valid signature by "%s"\n- Dated: %s\n- Fingerprint: %s\n' %
153 (name
, time
.ctime(sig
.get_timestamp()), sig
.fingerprint
))
154 if not sig
.is_trusted():
155 if interface
.uri
.startswith('/'):
156 buffer.insert_with_tags(iter, 'WARNING: This key is not in the trusted list\n')
158 buffer.insert_with_tags(iter, 'WARNING: This key is not in the trusted list (either you removed it, or '
159 'you trust one of the other signatures)\n')
161 buffer.insert_with_tags(iter, '%s\n' % sig
)
163 buffer.insert_with_tags(iter, 'No signature information (old style interface or out-of-date cache)\n')
170 def __init__(self
, policy
, interface
, widgets
):
172 self
.interface
= interface
174 self
.model
= gtk
.ListStore(str, str, bool)
176 self
.description
= Description(widgets
)
178 self
.lines
= self
.build_model()
179 for line
in self
.lines
:
180 self
.model
.append(line
)
182 add_remote_feed_button
= widgets
.get_widget('add_remote_feed')
183 add_remote_feed_button
.connect('clicked', lambda b
: add_remote_feed(policy
, widgets
.get_widget(), interface
))
185 add_local_feed_button
= widgets
.get_widget('add_local_feed')
186 add_local_feed_button
.connect('clicked', lambda b
: add_local_feed(policy
, interface
))
188 self
.remove_feed_button
= widgets
.get_widget('remove_feed')
189 def remove_feed(button
):
190 model
, iter = self
.tv
.get_selection().get_selected()
191 feed_uri
= model
[iter][Feeds
.URI
]
192 for x
in interface
.feeds
:
193 if x
.uri
== feed_uri
:
195 interface
.extra_feeds
.remove(x
)
196 writer
.save_interface(interface
)
200 dialog
.alert(self
.get_toplevel(),
201 _("Can't remove '%s' as you didn't add it.") % feed_uri
)
203 raise Exception("Missing feed '%s'!" % feed_uri
)
204 self
.remove_feed_button
.connect('clicked', remove_feed
)
206 self
.tv
= widgets
.get_widget('feeds_list')
207 self
.tv
.set_model(self
.model
)
208 text
= gtk
.CellRendererText()
209 self
.tv
.append_column(gtk
.TreeViewColumn('Source', text
, text
= Feeds
.URI
, sensitive
= Feeds
.USED
))
210 self
.tv
.append_column(gtk
.TreeViewColumn('Arch', text
, text
= Feeds
.ARCH
, sensitive
= Feeds
.USED
))
212 sel
= self
.tv
.get_selection()
213 sel
.set_mode(gtk
.SELECTION_BROWSE
)
214 sel
.connect('changed', self
.sel_changed
)
215 sel
.select_path((0,))
217 def build_model(self
):
218 usable_feeds
= frozenset(self
.policy
.usable_feeds(self
.interface
))
219 unusable_feeds
= frozenset(self
.interface
.feeds
) - usable_feeds
221 out
= [[self
.interface
.uri
, None, True]]
223 if self
.interface
.feeds
:
224 for feed
in usable_feeds
:
225 out
.append([feed
.uri
, feed
.arch
, True])
226 for feed
in unusable_feeds
:
227 out
.append([feed
.uri
, feed
.arch
, False])
230 def sel_changed(self
, sel
):
231 model
, miter
= sel
.get_selected()
232 if not miter
: return # build in progress
233 iface
= model
[miter
][Feeds
.URI
]
234 # Only enable removing user_override feeds
235 enable_remove
= False
236 for x
in self
.interface
.feeds
:
240 self
.remove_feed_button
.set_sensitive( enable_remove
)
241 self
.description
.set_details(iface_cache
.get_interface(iface
))
244 new_lines
= self
.build_model()
245 if new_lines
!= self
.lines
:
246 self
.lines
= new_lines
248 for line
in self
.lines
:
249 self
.model
.append(line
)
250 self
.tv
.get_selection().select_path((0,))
252 self
.sel_changed(self
.tv
.get_selection())
260 def __init__(self
, policy
, interface
, show_versions
= False):
263 widgets
= Template('interface_properties')
265 self
.interface
= interface
267 window
= widgets
.get_widget('interface_properties')
269 window
.set_title('Properties for ' + interface
.get_name())
270 window
.set_default_size(-1, gtk
.gdk
.screen_height() / 3)
272 self
.compile_button
= widgets
.get_widget('compile')
273 self
.compile_button
.connect('clicked', lambda b
: compile.compile(policy
, interface
))
274 window
.set_default_response(gtk
.RESPONSE_CANCEL
)
276 def response(dialog
, resp
):
277 if resp
== gtk
.RESPONSE_CANCEL
:
279 elif resp
== gtk
.RESPONSE_HELP
:
280 properties_help
.display()
281 window
.connect('response', response
)
283 notebook
= widgets
.get_widget('interface_notebook')
286 feeds
= Feeds(policy
, interface
, widgets
)
288 stability
= widgets
.get_widget('preferred_stability')
289 stability
.set_active(0)
290 if interface
.stability_policy
:
291 i
= [stable
, testing
, developer
].index(interface
.stability_policy
)
293 warn("Unknown stability policy %s", interface
.stability_policy
)
297 stability
.set_active(i
)
299 def set_stability_policy(combo
):
300 i
= stability
.get_active()
304 name
= stability
.get_model()[i
][0].lower()
305 new_stability
= stability_levels
[name
]
306 interface
.set_stability_policy(new_stability
)
307 writer
.save_interface(interface
)
309 stability
.connect('changed', set_stability_policy
)
311 self
.use_list
= ImplementationList(policy
, interface
, widgets
)
315 feeds
.tv
.grab_focus()
321 window
.connect('destroy', lambda s
: policy
.watchers
.remove(updated
))
322 policy
.watchers
.append(updated
)
329 self
.window
.destroy()
331 def shade_compile(self
):
332 self
.compile_button
.set_sensitive(have_source_for(self
.policy
, self
.interface
))
334 def update_list(self
):
335 ranked_items
= self
.policy
.solver
.details
.get(self
.interface
, None)
336 if ranked_items
is None:
337 # The Solver didn't get this far, but we should still display them!
338 ranked_items
= self
.interface
.implementations
.values()
340 self
.use_list
.set_items(ranked_items
)
343 def add_remote_feed(policy
, parent
, interface
):
345 d
= gtk
.MessageDialog(parent
, 0, gtk
.MESSAGE_QUESTION
, gtk
.BUTTONS_CANCEL
,
346 _('Enter the URL of the new source of implementations of this interface:'))
347 d
.add_button(gtk
.STOCK_ADD
, gtk
.RESPONSE_OK
)
348 d
.set_default_response(gtk
.RESPONSE_OK
)
351 align
= gtk
.VBox(False, 0)
352 align
.set_border_width(4)
354 d
.vbox
.pack_start(align
)
355 entry
.set_activates_default(True)
361 error_label
= gtk
.Label('')
362 error_label
.set_padding(4, 4)
363 align
.pack_start(error_label
)
369 error_label
.set_text(message
)
375 got_response
= DialogResponse(d
)
377 tasks
.check(got_response
)
378 resp
= got_response
.response
381 if resp
== gtk
.RESPONSE_OK
:
383 url
= entry
.get_text()
385 raise SafeException(_('Enter a URL'))
386 fetch
= policy
.fetcher
.download_and_import_feed(url
, iface_cache
)
388 d
.set_sensitive(False)
390 d
.set_sensitive(True)
393 iface
= iface_cache
.get_interface(url
)
395 d
.set_sensitive(True)
397 error('Failed to read interface')
399 if not iface
.feed_for
:
400 error("Feed '%s' is not a feed for '%s'." % (iface
.get_name(), interface
.get_name()))
401 elif interface
.uri
not in iface
.feed_for
:
402 error("This is not a feed for '%s'.\nOnly for:\n%s" %
403 (interface
.uri
, '\n'.join(iface
.feed_for
)))
404 elif iface
.uri
in [f
.uri
for f
in interface
.feeds
]:
405 error("Feed from '%s' has already been added!" % iface
.uri
)
407 interface
.extra_feeds
.append(Feed(iface
.uri
, arch
= None, user_override
= True))
408 writer
.save_interface(interface
)
411 except SafeException
, ex
:
416 except Exception, ex
:
418 traceback
.print_exc()
419 policy
.handler
.report_error(ex
)
421 def add_local_feed(policy
, interface
):
422 sel
= gtk
.FileSelection(_('Select XML feed file'))
423 sel
.set_has_separator(False)
425 from xml
.dom
import minidom
426 from zeroinstall
.injector
import reader
427 feed
= sel
.get_filename()
429 feed_targets
= policy
.get_feed_targets(feed
)
430 if interface
not in feed_targets
:
431 raise Exception("Not a valid feed for '%s'; this is a feed for:\n%s" %
433 '\n'.join([f
.uri
for f
in feed_targets
])))
434 if interface
.get_feed(feed
):
435 dialog
.alert(None, 'This feed is already registered.')
437 interface
.extra_feeds
.append(Feed(feed
, user_override
= True, arch
= None))
439 writer
.save_interface(interface
)
441 reader
.update_from_cache(interface
)
443 except Exception, ex
:
444 dialog
.alert(None, "Error in feed file '%s':\n\n%s" % (feed
, str(ex
)))
446 sel
.ok_button
.connect('clicked', ok
)
447 sel
.cancel_button
.connect('clicked', lambda b
: sel
.destroy())
450 def edit(policy
, interface
, show_versions
= False):
451 assert isinstance(interface
, Interface
)
452 if interface
in _dialogs
:
453 _dialogs
[interface
].destroy()
454 _dialogs
[interface
] = Properties(policy
, interface
, show_versions
)
456 properties_help
= help_box
.HelpBox("Injector Properties Help",
457 ('Interface properties', """
458 This window displays information about an interface. There are two tabs at the top: \
459 Feeds shows the places where the injector looks for implementations of the interface, while \
460 Versions shows the list of implementations found (from all feeds) in order of preference."""),
462 ('The Feeds tab', """
463 At the top is a list of feeds. By default, the injector uses the full name of the interface \
464 as the default feed location (so if you ask it to run the program "http://foo/bar.xml" then it will \
465 by default get the list of versions by downloading "http://foo/bar.xml".
467 You can add and remove feeds using the buttons on the right. The main feed may also add \
468 some extra feeds itself. If you've checked out a developer version of a program, you can use \
469 the 'Add Local Feed...' button to let the injector know about it, for example.
471 Below the list of feeds is a box describing the selected one:
473 - At the top is its short name.
474 - Below that is the address (a URL or filename).
475 - 'Last upstream change' shows the version of the cached copy of the interface file.
476 - 'Last checked' is the last time a fresh copy of the upstream interface file was \
478 - Then there is a longer description of the interface."""),
480 ('The Versions tab', """
481 This tab shows a list of all known implementations of the interface, from all the feeds. \
482 The columns have the following meanings:
484 Version gives the version number. High-numbered versions are considered to be \
485 better than low-numbered ones.
487 Released gives the date this entry was added to the feed.
489 Stability is 'stable' if the implementation is believed to be stable, 'buggy' if \
490 it is known to contain serious bugs, and 'testing' if its stability is not yet \
491 known. This information is normally supplied and updated by the author of the \
492 software, but you can override their rating (overridden values are shown in upper-case). \
493 You can also use the special level 'preferred'.
495 C(ached) indicates whether the implementation is already stored on your computer. \
496 In off-line mode, only cached implementations are considered for use.
498 Arch indicates what kind of computer system the implementation is for, or 'any' \
499 if it works with all types of system.
502 The implementations are listed in the injector's currently preferred order (the one \
503 at the top will actually be used). Usable implementations all come before unusable \
506 Unusable ones are those for incompatible \
507 architectures, those marked as 'buggy', versions explicitly marked as incompatible with \
508 another interface you are using and, in off-line mode, uncached implementations. Unusable \
509 implementations are shown crossed out.
511 For the usable implementations, the order is as follows:
513 - Preferred implementations come first.
515 - Then, if network use is set to 'Minimal', cached implementations come before \
518 - Then, implementations at or above the selected stability level come before all others.
520 - Then, higher-numbered versions come before low-numbered ones.
522 - Then cached come before non-cached (for 'Full' network use mode).
526 If there is no binary available for your system then you may be able to compile one from \
527 source by clicking on the Compile button. If no source is available, the Compile button will \