Update year to 2009 in various places
[zeroinstall/zeroinstall-rsl.git] / zeroinstall / injector / handler.py
blob916a8c2e039efeaa7cbca8f3bf9fca0f79e3786c
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) 2009, Thomas Leonard
11 # See the README file for details, or visit http://0install.net.
13 from logging import debug, warn
15 from zeroinstall import NeedDownload, SafeException
16 from zeroinstall.support import tasks
17 from zeroinstall.injector import download
19 class NoTrustedKeys(SafeException):
20 """Thrown by L{Handler.confirm_trust_keys} on failure."""
21 pass
23 class Handler(object):
24 """
25 This implementation uses the GLib mainloop. Note that QT4 can use the GLib mainloop too.
27 @ivar monitored_downloads: dict of downloads in progress
28 @type monitored_downloads: {URL: L{download.Download}}
29 @ivar n_completed_downloads: number of downloads which have finished for GUIs, etc (can be reset as desired).
30 @type n_completed_downloads: int
31 @ivar total_bytes_downloaded: informational counter for GUIs, etc (can be reset as desired). Updated when download finishes.
32 @type total_bytes_downloaded: int
33 """
35 __slots__ = ['monitored_downloads', '_loop', 'dry_run', 'total_bytes_downloaded', 'n_completed_downloads']
37 def __init__(self, mainloop = None, dry_run = False):
38 self.monitored_downloads = {}
39 self._loop = None
40 self.dry_run = dry_run
41 self.n_completed_downloads = 0
42 self.total_bytes_downloaded = 0
44 def monitor_download(self, dl):
45 """Called when a new L{download} is started.
46 This is mainly used by the GUI to display the progress bar."""
47 dl.start()
48 self.monitored_downloads[dl.url] = dl
49 self.downloads_changed()
51 @tasks.async
52 def download_done_stats():
53 yield dl.downloaded
54 # NB: we don't check for exceptions here; someone else should be doing that
55 try:
56 self.n_completed_downloads += 1
57 self.total_bytes_downloaded += dl.get_bytes_downloaded_so_far()
58 del self.monitored_downloads[dl.url]
59 self.downloads_changed()
60 except Exception, ex:
61 self.report_error(ex)
62 download_done_stats()
64 def impl_added_to_store(self, impl):
65 """Called by the L{fetch.Fetcher} when adding an implementation.
66 The GUI uses this to update its display.
67 @param impl: the implementation which has been added
68 @type impl: L{model.Implementation}
69 """
70 pass
72 def downloads_changed(self):
73 """This is just for the GUI to override to update its display."""
74 pass
76 def wait_for_blocker(self, blocker):
77 """Run a recursive mainloop until blocker is triggered.
78 @param blocker: event to wait on
79 @type blocker: L{tasks.Blocker}"""
80 if not blocker.happened:
81 import gobject
83 def quitter():
84 yield blocker
85 self._loop.quit()
86 quit = tasks.Task(quitter(), "quitter")
88 assert self._loop is None # Avoid recursion
89 self._loop = gobject.MainLoop(gobject.main_context_default())
90 try:
91 debug("Entering mainloop, waiting for %s", blocker)
92 self._loop.run()
93 finally:
94 self._loop = None
96 assert blocker.happened, "Someone quit the main loop!"
98 tasks.check(blocker)
100 def get_download(self, url, force = False, hint = None):
101 """Return the Download object currently downloading 'url'.
102 If no download for this URL has been started, start one now (and
103 start monitoring it).
104 If the download failed and force is False, return it anyway.
105 If force is True, abort any current or failed download and start
106 a new one.
107 @rtype: L{download.Download}
109 if self.dry_run:
110 raise NeedDownload(url)
112 try:
113 dl = self.monitored_downloads[url]
114 if dl and force:
115 dl.abort()
116 raise KeyError
117 except KeyError:
118 dl = download.Download(url, hint)
119 self.monitor_download(dl)
120 return dl
122 def confirm_trust_keys(self, interface, sigs, iface_xml):
123 """We don't trust any of the signatures yet. Ask the user.
124 When done update the L{trust} database, and then call L{trust.TrustDB.notify}.
125 @arg interface: the interface being updated
126 @arg sigs: a list of signatures (from L{gpg.check_stream})
127 @arg iface_xml: the downloaded data (not yet trusted)
128 @return: a blocker, if confirmation will happen asynchronously, or None
129 @rtype: L{tasks.Blocker}"""
130 from zeroinstall.injector import trust, gpg
131 assert sigs
132 valid_sigs = [s for s in sigs if isinstance(s, gpg.ValidSig)]
133 if not valid_sigs:
134 raise SafeException('No valid signatures found. Signatures:' +
135 ''.join(['\n- ' + str(s) for s in sigs]))
137 domain = trust.domain_from_url(interface.uri)
139 print "\nInterface:", interface.uri
140 print "The interface is correctly signed with the following keys:"
141 for x in valid_sigs:
142 print "-", x
144 if len(valid_sigs) == 1:
145 print "Do you want to trust this key to sign feeds from '%s'?" % domain
146 else:
147 print "Do you want to trust all of these keys to sign feeds from '%s'?" % domain
148 while True:
149 i = raw_input("Trust [Y/N] ")
150 if not i: continue
151 if i in 'Nn':
152 raise NoTrustedKeys('Not signed with a trusted key')
153 if i in 'Yy':
154 break
155 for key in valid_sigs:
156 print "Trusting", key.fingerprint, "for", domain
157 trust.trust_db.trust_key(key.fingerprint, domain)
159 trust.trust_db.notify()
161 def report_error(self, exception, tb = None):
162 """Report an exception to the user.
163 @param exception: the exception to report
164 @type exception: L{SafeException}
165 @param tb: optional traceback
166 @since: 0.25"""
167 warn("%s", exception)
168 #import traceback
169 #traceback.print_exception(exception, None, tb)