~pplr/ubuntuone-client/u1sync-unicode

« back to all changes in this revision

Viewing changes to bin/ubuntuone-client-applet

  • Committer: Rodney Dawes
  • Date: 2010-01-19 21:20:01 UTC
  • mto: This revision was merged to the branch mainline in revision 322.
  • Revision ID: rodney.dawes@canonical.com-20100119212001-uodnpmnmjv1mdb50
RemoveĀ theĀ applet

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
2
 
 
3
 
# ubuntuone-client-applet - Tray icon applet for managing Ubuntu One
4
 
#
5
 
# Author: Rodney Dawes <rodney.dawes@canonical.com>
6
 
#
7
 
# Copyright 2009 Canonical Ltd.
8
 
#
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.
12
 
#
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.
17
 
#
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/>.
20
 
 
21
 
from __future__ import with_statement
22
 
 
23
 
import pygtk
24
 
pygtk.require('2.0')
25
 
import gobject
26
 
import gtk
27
 
import os
28
 
import subprocess
29
 
import sys
30
 
import gettext
31
 
from ubuntuone import clientdefs
32
 
 
33
 
import dbus.service
34
 
 
35
 
# pylint: disable-msg=F0401
36
 
import pynotify
37
 
 
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
44
 
 
45
 
from ubuntuone.oauthdesktop.logger import setupLogging
46
 
logger = setupLogging("UbuntuOne.Client.Applet")
47
 
 
48
 
DBusGMainLoop(set_as_default=True)
49
 
 
50
 
_ = gettext.gettext
51
 
ngettext = gettext.ngettext
52
 
 
53
 
APPLET_BUS_NAME = "com.ubuntuone.ClientApplet"
54
 
APPLET_CONFIG_NAME = APPLET_BUS_NAME + ".Config"
55
 
 
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"
60
 
 
61
 
DBUS_IFACE_AUTH_NAME = "com.ubuntuone.Authentication"
62
 
 
63
 
OAUTH_REALM = "https://ubuntuone.com"
64
 
OAUTH_CONSUMER = "ubuntuone"
65
 
BOOKMARK_NAME = "Ubuntu One"
66
 
 
67
 
NOTIFY_ICON_SIZE = 48
68
 
 
69
 
# Why thank you GTK+ for enforcing style-set and breaking API
70
 
RCSTYLE = """
71
 
style 'dialogs' {
72
 
  GtkDialog::action-area-border = 12
73
 
  GtkDialog::button-spacing = 6
74
 
  GtkDialog::content-area-border = 0
75
 
}
76
 
widget_class '*Dialog*' style 'dialogs'
77
 
"""
78
 
 
79
 
CONF_FILE = os.path.join(xdg_config_home, "ubuntuone", "ubuntuone-client.conf")
80
 
 
81
 
 
82
 
def dbus_async(*args):
83
 
      """Simple handler to make dbus do stuff async."""
84
 
      pass
85
 
 
86
 
class AppletMain(object):
87
 
      """Main applet process class."""
88
 
 
89
 
      def __init__(self, *args, **kw):
90
 
            """Initializes the child threads and dbus monitor."""
91
 
            logger.info(_("Starting Ubuntu One client version %s") %
92
 
                        clientdefs.VERSION)
93
 
 
94
 
            # Whether or not we are authorized
95
 
            self.is_authorized = False
96
 
 
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))
100
 
 
101
 
            self.config = ConfigParser()
102
 
            self.config.read(CONF_FILE)
103
 
 
104
 
            if not self.config.has_section("ubuntuone"):
105
 
                  self.config.add_section("ubuntuone")
106
 
 
107
 
            if not self.config.has_option("ubuntuone", "show_applet"):
108
 
                  self.config.set("ubuntuone", "show_applet", "1")
109
 
 
110
 
            if not self.config.has_option("ubuntuone", "connect"):
111
 
                  self.config.set("ubuntuone", "connect", "0")
112
 
 
113
 
            if not self.config.has_option("ubuntuone", "connected"):
114
 
                  self.config.set("ubuntuone", "connected", "False")
115
 
 
116
 
            if not self.config.has_option("ubuntuone", "bookmarked"):
117
 
                  self.config.set("ubuntuone", "bookmarked", "False")
118
 
 
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")
122
 
 
123
 
            if not os.path.exists(CONF_FILE):
124
 
                  with open(CONF_FILE, "w+b") as f:
125
 
                        self.config.write(f)
126
 
 
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)
141
 
 
142
 
            self.__icon = AppletIcon(main=self, config=self.config)
143
 
 
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
147
 
 
148
 
            self.__start_storage_daemon()
149
 
            self.add_to_autostart()
150
 
 
151
 
            if self.connect == 2:
152
 
                  return
153
 
 
154
 
            if self.connect == 1 and not self.connected:
155
 
                  return
156
 
 
157
 
            try:
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)
165
 
 
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()
170
 
 
171
 
            def quit_error(e):
172
 
                  """Only log when quit fails."""
173
 
                  logger.error(_("Quit Error: %s") % e.get_dbus_message())
174
 
 
175
 
            try:
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:
182
 
                  quit_error(e)
183
 
 
184
 
            gtk.main_quit()
185
 
 
186
 
      def __no_credentials(self):
187
 
            """Signal callback for when no credentials exist in the keyring."""
188
 
            self.is_authorized = False
189
 
 
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())
195
 
 
196
 
            try:
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,
201
 
                                    do_login,
202
 
                                    reply_handler=dbus_async,
203
 
                                    error_handler=local_dbus_error)
204
 
            except DBusException, e:
205
 
                  local_dbus_error(e)
206
 
                  return False
207
 
 
208
 
            return False
209
 
 
210
 
      def __start_storage_daemon_maybe(self):
211
 
            """Start the storage daemon."""
212
 
            try:
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)
220
 
                  return False
221
 
 
222
 
            return False
223
 
 
224
 
      def __start_storage_daemon(self):
225
 
            """Need to call dbus from a idle callback"""
226
 
            gobject.idle_add(self.__start_storage_daemon_maybe)
227
 
 
228
 
      def add_to_autostart(self):
229
 
            """Add ourself to the autostart config."""
230
 
            autostart_entry = """[Desktop Entry]
231
 
Name=Ubuntu One
232
 
Exec=ubuntuone-client-applet
233
 
Icon=ubuntuone-client
234
 
Terminal=false
235
 
Type=Application
236
 
X-Ubuntu-Gettext-Domain=ubuntuone-client
237
 
X-KDE-autostart-after=panel
238
 
X-GNOME-Autostart-enabled=true
239
 
"""
240
 
            if not os.path.exists(os.path.join(xdg_config_home, "autostart")):
241
 
                  os.makedirs(os.path.join(xdg_config_home, "autostart"))
242
 
 
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)
248
 
 
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")
253
 
            try:
254
 
                  os.unlink(path)
255
 
            except OSError:
256
 
                  pass
257
 
 
258
 
      def main(self):
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)
262
 
 
263
 
            gtk.main()
264
 
 
265
 
      @property
266
 
      def authorized(self):
267
 
            """Are we authorized?"""
268
 
            return self.is_authorized
269
 
 
270
 
      def sd_dbus_error(self, error):
271
 
            """Got an error from DBus."""
272
 
            self.__icon.sd_dbus_error(error)
273
 
 
274
 
 
275
 
 
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)
283
 
      try:
284
 
            ret = subprocess.call(["ubuntuone-client-preferences"],
285
 
                                  env=os.environ)
286
 
      except OSError:
287
 
            ret = -1
288
 
      if ret != 0:
289
 
            logger.error(_("Failed to open Ubuntu One preferences"))
290
 
 
291
 
def do_config_open():
292
 
      """Do the preferences opening in a thread."""
293
 
      Thread(target=do_config_open_real, name="preferences").start()
294
 
 
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)
298
 
      if ret != 0:
299
 
            logger.error(_("Failed to run 'xdg-open %s'") % path_or_url)
300
 
 
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()
304
 
 
305
 
 
306
 
class AppletIcon(gtk.StatusIcon):
307
 
      """
308
 
      Custom StatusIcon derived from gtk.StatusIcon which supports
309
 
      animated icons and a few other nice things.
310
 
      """
311
 
 
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)
317
 
 
318
 
            # The AppletMain object
319
 
            self.__main = main
320
 
 
321
 
            # A ConfigParser object that we can poke at
322
 
            self.__config = config
323
 
            self.__show_when = self.__config.getint("ubuntuone", "show_applet")
324
 
 
325
 
            self.__managed_dir = None
326
 
 
327
 
            self.__size = 24
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],
331
 
                        "data"))
332
 
            self.__theme.append_search_path(iconpath)
333
 
 
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"))
338
 
 
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)
343
 
 
344
 
            self.__size_changed(self, self.__size)
345
 
 
346
 
            self.__litems = {}
347
 
            self.__ritems = {}
348
 
            self.__status_menu, self.__config_menu = self.__build_menus()
349
 
 
350
 
            self.__connected = False
351
 
            self.__need_update = False
352
 
            self.__fatal_error = False
353
 
 
354
 
            pynotify.init("Ubuntu One")
355
 
 
356
 
            # Managing applet visibility
357
 
            self.__visible = True
358
 
            self.__visible_id = 0
359
 
 
360
 
            # Up/Dn status
361
 
            self.__lock = Lock()
362
 
            self.__updating = 0
363
 
            self.__total = 0
364
 
            self.__last_id = 0
365
 
 
366
 
            self.__bus = dbus.SessionBus()
367
 
 
368
 
            # Our own DBus service, for the config to deal with
369
 
            self.__service = AppletConfig(icon=self)
370
 
 
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)
376
 
 
377
 
            self.__bus.add_signal_receiver(
378
 
                  handler_function=self.__status_changed,
379
 
                  signal_name="StatusChanged",
380
 
                  dbus_interface=DBUS_IFACE_STATUS_NAME)
381
 
 
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)
394
 
 
395
 
            self.set_visible(True)
396
 
 
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)
402
 
 
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()
408
 
 
409
 
      def set_connection_config(self, connect):
410
 
            """Update the connection config."""
411
 
            self.__config.set("ubuntuone", "connect", str(connect))
412
 
 
413
 
      def __update_transfer_status(self, done=False):
414
 
            """Update the status display."""
415
 
            with self.__lock:
416
 
                  text = _("Updating file %(transfers)d of %(total)d...") % \
417
 
                           dict(transfers=self.__updating, total=self.__total)
418
 
            label = self.__litems["status"].get_child()
419
 
            if done:
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."))
423
 
            else:
424
 
                  label.set_markup("<i>%s</i>" % text)
425
 
 
426
 
      def __queue_changed(self, queue):
427
 
            """Handle ContentQueueChanged."""
428
 
            total = 0
429
 
            d = queue.get('Download', None)
430
 
            if d is not None:
431
 
                  total += int(d.get('count', 0))
432
 
            d = queue.get('Upload', None)
433
 
            if d is not None:
434
 
                  total += int(d.get('count', 0))
435
 
            first = False
436
 
            last = False
437
 
            self.__visible = True
438
 
            self.set_tooltip(_("Updating files..."))
439
 
            self.update_visibility()
440
 
            with self.__lock:
441
 
                  if self.__total == 0:
442
 
                        first = True
443
 
                        last = False
444
 
                  if self.__total != 0 and total == 0:
445
 
                        first = False
446
 
                        last = True
447
 
                  self.__total = total + self.__updating
448
 
            if first:
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)
457
 
                  n.show()
458
 
            if last:
459
 
                  if self.__last_id != 0:
460
 
                        gobject.source_remove(self.__last_id)
461
 
                        self.__last_id = 0
462
 
                  self.__last_id = gobject.timeout_add_seconds(
463
 
                        15, self.__updating_completed)
464
 
 
465
 
      def __updating_completed(self):
466
 
            """Timeout to avoid multiple started/finished notifications."""
467
 
            really_last = False
468
 
            n = None
469
 
            with self.__lock:
470
 
                  done = self.__total - self.__updating
471
 
                  if done == 0:
472
 
                        really_last = True
473
 
            if not really_last:
474
 
                  return False
475
 
 
476
 
            with self.__lock:
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 })
482
 
                  self.__total = 0
483
 
                  self.__updating = 0
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)
488
 
            n.show()
489
 
            self.__update_transfer_status(True)
490
 
            return False
491
 
 
492
 
      def __transfer_started(self, path):
493
 
            """Handle the started signals."""
494
 
            with self.__lock:
495
 
                  self.__updating += 1
496
 
            self.__update_transfer_status()
497
 
 
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
503
 
 
504
 
            if (self.__visible and self.__show_when != 2) or self.__fatal_error:
505
 
                  self.set_visible(True)
506
 
                  return
507
 
 
508
 
            if self.__show_when == 2 and not self.__fatal_error:
509
 
                  self.set_visible(False)
510
 
                  return
511
 
 
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)
516
 
 
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)
521
 
 
522
 
            if self.__show_when != 0:
523
 
                  self.__visible = False
524
 
 
525
 
            state = status['name']
526
 
 
527
 
            self.set_tooltip("Ubuntu One")
528
 
 
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)
534
 
 
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
541
 
 
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)
559
 
                  n.show()
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")
563
 
 
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
569
 
 
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
576
 
 
577
 
                  def reauthorize_error(e):
578
 
                        """Simple dbus error handler."""
579
 
                        logger.error(_("Error clearing token: %s") % str(e))
580
 
 
581
 
                  try:
582
 
                        def token_cleared():
583
 
                              """Do the next step."""
584
 
                              if self.__main:
585
 
                                    self.__main.check_for_token(True)
586
 
 
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:
594
 
                        reauthorize_error(e)
595
 
 
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:
602
 
                        return
603
 
 
604
 
                  self.__fatal_error = True
605
 
                  self.__visible = True
606
 
 
607
 
                  # Pop up a notification
608
 
                  n = pynotify.Notification(
609
 
                        "Ubuntu One",
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)
619
 
                  n.show()
620
 
                  # Set the tooltip and icon on the applet
621
 
                  self.set_tooltip(_("Fatal Error"))
622
 
                  self.set_from_icon_name("ubuntuone-client-error")
623
 
 
624
 
            else:
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
635
 
 
636
 
            self.update_visibility()
637
 
 
638
 
            if self.__connected:
639
 
                  self.__litems["connect"].hide()
640
 
                  self.__litems["disconnect"].show()
641
 
            else:
642
 
                  self.__litems["connect"].show()
643
 
                  self.__litems["disconnect"].hide()
644
 
            self.__config.set("ubuntuone", "connected", self.__connected)
645
 
 
646
 
      def __hide_icon(self):
647
 
            """Timeout to hide tray icon after a period of inactivity."""
648
 
            if self.__show_when == 0:
649
 
                  return False
650
 
 
651
 
            self.__visible = False
652
 
            self.__visible_id = 0
653
 
            self.set_visible(False)
654
 
            return False
655
 
 
656
 
      def __get_root(self):
657
 
            """Method to get the rootdir from the sync daemon."""
658
 
            # Get the managed root directory
659
 
            try:
660
 
                  client = self.__bus.get_object(DBUS_IFACE_NAME, "/",
661
 
                                                 follow_name_owner_changes=True)
662
 
            except DBusException:
663
 
                  return False
664
 
 
665
 
            iface = dbus.Interface(client, DBUS_IFACE_SYNC_NAME)
666
 
            def got_root(root):
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,
671
 
                                      os.F_OK | os.R_OK):
672
 
                        self.__ritems["open"].set_sensitive(True)
673
 
                        self.__add_to_places()
674
 
                  else:
675
 
                        self.__ritems["open"].set_sensitive(False)
676
 
 
677
 
            def got_err(error):
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)
682
 
 
683
 
            iface.get_rootdir(reply_handler=got_root, error_handler=got_err)
684
 
 
685
 
            # Now get the current status
686
 
            try:
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)
694
 
                  return False
695
 
 
696
 
            return False
697
 
 
698
 
      def __auth_finished(self, *args, **kwargs):
699
 
            """Got the oauth token, get the root and status."""
700
 
            gobject.idle_add(self.__get_root)
701
 
 
702
 
      def __build_menus(self):
703
 
            """Create the pop-up menu items."""
704
 
            # Create the left-click menu
705
 
            lmenu = gtk.Menu()
706
 
 
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()
712
 
 
713
 
            sep = gtk.SeparatorMenuItem()
714
 
            lmenu.append(sep)
715
 
            sep.show()
716
 
 
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()
722
 
 
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)
727
 
 
728
 
            lmenu.show()
729
 
 
730
 
            # Create the right-click menu
731
 
            rmenu = gtk.Menu()
732
 
 
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()
737
 
 
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()
743
 
 
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()
748
 
 
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()
754
 
 
755
 
            sep = gtk.SeparatorMenuItem()
756
 
            rmenu.append(sep)
757
 
            sep.show()
758
 
 
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()
763
 
 
764
 
            rmenu.show()
765
 
 
766
 
            return lmenu, rmenu
767
 
 
768
 
      def __size_changed(self, icon, size, data=None):
769
 
            """Callback for when the size changes."""
770
 
            if size < 24:
771
 
                  self.__size = 16
772
 
            elif size >= 24 and size < 32:
773
 
                  self.__size = 24
774
 
            elif size >= 32 and size < 48:
775
 
                  self.__size = 32
776
 
            elif size >= 48 and size < 64:
777
 
                  self.__size = 48
778
 
            else:
779
 
                  self.__size = size
780
 
 
781
 
      def __popup_menu(self, icon, button, timestamp, data=None):
782
 
            """Pops up the context menu for the tray icon."""
783
 
            if button == 0:
784
 
                  self.__status_menu.popup(None, None,
785
 
                                           gtk.status_icon_position_menu,
786
 
                                           button, timestamp, icon)
787
 
            else:
788
 
                  self.__config_menu.popup(None, None,
789
 
                                           gtk.status_icon_position_menu,
790
 
                                           button, timestamp, icon)
791
 
 
792
 
      def __stop_syncdaemon(self):
793
 
            """Tell the syncdaemon to quit."""
794
 
            def quit_error(e):
795
 
                  """Just log and ignore."""
796
 
                  logger.error(_("Quit Error: %s") % e.get_dbus_message())
797
 
 
798
 
            try:
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:
805
 
                  quit_error(e)
806
 
 
807
 
      def __quit_applet(self, menuitem, data=None):
808
 
            """Quit the daemon and closes the applet."""
809
 
            self.__stop_syncdaemon()
810
 
 
811
 
            gtk.main_quit()
812
 
 
813
 
      def __toggle_state(self, menuitem, data=None):
814
 
            """Connects or disconnects the storage sync process."""
815
 
            try:
816
 
                  client = self.__bus.get_object(DBUS_IFACE_NAME, "/",
817
 
                                                 follow_name_owner_changes=True)
818
 
                  iface = dbus.Interface(client, DBUS_IFACE_SYNC_NAME)
819
 
                  if self.__connected:
820
 
                        iface.disconnect(reply_handler=dbus_async,
821
 
                                         error_handler=self.sd_dbus_error)
822
 
                  else:
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)
829
 
 
830
 
            self.__config.set("ubuntuone", "connected", not self.__connected)
831
 
            with open(CONF_FILE, "w+b") as f:
832
 
                  self.__config.write(f)
833
 
 
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):
837
 
                  return
838
 
 
839
 
            folder = "file://%s" % quote(self.__managed_dir)
840
 
            do_xdg_open(folder)
841
 
 
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)
847
 
                  return
848
 
 
849
 
            if self.__need_update:
850
 
                  do_xdg_open("apt:ubuntuone-storage-protocol?refresh=yes")
851
 
                  return
852
 
 
853
 
            # Popup the status menu
854
 
            self.emit("popup-menu", 0, gtk.get_current_event_time())
855
 
 
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)
860
 
            if ret != 0:
861
 
                  logger.error(_("Failed to run 'ubuntu-bug'"))
862
 
 
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()
866
 
 
867
 
      def __open_website(self, data=None, url=None):
868
 
            """Opens the one.ubuntu.com web site."""
869
 
            if url:
870
 
                  do_xdg_open(url)
871
 
            else:
872
 
                  do_xdg_open("https://one.ubuntu.com/")
873
 
 
874
 
 
875
 
      def __open_config(self, data=None):
876
 
            """Opens the preferences dialog."""
877
 
            do_config_open()
878
 
 
879
 
      def __add_to_places(self):
880
 
            """Add the managed directory to the .gtk-bookmarks file."""
881
 
            # Only add once
882
 
            if self.__config.getboolean("ubuntuone", "bookmarked"):
883
 
                  return
884
 
 
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)
889
 
                  in_file = False
890
 
                  for line in f:
891
 
                        if line == bookmarks_entry:
892
 
                              in_file = True
893
 
                  if not in_file:
894
 
                        f.write(bookmarks_entry)
895
 
 
896
 
            self.__config.set("ubuntuone", "bookmarked", "True")
897
 
            with open(CONF_FILE, "w+b") as f:
898
 
                  self.__config.write(f)
899
 
 
900
 
      def sd_dbus_error(self, error):
901
 
            """
902
 
            Handle DBus errors for crucial syncdaemon calls,
903
 
            and change the applet behavior slightly.
904
 
            """
905
 
            logger.error(_("DBus Error: %s") % error.get_dbus_message())
906
 
            if self.__fatal_error:
907
 
                  return
908
 
 
909
 
            self.__fatal_error = True
910
 
            self.__status_changed({'name' : 'UNKNOWN_ERROR'})
911
 
 
912
 
 
913
 
class AppletConfig(dbus.service.Object):
914
 
      """DBus Service object"""
915
 
 
916
 
      def __init__(self, icon, *args, **kwargs):
917
 
            """Initialize our magic."""
918
 
            self.icon = icon
919
 
            self.path = "/config"
920
 
            self.bus = dbus.SessionBus()
921
 
            bus_name = dbus.service.BusName(APPLET_BUS_NAME,
922
 
                                            bus=self.bus)
923
 
            dbus.service.Object.__init__(self, bus_name=bus_name,
924
 
                                         object_path=self.path)
925
 
 
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)
930
 
 
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)
935
 
 
936
 
 
937
 
if __name__ == "__main__":
938
 
      gettext.bindtextdomain(clientdefs.GETTEXT_PACKAGE, clientdefs.LOCALEDIR)
939
 
      gettext.textdomain(clientdefs.GETTEXT_PACKAGE)
940
 
 
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")
945
 
            do_config_open()
946
 
            sys.exit(0)
947
 
 
948
 
      gtk.rc_parse_string(RCSTYLE)
949
 
 
950
 
      icon = AppletMain()
951
 
      icon.main()