Improve progress bar display when downloading.
[zeroinstall/zeroinstall-mseaborn.git] / zeroinstall / 0launch-gui / gui.py
blob2cdb73a01b6a0dc4a62910fca435f154d2efb671
1 import gtk, os, gobject, sys
2 import gtk.glade
4 from zeroinstall.injector.iface_cache import iface_cache
5 from zeroinstall.injector.policy import Policy
6 from zeroinstall.injector import download, handler
7 from zeroinstall.injector.model import SafeException
8 from zeroinstall.injector.reader import InvalidInterface
9 from zeroinstall.support import tasks, pretty_size
10 import dialog
11 from checking import CheckingBox
13 version = '0.31'
15 # Singleton Policy
16 policy = None
18 gladefile = os.path.join(os.path.dirname(__file__), 'zero-install.glade')
20 # Wrapped for glade widget tree that throws a sensible exception if the widget isn't found
21 class Template:
22 def __init__(self, root):
23 self.widgets = gtk.glade.XML(gladefile, root)
24 self.root = root
26 def get_widget(self, name = None):
27 if not name:
28 name = self.root
29 widget = self.widgets.get_widget(name)
30 assert widget, "Widget '%s' not found in glade file '%s'" % (name, gladefile)
31 return widget
33 class GUIHandler(handler.Handler):
34 dl_callbacks = None # Download -> [ callback ]
35 pulse = None
36 policy = None
38 def __init__(self, policy):
39 handler.Handler.__init__(self)
40 self.policy = policy
42 def downloads_changed(self):
43 if self.monitored_downloads and self.pulse is None:
44 def pulse():
45 if self.policy.checking:
46 progress = self.policy.checking.progress
47 else:
48 progress = self.policy.window.progress
50 any_known = False
51 done = total = self.total_bytes_downloaded # Completed downloads
52 n_downloads = self.n_completed_downloads
53 # Now add downloads in progress...
54 for x in self.monitored_downloads.values():
55 if x.status != download.download_fetching: continue
56 n_downloads += 1
57 if x.expected_size:
58 any_known = True
59 so_far = x.get_bytes_downloaded_so_far()
60 total += x.expected_size or max(4096, so_far) # Guess about 4K for feeds/icons
61 done += so_far
63 progress_text = '%s / %s' % (pretty_size(done), pretty_size(total))
64 if n_downloads == 1:
65 progress.set_text('Downloading one file (%s)' % progress_text)
66 else:
67 progress.set_text('Downloading %d files (%s)' % (n_downloads, progress_text))
69 if total == 0 or (n_downloads < 2 and not any_known):
70 progress.pulse()
71 else:
72 progress.set_fraction(float(done) / total)
74 return True
75 pulse()
76 self.pulse = gobject.timeout_add(50, pulse)
77 self.policy.window.progress.show()
78 elif len(self.monitored_downloads) == 0:
79 # Reset counters
80 self.n_completed_downloads = 0
81 self.total_bytes_downloaded = 0
83 # Stop animation
84 if self.pulse:
85 gobject.source_remove(self.pulse)
86 self.policy.window.progress.hide()
87 self.pulse = None
89 if self.policy.checking:
90 self.policy.checking.updates_done(self.policy.versions_changed())
92 def confirm_trust_keys(self, interface, sigs, iface_xml):
93 if self.policy.checking:
94 # Switch to main view if there are keys to be confirmed
95 self.policy.checking.updates_done(True)
96 self.policy.window.show()
97 import trust_box
98 return trust_box.confirm_trust(interface, sigs, iface_xml, parent = self.policy.window.window)
100 def report_error(self, ex):
101 dialog.alert(None, str(ex))
103 class GUIPolicy(Policy):
104 window = None
105 checking = None # GtkDialog ("Checking for updates...")
106 original_implementation = None
107 download_only = None
108 widgets = None # Glade
110 def __init__(self, interface, download_only, src = False, restrictions = None):
111 Policy.__init__(self, interface, GUIHandler(self), src = src)
112 self.solver.record_details = True
113 global policy
114 assert policy is None
115 policy = self
117 self.widgets = Template('main')
119 if restrictions:
120 for r in restrictions:
121 self.root_restrictions.append(r)
123 self.download_only = download_only
125 import mainwindow
126 self.window = mainwindow.MainWindow(download_only)
127 root = iface_cache.get_interface(self.root)
128 self.window.browser.set_root(root)
130 self.watchers.append(self.update_display)
132 def show_details(self):
133 """The checking box has disappeared. Should we show the details window, or
134 just run the program right now?"""
135 if self.checking.show_details_clicked.happened:
136 return True # User clicked on the Details button
137 if not self.ready:
138 return True # Not ready to start (can't find an implementation)
139 if self.versions_changed():
140 return True # Confirm that the new version should be used
141 if self.get_uncached_implementations():
142 return True # Need to download something; check first
143 return False
145 def update_display(self):
146 self.window.set_response_sensitive(gtk.RESPONSE_OK, self.ready)
148 def main(self, refresh):
149 if refresh:
150 # If we have feeds then treat this as an update check,
151 # even if we've never seen the main interface before.
152 # Used the first time the GUI is used, for example.
153 root = iface_cache.get_interface(self.root)
154 if root.name is not None or root.feeds:
155 self.checking = CheckingBox(root)
157 solved = self.solve_with_downloads(force = refresh)
159 if self.checking:
160 self.checking.show()
162 error = None
163 blockers = [solved, self.checking.show_details_clicked, self.checking.cancelled]
164 yield blockers
165 try:
166 tasks.check(blockers)
167 except Exception, ex:
168 error = ex
170 if not (self.checking.show_details_clicked.happened or self.checking.cancelled.happened):
171 self.checking.updates_done(self.versions_changed())
172 blockers = tasks.TimeoutBlocker(0.5, "checking result timeout")
173 yield blockers
174 tasks.check(blockers)
175 self.checking.destroy()
177 show_details = self.show_details() or error
178 self.checking = None
179 if show_details:
180 self.window.show()
181 if error:
182 dialog.alert(self.window.window, "Failed to check for updates: %s" % ex)
183 yield []
184 else:
185 from zeroinstall.injector import selections
186 sels = selections.Selections(policy)
187 doc = sels.toDOM()
188 reply = doc.toxml('utf-8')
189 sys.stdout.write(('Length:%8x\n' % len(reply)) + reply)
190 self.window.destroy()
191 sys.exit(0) # Success
192 else:
193 self.window.show()
194 yield solved
195 try:
196 tasks.check(solved)
197 except Exception, ex:
198 import traceback
199 traceback.print_exc()
200 dialog.alert(self.window.window, str(ex))
201 yield []
203 def abort_all_downloads(self):
204 for dl in self.handler.monitored_downloads.values():
205 dl.abort()
207 def set_original_implementations(self):
208 assert self.original_implementation is None
209 self.original_implementation = policy.implementation.copy()
211 def versions_changed(self):
212 """Return whether we have now chosen any different implementations.
213 If so, we want to show the dialog to the user to confirm the new ones."""
214 if not self.ready:
215 return True
216 if not self.original_implementation:
217 return True # Shouldn't happen?
218 if len(self.original_implementation) != len(self.implementation):
219 return True
220 for iface in self.original_implementation:
221 old = self.original_implementation[iface]
222 if old is None:
223 return True
224 new = self.implementation.get(iface, None)
225 if new is None:
226 return True
227 if old.id != new.id:
228 return True
229 return False