Improve progress bar display when downloading.
[zeroinstall/zeroinstall-mseaborn.git] / zeroinstall / injector / handler.py
bloba05a1f908652d51d05b03993530f49b71d802df9
1 """
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}.
8 """
10 # Copyright (C) 2006, Thomas Leonard
11 # See the README file for details, or visit http://0install.net.
13 import os, sys
14 from logging import debug, info, warn
16 from zeroinstall import NeedDownload
17 from zeroinstall.support import tasks
18 from zeroinstall.injector import model, download
19 from zeroinstall.injector.iface_cache import iface_cache
21 class Handler(object):
22 """
23 This implementation of the handler interface uses the GLib mainloop.
25 @ivar monitored_downloads: dict of downloads in progress
26 @type monitored_downloads: {URL: L{download.Download}}
27 @ivar n_completed_downloads: number of downloads which have finished for GUIs, etc (can be reset as desired).
28 @type n_completed_downloads: int
29 @ivar total_bytes_downloaded: informational counter for GUIs, etc (can be reset as desired). Updated when download finishes.
30 @type total_bytes_downloaded: int
31 """
33 __slots__ = ['monitored_downloads', '_loop', 'dry_run', 'total_bytes_downloaded', 'n_completed_downloads']
35 def __init__(self, mainloop = None, dry_run = False):
36 self.monitored_downloads = {}
37 self._loop = None
38 self.dry_run = dry_run
39 self.n_completed_downloads = 0
40 self.total_bytes_downloaded = 0
42 def monitor_download(self, dl):
43 """Called when a new L{download} is started.
44 This is mainly used by the GUI to display the progress bar."""
45 dl.start()
46 self.monitored_downloads[dl.url] = dl
47 self.downloads_changed()
49 @tasks.async
50 def download_done():
51 yield dl.downloaded
52 # NB: we don't check for exceptions here; someone else should be doing that
53 try:
54 self.n_completed_downloads += 1
55 self.total_bytes_downloaded += dl.get_bytes_downloaded_so_far()
56 del self.monitored_downloads[dl.url]
57 self.downloads_changed()
58 except Exception, ex:
59 self.report_error(ex)
60 download_done()
62 def downloads_changed(self):
63 # This is just for the GUI to override
64 pass
66 def wait_for_blocker(self, blocker):
67 if not blocker.happened:
68 import gobject
70 def quitter():
71 yield blocker
72 self._loop.quit()
73 quit = tasks.Task(quitter(), "quitter")
75 assert self._loop is None # Avoid recursion
76 self._loop = gobject.MainLoop(gobject.main_context_default())
77 try:
78 debug("Entering mainloop, waiting for %s", blocker)
79 self._loop.run()
80 finally:
81 self._loop = None
83 assert blocker.happened, "Someone quit the main loop!"
85 tasks.check(blocker)
87 def get_download(self, url, force = False):
88 """Return the Download object currently downloading 'url'.
89 If no download for this URL has been started, start one now (and
90 start monitoring it).
91 If the download failed and force is False, return it anyway.
92 If force is True, abort any current or failed download and start
93 a new one.
94 @rtype: L{download.Download}
95 """
96 if self.dry_run:
97 raise NeedDownload(url)
99 try:
100 dl = self.monitored_downloads[url]
101 if dl and force:
102 dl.abort()
103 raise KeyError
104 except KeyError:
105 dl = download.Download(url)
106 self.monitor_download(dl)
107 return dl
109 def confirm_trust_keys(self, interface, sigs, iface_xml):
110 """We don't trust any of the signatures yet. Ask the user.
111 When done update the L{trust} database, and then call L{trust.TrustDB.notify}.
112 @arg interface: the interface being updated
113 @arg sigs: a list of signatures (from L{gpg.check_stream})
114 @arg iface_xml: the downloaded data (not yet trusted)
115 @return: a blocker, if confirmation will happen asynchronously, or None
117 from zeroinstall.injector import trust, gpg
118 assert sigs
119 valid_sigs = [s for s in sigs if isinstance(s, gpg.ValidSig)]
120 if not valid_sigs:
121 raise model.SafeException('No valid signatures found. Signatures:' +
122 ''.join(['\n- ' + str(s) for s in sigs]))
124 domain = trust.domain_from_url(interface.uri)
126 print "\nInterface:", interface.uri
127 print "The interface is correctly signed with the following keys:"
128 for x in valid_sigs:
129 print "-", x
131 if len(valid_sigs) == 1:
132 print "Do you want to trust this key to sign feeds from '%s'?" % domain
133 else:
134 print "Do you want to trust all of these keys to sign feeds from '%s'?" % domain
135 while True:
136 i = raw_input("Trust [Y/N] ")
137 if not i: continue
138 if i in 'Nn':
139 raise model.SafeException('Not signed with a trusted key')
140 if i in 'Yy':
141 break
142 for key in valid_sigs:
143 print "Trusting", key.fingerprint, "for", domain
144 trust.trust_db.trust_key(key.fingerprint, domain)
146 trust.trust_db.notify()
148 def report_error(self, exception):
149 """Report an exception to the user.
150 @param exception: the exception to report
151 @type exception: L{SafeException}
152 @since: 0.25"""
153 warn("%s", exception)