1
from zeroinstall.injector.model import *
2
from zeroinstall.injector.iface_cache import iface_cache
3
from zeroinstall.injector import writer, namespaces, gpg
5
import sets # Note: for Python 2.3; frozenset is only in Python 2.4
8
from dialog import Dialog
10
from impl_list import ImplementationList
15
_dialogs = {} # Interface -> Properties
28
def format_para(para):
29
lines = [l.strip() for l in para.split('\n')]
30
return ' '.join(lines)
32
def open_in_browser(link):
33
browser = os.environ.get('BROWSER', 'firefox')
38
os.spawnlp(os.P_NOWAIT, browser, browser, link)
41
print >>sys.stderr, "Error", ex
45
class Description(gtk.ScrolledWindow):
47
gtk.ScrolledWindow.__init__(self, None, None)
48
self.set_shadow_type(gtk.SHADOW_IN)
49
self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
50
description = gtk.TextView()
51
description.set_left_margin(4)
52
description.set_right_margin(4)
53
description.set_wrap_mode(gtk.WRAP_WORD)
54
description.set_editable(False)
55
description.set_cursor_visible(False)
56
description.connect('button-press-event', self.button_press)
59
self.buffer = description.get_buffer()
60
self.heading_style = self.buffer.create_tag(underline = True, scale = 1.2)
61
self.link_style = self.buffer.create_tag(underline = True, foreground = 'blue')
62
description.set_size_request(-1, 100)
64
def button_press(self, tv, bev):
65
if bev.type == gtk.gdk.BUTTON_PRESS and bev.button == 1:
66
x, y = tv.window_to_buffer_coords(tv.get_window_type(bev.window),
67
int(bev.x), int(bev.y))
68
itr = tv.get_iter_at_location(x, y)
69
if itr and self.link_style in itr.get_tags():
70
if not itr.begins_tag(self.link_style):
71
itr.backward_to_tag_toggle(self.link_style)
73
end.forward_to_tag_toggle(self.link_style)
74
target = itr.get_text(end).strip()
75
open_in_browser(target)
77
def set_details(self, interface):
79
heading_style = self.heading_style
81
buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
83
iter = buffer.get_start_iter()
85
buffer.insert_with_tags(iter,
86
'%s ' % interface.get_name(), heading_style)
87
buffer.insert(iter, '(%s)' % interface.summary)
89
buffer.insert(iter, '\n%s\n' % interface.uri)
91
# (converts to local time)
92
if interface.last_modified:
93
buffer.insert(iter, '\nLast upstream change: %s' % time.ctime(interface.last_modified))
95
if interface.last_checked:
96
buffer.insert(iter, '\nLast checked: %s' % time.ctime(interface.last_checked))
98
buffer.insert_with_tags(iter, '\n\nDescription\n', heading_style)
100
paragraphs = [format_para(p) for p in (interface.description or "-").split('\n\n')]
102
buffer.insert(iter, '\n\n'.join(paragraphs))
103
buffer.insert(iter, '\n')
105
if hasattr(interface, 'get_metadata'):
107
for x in interface.get_metadata(namespaces.XMLNS_IFACE, 'homepage'):
109
buffer.insert(iter, '\n')
111
buffer.insert(iter, 'Homepage: ')
112
buffer.insert_with_tags(iter, '%s\n' % x.content, self.link_style)
114
if hasattr(iface_cache, 'get_cached_signatures'):
115
buffer.insert_with_tags(iter, '\nSignatures\n', heading_style)
116
sigs = iface_cache.get_cached_signatures(interface.uri)
119
if isinstance(sig, gpg.ValidSig):
121
if hasattr(sig, 'get_details'):
122
details = sig.get_details()
124
if item[0] in ('pub', 'uid') and len(item) > 9:
127
buffer.insert_with_tags(iter, 'Valid signature by "%s"\n- Dated: %s\n- Fingerprint: %s\n' %
128
(name, time.ctime(sig.get_timestamp()), sig.fingerprint))
129
if not sig.is_trusted():
130
if interface.uri.startswith('/'):
131
buffer.insert_with_tags(iter, 'WARNING: This key is not in the trusted list\n')
133
buffer.insert_with_tags(iter, 'WARNING: This key is not in the trusted list (either you removed it, or '
134
'you trust one of the other signatures)\n')
136
buffer.insert_with_tags(iter, '%s\n' % sig)
138
buffer.insert_with_tags(iter, 'No signature information (old style interface or out-of-date cache)\n')
141
class Feeds(gtk.VPaned):
146
def __init__(self, interface):
147
gtk.VPaned.__init__(self)
148
self.set_border_width(4)
149
self.interface = interface
151
hbox = gtk.HBox(False, 4)
152
self.pack1(hbox, False, False)
154
self.model = gtk.ListStore(str, str, bool)
156
self.lines = self.build_model()
157
for line in self.lines:
158
self.model.append(line)
160
self.swin = gtk.ScrolledWindow()
161
self.swin.set_shadow_type(gtk.SHADOW_IN)
162
self.swin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
163
hbox.pack_start(self.swin, True, True, 0)
165
buttons_vbox = gtk.VButtonBox()
166
buttons_vbox.set_layout(gtk.BUTTONBOX_START)
167
buttons_vbox.set_spacing(4)
169
add_remote_feed_button = dialog.MixedButton(_('Add Remote Feed...'), gtk.STOCK_ADD, 0.0)
170
add_remote_feed_button.connect('clicked',
171
lambda b: add_remote_feed(self.get_toplevel(), interface))
172
buttons_vbox.add(add_remote_feed_button)
174
add_local_feed_button = dialog.MixedButton(_('Add Local Feed...'), gtk.STOCK_ADD, 0.0)
175
add_local_feed_button.connect('clicked', lambda b: add_local_feed(interface))
176
tips.set_tip(add_local_feed_button,
177
_('If you have another implementation of this interface (e.g., a '
178
'CVS checkout), you can add it to the list by registering the XML '
179
'feed file that came with it.'))
180
buttons_vbox.add(add_local_feed_button)
182
self.remove_feed_button = dialog.MixedButton(_('Remove Feed'), gtk.STOCK_REMOVE, 0.0)
183
def remove_feed(button):
184
model, iter = self.tv.get_selection().get_selected()
185
feed_uri = model[iter][Feeds.URI]
186
for x in interface.feeds:
187
if x.uri == feed_uri:
189
interface.feeds.remove(x)
190
writer.save_interface(interface)
194
dialog.alert(self.get_toplevel(),
195
_("Can't remove '%s' as you didn't add it.") % feed_uri)
197
raise Exception("Missing feed '%s'!" % feed_uri)
198
self.remove_feed_button.connect('clicked', remove_feed)
199
buttons_vbox.add(self.remove_feed_button)
201
hbox.pack_start(buttons_vbox, False, True, 0)
203
self.tv = gtk.TreeView(self.model)
204
text = gtk.CellRendererText()
205
self.tv.append_column(gtk.TreeViewColumn('Source', text, text = Feeds.URI, sensitive = Feeds.USED))
206
self.tv.append_column(gtk.TreeViewColumn('Arch', text, text = Feeds.ARCH, sensitive = Feeds.USED))
207
self.swin.add(self.tv)
209
self.description = Description()
210
self.add2(self.description)
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 = sets.ImmutableSet(policy.usable_feeds(self.interface))
219
unusable_feeds = sets.ImmutableSet(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, iter = sel.get_selected()
232
if not iter: return # build in progress
233
iface = model[iter][Feeds.URI]
234
self.remove_feed_button.set_sensitive(iface != self.interface.uri)
235
self.description.set_details(policy.get_interface(iface))
238
new_lines = self.build_model()
239
if new_lines != self.lines:
240
self.lines = new_lines
242
for line in self.lines:
243
self.model.append(line)
244
self.tv.get_selection().select_path((0,))
246
self.sel_changed(self.tv.get_selection())
248
class Properties(Dialog):
252
def __init__(self, interface):
253
Dialog.__init__(self)
254
self.interface = interface
255
self.set_title('Interface ' + interface.get_name())
256
self.set_default_size(-1,
257
gtk.gdk.screen_height() / 3)
259
self.add_button(gtk.STOCK_HELP, gtk.RESPONSE_HELP)
260
self.compile_button = self.add_mixed_button(_('Compile'),
261
gtk.STOCK_CONVERT, COMPILE)
262
self.compile_button.connect('clicked', lambda b: compile.compile(interface))
263
self.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CANCEL)
264
self.set_default_response(gtk.RESPONSE_CANCEL)
266
def response(dialog, resp):
267
if resp == gtk.RESPONSE_CANCEL:
270
# policy.begin_iface_download(interface, True)
271
elif resp == gtk.RESPONSE_HELP:
272
properties_help.display()
273
self.connect('response', response)
275
notebook = gtk.Notebook()
276
self.vbox.pack_start(notebook, True, True, 0)
278
feeds = Feeds(interface)
279
notebook.append_page(feeds, gtk.Label(_('Feeds')))
280
notebook.append_page(self.build_versions_column(interface), gtk.Label(_('Versions')))
285
feeds.tv.grab_focus()
291
self.connect('destroy', lambda s: policy.watchers.remove(updated))
292
policy.watchers.append(updated)
295
def shade_compile(self):
296
# Note: we don't want to actually fetch the source interfaces at
297
# this point, so we check whether:
298
# - We have a feed of type 'src' (not fetched), or
299
# - We have a source implementation in a regular feed
301
for f in self.interface.feeds:
302
if f.machine == 'src':
305
if have_src is False:
306
# Don't have any src feeds. Do we have a source implementation
307
# as part of a regular feed?
308
impls = self.interface.implementations.values()
309
for f in policy.usable_feeds(self.interface):
311
feed_iface = policy.get_interface(f.uri)
312
if feed_iface.implementations:
313
impls.extend(feed_iface.implementations.values())
315
pass # OK, will get called again later
316
except Exception, ex:
317
warn("Failed to load feed '%s': %s", f.uri, str(ex))
319
if x.machine == 'src':
322
self.compile_button.set_sensitive(have_src)
324
def update_list(self):
325
impls = policy.get_ranked_implementations(self.interface)
326
self.use_list.set_items(impls)
328
def build_versions_column(self, interface):
329
assert self.use_list is None
331
vbox = gtk.VBox(False, 2)
332
vbox.set_border_width(4)
334
hbox = gtk.HBox(False, 2)
335
vbox.pack_start(hbox, False, True, 2)
338
stability = gtk.combo_box_new_text()
340
stability.append_text('Use default setting')
341
stability.set_active(0)
342
for i, x in enumerate((stable, testing, developer)):
343
stability.append_text(str(x).capitalize())
344
if x is interface.stability_policy:
345
stability.set_active(i + 1)
346
hbox.pack_start(gtk.Label('Preferred stability:'), False, True, 2)
347
hbox.pack_start(eb, False, True, 0)
348
def set_stability_policy(combo):
349
i = stability.get_active()
353
name = stability.get_model()[i][0].lower()
354
new_stability = stability_levels[name]
355
interface.set_stability_policy(new_stability)
356
writer.save_interface(interface)
358
stability.connect('changed', set_stability_policy)
359
tips.set_tip(eb, _('Implementations at this stability level or higher '
360
'will be used in preference to others. You can use this '
361
'to override the global "Help test new versions" setting '
362
'just for this interface.'))
364
self.use_list = ImplementationList(interface)
365
vbox.pack_start(self.use_list, True, True, 2)
369
def add_remote_feed(parent, interface):
370
d = gtk.MessageDialog(parent, 0, gtk.MESSAGE_QUESTION, gtk.BUTTONS_CANCEL,
371
_('Enter the URL of the new source of implementations of this interface:'))
372
d.add_button(gtk.STOCK_ADD, gtk.RESPONSE_OK)
373
d.set_default_response(gtk.RESPONSE_OK)
376
align = gtk.VBox(False, 0)
377
align.set_border_width(4)
379
d.vbox.pack_start(align)
380
entry.set_activates_default(True)
386
error_label = gtk.Label('')
387
error_label.set_padding(4, 4)
388
align.pack_start(error_label)
392
error_label.set_text(message)
397
def download_done(iface):
398
d.set_sensitive(True)
400
error('Failed to read interface')
402
if not iface.feed_for:
403
error("Interface '%s' is not a feed." % iface.get_name())
404
elif interface.uri not in iface.feed_for:
405
error("Interface is not a feed for '%s'.\nOnly for:\n%s" %
406
(interface.uri, '\n'.join(iface.feed_for)))
407
elif iface.uri in [f.uri for f in interface.feeds]:
408
error("Feed from '%s' has already been added!" % iface.uri)
410
interface.feeds.append(Feed(iface.uri, arch = None, user_override = True))
411
writer.save_interface(interface)
415
def response(d, resp):
417
if resp == gtk.RESPONSE_OK:
419
url = entry.get_text()
421
raise SafeException(_('Enter a URL'))
422
iface = policy.get_interface(url)
423
policy.begin_iface_download(iface) # Force a refresh
424
d.set_sensitive(False)
425
policy.add_dl_callback(url, lambda: download_done(iface))
426
except SafeException, ex:
431
d.connect('response', response)
434
def add_local_feed(interface):
435
sel = gtk.FileSelection(_('Select XML feed file'))
436
sel.set_has_separator(False)
438
from xml.dom import minidom
439
from zeroinstall.injector import reader
440
feed = sel.get_filename()
442
if hasattr(policy, 'get_feed_targets'):
443
feed_targets = policy.get_feed_targets(feed)
444
if interface not in feed_targets:
445
raise Exception("Not a valid feed for '%s'; this is a feed for:\n%s" %
447
'\n'.join([f.uri for f in feed_targets])))
448
if interface.get_feed(feed):
449
dialog.alert(None, 'This feed is already registered.')
451
interface.feeds.append(Feed(feed, user_override = True, arch = None))
453
doc = minidom.parse(feed)
454
uri = doc.documentElement.getAttribute('uri')
456
raise Exception("Missing uri attribute in interface file '%s'" % feed)
457
if uri != interface.uri:
458
raise Exception("Feed is for interface '%s', not '%s'" %
459
(uri, interface.uri))
460
if feed in interface.feeds:
461
raise Exception("Feed is already registered")
462
interface.feeds.append(feed)
463
writer.save_interface(interface)
465
reader.update_from_cache(interface)
467
except Exception, ex:
468
dialog.alert(None, "Error in feed file '%s':\n\n%s" % (feed, str(ex)))
470
sel.ok_button.connect('clicked', ok)
471
sel.cancel_button.connect('clicked', lambda b: sel.destroy())
475
assert isinstance(interface, Interface)
476
if interface in _dialogs:
477
_dialogs[interface].destroy()
478
_dialogs[interface] = Properties(interface)
479
_dialogs[interface].show()
481
properties_help = help_box.HelpBox("Injector Properties Help",
482
('Interface properties', """
483
This window displays information about an interface. There are two tabs at the top: \
484
Feeds shows the places where the injector looks for implementations of the interface, while \
485
Versions shows the list of implementations found (from all feeds) in order of preference."""),
487
('The Feeds tab', """
488
At the top is a list of feeds. By default, the injector uses the full name of the interface \
489
as the default feed location (so if you ask it to run the program "http://foo/bar.xml" then it will \
490
by default get the list of versions by downloading "http://foo/bar.xml".
492
You can add and remove feeds using the buttons on the right. The main feed may also add \
493
some extra feeds itself. If you've checked out a developer version of a program, you can use \
494
the 'Add Local Feed...' button to let the injector know about it, for example.
496
Below the list of feeds is a box describing the selected one:
498
- At the top is its short name.
499
- Below that is the address (a URL or filename).
500
- 'Last upstream change' shows the version of the cached copy of the interface file.
501
- 'Last checked' is the last time a fresh copy of the upstream interface file was \
503
- Then there is a longer description of the interface."""),
505
('The Versions tab', """
506
This tab shows a list of all known implementations of the interface, from all the feeds. \
507
The columns have the following meanings:
509
Version gives the version number. High-numbered versions are considered to be \
510
better than low-numbered ones.
512
Released gives the date this entry was added to the feed.
514
Stability is 'stable' if the implementation is believed to be stable, 'buggy' if \
515
it is known to contain serious bugs, and 'testing' if its stability is not yet \
516
known. This information is normally supplied and updated by the author of the \
517
software, but you can override their rating (overridden values are shown in upper-case). \
518
You can also use the special level 'preferred'.
520
C(ached) indicates whether the implementation is already stored on your computer. \
521
In off-line mode, only cached implementations are considered for use.
523
Arch indicates what kind of computer system the implementation is for, or 'any' \
524
if it works with all types of system.
527
The implementations are listed in the injector's currently preferred order (the one \
528
at the top will actually be used). Usable implementations all come before unusable \
531
Unusable ones are those for incompatible \
532
architectures, those marked as 'buggy', versions explicitly marked as incompatible with \
533
another interface you are using and, in off-line mode, uncached implementations. Unusable \
534
implementations are shown crossed out.
536
For the usable implementations, the order is as follows:
538
- Preferred implementations come first.
540
- Then, if network use is set to 'Minimal', cached implementations come before \
543
- Then, implementations at or above the selected stability level come before all others.
545
- Then, higher-numbered versions come before low-numbered ones.
547
- Then cached come before non-cached (for 'Full' network use mode).
551
If there is no binary available for your system then you may be able to compile one from \
552
source by clicking on the Compile button. If no source is available, the Compile button will \