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
11
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
29 """Thrown by L{Handler.confirm_import_feed} on failure."""
30 pass
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
48 - def __init__(self, mainloop = None, dry_run = False):
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
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
83 """This is just for the GUI to override to update its display."""
84 pass
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
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
283
284 - def print(self, *args, **kwargs):
287