Package zeroinstall :: Package injector :: Module handler
[frames] | no frames]

Source Code for Module zeroinstall.injector.handler

  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. 
  6   
  7  To do this, you supply a L{Handler} to the L{policy}. 
  8  """ 
  9   
 10  # Copyright (C) 2009, Thomas Leonard 
 11  # See the README file for details, or visit http://0install.net. 
 12   
 13  from __future__ import print_function 
 14   
 15  from zeroinstall import _ 
 16  import sys 
 17  from logging import warn, info 
 18   
 19  from zeroinstall import SafeException 
 20  from zeroinstall.support import tasks 
 21  from zeroinstall.injector import download 
22 23 -class NoTrustedKeys(SafeException):
24 """Thrown by L{Handler.confirm_import_feed} on failure.""" 25 pass
26
27 -class Handler(object):
28 """ 29 A Handler is used to interact with the user (e.g. to confirm keys, display download progress, etc). 30 31 @ivar monitored_downloads: set of downloads in progress 32 @type monitored_downloads: {L{download.Download}} 33 @ivar n_completed_downloads: number of downloads which have finished for GUIs, etc (can be reset as desired). 34 @type n_completed_downloads: int 35 @ivar total_bytes_downloaded: informational counter for GUIs, etc (can be reset as desired). Updated when download finishes. 36 @type total_bytes_downloaded: int 37 @ivar dry_run: instead of starting a download, just report what we would have downloaded 38 @type dry_run: bool 39 """ 40 41 __slots__ = ['monitored_downloads', 'dry_run', 'total_bytes_downloaded', 'n_completed_downloads'] 42
43 - def __init__(self, mainloop = None, dry_run = False):
44 self.monitored_downloads = set() 45 self.dry_run = dry_run 46 self.n_completed_downloads = 0 47 self.total_bytes_downloaded = 0
48
49 - def monitor_download(self, dl):
50 """Called when a new L{download} is started. 51 This is mainly used by the GUI to display the progress bar.""" 52 self.monitored_downloads.add(dl) 53 self.downloads_changed() 54 55 @tasks.async 56 def download_done_stats(): 57 yield dl.downloaded 58 # NB: we don't check for exceptions here; someone else should be doing that 59 try: 60 self.n_completed_downloads += 1 61 self.total_bytes_downloaded += dl.get_bytes_downloaded_so_far() 62 self.monitored_downloads.remove(dl) 63 self.downloads_changed() 64 except Exception as ex: 65 self.report_error(ex)
66 download_done_stats()
67
68 - def impl_added_to_store(self, impl):
69 """Called by the L{fetch.Fetcher} when adding an implementation. 70 The GUI uses this to update its display. 71 @param impl: the implementation which has been added 72 @type impl: L{model.Implementation} 73 """ 74 pass
75
76 - def downloads_changed(self):
77 """This is just for the GUI to override to update its display.""" 78 pass
79
80 - def wait_for_blocker(self, blocker):
81 """@deprecated: use tasks.wait_for_blocker instead""" 82 tasks.wait_for_blocker(blocker)
83 84 @tasks.async
85 - def confirm_import_feed(self, pending, valid_sigs):
86 """Sub-classes should override this method to interact with the user about new feeds. 87 If multiple feeds need confirmation, L{trust.TrustMgr.confirm_keys} will only invoke one instance of this 88 method at a time. 89 @param pending: the new feed to be imported 90 @type pending: L{PendingFeed} 91 @param valid_sigs: maps signatures to a list of fetchers collecting information about the key 92 @type valid_sigs: {L{gpg.ValidSig} : L{fetch.KeyInfoFetcher}} 93 @since: 0.42""" 94 from zeroinstall.injector import trust 95 96 assert valid_sigs 97 98 domain = trust.domain_from_url(pending.url) 99 100 # Ask on stderr, because we may be writing XML to stdout 101 print(_("Feed: %s") % pending.url, file=sys.stderr) 102 print(_("The feed is correctly signed with the following keys:"), file=sys.stderr) 103 for x in valid_sigs: 104 print("-", x, file=sys.stderr) 105 106 def text(parent): 107 text = "" 108 for node in parent.childNodes: 109 if node.nodeType == node.TEXT_NODE: 110 text = text + node.data 111 return text
112 113 shown = set() 114 key_info_fetchers = valid_sigs.values() 115 while key_info_fetchers: 116 old_kfs = key_info_fetchers 117 key_info_fetchers = [] 118 for kf in old_kfs: 119 infos = set(kf.info) - shown 120 if infos: 121 if len(valid_sigs) > 1: 122 print("%s: " % kf.fingerprint) 123 for key_info in infos: 124 print("-", text(key_info), file=sys.stderr) 125 shown.add(key_info) 126 if kf.blocker: 127 key_info_fetchers.append(kf) 128 if key_info_fetchers: 129 for kf in key_info_fetchers: print(kf.status, file=sys.stderr) 130 stdin = tasks.InputBlocker(0, 'console') 131 blockers = [kf.blocker for kf in key_info_fetchers] + [stdin] 132 yield blockers 133 for b in blockers: 134 try: 135 tasks.check(b) 136 except Exception as ex: 137 warn(_("Failed to get key info: %s"), ex) 138 if stdin.happened: 139 print(_("Skipping remaining key lookups due to input from user"), file=sys.stderr) 140 break 141 if not shown: 142 print(_("Warning: Nothing known about this key!"), file=sys.stderr) 143 144 if len(valid_sigs) == 1: 145 print(_("Do you want to trust this key to sign feeds from '%s'?") % domain, file=sys.stderr) 146 else: 147 print(_("Do you want to trust all of these keys to sign feeds from '%s'?") % domain, file=sys.stderr) 148 while True: 149 print(_("Trust [Y/N] "), end=' ', file=sys.stderr) 150 i = raw_input() 151 if not i: continue 152 if i in 'Nn': 153 raise NoTrustedKeys(_('Not signed with a trusted key')) 154 if i in 'Yy': 155 break 156 for key in valid_sigs: 157 print(_("Trusting %(key_fingerprint)s for %(domain)s") % {'key_fingerprint': key.fingerprint, 'domain': domain}, file=sys.stderr) 158 trust.trust_db.trust_key(key.fingerprint, domain) 159 160 @tasks.async
161 - def confirm_install(self, msg):
162 """We need to check something with the user before continuing with the install. 163 @raise download.DownloadAborted: if the user cancels""" 164 yield 165 print(msg, file=sys.stderr) 166 while True: 167 sys.stderr.write(_("Install [Y/N] ")) 168 i = raw_input() 169 if not i: continue 170 if i in 'Nn': 171 raise download.DownloadAborted() 172 if i in 'Yy': 173 break
174
175 - def report_error(self, exception, tb = None):
176 """Report an exception to the user. 177 @param exception: the exception to report 178 @type exception: L{SafeException} 179 @param tb: optional traceback 180 @since: 0.25""" 181 warn("%s", str(exception) or type(exception))
182 #import traceback
183 #traceback.print_exception(exception, None, tb) 184 185 -class ConsoleHandler(Handler):
186 """A Handler that displays progress on stdout (a tty). 187 @since: 0.44""" 188 last_msg_len = None 189 update = None 190 disable_progress = 0 191 screen_width = None 192
193 - def downloads_changed(self):
194 import gobject 195 if self.monitored_downloads and self.update is None: 196 if self.screen_width is None: 197 try: 198 import curses 199 curses.setupterm() 200 self.screen_width = curses.tigetnum('cols') or 80 201 except Exception as ex: 202 info("Failed to initialise curses library: %s", ex) 203 self.screen_width = 80 204 self.show_progress() 205 self.update = gobject.timeout_add(200, self.show_progress) 206 elif len(self.monitored_downloads) == 0: 207 if self.update: 208 gobject.source_remove(self.update) 209 self.update = None 210 print() 211 self.last_msg_len = None
212
213 - def show_progress(self):
214 if not self.monitored_downloads: return True 215 urls = [(dl.url, dl) for dl in self.monitored_downloads] 216 217 if self.disable_progress: return True 218 219 screen_width = self.screen_width - 2 220 item_width = max(16, screen_width / len(self.monitored_downloads)) 221 url_width = item_width - 7 222 223 msg = "" 224 for url, dl in sorted(urls): 225 so_far = dl.get_bytes_downloaded_so_far() 226 leaf = url.rsplit('/', 1)[-1] 227 if len(leaf) >= url_width: 228 display = leaf[:url_width] 229 else: 230 display = url[-url_width:] 231 if dl.expected_size: 232 msg += "[%s %d%%] " % (display, int(so_far * 100 / dl.expected_size)) 233 else: 234 msg += "[%s] " % (display) 235 msg = msg[:screen_width] 236 237 if self.last_msg_len is None: 238 sys.stdout.write(msg) 239 else: 240 sys.stdout.write(chr(13) + msg) 241 if len(msg) < self.last_msg_len: 242 sys.stdout.write(" " * (self.last_msg_len - len(msg))) 243 244 self.last_msg_len = len(msg) 245 sys.stdout.flush() 246 247 return True
248
249 - def clear_display(self):
250 if self.last_msg_len != None: 251 sys.stdout.write(chr(13) + " " * self.last_msg_len + chr(13)) 252 sys.stdout.flush() 253 self.last_msg_len = None
254
255 - def report_error(self, exception, tb = None):
256 self.clear_display() 257 Handler.report_error(self, exception, tb)
258
259 - def confirm_import_feed(self, pending, valid_sigs):
260 self.clear_display() 261 self.disable_progress += 1 262 blocker = Handler.confirm_import_feed(self, pending, valid_sigs) 263 @tasks.async 264 def enable(): 265 yield blocker 266 self.disable_progress -= 1 267 self.show_progress()
268 enable() 269 return blocker
270