Merge lp:~dobey/ubuntuone-client/updown-visibility into lp:ubuntuone-client

Proposed by dobey
Status: Merged
Approved by: Joshua Blount
Approved revision: 172
Merged at revision: not available
Proposed branch: lp:~dobey/ubuntuone-client/updown-visibility
Merge into: lp:ubuntuone-client
Diff against target: None lines
To merge this branch: bzr merge lp:~dobey/ubuntuone-client/updown-visibility
Reviewer Review Type Date Requested Status
Joshua Blount (community) Approve
John O'Brien (community) Approve
Review via email: mp+10587@code.launchpad.net

Commit message

Handle the new ContentQueue signal
Maintain state for transfer status information
Change the left click behavior to open a menu by default
Update a menu item in the left click menu with transfer status
Pop up notifications to signal start and end of transfers

To post a comment you must log in.
Revision history for this message
John O'Brien (jdobrien) wrote :

changes look good, tests run, and it doesn't break ubunet....who could ask for anything more!

Revision history for this message
John O'Brien (jdobrien) wrote :

...except that the reviewer select "Approve"

review: Approve
Revision history for this message
Joshua Blount (jblount) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/ubuntuone-client-applet'
2--- bin/ubuntuone-client-applet 2009-08-18 19:47:56 +0000
3+++ bin/ubuntuone-client-applet 2009-08-24 01:12:37 +0000
4@@ -40,6 +40,7 @@
5 from dbus.mainloop.glib import DBusGMainLoop
6 from ubuntuone.oauthdesktop.main import Login
7 from xdg.BaseDirectory import xdg_config_home
8+from threading import Lock
9 from urllib import quote
10
11 from ubuntuone.oauthdesktop.logger import setupLogging
12@@ -311,8 +312,9 @@
13 self.__size = 24
14 self.__size_changed(self, self.__size)
15
16- self.__items = {}
17- self.__menu = self.__build_menus()
18+ self.__litems = {}
19+ self.__ritems = {}
20+ self.__status_menu, self.__config_menu = self.__build_menus()
21
22 self.__connected = False
23 self.__need_update = False
24@@ -321,10 +323,15 @@
25 pynotify.init("Ubuntu One")
26
27 # Managing applet visibility
28- self.__persist = True
29 self.__visible = True
30 self.__visible_id = 0
31
32+ # Up/Dn status
33+ self.__lock = Lock()
34+ self.__updating = 0
35+ self.__total = 0
36+ self.__last_id = 0
37+
38 self.__bus = dbus.SessionBus()
39
40 self.__bus.add_signal_receiver(
41@@ -332,28 +339,140 @@
42 signal_name="StatusChanged",
43 dbus_interface=DBUS_IFACE_STATUS_NAME)
44
45+ self.__bus.add_signal_receiver(
46+ handler_function=self.__queue_changed,
47+ signal_name="ContentQueueChanged",
48+ dbus_interface=DBUS_IFACE_STATUS_NAME)
49+ self.__bus.add_signal_receiver(
50+ handler_function=self.__transfer_started,
51+ signal_name="UploadStarted",
52+ dbus_interface=DBUS_IFACE_STATUS_NAME)
53+ self.__bus.add_signal_receiver(
54+ handler_function=self.__transfer_started,
55+ signal_name="DownloadStarted",
56+ dbus_interface=DBUS_IFACE_STATUS_NAME)
57+
58 gobject.idle_add(self.__get_root)
59
60+ def __update_transfer_status(self, done=False):
61+ """Update the status display."""
62+ with self.__lock:
63+ text = "Updating %(transfers)d of %(total)d files..." % (
64+ { 'transfers' : self.__updating,
65+ 'total' : self.__total})
66+ label = self.__litems["status"].get_child()
67+ if done:
68+ self.set_tooltip(_("Files updated."))
69+ label.set_text(_("Your files are up to date."))
70+ else:
71+ label.set_markup("<i>%s</i>" % text)
72+
73+ def __queue_changed(self, queue):
74+ """Handle ContentQueueChanged."""
75+ total = 0
76+ d = queue.get('Download', None)
77+ if d is not None:
78+ total += int(d.get('count', 0))
79+ d = queue.get('Upload', None)
80+ if d is not None:
81+ total += int(d.get('count', 0))
82+ first = False
83+ last = False
84+ self.__visible = True
85+ self.set_tooltip(_("Updating files..."))
86+ self.__update_visibility()
87+ with self.__lock:
88+ if self.__total == 0:
89+ first = True
90+ last = False
91+ if self.__total != 0 and total == 0:
92+ first = False
93+ last = True
94+ self.__total = total + self.__updating
95+ if first:
96+ n = pynotify.Notification(
97+ "Updating Files...",
98+ _("Ubuntu One is now updating your files."))
99+ pixbuf = self.__theme.load_icon(
100+ "ubuntuone-client", 24,
101+ gtk.ICON_LOOKUP_GENERIC_FALLBACK)
102+ n.set_icon_from_pixbuf(pixbuf)
103+ n.show()
104+ if last:
105+ if self.__last_id != 0:
106+ gobject.source_remove(self.__last_id)
107+ self.__last_id = 0
108+ self.__last_id = gobject.timeout_add_seconds(
109+ 15, self.__updating_completed)
110+
111+ def __updating_completed(self):
112+ """Timeout to avoid multiple started/finished notifications."""
113+ really_last = False
114+ n = None
115+ with self.__lock:
116+ done = self.__total - self.__updating
117+ if done == 0:
118+ really_last = True
119+ if not really_last:
120+ return False
121+
122+ with self.__lock:
123+ n = pynotify.Notification(
124+ "Updating Finished",
125+ _("Ubuntu One finished updating %(total)d files.") %
126+ { 'total' : self.__total })
127+ self.__total = 0
128+ self.__updating = 0
129+ pixbuf = self.__theme.load_icon(
130+ "ubuntuone-client", 24,
131+ gtk.ICON_LOOKUP_GENERIC_FALLBACK)
132+ n.set_icon_from_pixbuf(pixbuf)
133+ n.show()
134+ self.__update_transfer_status(True)
135+ return False
136+
137+ def __transfer_started(self, path):
138+ """Handle the started signals."""
139+ with self.__lock:
140+ self.__updating += 1
141+ self.__update_transfer_status()
142+
143+ def __update_visibility(self):
144+ """Update the icon's visibility."""
145+ if self.__visible_id != 0:
146+ gobject.source_remove(self.__visible_id)
147+ self.__visible_id = 0
148+
149+ if self.__visible:
150+ self.set_visible(True)
151+ return
152+
153+ # If the icon is shown, set up a timeout to hide it
154+ if self.get_visible():
155+ self.__visible_id = gobject.timeout_add_seconds(
156+ 30, self.__hide_icon)
157+
158 def __status_changed(self, status):
159 """The sync daemon status changed."""
160 if self.__managed_dir is None:
161 gobject.idle_add(self.__get_root)
162
163- self.__persist = False
164- self.__visible = True
165+ self.__visible = False
166+ state = status['name']
167
168- if status['name'] == "OFFLINE" or \
169- status['name'].startswith("INIT") or \
170- status['name'].startswith("READY"):
171+ if state == "OFFLINE" or state.startswith("INIT") or \
172+ state.startswith("READY"):
173 self.set_from_icon_name("ubuntuone-client-offline")
174 self.__connected = False
175- self.__persist = True
176 self.__visible = True
177- elif status['name'] == "AUTH_FAILED":
178+ elif state == "IDLE" or state.startswith("READING") or \
179+ state.startswith("SCANNING"):
180+ self.__connected = True
181+ self.__visible = False
182+ elif state == "AUTH_FAILED":
183 self.set_from_icon_name("ubuntuone-client-error")
184 self.set_tooltip(_("Authentication failed"))
185 self.__connected = False
186- self.__persist = True
187 self.__visible = True
188 try:
189 def reauthorize_error(error):
190@@ -374,17 +493,16 @@
191 error_handler=reauthorize_error)
192 except DBusException:
193 pass
194- elif status['name'] == "UNKNOWN_ERROR":
195+ elif state == "UNKNOWN_ERROR":
196 # Disable some menu items
197- self.__items["connect"].set_sensitive(False)
198- self.__items["disconnect"].set_sensitive(False)
199+ self.__litems["connect"].set_sensitive(False)
200+ self.__litems["disconnect"].set_sensitive(False)
201 # Change the behavior to file a bug
202 if self.__fatal_error:
203 return
204
205 self.__fatal_error = True
206- self.__persist = True
207- self.__visibile = True
208+ self.__visible = True
209
210 # Pop up a notification
211 n = pynotify.Notification(
212@@ -402,45 +520,28 @@
213 # Set the tooltip and icon on the applet
214 self.set_tooltip(_("Fatal Error"))
215 self.set_from_icon_name("ubuntuone-client-error")
216- if self.__visible_id != 0:
217- gobject.source_remove(self.__visible_id)
218- self.__visible_id = 0
219- self.set_visible(True)
220- return
221 else:
222 self.__connected = True
223 self.set_from_icon_name("ubuntuone-client")
224- self.__visible = True
225- if status['name'].startswith("CONNECTING") or \
226- status['name'].startswith("START_CONNECTING") or \
227- status['name'].startswith("AUTHENTICATING") or \
228- status['name'].startswith("CONNECTED"):
229- self.set_from_icon_name("ubuntuone-client-offline")
230+ if state.startswith("CONNECTING") or \
231+ state.startswith("START_CONNECTING") or \
232+ state.startswith("AUTHENTICATING") or \
233+ state.startswith("CONNECTED") or \
234+ state.startswith("START_CONNECTED"):
235+ self.set_from_icon_name("ubuntuone-client")
236 self.set_tooltip(_("Connecting"))
237- else:
238- self.set_tooltip("Ubuntu One")
239- self.__visible = False
240+ self.__visible = True
241
242- # If we're visible, set up a reasonable timeout to hide ourself,
243- # and if not shown yet, show ourself
244- if self.__visible:
245- if not self.get_visible():
246- self.set_visible(True)
247- if self.__visible_id != 0:
248- gobject.source_remove(self.__visible_id)
249- self.__visible_id = 0
250- if not self.__persist:
251- self.__visible_id = gobject.timeout_add_seconds(
252- 30, self.__toggle_visible)
253+ self.__update_visibility()
254
255 if self.__connected:
256- self.__items["connect"].hide()
257- self.__items["disconnect"].show()
258+ self.__litems["connect"].hide()
259+ self.__litems["disconnect"].show()
260 else:
261- self.__items["connect"].show()
262- self.__items["disconnect"].hide()
263+ self.__litems["connect"].show()
264+ self.__litems["disconnect"].hide()
265
266- def __toggle_visible(self):
267+ def __hide_icon(self):
268 """Timeout to hide tray icon after a period of inactivity."""
269 self.__visible = False
270 self.__visible_id = 0
271@@ -464,16 +565,16 @@
272 if os.path.isdir(self.__managed_dir) and \
273 os.access(self.__managed_dir,
274 os.F_OK | os.R_OK):
275- self.__items["open"].set_sensitive(True)
276+ self.__ritems["open"].set_sensitive(True)
277 self.__add_to_places()
278 else:
279- self.__items["open"].set_sensitive(False)
280+ self.__ritems["open"].set_sensitive(False)
281
282 def got_err(error):
283 """Handle error from the dbus callback."""
284 self.dbus_error(error)
285 self.__managed_dir = None
286- self.__items["open"].set_sensitive(False)
287+ self.__ritems["open"].set_sensitive(False)
288
289 iface.get_rootdir(reply_handler=got_root, error_handler=got_err)
290
291@@ -491,49 +592,63 @@
292
293 def __build_menus(self):
294 """Create the pop-up menu items."""
295- menu = gtk.Menu()
296-
297- self.__items["connect"] = gtk.ImageMenuItem(stock_id=gtk.STOCK_CONNECT)
298- menu.append(self.__items["connect"])
299- self.__items["connect"].connect("activate", self.__toggle_state)
300- self.__items["connect"].show()
301-
302- self.__items["disconnect"] = gtk.ImageMenuItem(stock_id=gtk.STOCK_DISCONNECT)
303- menu.append(self.__items["disconnect"])
304- self.__items["disconnect"].connect("activate", self.__toggle_state)
305-
306- sep = gtk.SeparatorMenuItem()
307- menu.append(sep)
308- sep.show()
309-
310- self.__items["bug"] = gtk.MenuItem(label=_("_Report a Problem"))
311- menu.append(self.__items["bug"])
312- self.__items["bug"].connect("activate", self.__report_problem)
313- self.__items["bug"].show()
314-
315- self.__items["open"] = gtk.MenuItem(label=_("_Open Folder"))
316- menu.append(self.__items["open"])
317- self.__items["open"].connect("activate", self.__open_folder)
318- self.__items["open"].set_sensitive(False)
319- self.__items["open"].show()
320-
321- self.__items["web"] = gtk.MenuItem(label=_("_Go to Web"))
322- menu.append(self.__items["web"])
323- self.__items["web"].connect("activate", self.__open_website)
324- self.__items["web"].show()
325-
326- sep = gtk.SeparatorMenuItem()
327- menu.append(sep)
328- sep.show()
329-
330- self.__items["quit"] = gtk.ImageMenuItem(stock_id=gtk.STOCK_QUIT)
331- menu.append(self.__items["quit"])
332- self.__items["quit"].connect("activate", self.__quit_applet)
333- self.__items["quit"].show()
334-
335- menu.show()
336-
337- return menu
338+ # Create the left-click menu
339+ lmenu = gtk.Menu()
340+
341+ self.__litems["status"] = gtk.MenuItem(
342+ label=_("Your files are up to date."))
343+ lmenu.append(self.__litems["status"])
344+ self.__litems["status"].set_sensitive(False)
345+ self.__litems["status"].show()
346+
347+ sep = gtk.SeparatorMenuItem()
348+ lmenu.append(sep)
349+ sep.show()
350+
351+ self.__litems["connect"] = gtk.ImageMenuItem(
352+ stock_id=gtk.STOCK_CONNECT)
353+ lmenu.append(self.__litems["connect"])
354+ self.__litems["connect"].connect("activate", self.__toggle_state)
355+ self.__litems["connect"].show()
356+
357+ self.__litems["disconnect"] = gtk.ImageMenuItem(
358+ stock_id=gtk.STOCK_DISCONNECT)
359+ lmenu.append(self.__litems["disconnect"])
360+ self.__litems["disconnect"].connect("activate", self.__toggle_state)
361+
362+ lmenu.show()
363+
364+ # Create the right-click menu
365+ rmenu = gtk.Menu()
366+
367+ self.__ritems["bug"] = gtk.MenuItem(label=_("_Report a Problem"))
368+ rmenu.append(self.__ritems["bug"])
369+ self.__ritems["bug"].connect("activate", self.__report_problem)
370+ self.__ritems["bug"].show()
371+
372+ self.__ritems["open"] = gtk.MenuItem(label=_("_Open Folder"))
373+ rmenu.append(self.__ritems["open"])
374+ self.__ritems["open"].connect("activate", self.__open_folder)
375+ self.__ritems["open"].set_sensitive(False)
376+ self.__ritems["open"].show()
377+
378+ self.__ritems["web"] = gtk.MenuItem(label=_("_Go to Web"))
379+ rmenu.append(self.__ritems["web"])
380+ self.__ritems["web"].connect("activate", self.__open_website)
381+ self.__ritems["web"].show()
382+
383+ sep = gtk.SeparatorMenuItem()
384+ rmenu.append(sep)
385+ sep.show()
386+
387+ self.__ritems["quit"] = gtk.ImageMenuItem(stock_id=gtk.STOCK_QUIT)
388+ rmenu.append(self.__ritems["quit"])
389+ self.__ritems["quit"].connect("activate", self.__quit_applet)
390+ self.__ritems["quit"].show()
391+
392+ rmenu.show()
393+
394+ return lmenu, rmenu
395
396 def __size_changed(self, icon, size, data=None):
397 """Callback for when the size changes."""
398@@ -550,8 +665,14 @@
399
400 def __popup_menu(self, icon, button, timestamp, data=None):
401 """Pops up the context menu for the tray icon."""
402- self.__menu.popup(None, None, gtk.status_icon_position_menu,
403- button, timestamp, icon)
404+ if button == 0:
405+ self.__status_menu.popup(None, None,
406+ gtk.status_icon_position_menu,
407+ button, timestamp, icon)
408+ else:
409+ self.__config_menu.popup(None, None,
410+ gtk.status_icon_position_menu,
411+ button, timestamp, icon)
412
413 def __quit_applet(self, menuitem, data=None):
414 """Quit the daemon and closes the applet."""
415@@ -601,7 +722,8 @@
416 do_xdg_open("apt:ubuntuone-storage-protocol?refresh=yes")
417 return
418
419- self.__open_folder()
420+ # Popup the status menu
421+ self.emit("popup-menu", 0, gtk.get_current_event_time())
422
423 def __report_problem(self, data=None):
424 """Runs apport to report a problem against our code."""

Subscribers

People subscribed via source and target branches