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