2 Integrates download callbacks with an external mainloop.
3 While things are being downloaded, Zero Install returns control to your program.
4 Your mainloop is responsible for monitoring the state of the downloads and notifying
5 Zero Install when they are complete.
7 To do this, you supply a L{Handler} to the L{policy}.
10 # Copyright (C) 2009, Thomas Leonard
11 # See the README file for details, or visit http://0install.net.
13 from zeroinstall
import _
15 from logging
import debug
, warn
17 from zeroinstall
import NeedDownload
, SafeException
18 from zeroinstall
.support
import tasks
19 from zeroinstall
.injector
import download
21 class NoTrustedKeys(SafeException
):
22 """Thrown by L{Handler.confirm_trust_keys} on failure."""
25 class Handler(object):
27 This implementation uses the GLib mainloop. Note that QT4 can use the GLib mainloop too.
29 @ivar monitored_downloads: dict of downloads in progress
30 @type monitored_downloads: {URL: L{download.Download}}
31 @ivar n_completed_downloads: number of downloads which have finished for GUIs, etc (can be reset as desired).
32 @type n_completed_downloads: int
33 @ivar total_bytes_downloaded: informational counter for GUIs, etc (can be reset as desired). Updated when download finishes.
34 @type total_bytes_downloaded: int
37 __slots__
= ['monitored_downloads', '_loop', 'dry_run', 'total_bytes_downloaded', 'n_completed_downloads']
39 def __init__(self
, mainloop
= None, dry_run
= False):
40 self
.monitored_downloads
= {}
42 self
.dry_run
= dry_run
43 self
.n_completed_downloads
= 0
44 self
.total_bytes_downloaded
= 0
46 def monitor_download(self
, dl
):
47 """Called when a new L{download} is started.
48 This is mainly used by the GUI to display the progress bar."""
50 self
.monitored_downloads
[dl
.url
] = dl
51 self
.downloads_changed()
54 def download_done_stats():
56 # NB: we don't check for exceptions here; someone else should be doing that
58 self
.n_completed_downloads
+= 1
59 self
.total_bytes_downloaded
+= dl
.get_bytes_downloaded_so_far()
60 del self
.monitored_downloads
[dl
.url
]
61 self
.downloads_changed()
66 def impl_added_to_store(self
, impl
):
67 """Called by the L{fetch.Fetcher} when adding an implementation.
68 The GUI uses this to update its display.
69 @param impl: the implementation which has been added
70 @type impl: L{model.Implementation}
74 def downloads_changed(self
):
75 """This is just for the GUI to override to update its display."""
78 def wait_for_blocker(self
, blocker
):
79 """Run a recursive mainloop until blocker is triggered.
80 @param blocker: event to wait on
81 @type blocker: L{tasks.Blocker}"""
82 if not blocker
.happened
:
88 quit
= tasks
.Task(quitter(), "quitter")
90 assert self
._loop
is None # Avoid recursion
91 self
._loop
= gobject
.MainLoop(gobject
.main_context_default())
93 debug(_("Entering mainloop, waiting for %s"), blocker
)
98 assert blocker
.happened
, "Someone quit the main loop!"
102 def get_download(self
, url
, force
= False, hint
= None):
103 """Return the Download object currently downloading 'url'.
104 If no download for this URL has been started, start one now (and
105 start monitoring it).
106 If the download failed and force is False, return it anyway.
107 If force is True, abort any current or failed download and start
109 @rtype: L{download.Download}
112 raise NeedDownload(url
)
115 dl
= self
.monitored_downloads
[url
]
120 dl
= download
.Download(url
, hint
)
121 self
.monitor_download(dl
)
124 def confirm_trust_keys(self
, interface
, sigs
, iface_xml
):
125 """We don't trust any of the signatures yet. Ask the user.
126 When done update the L{trust} database, and then call L{trust.TrustDB.notify}.
127 @arg interface: the interface being updated
128 @arg sigs: a list of signatures (from L{gpg.check_stream})
129 @arg iface_xml: the downloaded data (not yet trusted)
130 @return: a blocker, if confirmation will happen asynchronously, or None
131 @rtype: L{tasks.Blocker}"""
132 from zeroinstall
.injector
import trust
, gpg
134 valid_sigs
= [s
for s
in sigs
if isinstance(s
, gpg
.ValidSig
)]
136 raise SafeException('No valid signatures found on "%s". Signatures:%s' %
137 (interface
.uri
, ''.join(['\n- ' + str(s
) for s
in sigs
])))
139 domain
= trust
.domain_from_url(interface
.uri
)
141 # Ask on stderr, because we may be writing XML to stdout
142 print >>sys
.stderr
, "\nInterface:", interface
.uri
143 print >>sys
.stderr
, "The interface is correctly signed with the following keys:"
145 print >>sys
.stderr
, "-", x
147 if len(valid_sigs
) == 1:
148 print >>sys
.stderr
, "Do you want to trust this key to sign feeds from '%s'?" % domain
150 print >>sys
.stderr
, "Do you want to trust all of these keys to sign feeds from '%s'?" % domain
152 print >>sys
.stderr
, "Trust [Y/N] ",
156 raise NoTrustedKeys(_('Not signed with a trusted key'))
159 for key
in valid_sigs
:
160 print >>sys
.stderr
, "Trusting", key
.fingerprint
, "for", domain
161 trust
.trust_db
.trust_key(key
.fingerprint
, domain
)
163 trust
.trust_db
.notify()
165 def report_error(self
, exception
, tb
= None):
166 """Report an exception to the user.
167 @param exception: the exception to report
168 @type exception: L{SafeException}
169 @param tb: optional traceback
171 warn("%s", str(exception
) or type(exception
))
173 #traceback.print_exception(exception, None, tb)