~nataliabidart/ubuntu/natty/ubuntuone-control-panel/ubuntuone-control-panel-0.9.3

« back to all changes in this revision

Viewing changes to ubuntuone/controlpanel/gtk/gui.py

Tags: upstream-0.5.1
ImportĀ upstreamĀ versionĀ 0.5.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
46
46
from ubuntuone.controlpanel.gtk.widgets import GreyableBin
47
47
 
48
48
from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,
49
 
    DBUS_PREFERENCES_IFACE)
 
49
    DBUS_PREFERENCES_IFACE, backend)
50
50
from ubuntuone.controlpanel.backend import (DEVICE_TYPE_PHONE,
51
51
    DEVICE_TYPE_COMPUTER, bool_str)
52
52
from ubuntuone.controlpanel.logger import setup_logging, log_call
53
53
from ubuntuone.controlpanel.utils import get_data_file
54
54
 
 
55
from ubuntuone.controlpanel.gtk import package_manager
55
56
 
56
57
logger = setup_logging('gtk.gui')
57
58
_ = gettext.gettext
77
78
VALUE_ERROR = _('Value could not be retrieved.')
78
79
WARNING_MARKUP = '<span foreground="%s"><b>%%s</b></span>' % ORANGE
79
80
KILOBYTES = 1024
 
81
NO_OP = lambda *a, **kw: None
 
82
 
 
83
 
 
84
def error_handler(*args, **kwargs):
 
85
    """Log errors when calling D-Bus methods in a async way."""
 
86
    logger.error('Error handler received: %r, %r', args, kwargs)
80
87
 
81
88
 
82
89
def filter_by_app_name(f):
149
156
class ControlPanelWindow(gtk.Window):
150
157
    """The main window for the Ubuntu One control panel."""
151
158
 
152
 
    TITLE = _('My %(app_name)s Account')
 
159
    TITLE = _('My %(app_name)s Dashboard')
153
160
 
154
161
    def __init__(self):
155
162
        super(ControlPanelWindow, self).__init__()
351
358
        settings = {TC_URL_KEY: U1_TC_URL, HELP_TEXT_KEY: U1_DESCRIPTION,
352
359
                    WINDOW_ID_KEY: str(self._window_id),
353
360
                    PING_URL_KEY: U1_PING_URL}
354
 
        self.sso_backend.register(U1_APP_NAME, settings)
 
361
        self.sso_backend.register(U1_APP_NAME, settings,
 
362
            reply_handler=NO_OP, error_handler=error_handler)
355
363
        self.set_property('greyed', True)
356
364
        self.warning_label.set_text('')
357
365
 
360
368
        settings = {TC_URL_KEY: U1_TC_URL, HELP_TEXT_KEY: U1_DESCRIPTION,
361
369
                    WINDOW_ID_KEY: str(self._window_id),
362
370
                    PING_URL_KEY: U1_PING_URL}
363
 
        self.sso_backend.login(U1_APP_NAME, settings)
 
371
        self.sso_backend.login(U1_APP_NAME, settings,
 
372
            reply_handler=NO_OP, error_handler=error_handler)
364
373
        self.set_property('greyed', True)
365
374
        self.warning_label.set_text('')
366
375
 
407
416
        else:
408
417
            self.set_sensitive(True)
409
418
            self.warning_label.set_text('')
410
 
            self.sso_backend.find_credentials(U1_APP_NAME, {})
411
 
 
412
 
 
413
 
class AccountPanel(UbuntuOneBin, ControlPanelMixin):
414
 
    """The account panel. The user can manage the subscription."""
 
419
            self.sso_backend.find_credentials(U1_APP_NAME, {},
 
420
                reply_handler=NO_OP, error_handler=error_handler)
 
421
 
 
422
 
 
423
class DashboardPanel(UbuntuOneBin, ControlPanelMixin):
 
424
    """The dashboard panel. The user can manage the subscription."""
415
425
 
416
426
    TITLE = _('Welcome to Ubuntu One!')
417
427
    NAME = _('Name')
420
430
 
421
431
    def __init__(self):
422
432
        UbuntuOneBin.__init__(self)
423
 
        ControlPanelMixin.__init__(self, filename='account.ui')
 
433
        ControlPanelMixin.__init__(self, filename='dashboard.ui')
424
434
        self.add(self.itself)
425
435
        self.show()
426
436
 
515
525
        volume_id = checkbutton.get_label()
516
526
        subscribed = bool_str(checkbutton.get_active())
517
527
        self.backend.change_volume_settings(volume_id,
518
 
                                            {'subscribed': subscribed})
 
528
            {'subscribed': subscribed},
 
529
            reply_handler=NO_OP, error_handler=error_handler)
519
530
 
520
531
    def load(self):
521
532
        """Load the volume list."""
522
 
        self.backend.volumes_info()
 
533
        self.backend.volumes_info(reply_handler=NO_OP,
 
534
                                  error_handler=error_handler)
523
535
        self.message.start()
524
536
 
525
537
 
564
576
        # Not disabling the GUI to avoid annyong twitchings
565
577
        #self.set_sensitive(False)
566
578
        self.warning_label.set_text('')
567
 
        self.backend.change_device_settings(self.id, self.__dict__)
 
579
        self.backend.change_device_settings(self.id, self.__dict__,
 
580
            reply_handler=NO_OP, error_handler=error_handler)
568
581
 
569
582
    def _block_signals(f):
570
583
        """Execute 'f' while having the _updating flag set."""
590
603
 
591
604
    def on_remove_clicked(self, widget):
592
605
        """Remove button was clicked or activated."""
593
 
        self.backend.remove_device(self.id)
 
606
        self.backend.remove_device(self.id,
 
607
            reply_handler=NO_OP, error_handler=error_handler)
594
608
        self.set_sensitive(False)
595
609
 
596
610
    @_block_signals
746
760
 
747
761
    def load(self):
748
762
        """Load the device list."""
749
 
        self.backend.devices_info()
 
763
        self.backend.devices_info(reply_handler=NO_OP,
 
764
                                  error_handler=error_handler)
750
765
        self.message.start()
751
766
 
752
767
 
753
 
class ApplicationsPanel(UbuntuOneBin, ControlPanelMixin):
754
 
    """The applications panel."""
 
768
class InstallPackage(gtk.VBox, ControlPanelMixin):
 
769
    """A widget to process the install of a package."""
 
770
 
 
771
    INSTALL_PACKAGE = _('You need to install the package <i>%(package_name)s'
 
772
                        '</i> in order to enable replication.')
 
773
    INSTALLING = _('The package <i>%(package_name)s</i> is being installed, '
 
774
                   'please wait...')
 
775
    FAILED_INSTALL = _('The installation of <i>%(package_name)s</i> failed.')
 
776
    SUCCESS_INSTALL = _('The installation of <i>%(package_name)s</i> '
 
777
                        'was successful.')
 
778
 
 
779
    def __init__(self, package_name):
 
780
        gtk.VBox.__init__(self)
 
781
        ControlPanelMixin.__init__(self, filename='install.ui')
 
782
        self.add(self.itself)
 
783
 
 
784
        self.package_name = package_name
 
785
        self.package_manager = package_manager.PackageManager()
 
786
        self.args = {'package_name': self.package_name}
 
787
        self.transaction = None
 
788
 
 
789
        self.progress_bar = None
 
790
        self.install_label.set_markup(self.INSTALL_PACKAGE % self.args)
 
791
 
 
792
        self.show()
 
793
 
 
794
    @package_manager.inline_callbacks
 
795
    def on_install_button_clicked(self, button):
 
796
        """The install button was clicked."""
 
797
        try:
 
798
            # create the install transaction
 
799
            self.transaction = yield self.package_manager.install(
 
800
                                    self.package_name)
 
801
 
 
802
            # create the progress bar and pack it to the box
 
803
            self.progress_bar = package_manager.PackageManagerProgressBar(
 
804
                                    self.transaction)
 
805
            self.progress_bar.show()
 
806
 
 
807
            self.itself.remove(self.install_button_box)
 
808
            self.itself.pack_start(self.progress_bar)
 
809
 
 
810
            self.transaction.connect('finished', self.on_install_finished)
 
811
            self.install_label.set_markup(self.INSTALLING % self.args)
 
812
            yield self.transaction.run()
 
813
        except:  # pylint: disable=W0702
 
814
            self._set_warning(self.FAILED_INSTALL % self.args,
 
815
                              self.install_label)
 
816
 
 
817
    @log_call(logger.info)
 
818
    def on_install_finished(self, transaction, exit_code):
 
819
        """The installation finished."""
 
820
        self.progress_bar.set_sensitive(False)
 
821
 
 
822
        if exit_code != package_manager.aptdaemon.enums.EXIT_SUCCESS:
 
823
            if hasattr(transaction, 'error'):
 
824
                logger.error('transaction failed: %r', transaction.error)
 
825
            self._set_warning(self.FAILED_INSTALL % self.args,
 
826
                              self.install_label)
 
827
        else:
 
828
            self.install_label.set_markup(self.SUCCESS_INSTALL % self.args)
 
829
            self.emit('finished')
 
830
 
 
831
 
 
832
class Service(gtk.VBox, ControlPanelMixin):
 
833
    """A service."""
 
834
 
 
835
    CHANGE_ERROR = _('The settings could not be changed,\n'
 
836
                     'previous values were restored.')
 
837
 
 
838
    def __init__(self, service_id, name, *args, **kwargs):
 
839
        gtk.VBox.__init__(self)
 
840
        ControlPanelMixin.__init__(self)
 
841
        self.id = service_id
 
842
 
 
843
        self.warning_label = gtk.Label()
 
844
        self.pack_start(self.warning_label, expand=False)
 
845
 
 
846
        self.button = gtk.CheckButton(label=name)
 
847
        self.pack_start(self.button, expand=False)
 
848
 
 
849
        self.show_all()
 
850
 
 
851
 
 
852
class FilesService(Service):
 
853
    """The file sync service."""
 
854
 
 
855
    FILES_SERVICE_NAME = _('Files')
 
856
 
 
857
    def __init__(self):
 
858
        Service.__init__(self, service_id='files',
 
859
                         name=self.FILES_SERVICE_NAME)
 
860
 
 
861
        self.set_sensitive(False)
 
862
 
 
863
        self.backend.connect_to_signal('FileSyncStatusChanged',
 
864
                                       self.on_file_sync_status_changed)
 
865
        self.backend.connect_to_signal('FilesEnabled', self.on_files_enabled)
 
866
        self.backend.connect_to_signal('FilesDisabled', self.on_files_disabled)
 
867
 
 
868
        self.backend.file_sync_status(reply_handler=NO_OP,
 
869
                                      error_handler=error_handler)
 
870
 
 
871
    @log_call(logger.debug)
 
872
    def on_file_sync_status_changed(self, status):
 
873
        """File sync status changed."""
 
874
        enabled = status != backend.FILE_SYNC_DISABLED
 
875
        self.button.set_active(enabled)
 
876
 
 
877
        if not self.is_sensitive():
 
878
            # first time we're getting this event
 
879
            self.button.connect('toggled', self.on_button_toggled)
 
880
            self.set_sensitive(True)
 
881
 
 
882
    def on_files_enabled(self):
 
883
        """Files service was enabled."""
 
884
        self.on_file_sync_status_changed('enabled!')
 
885
 
 
886
    def on_files_disabled(self):
 
887
        """Files service was disabled."""
 
888
        self.on_file_sync_status_changed(backend.FILE_SYNC_DISABLED)
 
889
 
 
890
    @log_call(logger.debug)
 
891
    def on_button_toggled(self, button):
 
892
        """Button was toggled, exclude/replicate the service properly."""
 
893
        logger.info('File sync enabled? %r', self.button.get_active())
 
894
        if self.button.get_active():
 
895
            self.backend.enable_files(reply_handler=NO_OP,
 
896
                                      error_handler=error_handler)
 
897
        else:
 
898
            self.backend.disable_files(reply_handler=NO_OP,
 
899
                                       error_handler=error_handler)
 
900
 
 
901
 
 
902
class DesktopcouchService(Service):
 
903
    """A desktopcouch service."""
 
904
 
 
905
    def __init__(self, service_id, name, enabled, dependency=None):
 
906
        Service.__init__(self, service_id, name)
 
907
 
 
908
        self.backend.connect_to_signal('ReplicationSettingsChanged',
 
909
            self.on_replication_settings_changed)
 
910
        self.backend.connect_to_signal('ReplicationSettingsChangeError',
 
911
            self.on_replication_settings_change_error)
 
912
 
 
913
        self.button.set_active(enabled)
 
914
 
 
915
        self.dependency = None
 
916
        if dependency is not None:
 
917
            self.dependency = InstallPackage(dependency)
 
918
            self.dependency.connect('finished', self.on_depedency_finished)
 
919
            self.pack_start(self.dependency, expand=False)
 
920
            self.button.set_sensitive(False)
 
921
 
 
922
        self.button.connect('toggled', self.on_button_toggled)
 
923
 
 
924
    def on_depedency_finished(self, widget):
 
925
        """The dependency was installed."""
 
926
        self.button.set_sensitive(True)
 
927
        self.remove(self.dependency)
 
928
        self.dependency = None
 
929
 
 
930
    @log_call(logger.debug)
 
931
    def on_button_toggled(self, button):
 
932
        """Button was toggled, exclude/replicate the service properly."""
 
933
        logger.info('Starting replication for %r? %r',
 
934
                    self.id, self.button.get_active())
 
935
 
 
936
        args = {'enabled': bool_str(self.button.get_active())}
 
937
        self.backend.change_replication_settings(self.id, args,
 
938
            reply_handler=NO_OP, error_handler=error_handler)
 
939
 
 
940
    @log_call(logger.info)
 
941
    def on_replication_settings_changed(self, replication_id):
 
942
        """The change of settings for this replication succeded."""
 
943
        if replication_id != self.id:
 
944
            return
 
945
        self.warning_label.set_text('')
 
946
 
 
947
    @log_call(logger.error)
 
948
    def on_replication_settings_change_error(self, replication_id,
 
949
                                             error_dict=None):
 
950
        """The change of settings for this replication failed."""
 
951
        if replication_id != self.id:
 
952
            return
 
953
        self.button.set_active(not self.button.get_active())
 
954
        self._set_warning(self.CHANGE_ERROR, self.warning_label)
 
955
 
 
956
 
 
957
class ServicesPanel(UbuntuOneBin, ControlPanelMixin):
 
958
    """The services panel."""
755
959
 
756
960
    TITLE = _('Ubuntu One services including data sync are enabled for the '
757
 
              'data types and applications listed below:')
 
961
              'data types and services listed below.')
 
962
    CHOOSE_SERVICES = _('Choose services to synchronize with this computer:')
 
963
    DESKTOPCOUCH_PKG = 'desktopcouch-ubuntuone'
 
964
    BOOKMARKS = _('Bookmarks (Firefox)')
 
965
    CONTACTS = _('Contacts (Evolution)')
 
966
    NO_PAIRING_RECORD = _('There is no Ubuntu One pairing record.')
758
967
 
759
968
    def __init__(self):
760
969
        UbuntuOneBin.__init__(self)
761
 
        ControlPanelMixin.__init__(self, filename='applications.ui')
 
970
        ControlPanelMixin.__init__(self, filename='services.ui')
762
971
        self.add(self.itself)
 
972
 
 
973
        self.package_manager = package_manager.PackageManager()
 
974
        self.install_box = None
 
975
 
 
976
        self.backend.connect_to_signal('ReplicationsInfoReady',
 
977
                                       self.on_replications_info_ready)
 
978
        self.backend.connect_to_signal('ReplicationsInfoError',
 
979
                                       self.on_replications_info_error)
 
980
 
 
981
        self.files.pack_start(FilesService(), expand=False)
 
982
 
763
983
        self.show()
764
984
 
 
985
    @property
 
986
    def has_desktopcouch(self):
 
987
        """Is desktopcouch installed?"""
 
988
        return self.package_manager.is_installed(self.DESKTOPCOUCH_PKG)
 
989
 
 
990
    @log_call(logger.debug)
 
991
    def load(self):
 
992
        """Load info."""
 
993
        if self.install_box is not None:
 
994
            self.itself.remove(self.install_box)
 
995
            self.install_box = None
 
996
 
 
997
        logger.info('load: has_desktopcouch? %r', self.has_desktopcouch)
 
998
        if not self.has_desktopcouch:
 
999
            self.message.set_text('')
 
1000
            self.replications.hide()
 
1001
 
 
1002
            self.install_box = InstallPackage(self.DESKTOPCOUCH_PKG)
 
1003
            self.install_box.connect('finished', self.load_replications)
 
1004
            self.itself.pack_start(self.install_box, expand=False)
 
1005
            self.itself.reorder_child(self.install_box, 0)
 
1006
        else:
 
1007
            self.load_replications()
 
1008
 
765
1009
        self.message.stop()
766
 
        self.message.set_text('Under construction')
 
1010
 
 
1011
    @log_call(logger.debug)
 
1012
    def load_replications(self, *args):
 
1013
        """Load replications info."""
 
1014
        # ask replications to the backend
 
1015
        self.message.start()
 
1016
        self.backend.replications_info(reply_handler=NO_OP,
 
1017
                                       error_handler=error_handler)
 
1018
 
 
1019
    @log_call(logger.debug)
 
1020
    def on_replications_info_ready(self, info):
 
1021
        """The replication info is ready."""
 
1022
        self.on_success(self.CHOOSE_SERVICES)
 
1023
 
 
1024
        self.replications.show()
 
1025
 
 
1026
        if self.install_box is not None:
 
1027
            self.itself.remove(self.install_box)
 
1028
            self.install_box = None
 
1029
 
 
1030
        for child in self.replications.get_children():
 
1031
            self.replications.remove(child)
 
1032
 
 
1033
        for item in info:
 
1034
            pkg = item['dependency']
 
1035
            child = DesktopcouchService(service_id=item['replication_id'],
 
1036
                                        name=item['name'],  # self.BOOKMARKS,
 
1037
                                        enabled=bool(item['enabled']),
 
1038
                                        dependency=pkg if pkg else None)
 
1039
            self.replications.pack_start(child, expand=False)
 
1040
 
 
1041
    @log_call(logger.error)
 
1042
    def on_replications_info_error(self, error_dict=None):
 
1043
        """The replication info can not be retrieved."""
 
1044
        if error_dict is not None and \
 
1045
           error_dict.get('error_type', None) == 'NoPairingRecord':
 
1046
            self.on_error(self.NO_PAIRING_RECORD)
 
1047
        else:
 
1048
            self.on_error()
767
1049
 
768
1050
 
769
1051
class ManagementPanel(gtk.VBox, ControlPanelMixin):
770
1052
    """The management panel.
771
1053
 
772
 
    The user can manage account, folders, devices and applications.
 
1054
    The user can manage dashboard, folders, devices and services.
773
1055
 
774
1056
    """
775
1057
 
817
1099
        self.status_label = LabelLoading(LOADING, fg_color=DEFAULT_FG)
818
1100
        self.status_box.pack_end(self.status_label, expand=False)
819
1101
 
820
 
        self.account = AccountPanel()
 
1102
        self.dashboard = DashboardPanel()
821
1103
        self.folders = FoldersPanel()
822
1104
        self.devices = DevicesPanel()
823
 
        self.applications = ApplicationsPanel()
 
1105
        self.services = ServicesPanel()
824
1106
 
825
1107
        cb = lambda button, page_num: self.notebook.set_current_page(page_num)
826
 
        self.tabs = (u'account', u'folders', u'devices', u'applications')
 
1108
        self.tabs = (u'dashboard', u'folders', u'devices', u'services')
827
1109
        for page_num, tab in enumerate(self.tabs):
828
1110
            setattr(self, ('%s_page' % tab).upper(), page_num)
829
1111
            button = getattr(self, '%s_button' % tab)
835
1117
 
836
1118
        self.folders_button.connect('clicked', lambda b: self.folders.load())
837
1119
        self.devices_button.connect('clicked', lambda b: self.devices.load())
 
1120
        self.services_button.connect('clicked', lambda b: self.services.load())
838
1121
        self.devices.connect('local-device-removed',
839
1122
                             lambda widget: self.emit('local-device-removed'))
840
1123
 
855
1138
 
856
1139
    def load(self):
857
1140
        """Load the account info and file sync status list."""
858
 
        self.backend.account_info()
859
 
        self.backend.file_sync_status()
860
 
        self.account_button.clicked()
 
1141
        self.backend.account_info(reply_handler=NO_OP,
 
1142
                                  error_handler=error_handler)
 
1143
        self.backend.file_sync_status(reply_handler=NO_OP,
 
1144
                                      error_handler=error_handler)
 
1145
        self.dashboard_button.clicked()
861
1146
 
862
1147
    @log_call(logger.debug)
863
1148
    def on_account_info_ready(self, info):
910
1195
 
911
1196
gobject.signal_new('local-device-removed', DevicesPanel,
912
1197
                    gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
 
1198
 
 
1199
gobject.signal_new('finished', InstallPackage,
 
1200
                    gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())