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