New release.
[zeroinstall.git] / zeroinstall / 0launch-gui / gui.py
blob178d80cc3ca07bac0fcb882d83a73660a26f3c07
1 import gtk, os, gobject, sys
3 from zeroinstall.injector.policy import Policy
4 from zeroinstall.injector import download
5 from zeroinstall.injector.model import SafeException
6 from zeroinstall.injector.reader import InvalidInterface
7 import dialog
8 from checking import CheckingBox
10 version = '0.24.1'
12 # Singleton Policy
13 policy = None
15 class GUIPolicy(Policy):
16 window = None
17 pulse = None
18 monitored_downloads = None
19 checking = None # GtkDialog ("Checking for updates...")
20 original_implementation = None
21 download_only = None
22 prog_args = None
23 main_exec = None
24 dl_callbacks = {} # Download -> [ callback ]
26 def __init__(self, interface, prog_args, download_only, refresh, main, src = False, restrictions = None):
27 if src:
28 Policy.__init__(self, interface, src = src)
29 else:
30 Policy.__init__(self, interface) # For older versions
31 global policy
32 assert policy is None
34 if restrictions:
35 for r in restrictions:
36 self.root_restrictions.append(r)
38 policy = self
39 self.main_exec = main
40 self.prog_args = prog_args
41 self.download_only = download_only
42 self.monitored_downloads = []
44 import mainwindow
45 self.window = mainwindow.MainWindow(prog_args, download_only)
46 root = policy.get_interface(policy.root)
47 self.window.browser.set_root(root)
49 if refresh:
50 # If we have feeds then treat this as a refresh,
51 # even if we've never seen the main interface before.
52 # Used the first time the GUI is used, for example.
53 if root.name is not None or root.feeds:
54 self.checking = CheckingBox(root)
56 self.refresh_all(force = False)
58 def show_details(self):
59 """The checking box has disappeared. Should we show the details window, or
60 just run the program right now?"""
61 if self.checking.show_details:
62 return True # User clicked on the Details button
63 if not self.ready:
64 return True # Not ready to start (can't find an implementation)
65 if self.versions_changed():
66 return True # Confirm that the new version should be used
67 if self.get_uncached_implementations():
68 return True # Need to download something; check first
69 return False
71 def get_download(self, url, force = False):
72 # For injector >= 0.20
73 dl = None
74 for dl in self.monitored_downloads:
75 if dl.url == url:
76 if force:
77 dl.abort()
78 dl = None
79 break
80 else:
81 dl = None
82 if dl is None:
83 dl = download.Download(url)
84 self.monitor_download(dl)
85 return dl
87 def add_dl_callback(self, url, callback):
88 """If url is being downloaded now, call callback() when it completes.
89 Otherwise, call callback() now."""
90 for dl in self.monitored_downloads:
91 if dl.url == url:
92 callbacks = self.dl_callbacks.get(dl, [])
93 callbacks.append(callback)
94 self.dl_callbacks[dl] = callbacks
95 return
96 callback()
98 def monitor_download(self, dl):
99 from zeroinstall.injector import download
100 if hasattr(dl, 'interface'):
101 name = dl.interface
102 else:
103 name = os.path.basename(dl.url)
105 self.monitored_downloads.append(dl)
107 error_stream = dl.start()
108 def error_ready(src, cond):
109 got = os.read(src.fileno(), 100)
110 if not got:
111 error_stream.close()
112 self.monitored_downloads.remove(dl)
113 if len(self.monitored_downloads) == 0:
114 gobject.source_remove(self.pulse)
115 self.window.progress.hide()
116 self.pulse = None
117 try:
118 data = dl.error_stream_closed()
119 if data:
120 if isinstance(dl, download.InterfaceDownload):
121 self.check_signed_data(dl, data)
122 elif isinstance(dl, download.ImplementationDownload):
123 self.add_to_cache(dl.source, data)
124 if hasattr(download, 'IconDownload') and \
125 isinstance(dl, download.IconDownload):
126 if self.window:
127 self.window.browser.build_tree()
128 except download.DownloadError, ex:
129 dialog.alert(self.window,
130 "Error downloading '%s':\n\n%s" %
131 (name, ex))
132 except InvalidInterface, ex:
133 dialog.alert(self.window,
134 "Syntax error in downloaded interface '%s':\n\n%s" %
135 (name, ex))
136 except SafeException, ex:
137 dialog.alert(self.window,
138 "Error fetching '%s':\n\n%s" %
139 (name, ex))
141 for cb in self.dl_callbacks.get(dl, []):
142 cb()
144 if len(self.monitored_downloads) == 0 and self.checking:
145 self.checking.updates_done(self.versions_changed())
146 return False
147 dl.error_stream_data(got)
148 return True
150 gobject.io_add_watch(error_stream,
151 gobject.IO_IN | gobject.IO_HUP,
152 error_ready)
154 if self.pulse is None:
155 def pulse():
156 if self.checking:
157 self.checking.progress.pulse()
158 else:
159 self.window.progress.pulse()
160 return True
161 self.pulse = gobject.timeout_add(50, pulse)
162 self.window.progress.show()
164 def store_icon(self, interface, stream):
165 Policy.store_icon(self, interface, stream)
166 if self.window:
167 self.window.browser.build_tree()
169 def recalculate(self):
170 Policy.recalculate(self)
171 try:
172 self.ready
173 except:
174 self.ready = True
175 print >>sys.stderr, "Your version of the injector is very old. " \
176 "Try upgrading (http://0install.net/injector.html)"
177 else:
178 self.window.set_response_sensitive(gtk.RESPONSE_OK, self.ready)
180 def confirm_trust_keys(self, interface, sigs, iface_xml):
181 import trust_box
182 trust_box.confirm_trust(interface, sigs, iface_xml)
184 def main(self):
185 if self.checking:
186 self.checking.show()
187 if not self.monitored_downloads:
188 self.checking.updates_done(self.versions_changed())
189 dialog.wait_for_no_windows()
190 show_details = self.show_details()
191 self.checking = None
192 if show_details:
193 self.window.show()
194 gtk.main()
195 else:
196 import download_box
197 download_box.download_with_gui(self.window,
198 self.prog_args, main = self.main_exec,
199 run_afterwards = not self.download_only)
200 else:
201 self.window.show()
202 gtk.main()
204 def get_best_source(self, impl):
205 """Return the best download source for this implementation."""
206 if impl.download_sources:
207 return impl.download_sources[0]
208 return None
210 def refresh_all(self, force = True):
211 if hasattr(Policy, 'refresh_all'):
212 Policy.refresh_all(self, force)
213 else:
214 # XXX: Remove this. Moved to Policy.
215 for x in self.walk_interfaces():
216 self.begin_iface_download(x, force)
218 def abort_all_downloads(self):
219 for x in self.monitored_downloads[:]:
220 x.abort()
222 def set_original_implementations(self):
223 assert self.original_implementation is None
224 self.original_implementation = policy.implementation.copy()
226 def versions_changed(self):
227 """Return whether we have now chosen any different implementations.
228 If so, we want to show the dialog to the user to confirm the new ones."""
229 if not self.ready:
230 return True
231 if not self.original_implementation:
232 return True # Shouldn't happen?
233 if len(self.original_implementation) != len(self.implementation):
234 return True
235 for iface in self.original_implementation:
236 old = self.original_implementation[iface]
237 if old is None:
238 return True
239 new = self.implementation.get(iface, None)
240 if new is None:
241 return True
242 if old.id != new.id:
243 return True
244 return False
246 def pretty_size(size):
247 if size is None:
248 return '?'
249 if size < 2048:
250 return '%d bytes' % size
251 size = float(size)
252 for unit in ('Kb', 'Mb', 'Gb', 'Tb'):
253 size /= 1024
254 if size < 2048:
255 break
256 return '%.1f %s' % (size, unit)