3
# ubuntuone-client-applet - Tray icon applet for managing Ubuntu One
5
# Author: Rodney Dawes <rodney.dawes@canonical.com>
7
# Copyright 2009 Canonical Ltd.
9
# This program is free software: you can redistribute it and/or modify it
10
# under the terms of the GNU General Public License version 3, as published
11
# by the Free Software Foundation.
13
# This program is distributed in the hope that it will be useful, but
14
# WITHOUT ANY WARRANTY; without even the implied warranties of
15
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16
# PURPOSE. See the GNU General Public License for more details.
18
# You should have received a copy of the GNU General Public License along
19
# with this program. If not, see <http://www.gnu.org/licenses/>.
21
from __future__ import with_statement
31
from ubuntuone import clientdefs
35
# pylint: disable-msg=F0401
38
from ConfigParser import ConfigParser
39
from dbus.exceptions import DBusException
40
from dbus.mainloop.glib import DBusGMainLoop
41
from xdg.BaseDirectory import xdg_config_home
42
from threading import Lock, Thread
43
from urllib import quote
45
from ubuntuone.oauthdesktop.logger import setupLogging
46
logger = setupLogging("UbuntuOne.Client.Applet")
48
DBusGMainLoop(set_as_default=True)
51
ngettext = gettext.ngettext
53
APPLET_BUS_NAME = "com.ubuntuone.ClientApplet"
54
APPLET_CONFIG_NAME = APPLET_BUS_NAME + ".Config"
56
DBUS_IFACE_NAME = "com.ubuntuone.SyncDaemon"
57
DBUS_IFACE_SYNC_NAME = "com.ubuntuone.SyncDaemon.SyncDaemon"
58
DBUS_IFACE_STATUS_NAME = "com.ubuntuone.SyncDaemon.Status"
59
DBUS_IFACE_CONFIG_NAME = "com.ubuntuone.SyncDaemon.Config"
61
DBUS_IFACE_AUTH_NAME = "com.ubuntuone.Authentication"
63
OAUTH_REALM = "https://ubuntuone.com"
64
OAUTH_CONSUMER = "ubuntuone"
65
BOOKMARK_NAME = "Ubuntu One"
69
# Why thank you GTK+ for enforcing style-set and breaking API
72
GtkDialog::action-area-border = 12
73
GtkDialog::button-spacing = 6
74
GtkDialog::content-area-border = 0
76
widget_class '*Dialog*' style 'dialogs'
79
CONF_FILE = os.path.join(xdg_config_home, "ubuntuone", "ubuntuone-client.conf")
82
def dbus_async(*args):
83
"""Simple handler to make dbus do stuff async."""
86
class AppletMain(object):
87
"""Main applet process class."""
89
def __init__(self, *args, **kw):
90
"""Initializes the child threads and dbus monitor."""
91
logger.info(_("Starting Ubuntu One client version %s") %
94
# Whether or not we are authorized
95
self.is_authorized = False
97
# Load the config, with some defaults if it doesn't exist yet
98
if not os.path.isdir(os.path.dirname(CONF_FILE)):
99
os.makedirs(os.path.dirname(CONF_FILE))
101
self.config = ConfigParser()
102
self.config.read(CONF_FILE)
104
if not self.config.has_section("ubuntuone"):
105
self.config.add_section("ubuntuone")
107
if not self.config.has_option("ubuntuone", "show_applet"):
108
self.config.set("ubuntuone", "show_applet", "1")
110
if not self.config.has_option("ubuntuone", "connect"):
111
self.config.set("ubuntuone", "connect", "0")
113
if not self.config.has_option("ubuntuone", "connected"):
114
self.config.set("ubuntuone", "connected", "False")
116
if not self.config.has_option("ubuntuone", "bookmarked"):
117
self.config.set("ubuntuone", "bookmarked", "False")
119
self.show_applet = self.config.getint("ubuntuone", "show_applet")
120
self.connect = self.config.getint("ubuntuone", "connect")
121
self.connected = self.config.getboolean("ubuntuone", "connected")
123
if not os.path.exists(CONF_FILE):
124
with open(CONF_FILE, "w+b") as f:
127
# Handle some DBus signals
128
self.__bus = dbus.SessionBus()
129
self.__bus.add_signal_receiver(
130
handler_function=self.__new_credentials,
131
signal_name="NewCredentials",
132
dbus_interface=DBUS_IFACE_AUTH_NAME)
133
self.__bus.add_signal_receiver(
134
handler_function=self.__auth_denied,
135
signal_name="AuthorizationDenied",
136
dbus_interface=DBUS_IFACE_AUTH_NAME)
137
self.__bus.add_signal_receiver(
138
handler_function=self.__no_credentials,
139
signal_name="NoCredentials",
140
dbus_interface=DBUS_IFACE_AUTH_NAME)
142
self.__icon = AppletIcon(main=self, config=self.config)
144
def __new_credentials(self, realm=None, consumer_key=None, sender=None):
145
"""Signal callback for when we get new credentials."""
146
self.is_authorized = True
148
self.__start_storage_daemon()
149
self.add_to_autostart()
151
if self.connect == 2:
154
if self.connect == 1 and not self.connected:
158
client = self.__bus.get_object(DBUS_IFACE_NAME, "/",
159
follow_name_owner_changes=True)
160
iface = dbus.Interface(client, DBUS_IFACE_SYNC_NAME)
161
iface.connect(reply_handler=dbus_async,
162
error_handler=self.sd_dbus_error)
163
except DBusException, e:
164
self.sd_dbus_error(e)
166
def __auth_denied(self):
167
"""Signal callback for when auth was denied by user."""
168
self.is_authorized = False
169
self.remove_from_autostart()
172
"""Only log when quit fails."""
173
logger.error(_("Quit Error: %s") % e.get_dbus_message())
176
client = self.__bus.get_object(DBUS_IFACE_NAME, "/",
177
follow_name_owner_changes=True)
178
iface = dbus.Interface(client, DBUS_IFACE_SYNC_NAME)
179
iface.quit(reply_handler=dbus_async,
180
error_handler=quit_error)
181
except DBusException, e:
186
def __no_credentials(self):
187
"""Signal callback for when no credentials exist in the keyring."""
188
self.is_authorized = False
190
def check_for_token(self, do_login=False):
191
"""Method to check for an existing token."""
192
def local_dbus_error(e):
193
"""Can't talk to ourself?"""
194
logger.error(_("Internal Error: %s") % e.get_dbus_message())
197
client = self.__bus.get_object(DBUS_IFACE_AUTH_NAME, "/",
198
follow_name_owner_changes=True)
199
iface = dbus.Interface(client, DBUS_IFACE_AUTH_NAME)
200
iface.maybe_login(OAUTH_REALM, OAUTH_CONSUMER,
202
reply_handler=dbus_async,
203
error_handler=local_dbus_error)
204
except DBusException, e:
210
def __start_storage_daemon_maybe(self):
211
"""Start the storage daemon."""
213
client = self.__bus.get_object(DBUS_IFACE_NAME, "/",
214
follow_name_owner_changes=True)
215
iface = dbus.Interface(client, DBUS_IFACE_SYNC_NAME)
216
iface.get_rootdir(reply_handler=dbus_async,
217
error_handler=self.sd_dbus_error)
218
except DBusException, e:
219
self.sd_dbus_error(e)
224
def __start_storage_daemon(self):
225
"""Need to call dbus from a idle callback"""
226
gobject.idle_add(self.__start_storage_daemon_maybe)
228
def add_to_autostart(self):
229
"""Add ourself to the autostart config."""
230
autostart_entry = """[Desktop Entry]
232
Exec=ubuntuone-client-applet
233
Icon=ubuntuone-client
236
X-Ubuntu-Gettext-Domain=ubuntuone-client
237
X-KDE-autostart-after=panel
238
X-GNOME-Autostart-enabled=true
240
if not os.path.exists(os.path.join(xdg_config_home, "autostart")):
241
os.makedirs(os.path.join(xdg_config_home, "autostart"))
243
file_path = os.path.join(xdg_config_home, "autostart",
244
"ubuntuone-client-applet.desktop")
245
if not os.path.exists(file_path):
246
with open(file_path, "w+") as f:
247
f.write(autostart_entry)
249
def remove_from_autostart(self):
250
"""Remove ourself from the autostart config."""
251
path = os.path.join(xdg_config_home, "autostart",
252
"ubuntuone-client-applet.desktop")
259
"""Starts the gtk main loop."""
260
if self.connect != 2 or (self.connect == 1 and self.connected):
261
gobject.idle_add(self.check_for_token, True)
266
def authorized(self):
267
"""Are we authorized?"""
268
return self.is_authorized
270
def sd_dbus_error(self, error):
271
"""Got an error from DBus."""
272
self.__icon.sd_dbus_error(error)
276
def do_config_open_real():
277
"""Opens the preferences dialog."""
278
paths = os.environ["PATH"].split(":")
279
dirpath = os.path.join(os.getcwd(), "bin")
280
if os.path.isdir(dirpath):
281
paths.insert(0, dirpath)
282
os.environ["PATH"] = ":".join(paths)
284
ret = subprocess.call(["ubuntuone-client-preferences"],
289
logger.error(_("Failed to open Ubuntu One preferences"))
291
def do_config_open():
292
"""Do the preferences opening in a thread."""
293
Thread(target=do_config_open_real, name="preferences").start()
295
def do_xdg_open_real(path_or_url):
296
"""Utility method to run xdg-open with path_or_url."""
297
ret = subprocess.call(["xdg-open", path_or_url], env=os.environ)
299
logger.error(_("Failed to run 'xdg-open %s'") % path_or_url)
301
def do_xdg_open(path_or_url):
302
"""Do the xdg-open in a thread."""
303
Thread(target=do_xdg_open_real, name="xdg", args=(path_or_url,)).start()
306
class AppletIcon(gtk.StatusIcon):
308
Custom StatusIcon derived from gtk.StatusIcon which supports
309
animated icons and a few other nice things.
312
def __init__(self, main=None, config=None, *args, **kw):
313
"""Initializes our custom StatusIcon based widget."""
314
super(AppletIcon, self).__init__(*args, **kw)
315
# Hide until we get status, to avoid flickering
316
self.set_visible(False)
318
# The AppletMain object
321
# A ConfigParser object that we can poke at
322
self.__config = config
323
self.__show_when = self.__config.getint("ubuntuone", "show_applet")
325
self.__managed_dir = None
328
self.__theme = gtk.icon_theme_get_default()
329
iconpath = os.path.abspath(os.path.join(
330
os.path.split(os.path.dirname(__file__))[0],
332
self.__theme.append_search_path(iconpath)
334
self.__theme.append_search_path(os.path.sep + os.path.join(
335
"usr", "share", "ubuntuone-client", "icons"))
336
self.__theme.append_search_path(os.path.sep + os.path.join(
337
"usr", "local", "share", "ubuntuone-client", "icons"))
339
self.set_from_icon_name('ubuntuone-client-offline')
340
self.set_tooltip(_("Disconnected"))
341
self.connect("popup-menu", self.__popup_menu)
342
self.connect("activate", self.__do_action)
344
self.__size_changed(self, self.__size)
348
self.__status_menu, self.__config_menu = self.__build_menus()
350
self.__connected = False
351
self.__need_update = False
352
self.__fatal_error = False
354
pynotify.init("Ubuntu One")
356
# Managing applet visibility
357
self.__visible = True
358
self.__visible_id = 0
366
self.__bus = dbus.SessionBus()
368
# Our own DBus service, for the config to deal with
369
self.__service = AppletConfig(icon=self)
371
# DBus signal handling
372
self.__bus.add_signal_receiver(
373
handler_function=self.__auth_finished,
374
signal_name="NewCredentials",
375
dbus_interface=DBUS_IFACE_AUTH_NAME)
377
self.__bus.add_signal_receiver(
378
handler_function=self.__status_changed,
379
signal_name="StatusChanged",
380
dbus_interface=DBUS_IFACE_STATUS_NAME)
382
self.__bus.add_signal_receiver(
383
handler_function=self.__queue_changed,
384
signal_name="ContentQueueChanged",
385
dbus_interface=DBUS_IFACE_STATUS_NAME)
386
self.__bus.add_signal_receiver(
387
handler_function=self.__transfer_started,
388
signal_name="UploadStarted",
389
dbus_interface=DBUS_IFACE_STATUS_NAME)
390
self.__bus.add_signal_receiver(
391
handler_function=self.__transfer_started,
392
signal_name="DownloadStarted",
393
dbus_interface=DBUS_IFACE_STATUS_NAME)
395
self.set_visible(True)
397
def set_from_icon_name(self, icon):
398
"""Handle fallbacks for setting our icon."""
399
pixbuf = self.__theme.load_icon(icon, self.__size,
400
gtk.ICON_LOOKUP_GENERIC_FALLBACK)
401
self.set_from_pixbuf(pixbuf)
403
def set_visibility_config(self, visibility):
404
"""Update the visibility configuration."""
405
self.__show_when = int(visibility)
406
self.__config.set("ubuntuone", "show_applet", str(self.__show_when))
407
self.update_visibility()
409
def set_connection_config(self, connect):
410
"""Update the connection config."""
411
self.__config.set("ubuntuone", "connect", str(connect))
413
def __update_transfer_status(self, done=False):
414
"""Update the status display."""
416
text = _("Updating file %(transfers)d of %(total)d...") % \
417
dict(transfers=self.__updating, total=self.__total)
418
label = self.__litems["status"].get_child()
420
self.set_tooltip(_("Files updated."))
421
self.set_from_icon_name("ubuntuone-client-idle")
422
label.set_text(_("Your files are up to date."))
424
label.set_markup("<i>%s</i>" % text)
426
def __queue_changed(self, queue):
427
"""Handle ContentQueueChanged."""
429
d = queue.get('Download', None)
431
total += int(d.get('count', 0))
432
d = queue.get('Upload', None)
434
total += int(d.get('count', 0))
437
self.__visible = True
438
self.set_tooltip(_("Updating files..."))
439
self.update_visibility()
441
if self.__total == 0:
444
if self.__total != 0 and total == 0:
447
self.__total = total + self.__updating
449
self.set_from_icon_name("ubuntuone-client-updating")
450
n = pynotify.Notification(
451
_("Updating files..."),
452
_("Ubuntu One is now updating your files."))
453
pixbuf = self.__theme.load_icon(
454
"ubuntuone-client-updating", NOTIFY_ICON_SIZE,
455
gtk.ICON_LOOKUP_GENERIC_FALLBACK)
456
n.set_icon_from_pixbuf(pixbuf)
459
if self.__last_id != 0:
460
gobject.source_remove(self.__last_id)
462
self.__last_id = gobject.timeout_add_seconds(
463
15, self.__updating_completed)
465
def __updating_completed(self):
466
"""Timeout to avoid multiple started/finished notifications."""
470
done = self.__total - self.__updating
477
n = pynotify.Notification(
478
_("Updating Finished"),
479
ngettext("Ubuntu One finished updating %(total)d file.",
480
"Ubuntu One finished updating %(total)d files.",
481
self.__total) % { 'total' : self.__total })
484
pixbuf = self.__theme.load_icon(
485
"ubuntuone-client-updating", NOTIFY_ICON_SIZE,
486
gtk.ICON_LOOKUP_GENERIC_FALLBACK)
487
n.set_icon_from_pixbuf(pixbuf)
489
self.__update_transfer_status(True)
492
def __transfer_started(self, path):
493
"""Handle the started signals."""
496
self.__update_transfer_status()
498
def update_visibility(self):
499
"""Update the icon's visibility."""
500
if self.__visible_id != 0:
501
gobject.source_remove(self.__visible_id)
502
self.__visible_id = 0
504
if (self.__visible and self.__show_when != 2) or self.__fatal_error:
505
self.set_visible(True)
508
if self.__show_when == 2 and not self.__fatal_error:
509
self.set_visible(False)
512
# If the icon is shown, set up a timeout to hide it
513
if self.get_visible():
514
self.__visible_id = gobject.timeout_add_seconds(
515
30, self.__hide_icon)
517
def __status_changed(self, status):
518
"""The sync daemon status changed."""
519
if self.__managed_dir is None:
520
gobject.idle_add(self.__get_root)
522
if self.__show_when != 0:
523
self.__visible = False
525
state = status['name']
527
self.set_tooltip("Ubuntu One")
529
if self.__fatal_error and state != "UNKNOWN_ERROR":
530
# Just blow your nose, and it's fixed, isn't it.
531
self.__fatal_error = False
532
self.__litems["connect"].set_sensitive(True)
533
self.__litems["disconnect"].set_sensitive(True)
535
if state == "OFFLINE" or state.startswith("INIT") or \
536
state.startswith("READY"):
537
self.set_from_icon_name("ubuntuone-client-offline")
538
self.set_tooltip(_("Disconnected"))
539
self.__connected = False
540
self.__visible = True
542
elif state == "CAPABILITIES_MISMATCH":
543
self.__connected = False
544
self.__visible = True
545
# Pop up a notification
546
n = pynotify.Notification(
547
_("Capabilities Mismatch"),
548
_("There was a capabilities mismatch while attempting "
549
"to connect to the Ubuntu One server. You may "
550
"have installed a newer version of the client, for "
551
"which the server does not yet provide support. "
552
"A new version of the server should be accessible "
553
"soon. Please be patient while we update."))
554
pixbuf = self.__theme.load_icon(
555
"ubuntuone-client-error", NOTIFY_ICON_SIZE,
556
gtk.ICON_LOOKUP_GENERIC_FALLBACK)
557
n.set_icon_from_pixbuf(pixbuf)
558
n.set_urgency(pynotify.URGENCY_CRITICAL)
560
# Set the tooltip and icon on the applet
561
self.set_tooltip(_("Capabilities mismatch with server."))
562
self.set_from_icon_name("ubuntuone-client-error")
564
elif state == "IDLE" or state.startswith("READING") or \
565
state.startswith("SCANNING"):
566
self.__connected = True
567
if self.__show_when != 0:
568
self.__visible = False
570
elif state == "AUTH_FAILED":
571
self.__stop_syncdaemon()
572
self.set_from_icon_name("ubuntuone-client-error")
573
self.set_tooltip(_("Authentication failed"))
574
self.__connected = False
575
self.__visible = True
577
def reauthorize_error(e):
578
"""Simple dbus error handler."""
579
logger.error(_("Error clearing token: %s") % str(e))
583
"""Do the next step."""
585
self.__main.check_for_token(True)
587
client = self.__bus.get_object(DBUS_IFACE_AUTH_NAME,
588
"/", follow_name_owner_changes=True)
589
iface = dbus.Interface(client, DBUS_IFACE_AUTH_NAME)
590
iface.clear_token(OAUTH_REALM, OAUTH_CONSUMER,
591
reply_handler=token_cleared,
592
error_handler=reauthorize_error)
593
except DBusException, e:
596
elif state == "UNKNOWN_ERROR":
597
# Disable some menu items
598
self.__litems["connect"].set_sensitive(False)
599
self.__litems["disconnect"].set_sensitive(False)
600
# Change the behavior to file a bug
601
if self.__fatal_error:
604
self.__fatal_error = True
605
self.__visible = True
607
# Pop up a notification
608
n = pynotify.Notification(
610
_("There was a fatal error in Ubuntu One. " +
611
"This may be a bug in the software. "
612
"Please click on the Ubuntu One icon " +
613
"in your panel to report a bug."))
614
pixbuf = self.__theme.load_icon(
615
"ubuntuone-client-error", NOTIFY_ICON_SIZE,
616
gtk.ICON_LOOKUP_GENERIC_FALLBACK)
617
n.set_icon_from_pixbuf(pixbuf)
618
n.set_urgency(pynotify.URGENCY_CRITICAL)
620
# Set the tooltip and icon on the applet
621
self.set_tooltip(_("Fatal Error"))
622
self.set_from_icon_name("ubuntuone-client-error")
625
self.__connected = True
626
self.set_from_icon_name("ubuntuone-client-idle")
627
if state.startswith("CONNECTING") or \
628
state.startswith("START_CONNECTING") or \
629
state.startswith("AUTHENTICATING") or \
630
state.startswith("CONNECTED") or \
631
state.startswith("START_CONNECTED"):
632
self.set_from_icon_name("ubuntuone-client-idle")
633
self.set_tooltip(_("Connecting"))
634
self.__visible = True
636
self.update_visibility()
639
self.__litems["connect"].hide()
640
self.__litems["disconnect"].show()
642
self.__litems["connect"].show()
643
self.__litems["disconnect"].hide()
644
self.__config.set("ubuntuone", "connected", self.__connected)
646
def __hide_icon(self):
647
"""Timeout to hide tray icon after a period of inactivity."""
648
if self.__show_when == 0:
651
self.__visible = False
652
self.__visible_id = 0
653
self.set_visible(False)
656
def __get_root(self):
657
"""Method to get the rootdir from the sync daemon."""
658
# Get the managed root directory
660
client = self.__bus.get_object(DBUS_IFACE_NAME, "/",
661
follow_name_owner_changes=True)
662
except DBusException:
665
iface = dbus.Interface(client, DBUS_IFACE_SYNC_NAME)
667
"""We got the root dir."""
668
self.__managed_dir = root
669
if os.path.isdir(self.__managed_dir) and \
670
os.access(self.__managed_dir,
672
self.__ritems["open"].set_sensitive(True)
673
self.__add_to_places()
675
self.__ritems["open"].set_sensitive(False)
678
"""Handle error from the dbus callback."""
679
self.sd_dbus_error(error)
680
self.__managed_dir = None
681
self.__ritems["open"].set_sensitive(False)
683
iface.get_rootdir(reply_handler=got_root, error_handler=got_err)
685
# Now get the current status
687
client = self.__bus.get_object(DBUS_IFACE_NAME, "/status",
688
follow_name_owner_changes=True)
689
iface = dbus.Interface(client, DBUS_IFACE_STATUS_NAME)
690
iface.current_status(reply_handler=self.__status_changed,
691
error_handler=self.sd_dbus_error)
692
except DBusException, e:
693
self.sd_dbus_error(e)
698
def __auth_finished(self, *args, **kwargs):
699
"""Got the oauth token, get the root and status."""
700
gobject.idle_add(self.__get_root)
702
def __build_menus(self):
703
"""Create the pop-up menu items."""
704
# Create the left-click menu
707
self.__litems["status"] = gtk.MenuItem(
708
label=_("Your files are up to date."))
709
lmenu.append(self.__litems["status"])
710
self.__litems["status"].set_sensitive(False)
711
self.__litems["status"].show()
713
sep = gtk.SeparatorMenuItem()
717
self.__litems["connect"] = gtk.ImageMenuItem(
718
stock_id=gtk.STOCK_CONNECT)
719
lmenu.append(self.__litems["connect"])
720
self.__litems["connect"].connect("activate", self.__toggle_state)
721
self.__litems["connect"].show()
723
self.__litems["disconnect"] = gtk.ImageMenuItem(
724
stock_id=gtk.STOCK_DISCONNECT)
725
lmenu.append(self.__litems["disconnect"])
726
self.__litems["disconnect"].connect("activate", self.__toggle_state)
730
# Create the right-click menu
733
self.__ritems["bug"] = gtk.MenuItem(label=_("_Report a Problem"))
734
rmenu.append(self.__ritems["bug"])
735
self.__ritems["bug"].connect("activate", self.__report_problem)
736
self.__ritems["bug"].show()
738
self.__ritems["open"] = gtk.MenuItem(label=_("_Open Folder"))
739
rmenu.append(self.__ritems["open"])
740
self.__ritems["open"].connect("activate", self.__open_folder)
741
self.__ritems["open"].set_sensitive(False)
742
self.__ritems["open"].show()
744
self.__ritems["web"] = gtk.MenuItem(label=_("_Go to Web"))
745
rmenu.append(self.__ritems["web"])
746
self.__ritems["web"].connect("activate", self.__open_website)
747
self.__ritems["web"].show()
749
self.__ritems["config"] = gtk.ImageMenuItem(
750
stock_id=gtk.STOCK_PREFERENCES)
751
rmenu.append(self.__ritems["config"])
752
self.__ritems["config"].connect("activate", self.__open_config)
753
self.__ritems["config"].show()
755
sep = gtk.SeparatorMenuItem()
759
self.__ritems["quit"] = gtk.ImageMenuItem(stock_id=gtk.STOCK_QUIT)
760
rmenu.append(self.__ritems["quit"])
761
self.__ritems["quit"].connect("activate", self.__quit_applet)
762
self.__ritems["quit"].show()
768
def __size_changed(self, icon, size, data=None):
769
"""Callback for when the size changes."""
772
elif size >= 24 and size < 32:
774
elif size >= 32 and size < 48:
776
elif size >= 48 and size < 64:
781
def __popup_menu(self, icon, button, timestamp, data=None):
782
"""Pops up the context menu for the tray icon."""
784
self.__status_menu.popup(None, None,
785
gtk.status_icon_position_menu,
786
button, timestamp, icon)
788
self.__config_menu.popup(None, None,
789
gtk.status_icon_position_menu,
790
button, timestamp, icon)
792
def __stop_syncdaemon(self):
793
"""Tell the syncdaemon to quit."""
795
"""Just log and ignore."""
796
logger.error(_("Quit Error: %s") % e.get_dbus_message())
799
client = self.__bus.get_object(DBUS_IFACE_NAME, "/",
800
follow_name_owner_changes=True)
801
iface = dbus.Interface(client, DBUS_IFACE_SYNC_NAME)
802
iface.quit(reply_handler=dbus_async,
803
error_handler=quit_error)
804
except DBusException, e:
807
def __quit_applet(self, menuitem, data=None):
808
"""Quit the daemon and closes the applet."""
809
self.__stop_syncdaemon()
813
def __toggle_state(self, menuitem, data=None):
814
"""Connects or disconnects the storage sync process."""
816
client = self.__bus.get_object(DBUS_IFACE_NAME, "/",
817
follow_name_owner_changes=True)
818
iface = dbus.Interface(client, DBUS_IFACE_SYNC_NAME)
820
iface.disconnect(reply_handler=dbus_async,
821
error_handler=self.sd_dbus_error)
823
if self.__main and self.__main.authorized is False:
824
self.__main.check_for_token(do_login=True)
825
iface.connect(reply_handler=dbus_async,
826
error_handler=self.sd_dbus_error)
827
except DBusException, e:
828
self.sd_dbus_error(e)
830
self.__config.set("ubuntuone", "connected", not self.__connected)
831
with open(CONF_FILE, "w+b") as f:
832
self.__config.write(f)
834
def __open_folder(self, data=None):
835
"""Opens the storage folder in the file manager."""
836
if not self.__managed_dir or not os.path.isdir(self.__managed_dir):
839
folder = "file://%s" % quote(self.__managed_dir)
842
def __do_action(self, data=None):
843
"""Handles the most appropriate action when the icon is clicked."""
844
if self.__fatal_error:
845
self.__report_problem()
846
self.__quit_applet(None)
849
if self.__need_update:
850
do_xdg_open("apt:ubuntuone-storage-protocol?refresh=yes")
853
# Popup the status menu
854
self.emit("popup-menu", 0, gtk.get_current_event_time())
856
def __report_problem_real(self):
857
"""Runs apport to report a problem against our code."""
858
args = ["ubuntu-bug", "ubuntuone-client"]
859
ret = subprocess.call(args, env=os.environ)
861
logger.error(_("Failed to run 'ubuntu-bug'"))
863
def __report_problem(self, data=None):
864
"""Pops another thread to run apport in."""
865
Thread(target=self.__report_problem_real, name="apport").start()
867
def __open_website(self, data=None, url=None):
868
"""Opens the one.ubuntu.com web site."""
872
do_xdg_open("https://one.ubuntu.com/")
875
def __open_config(self, data=None):
876
"""Opens the preferences dialog."""
879
def __add_to_places(self):
880
"""Add the managed directory to the .gtk-bookmarks file."""
882
if self.__config.getboolean("ubuntuone", "bookmarked"):
885
path = os.path.join(os.path.expanduser("~"), ".gtk-bookmarks")
886
with open(path, "a+") as f:
887
bookmarks_entry = "file://%s %s\n" % (
888
quote(self.__managed_dir), BOOKMARK_NAME)
891
if line == bookmarks_entry:
894
f.write(bookmarks_entry)
896
self.__config.set("ubuntuone", "bookmarked", "True")
897
with open(CONF_FILE, "w+b") as f:
898
self.__config.write(f)
900
def sd_dbus_error(self, error):
902
Handle DBus errors for crucial syncdaemon calls,
903
and change the applet behavior slightly.
905
logger.error(_("DBus Error: %s") % error.get_dbus_message())
906
if self.__fatal_error:
909
self.__fatal_error = True
910
self.__status_changed({'name' : 'UNKNOWN_ERROR'})
913
class AppletConfig(dbus.service.Object):
914
"""DBus Service object"""
916
def __init__(self, icon, *args, **kwargs):
917
"""Initialize our magic."""
919
self.path = "/config"
920
self.bus = dbus.SessionBus()
921
bus_name = dbus.service.BusName(APPLET_BUS_NAME,
923
dbus.service.Object.__init__(self, bus_name=bus_name,
924
object_path=self.path)
926
@dbus.service.method(APPLET_CONFIG_NAME,
927
in_signature='i', out_signature='')
928
def set_visibility_config(self, visibility):
929
self.icon.set_visibility_config(visibility)
931
@dbus.service.method(APPLET_CONFIG_NAME,
932
in_signature='i', out_signature='')
933
def set_connection_config(self, connect):
934
self.icon.set_connection_config(connect)
937
if __name__ == "__main__":
938
gettext.bindtextdomain(clientdefs.GETTEXT_PACKAGE, clientdefs.LOCALEDIR)
939
gettext.textdomain(clientdefs.GETTEXT_PACKAGE)
941
# Register DBus service for making sure we run only one instance
942
bus = dbus.SessionBus()
943
if bus.request_name(APPLET_BUS_NAME, dbus.bus.NAME_FLAG_DO_NOT_QUEUE) == dbus.bus.REQUEST_NAME_REPLY_EXISTS:
944
print _("Ubuntu One client applet already running, quitting")
948
gtk.rc_parse_string(RCSTYLE)