Removed warning about old GnuPG.
[zeroinstall.git] / zeroinstall / injector / handler.py
blob9ae282b37758eb7729c946d778458884343d32c1
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.injector import model, download
17 from zeroinstall.injector.iface_cache import iface_cache
19 class Handler(object):
20 """
21 This implementation of the handler interface uses the GLib mainloop.
23 @ivar monitored_downloads: dict of downloads in progress
24 @type monitored_downloads: {URL: (error_stream, L{download.Download})}
25 """
27 __slots__ = ['monitored_downloads', '_loop', '_loop_errors']
29 def __init__(self, mainloop = None):
30 self.monitored_downloads = {}
31 self._loop = None
32 self._loop_errors = None
34 def monitor_download(self, dl):
35 """Called when a new L{download} is started.
36 Call L{download.Download.start} to start the download and get the error
37 stream, and then call L{download.Download.error_stream_data} whenever
38 you read any data from it, including nothing (end-of-file), which
39 indicates that the download is finished."""
40 error_stream = dl.start()
41 self.monitored_downloads[dl.url] = (error_stream, dl)
43 import gobject
44 gobject.io_add_watch(error_stream.fileno(),
45 gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP,
46 self._error_stream_ready, dl)
48 def _error_stream_ready(self, fd, cond, dl):
49 debug("Download stream for %s is ready...", dl)
50 errors = os.read(fd, 100)
51 if errors:
52 debug("Read data: %s", errors)
53 dl.error_stream_data(errors)
54 return True
55 else:
56 debug("End-of-stream. Download is finished.")
57 del self.monitored_downloads[dl.url]
58 try:
59 dl.error_stream_closed()
60 except Exception, ex:
61 self.report_error(ex)
62 if self._loop and not self.monitored_downloads:
63 # Exit from wait_for_downloads if this was the last one
64 debug("Exiting mainloop")
65 self._loop.quit()
66 return False
68 def wait_for_downloads(self):
69 """Monitor all downloads, waiting until they are complete. This is suitable
70 for use by non-interactive programs.
71 @return: list of error messages, one per failed download (since 0.32)
72 @rtype: [str] or None"""
74 import gobject
76 if self.monitored_downloads:
77 assert self._loop is None # Avoid recursion
78 self._loop_errors = []
79 self._loop = gobject.MainLoop(gobject.main_context_default())
80 try:
81 debug("Entering mainloop, waiting for %d download(s)", len(self.monitored_downloads))
82 self._loop.run()
83 finally:
84 self._loop = None
85 errors = self._loop_errors
86 self._loop_errors = None
87 if errors:
88 return errors
89 else:
90 debug("No downloads in progress, so not waiting")
91 return None
93 def get_download(self, url, force = False):
94 """Return the Download object currently downloading 'url'.
95 If no download for this URL has been started, start one now (and
96 start monitoring it).
97 If the download failed and force is False, return it anyway.
98 If force is True, abort any current or failed download and start
99 a new one.
100 @rtype: L{download.Download}
102 try:
103 e, dl = self.monitored_downloads[url]
104 if dl and force:
105 dl.abort()
106 raise KeyError
107 except KeyError:
108 dl = download.Download(url)
109 self.monitor_download(dl)
110 return dl
112 def confirm_trust_keys(self, interface, sigs, iface_xml):
113 """We don't trust any of the signatures yet. Ask the user.
114 When done update the L{trust} database, and then call L{trust.TrustDB.notify}.
115 @arg interface: the interface being updated
116 @arg sigs: a list of signatures (from L{gpg.check_stream})
117 @arg iface_xml: the downloaded data (not yet trusted)
119 from zeroinstall.injector import trust, gpg
120 assert sigs
121 valid_sigs = [s for s in sigs if isinstance(s, gpg.ValidSig)]
122 if not valid_sigs:
123 raise model.SafeException('No valid signatures found. Signatures:' +
124 ''.join(['\n- ' + str(s) for s in sigs]))
126 domain = trust.domain_from_url(interface.uri)
128 print "\nInterface:", interface.uri
129 print "The interface is correctly signed with the following keys:"
130 for x in valid_sigs:
131 print "-", x
133 if len(valid_sigs) == 1:
134 print "Do you want to trust this key to sign feeds from '%s'?" % domain
135 else:
136 print "Do you want to trust all of these keys to sign feeds from '%s'?" % domain
137 while True:
138 i = raw_input("Trust [Y/N] ")
139 if not i: continue
140 if i in 'Nn':
141 raise model.SafeException('Not signed with a trusted key')
142 if i in 'Yy':
143 break
144 for key in valid_sigs:
145 print "Trusting", key.fingerprint, "for", domain
146 trust.trust_db.trust_key(key.fingerprint, domain)
148 trust.trust_db.notify()
150 def report_error(self, exception):
151 """Report an exception to the user.
152 @param exception: the exception to report
153 @type exception: L{SafeException}
154 @since: 0.25"""
155 if self._loop_errors is None:
156 warn("%s", exception)
157 else:
158 self._loop_errors.append(str(exception))
159 info("%s", exception) # (will get reported later)