=== modified file 'data/install.ui'
--- data/install.ui 2010-12-24 14:53:03 +0000
+++ data/install.ui 2011-01-06 20:36:09 +0000
@@ -23,11 +23,12 @@
True
@@ -43,4 +44,8 @@
+
=== modified file 'po/POTFILES.in'
--- po/POTFILES.in 2011-01-04 16:12:56 +0000
+++ po/POTFILES.in 2011-01-06 20:36:09 +0000
@@ -1,8 +1,9 @@
ubuntuone-control-panel-gtk.desktop.in
ubuntuone/controlpanel/gtk/gui.py
[type: gettext/glade] data/dashboard.ui
-[type: gettext/glade] data/services.ui
[type: gettext/glade] data/device.ui
[type: gettext/glade] data/devices.ui
+[type: gettext/glade] data/install.ui
[type: gettext/glade] data/management.ui
[type: gettext/glade] data/overview.ui
+[type: gettext/glade] data/services.ui
=== modified file 'pylintrc'
--- pylintrc 2010-10-13 18:55:23 +0000
+++ pylintrc 2011-01-06 20:36:09 +0000
@@ -272,7 +272,7 @@
max-line-length=79
# Maximum number of lines in a module
-max-module-lines=2000
+max-module-lines=2500
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
=== modified file 'ubuntuone/controlpanel/backend.py'
--- ubuntuone/controlpanel/backend.py 2010-12-23 18:20:56 +0000
+++ ubuntuone/controlpanel/backend.py 2011-01-06 20:36:09 +0000
@@ -22,6 +22,7 @@
from twisted.internet.defer import inlineCallbacks, returnValue
from ubuntuone.controlpanel import dbus_client
+from ubuntuone.controlpanel import replication_client
from ubuntuone.controlpanel.logger import setup_logging, log_call
from ubuntuone.controlpanel.webclient import WebClient
@@ -48,6 +49,9 @@
MSG_KEY = 'message'
STATUS_KEY = 'status'
+BOOKMARKS_PKG = 'xul-ext-bindwood'
+CONTACTS_PKG = 'evolution-couchdb'
+
def bool_str(value):
"""Return the string representation of a bool (dbus-compatible)."""
@@ -300,7 +304,7 @@
"""
if 'subscribed' in settings:
- subscribed = settings['subscribed']
+ subscribed = bool(settings['subscribed'])
if subscribed:
yield self.subscribe_volume(volume_id)
else:
@@ -321,6 +325,42 @@
yield dbus_client.unsubscribe_folder(volume_id)
@log_call(logger.debug)
+ @inlineCallbacks
+ def replications_info(self):
+ """Get the user replications info."""
+ replications = yield replication_client.get_replications()
+ exclusions = yield replication_client.get_exclusions()
+
+ result = []
+ for rep in replications:
+ dependency = ''
+ if rep == replication_client.BOOKMARKS:
+ dependency = BOOKMARKS_PKG
+ elif rep == replication_client.CONTACTS:
+ dependency = CONTACTS_PKG
+
+ repd = {
+ "replication_id": rep,
+ "name": rep, # this may change to be more user friendly
+ "enabled": bool_str(rep not in exclusions),
+ "dependency": dependency,
+ }
+ result.append(repd)
+
+ returnValue(result)
+
+ @log_call(logger.info)
+ @inlineCallbacks
+ def change_replication_settings(self, replication_id, settings):
+ """Change the settings for the given replication."""
+ if 'enabled' in settings:
+ if bool(settings['enabled']):
+ yield replication_client.replicate(replication_id)
+ else:
+ yield replication_client.exclude(replication_id)
+ returnValue(replication_id)
+
+ @log_call(logger.debug)
def query_bookmark_extension(self):
"""True if the bookmark extension has been installed."""
# still pending (LP: #673672)
=== modified file 'ubuntuone/controlpanel/dbus_service.py'
--- ubuntuone/controlpanel/dbus_service.py 2010-12-23 18:20:56 +0000
+++ ubuntuone/controlpanel/dbus_service.py 2011-01-06 20:36:09 +0000
@@ -339,6 +339,47 @@
@log_call(logger.debug)
@method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
+ def replications_info(self):
+ """Return the replications info."""
+ d = self.backend.replications_info()
+ d.addCallback(self.ReplicationsInfoReady)
+ d.addErrback(transform_failure(self.ReplicationsInfoError))
+
+ @log_call(logger.debug)
+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")
+ def ReplicationsInfoReady(self, info):
+ """The replications info is ready."""
+
+ @log_call(logger.error)
+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
+ def ReplicationsInfoError(self, error):
+ """Problem getting the replications info."""
+
+ #---
+
+ @log_call(logger.info)
+ @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}")
+ def change_replication_settings(self, replication_id, settings):
+ """Configure a given replication."""
+ d = self.backend.change_replication_settings(replication_id, settings)
+ d.addCallback(self.ReplicationSettingsChanged)
+ d.addErrback(transform_failure(self.ReplicationSettingsChangeError),
+ replication_id)
+
+ @log_call(logger.info)
+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
+ def ReplicationSettingsChanged(self, replication_id):
+ """The settings for the replication were changed."""
+
+ @log_call(logger.error)
+ @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="sa{ss}")
+ def ReplicationSettingsChangeError(self, replication_id, error):
+ """Problem changing settings for the replication."""
+
+ #---
+
+ @log_call(logger.debug)
+ @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
def query_bookmark_extension(self):
"""Check if the extension to sync bookmarks is installed."""
d = self.backend.query_bookmark_extension()
=== modified file 'ubuntuone/controlpanel/gtk/gui.py'
--- ubuntuone/controlpanel/gtk/gui.py 2010-12-26 15:02:52 +0000
+++ ubuntuone/controlpanel/gtk/gui.py 2011-01-06 20:36:09 +0000
@@ -78,6 +78,12 @@
VALUE_ERROR = _('Value could not be retrieved.')
WARNING_MARKUP = '%%s' % ORANGE
KILOBYTES = 1024
+NO_OP = lambda *a, **kw: None
+
+
+def error_handler(*args, **kwargs):
+ """Log errors when calling D-Bus methods in a async way."""
+ logger.error('Error handler received: %r, %r', args, kwargs)
def filter_by_app_name(f):
@@ -352,7 +358,8 @@
settings = {TC_URL_KEY: U1_TC_URL, HELP_TEXT_KEY: U1_DESCRIPTION,
WINDOW_ID_KEY: str(self._window_id),
PING_URL_KEY: U1_PING_URL}
- self.sso_backend.register(U1_APP_NAME, settings)
+ self.sso_backend.register(U1_APP_NAME, settings,
+ reply_handler=NO_OP, error_handler=error_handler)
self.set_property('greyed', True)
self.warning_label.set_text('')
@@ -361,7 +368,8 @@
settings = {TC_URL_KEY: U1_TC_URL, HELP_TEXT_KEY: U1_DESCRIPTION,
WINDOW_ID_KEY: str(self._window_id),
PING_URL_KEY: U1_PING_URL}
- self.sso_backend.login(U1_APP_NAME, settings)
+ self.sso_backend.login(U1_APP_NAME, settings,
+ reply_handler=NO_OP, error_handler=error_handler)
self.set_property('greyed', True)
self.warning_label.set_text('')
@@ -408,7 +416,8 @@
else:
self.set_sensitive(True)
self.warning_label.set_text('')
- self.sso_backend.find_credentials(U1_APP_NAME, {})
+ self.sso_backend.find_credentials(U1_APP_NAME, {},
+ reply_handler=NO_OP, error_handler=error_handler)
class DashboardPanel(UbuntuOneBin, ControlPanelMixin):
@@ -516,11 +525,13 @@
volume_id = checkbutton.get_label()
subscribed = bool_str(checkbutton.get_active())
self.backend.change_volume_settings(volume_id,
- {'subscribed': subscribed})
+ {'subscribed': subscribed},
+ reply_handler=NO_OP, error_handler=error_handler)
def load(self):
"""Load the volume list."""
- self.backend.volumes_info()
+ self.backend.volumes_info(reply_handler=NO_OP,
+ error_handler=error_handler)
self.message.start()
@@ -565,7 +576,8 @@
# Not disabling the GUI to avoid annyong twitchings
#self.set_sensitive(False)
self.warning_label.set_text('')
- self.backend.change_device_settings(self.id, self.__dict__)
+ self.backend.change_device_settings(self.id, self.__dict__,
+ reply_handler=NO_OP, error_handler=error_handler)
def _block_signals(f):
"""Execute 'f' while having the _updating flag set."""
@@ -591,7 +603,8 @@
def on_remove_clicked(self, widget):
"""Remove button was clicked or activated."""
- self.backend.remove_device(self.id)
+ self.backend.remove_device(self.id,
+ reply_handler=NO_OP, error_handler=error_handler)
self.set_sensitive(False)
@_block_signals
@@ -747,7 +760,8 @@
def load(self):
"""Load the device list."""
- self.backend.devices_info()
+ self.backend.devices_info(reply_handler=NO_OP,
+ error_handler=error_handler)
self.message.start()
@@ -818,12 +832,18 @@
class Service(gtk.VBox, ControlPanelMixin):
"""A service."""
- def __init__(self, name, localized_name, *args, **kwargs):
+ CHANGE_ERROR = _('The settings could not be changed,\n'
+ 'previous values were restored.')
+
+ def __init__(self, service_id, name, *args, **kwargs):
gtk.VBox.__init__(self)
ControlPanelMixin.__init__(self)
- self.service_name = name
-
- self.button = gtk.CheckButton(label=localized_name)
+ self.id = service_id
+
+ self.warning_label = gtk.Label()
+ self.pack_start(self.warning_label, expand=False)
+
+ self.button = gtk.CheckButton(label=name)
self.pack_start(self.button, expand=False)
self.show_all()
@@ -835,15 +855,18 @@
FILES_SERVICE_NAME = _('Files')
def __init__(self):
- Service.__init__(self, name='files',
- localized_name=self.FILES_SERVICE_NAME)
+ Service.__init__(self, service_id='files',
+ name=self.FILES_SERVICE_NAME)
self.set_sensitive(False)
+
self.backend.connect_to_signal('FileSyncStatusChanged',
self.on_file_sync_status_changed)
self.backend.connect_to_signal('FilesEnabled', self.on_files_enabled)
self.backend.connect_to_signal('FilesDisabled', self.on_files_disabled)
- self.backend.file_sync_status()
+
+ self.backend.file_sync_status(reply_handler=NO_OP,
+ error_handler=error_handler)
@log_call(logger.debug)
def on_file_sync_status_changed(self, status):
@@ -869,19 +892,24 @@
"""Button was toggled, exclude/replicate the service properly."""
logger.info('File sync enabled? %r', self.button.get_active())
if self.button.get_active():
- self.backend.enable_files()
+ self.backend.enable_files(reply_handler=NO_OP,
+ error_handler=error_handler)
else:
- self.backend.disable_files()
+ self.backend.disable_files(reply_handler=NO_OP,
+ error_handler=error_handler)
class DesktopcouchService(Service):
"""A desktopcouch service."""
- def __init__(self, name, localized_name,
- replication_service, dependency=None):
- Service.__init__(self, name, localized_name)
- self.replication_service = replication_service
- enabled = name not in self.replication_service.all_exclusions()
+ def __init__(self, service_id, name, enabled, dependency=None):
+ Service.__init__(self, service_id, name)
+
+ self.backend.connect_to_signal('ReplicationSettingsChanged',
+ self.on_replication_settings_changed)
+ self.backend.connect_to_signal('ReplicationSettingsChangeError',
+ self.on_replication_settings_change_error)
+
self.button.set_active(enabled)
self.dependency = None
@@ -903,11 +931,27 @@
def on_button_toggled(self, button):
"""Button was toggled, exclude/replicate the service properly."""
logger.info('Starting replication for %r? %r',
- self.service_name, self.button.get_active())
- if self.button.get_active():
- self.replication_service.replicate(self.service_name)
- else:
- self.replication_service.exclude(self.service_name)
+ self.id, self.button.get_active())
+
+ args = {'enabled': bool_str(self.button.get_active())}
+ self.backend.change_replication_settings(self.id, args,
+ reply_handler=NO_OP, error_handler=error_handler)
+
+ @log_call(logger.info)
+ def on_replication_settings_changed(self, replication_id):
+ """The change of settings for this replication succeded."""
+ if replication_id != self.id:
+ return
+ self.warning_label.set_text('')
+
+ @log_call(logger.error)
+ def on_replication_settings_change_error(self, replication_id,
+ error_dict=None):
+ """The change of settings for this replication failed."""
+ if replication_id != self.id:
+ return
+ self.button.set_active(not self.button.get_active())
+ self._set_warning(self.CHANGE_ERROR, self.warning_label)
class ServicesPanel(UbuntuOneBin, ControlPanelMixin):
@@ -916,32 +960,33 @@
TITLE = _('Ubuntu One services including data sync are enabled for the '
'data types and services listed below.')
CHOOSE_SERVICES = _('Choose services to synchronize with this computer:')
- DESKTOPCOUCH_PKG = 'desktopcouch'
- BINDWOOD_PKG = 'xul-ext-bindwood'
- EVOCOUCH_PKG = 'evolution-couchdb'
+ DESKTOPCOUCH_PKG = 'desktopcouch-ubuntuone'
BOOKMARKS = _('Bookmarks (Firefox)')
CONTACTS = _('Contacts (Evolution)')
NO_PAIRING_RECORD = _('There is no Ubuntu One pairing record.')
- def __init__(self, replication_exclusion_class=None):
+ def __init__(self):
UbuntuOneBin.__init__(self)
ControlPanelMixin.__init__(self, filename='services.ui')
self.add(self.itself)
- self.replication_exclusion_class = replication_exclusion_class
- self.replication_service = None
- self.has_desktopcouch = False
- self.has_bindwood = False
- self.has_evocouch = False
self.package_manager = package_manager.PackageManager()
self.install_box = None
- self.bookmarks = None
- self.contacts = None
+
+ self.backend.connect_to_signal('ReplicationsInfoReady',
+ self.on_replications_info_ready)
+ self.backend.connect_to_signal('ReplicationsInfoError',
+ self.on_replications_info_error)
self.files.pack_start(FilesService(), expand=False)
self.show()
+ @property
+ def has_desktopcouch(self):
+ """Is desktopcouch installed?"""
+ return self.package_manager.is_installed(self.DESKTOPCOUCH_PKG)
+
@log_call(logger.debug)
def load(self):
"""Load info."""
@@ -949,16 +994,7 @@
self.itself.remove(self.install_box)
self.install_box = None
- self.has_desktopcouch = \
- self.package_manager.is_installed(self.DESKTOPCOUCH_PKG)
- self.has_bindwood = \
- self.package_manager.is_installed(self.BINDWOOD_PKG)
- self.has_evocouch = \
- self.package_manager.is_installed(self.EVOCOUCH_PKG)
-
- logger.info('load: has_desktopcouch? %r has_bindwood? %s '
- 'has_evocouch? %s', self.has_desktopcouch,
- self.has_bindwood, self.has_evocouch)
+ logger.info('load: has_desktopcouch? %r', self.has_desktopcouch)
if not self.has_desktopcouch:
self.message.set_text('')
self.replications.hide()
@@ -975,46 +1011,41 @@
@log_call(logger.debug)
def load_replications(self, *args):
"""Load replications info."""
+ # ask replications to the backend
+ self.message.start()
+ self.backend.replications_info(reply_handler=NO_OP,
+ error_handler=error_handler)
+
+ @log_call(logger.debug)
+ def on_replications_info_ready(self, info):
+ """The replication info is ready."""
+ self.on_success(self.CHOOSE_SERVICES)
+
self.replications.show()
if self.install_box is not None:
self.itself.remove(self.install_box)
self.install_box = None
- self.message.set_text(self.CHOOSE_SERVICES)
for child in self.replications.get_children():
self.replications.remove(child)
- # Unable to import 'desktopcouch.application.replication_services'
- # pylint: disable=F0401
- if self.replication_exclusion_class is None:
- from desktopcouch.application.replication_services import \
- ubuntuone as u1rep
- self.replication_exclusion_class = u1rep.ReplicationExclusion
-
- if self.replication_service is None:
- try:
- self.replication_service = self.replication_exclusion_class()
- except ValueError:
- logger.exception('Can not load replications:')
- self._set_warning(self.NO_PAIRING_RECORD, self.message)
- return
-
- pkg = None
- if not self.has_bindwood:
- pkg = self.BINDWOOD_PKG
- self.bookmarks = DesktopcouchService('bookmarks', self.BOOKMARKS,
- self.replication_service,
- dependency=pkg)
- self.replications.pack_start(self.bookmarks, expand=False)
-
- pkg = None
- if not self.has_evocouch:
- pkg = self.EVOCOUCH_PKG
- self.contacts = DesktopcouchService('contacts', self.CONTACTS,
- self.replication_service,
- dependency=pkg)
- self.replications.pack_start(self.contacts, expand=False)
+ for item in info:
+ pkg = item['dependency']
+ child = DesktopcouchService(service_id=item['replication_id'],
+ name=item['name'], # self.BOOKMARKS,
+ enabled=bool(item['enabled']),
+ dependency=pkg if pkg else None)
+ self.replications.pack_start(child, expand=False)
+
+ @log_call(logger.error)
+ def on_replications_info_error(self, error_dict=None):
+ """The replication info can not be retrieved."""
+ if error_dict is not None and \
+ error_dict.get('error_type', None) == 'NoPairingRecord':
+ self.on_error(self.NO_PAIRING_RECORD)
+ else:
+ self.on_error()
class ManagementPanel(gtk.VBox, ControlPanelMixin):
@@ -1107,8 +1138,10 @@
def load(self):
"""Load the account info and file sync status list."""
- self.backend.account_info()
- self.backend.file_sync_status()
+ self.backend.account_info(reply_handler=NO_OP,
+ error_handler=error_handler)
+ self.backend.file_sync_status(reply_handler=NO_OP,
+ error_handler=error_handler)
self.dashboard_button.clicked()
@log_call(logger.debug)
=== modified file 'ubuntuone/controlpanel/gtk/tests/__init__.py'
--- ubuntuone/controlpanel/gtk/tests/__init__.py 2010-12-23 19:17:53 +0000
+++ ubuntuone/controlpanel/gtk/tests/__init__.py 2011-01-06 20:36:09 +0000
@@ -53,6 +53,15 @@
'max_upload_speed': '1000', 'max_download_speed': '72548'}, # local
]
+FAKE_REPLICATIONS_INFO = [
+ {'replication_id': 'foo', 'name': 'Bar',
+ 'enabled': 'True', 'dependency': ''},
+ {'replication_id': 'yadda', 'name': 'Foo',
+ 'enabled': '', 'dependency': 'a very weird one'},
+ {'replication_id': 'yoda', 'name': 'Figthers',
+ 'enabled': 'True', 'dependency': 'other dep'},
+]
+
class FakedObject(object):
"""Fake an object, record every call."""
@@ -117,9 +126,11 @@
object_path = gui.DBUS_PREFERENCES_PATH
iface = gui.DBUS_PREFERENCES_IFACE
exposed_methods = [
- 'account_info', 'devices_info', 'change_device_settings',
- 'volumes_info', 'change_volume_settings', 'file_sync_status',
- 'remove_device', 'enable_files', 'disable_files',
+ 'account_info', # account
+ 'devices_info', 'change_device_settings', 'remove_device', # devices
+ 'volumes_info', 'change_volume_settings', # volumes
+ 'replications_info', 'change_replication_settings', # replications
+ 'file_sync_status', 'enable_files', 'disable_files', # files
]
@@ -157,13 +168,3 @@
yield
self._installed[package_name] = True
gui.package_manager.return_value(FakedTransaction([package_name]))
-
-
-class FakedReplication(object):
- """Faked a DC replication exclusion."""
-
- def __init__(self):
- self._exclusions = set()
- self.all_exclusions = lambda: self._exclusions
- self.replicate = self._exclusions.remove
- self.exclude = self._exclusions.add
=== modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py'
--- ubuntuone/controlpanel/gtk/tests/test_gui.py 2010-12-26 15:02:52 +0000
+++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-01-06 20:36:09 +0000
@@ -26,9 +26,10 @@
from ubuntuone.controlpanel.gtk import gui
from ubuntuone.controlpanel.gtk.tests import (FAKE_ACCOUNT_INFO,
- FAKE_VOLUMES_INFO, FAKE_DEVICE_INFO, FAKE_DEVICES_INFO,
+ FAKE_DEVICE_INFO, FAKE_DEVICES_INFO,
+ FAKE_VOLUMES_INFO, FAKE_REPLICATIONS_INFO,
FakedNMState, FakedSSOBackend, FakedSessionBus, FakedInterface,
- FakedPackageManager, FakedReplication,
+ FakedPackageManager,
)
from ubuntuone.controlpanel.tests import TOKEN, TestCase
from ubuntuone.controlpanel.gtk.tests.test_package_manager import (
@@ -37,6 +38,8 @@
# Attribute 'yyy' defined outside __init__, access to a protected member
# pylint: disable=W0201, W0212
+# Too many lines in module
+# pylint: disable=C0302
class BaseTestCase(TestCase):
@@ -96,7 +99,9 @@
if backend is None:
backend = self.ui.backend
self.assertIn(method_name, backend._called)
- self.assertEqual(backend._called[method_name], (args, {}))
+ kwargs = {'reply_handler': gui.NO_OP,
+ 'error_handler': gui.error_handler}
+ self.assertEqual(backend._called[method_name], (args, kwargs))
def assert_warning_correct(self, warning, text):
"""Check that 'warning' is visible, showing 'text'."""
@@ -1361,9 +1366,9 @@
"""The test suite for a service."""
klass = gui.Service
- name = 'dc_test'
- localized_name = u'Qué lindo test!'
- kwargs = {'name': 'dc_test', 'localized_name': u'Qué lindo test!'}
+ service_id = 'dc_test'
+ name = u'Qué lindo test!'
+ kwargs = {'service_id': service_id, 'name': name}
def test_is_an_box(self):
"""Inherits from gtk.VBox."""
@@ -1373,30 +1378,35 @@
"""Is visible."""
self.assertTrue(self.ui.get_visible())
+ def test_warning_label_is_cleared(self):
+ """The warning label is cleared."""
+ self.assertEqual(self.ui.warning_label.get_text(), '')
+
+ def test_warning_label_packed(self):
+ """The warning label is packed as child."""
+ self.assertIn(self.ui.warning_label, self.ui.get_children())
+
def test_check_button_packed(self):
- """A check button is packed as only child."""
+ """A check button is packed as child."""
self.assertIn(self.ui.button, self.ui.get_children())
def test_label(self):
"""The label is set."""
- self.assertEqual(self.localized_name, self.ui.button.get_label())
+ self.assertEqual(self.name, self.ui.button.get_label())
- def test_service_name(self):
- """The service_name is set."""
- self.assertEqual(self.name, self.ui.service_name)
+ def test_service_id(self):
+ """The service id is set."""
+ self.assertEqual(self.service_id, self.ui.id)
class FilesServiceTestCase(ServiceTestCase):
"""The test suite for the file sync service."""
klass = gui.FilesService
+ service_id = 'files'
+ name = gui.FilesService.FILES_SERVICE_NAME
kwargs = {}
- def setUp(self):
- self.name = 'files'
- self.localized_name = gui.FilesService.FILES_SERVICE_NAME
- super(FilesServiceTestCase, self).setUp()
-
def test_backend_account_signals(self):
"""The proper signals are connected to the backend."""
self.assertEqual(self.ui.backend._signals['FileSyncStatusChanged'],
@@ -1461,35 +1471,36 @@
"""The test suite for a desktopcouch service."""
klass = gui.DesktopcouchService
+ enabled = True
def setUp(self):
- self.replication = FakedReplication()
- self.name = self.kwargs['name']
- self.kwargs['replication_service'] = self.replication
+ self.kwargs['enabled'] = self.enabled
super(DesktopcouchServiceTestCase, self).setUp()
+ def modify_settings(self):
+ """Modify settings so values actually change."""
+ self.ui.button.set_active(not self.ui.button.get_active())
+
+ def test_backend_account_signals(self):
+ """The proper signals are connected to the backend."""
+ self.assertEqual(
+ self.ui.backend._signals['ReplicationSettingsChanged'],
+ [self.ui.on_replication_settings_changed])
+ self.assertEqual(
+ self.ui.backend._signals['ReplicationSettingsChangeError'],
+ [self.ui.on_replication_settings_change_error])
+
def test_active(self):
- """Is active since replication has an empty database."""
- self.assertTrue(self.ui.button.get_active())
-
- def test_not_active(self):
- """Is not active since 'name' is excluded on replication database."""
- self.replication.exclude(self.name)
- self.ui = self.klass(**self.kwargs)
- self.assertFalse(self.ui.button.get_active())
+ """Is active if enabled."""
+ self.assertEqual(self.enabled, self.ui.button.get_active())
def test_on_button_toggled(self):
"""When toggling the button, the DC exclude list is updated."""
- assert self.ui.button.get_active()
self.ui.button.set_active(not self.ui.button.get_active())
- self.assertEqual(set([self.name]), self.replication.all_exclusions())
- def test_on_button_toggled_twice(self):
- """When toggling the button twice, the DC exclude list is updated."""
- assert self.ui.button.get_active()
- self.ui.button.set_active(not self.ui.button.get_active())
- self.ui.button.set_active(not self.ui.button.get_active())
- self.assertEqual(set(), self.replication.all_exclusions())
+ args = (self.service_id,
+ {'enabled': gui.bool_str(self.ui.button.get_active())})
+ self.assert_backend_called('change_replication_settings', args)
def test_dependency(self):
"""The dependency box is None."""
@@ -1499,6 +1510,72 @@
"""The check button is sensitive."""
self.assertTrue(self.ui.button.get_sensitive())
+ def test_on_replication_settings_changed(self):
+ """When settings were changed for this replication, enable it."""
+ new_val = not self.ui.button.get_active()
+ self.ui.button.set_active(new_val)
+
+ self.ui.on_replication_settings_changed(replication_id=self.ui.id)
+
+ self.assertEqual(self.ui.warning_label.get_text(), '')
+ self.assertEqual(new_val, self.ui.button.get_active())
+
+ def test_on_replication_settings_changed_after_error(self):
+ """Change success after error."""
+ self.ui.button.set_active(not self.ui.button.get_active())
+ self.ui.on_replication_settings_change_error(replication_id=self.ui.id)
+
+ self.test_on_replication_settings_changed()
+
+ def test_on_replication_settings_changed_different_id(self):
+ """When settings were changed for other rep, nothing changes."""
+ self.ui.button.set_active(not self.ui.button.get_active())
+ self.ui.on_replication_settings_changed(replication_id='yadda')
+
+ self.assertEqual(self.ui.warning_label.get_text(), '')
+
+ def test_on_replication_settings_changed_different_id_after_error(self):
+ """When settings were changed for other + error, nothing changes."""
+ self.ui.on_replication_settings_change_error(replication_id=self.ui.id)
+ self.ui.on_replication_settings_changed(replication_id='yadda')
+
+ self.assert_warning_correct(self.ui.warning_label,
+ self.ui.CHANGE_ERROR)
+
+ def test_on_replication_settings_change_error(self):
+ """When settings were not changed, notify the user.
+
+ Also, confirm that old value was restored.
+
+ """
+ old_val = self.ui.button.get_active()
+ self.ui.button.set_active(not old_val)
+ self.ui.on_replication_settings_change_error(replication_id=self.ui.id)
+
+ self.assert_warning_correct(self.ui.warning_label,
+ self.ui.CHANGE_ERROR)
+ self.assertEqual(old_val, self.ui.button.get_active())
+
+ def test_on_replication_settings_change_error_after_success(self):
+ """Change error after success."""
+ self.ui.button.set_active(not self.ui.button.get_active())
+ self.ui.on_replication_settings_changed(replication_id=self.ui.id)
+
+ self.test_on_replication_settings_change_error()
+
+ def test_on_replication_settings_change_error_different_id(self):
+ """When settings were not changed for other replication, do nothing."""
+ self.ui.button.set_active(not self.ui.button.get_active())
+ self.ui.on_replication_settings_change_error(replication_id='yudo')
+
+ self.assertEqual(self.ui.warning_label.get_text(), '')
+
+
+class DesktopcouchServiceDisabledAtStartupTestCase(ServiceTestCase):
+ """The test suite for a desktopcouch service when enabled=False."""
+
+ enabled = False
+
class DesktopcouchServiceWithDependencyTestCase(DesktopcouchServiceTestCase):
"""The test suite for a desktopcouch service when it needs a dependency."""
@@ -1532,7 +1609,8 @@
self.ui.dependency.emit('finished')
self.assertTrue(self.ui.dependency is None)
- self.assertEqual(self.ui.get_children(), [self.ui.button])
+ self.assertEqual(sorted(self.ui.get_children()),
+ sorted([self.ui.button, self.ui.warning_label]))
class ServicesTestCase(ControlPanelMixinTestCase):
@@ -1562,6 +1640,13 @@
"""The install box is None."""
self.assertTrue(self.ui.install_box is None)
+ def test_backend_signals(self):
+ """The proper signals are connected to the backend."""
+ self.assertEqual(self.ui.backend._signals['ReplicationsInfoReady'],
+ [self.ui.on_replications_info_ready])
+ self.assertEqual(self.ui.backend._signals['ReplicationsInfoError'],
+ [self.ui.on_replications_info_error])
+
class ServicesFilesTestCase(ServicesTestCase):
"""The test suite for the services panel (files section)."""
@@ -1589,22 +1674,10 @@
self.assertFalse(self.ui.message.active)
self.assertEqual(self.ui.message.get_text(), '')
- def test_replication_service(self):
- """Has a replication service."""
- self.assertEqual(self.ui.replication_service, None)
-
def test_has_desktopcouch(self):
"""Has desktopcouch installed?"""
self.assertFalse(self.ui.has_desktopcouch)
- def test_has_bindwood(self):
- """Has bindwood installed?"""
- self.assertFalse(self.ui.has_bindwood)
-
- def test_has_evocouch(self):
- """Has evocouch installed?"""
- self.assertFalse(self.ui.has_evocouch)
-
def test_install_box_is_hidden(self):
"""The install box is not hidden."""
self.assertTrue(self.ui.install_box.get_visible())
@@ -1629,41 +1702,27 @@
self.assertEqual(self._called, ((self.ui.install_box,), {}))
+ def test_load_replications(self):
+ """The load_replications starts the spinner and calls the backend."""
+ self.ui.load_replications()
+
+ self.assertTrue(self.ui.message.active)
+ self.assert_backend_called('replications_info', ())
+
class ServicesWithDesktopcouchTestCase(ServicesTestCase):
"""The test suite for the services panel."""
- kwargs = {'replication_exclusion_class': FakedReplication}
-
def setUp(self):
super(ServicesWithDesktopcouchTestCase, self).setUp()
self.ui.package_manager._installed[self.ui.DESKTOPCOUCH_PKG] = True
- self.ui.load()
+ self.ui.on_replications_info_ready(info=FAKE_REPLICATIONS_INFO)
def test_message(self):
"""Global load message is stopped and proper test is shown."""
self.assertFalse(self.ui.message.active)
self.assertEqual(self.ui.message.get_text(), self.ui.CHOOSE_SERVICES)
- def test_replication_service(self):
- """Has a replication service."""
- self.assertIsInstance(self.ui.replication_service, FakedReplication)
-
- def test_no_pairing_record(self):
- """The pairing record is not in place."""
-
- def no_pairing_record(*a):
- """Fake a ReplicationExclusion with no pairing record."""
- raise ValueError("No pairing record for ubuntuone.")
-
- self.ui.replication_exclusion_class = no_pairing_record
- self.ui.replication_service = None
- self.ui.load()
-
- self.assertEqual(self.ui.replications.get_children(), [])
- self.assertFalse(self.ui.message.active)
- self.assert_warning_correct(self.ui.message, self.ui.NO_PAIRING_RECORD)
-
def test_has_desktopcouch(self):
"""Has desktopcouch installed?"""
self.assertTrue(self.ui.has_desktopcouch)
@@ -1673,79 +1732,67 @@
self.assertTrue(self.ui.replications.get_visible())
children = self.ui.replications.get_children()
- self.assertEqual(len(children), 2)
- for child in children:
+ self.assertEqual(len(children), len(FAKE_REPLICATIONS_INFO))
+ for expected, child in zip(FAKE_REPLICATIONS_INFO, children):
self.assertIsInstance(child, gui.DesktopcouchService)
-
- self.assertTrue(self.ui.bookmarks is children[0])
- self.assertTrue(self.ui.contacts is children[1])
-
- def test_replications_after_loading_twice(self):
- """Has proper child after loading twice."""
- self.ui.load()
+ self.assertEqual(expected['replication_id'], child.id)
+ self.assertEqual(expected['name'], child.button.get_label())
+ self.assertEqual(bool(expected['enabled']),
+ child.button.get_active())
+
+ if expected['dependency']:
+ self.assertTrue(child.dependency is not None)
+ self.assertEqual(expected['dependency'],
+ child.dependency.package_name)
+ else:
+ self.assertTrue(child.dependency is None)
+
+ def test_replications_after_getting_info_twice(self):
+ """Has proper child after getting backend info twice."""
+ self.ui.on_replications_info_ready(info=FAKE_REPLICATIONS_INFO)
self.test_replications()
- def test_bookmarks(self):
- """The bookmarks is correct."""
- self.assertEqual(self.ui.bookmarks.service_name, 'bookmarks')
- self.assertEqual(self.ui.bookmarks.button.get_label(),
- self.ui.BOOKMARKS)
- self.assertTrue(self.ui.bookmarks.replication_service is
- self.ui.replication_service)
-
- def test_bookmarks_dependency(self):
- """The bookmarks dependency is correct."""
- self.assertTrue(self.ui.bookmarks.dependency is not None)
- self.assertEqual(self.ui.bookmarks.dependency.package_name,
- self.ui.BINDWOOD_PKG)
-
- def test_contacts(self):
- """The contacts is correct."""
- self.assertEqual(self.ui.contacts.service_name, 'contacts')
- self.assertEqual(self.ui.contacts.button.get_label(),
- self.ui.CONTACTS)
- self.assertTrue(self.ui.contacts.replication_service is
- self.ui.replication_service)
-
- def test_contacts_dependency(self):
- """The contacts dependency is correct."""
- self.assertTrue(self.ui.contacts.dependency is not None)
- self.assertEqual(self.ui.contacts.dependency.package_name,
- self.ui.EVOCOUCH_PKG)
-
-
-class ServicesWithDCAndBindwoodTestCase(ServicesWithDesktopcouchTestCase):
- """The test suite for the services panel."""
-
- def setUp(self):
- super(ServicesWithDCAndBindwoodTestCase, self).setUp()
- self.ui.package_manager._installed[self.ui.BINDWOOD_PKG] = True
- self.ui.load()
-
- def test_has_bindwood(self):
- """Has bindwood installed?"""
- self.assertTrue(self.ui.has_bindwood)
-
- def test_bookmarks_dependency(self):
- """The bookmarks dependency is correct."""
- self.assertTrue(self.ui.bookmarks.dependency is None)
-
-
-class ServicesWithDCAndEvocouchTestCase(ServicesWithDesktopcouchTestCase):
- """The test suite for the services panel."""
-
- def setUp(self):
- super(ServicesWithDCAndEvocouchTestCase, self).setUp()
- self.ui.package_manager._installed[self.ui.EVOCOUCH_PKG] = True
- self.ui.load()
-
- def test_has_evocouch(self):
- """Has evocoucg installed?"""
- self.assertTrue(self.ui.has_evocouch)
-
- def test_contacts_dependency(self):
- """The bookmarks dependency is correct."""
- self.assertTrue(self.ui.contacts.dependency is None)
+
+class ServicesWithDesktopcouchErrorTestCase(ServicesTestCase):
+ """The test suite for the services panel."""
+
+ def setUp(self):
+ super(ServicesWithDesktopcouchErrorTestCase, self).setUp()
+ self.ui.package_manager._installed[self.ui.DESKTOPCOUCH_PKG] = True
+
+ def test_no_pairing_record(self):
+ """The pairing record is not in place."""
+ error_dict = {'error_type': 'NoPairingRecord'}
+ self.ui.on_replications_info_error(error_dict)
+
+ self.assertEqual(self.ui.replications.get_children(), [])
+ self.assertFalse(self.ui.message.active)
+ self.assert_warning_correct(self.ui.message, self.ui.NO_PAIRING_RECORD)
+
+ def test_other_error(self):
+ """There was an error other than no pairing record."""
+ error_dict = {'error_type': 'OtherError'}
+ self.ui.on_replications_info_error(error_dict)
+
+ self.assertEqual(self.ui.replications.get_children(), [])
+ self.assertFalse(self.ui.message.active)
+ self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR)
+
+ def test_empty_dict(self):
+ """Handle empty dicts errors."""
+ self.ui.on_replications_info_error(error_dict={})
+
+ self.assertEqual(self.ui.replications.get_children(), [])
+ self.assertFalse(self.ui.message.active)
+ self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR)
+
+ def test_error_dict_none(self):
+ """HGandle empty dicts errors."""
+ self.ui.on_replications_info_error(error_dict=None)
+
+ self.assertEqual(self.ui.replications.get_children(), [])
+ self.assertFalse(self.ui.message.active)
+ self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR)
class ManagementPanelTestCase(ControlPanelMixinTestCase):
=== modified file 'ubuntuone/controlpanel/integrationtests/test_dbus_service.py'
--- ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2010-12-23 18:20:56 +0000
+++ ubuntuone/controlpanel/integrationtests/test_dbus_service.py 2011-01-06 20:36:09 +0000
@@ -84,6 +84,11 @@
},
]
+SAMPLE_REPLICATIONS_INFO = [
+ {'replication_id': 'yadda', 'wait for it': 'awesome'},
+ {'replication_id': 'yoda', 'something else': 'awesome'},
+]
+
class DBusServiceMainTestCase(mocker.MockerTestCase):
"""Tests for the main function."""
@@ -166,6 +171,19 @@
"""Configure a given volume."""
return self._process(volume_id)
+ def replications_info(self):
+ """Start the replication exclusion service if needed.
+
+ Return the replication info, which is a dictionary of (replication
+ name, enabled).
+
+ """
+ return self._process(SAMPLE_REPLICATIONS_INFO)
+
+ def change_replication_settings(self, replication_id, settings):
+ """Configure a given replication."""
+ return self._process(replication_id)
+
def query_bookmark_extension(self):
"""True if the bookmark extension has been installed."""
return self._process(False)
@@ -258,13 +276,13 @@
self.assertEqual(expected, result)
-class OperationsTestCase(TestCase):
- """Test for the DBus service operations."""
+class BaseTestCase(TestCase):
+ """Base test case for the DBus service."""
timeout = 3
def setUp(self):
- super(OperationsTestCase, self).setUp()
+ super(BaseTestCase, self).setUp()
dbus_service.init_mainloop()
be = dbus_service.publish_backend(MockBackend())
self.addCleanup(be.remove_from_connection)
@@ -279,7 +297,7 @@
def tearDown(self):
self.backend = None
self.deferred = None
- super(OperationsTestCase, self).tearDown()
+ super(BaseTestCase, self).tearDown()
def got_error(self, *a):
"""Some error happened in the DBus call."""
@@ -322,6 +340,10 @@
return self.deferred
+
+class OperationsTestCase(BaseTestCase):
+ """Test for the DBus service operations."""
+
def test_account_info_returned(self):
"""The account info is successfully returned."""
@@ -416,9 +438,9 @@
def test_volumes_info(self):
"""The volumes info is reported."""
- def got_signal(volumes_dict):
+ def got_signal(volumes):
"""The correct info was received."""
- self.assertEqual(volumes_dict, SAMPLE_VOLUMES_INFO)
+ self.assertEqual(volumes, SAMPLE_VOLUMES_INFO)
self.deferred.callback("success")
args = ("VolumesInfoReady", "VolumesInfoError", got_signal,
@@ -439,6 +461,32 @@
expected_volume_id, {'subscribed': ''})
return self.assert_correct_method_call(*args)
+ def test_replications_info(self):
+ """The replications info is reported."""
+
+ def got_signal(replications):
+ """The correct info was received."""
+ self.assertEqual(replications, SAMPLE_REPLICATIONS_INFO)
+ self.deferred.callback("success")
+
+ args = ("ReplicationsInfoReady", "ReplicationsInfoError", got_signal,
+ self.backend.replications_info)
+ return self.assert_correct_method_call(*args)
+
+ def test_change_replication_settings(self):
+ """The replication settings are successfully changed."""
+ expected_replication_id = SAMPLE_REPLICATIONS_INFO[0]['replication_id']
+
+ def got_signal(replication_id):
+ """The correct replication was changed."""
+ self.assertEqual(replication_id, expected_replication_id)
+ self.deferred.callback("success")
+
+ args = ("ReplicationSettingsChanged", "ReplicationSettingsChangeError",
+ got_signal, self.backend.change_replication_settings,
+ expected_replication_id, {'enabled': ''})
+ return self.assert_correct_method_call(*args)
+
def test_query_bookmarks_extension(self):
"""The bookmarks extension is queried."""
@@ -495,7 +543,7 @@
error_sig, success_sig, got_error_signal, method, *args)
-class FileSyncTestCase(OperationsTestCase):
+class FileSyncTestCase(BaseTestCase):
"""Test for the DBus service when requesting file sync status."""
def assert_correct_status_signal(self, status, sync_signal,
=== modified file 'ubuntuone/controlpanel/logger.py'
--- ubuntuone/controlpanel/logger.py 2010-12-23 18:20:56 +0000
+++ ubuntuone/controlpanel/logger.py 2011-01-06 20:36:09 +0000
@@ -53,10 +53,7 @@
logger.addHandler(MAIN_HANDLER)
if os.environ.get('DEBUG'):
debug_handler = logging.StreamHandler(sys.stderr)
- if prefix is not None:
- fmt = prefix + "%(name)s - %(levelname)s\n%(message)s\n"
- formatter = logging.Formatter(fmt)
- debug_handler.setFormatter(formatter)
+ debug_handler.setFormatter(basic_formatter)
logger.addHandler(debug_handler)
return logger
=== added file 'ubuntuone/controlpanel/replication_client.py'
--- ubuntuone/controlpanel/replication_client.py 1970-01-01 00:00:00 +0000
+++ ubuntuone/controlpanel/replication_client.py 2011-01-06 20:36:09 +0000
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+
+# Authors: Natalia B. Bidart
+#
+# Copyright 2010 Canonical Ltd.
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranties of
+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see .
+
+"""Client to use replication services."""
+
+from twisted.internet.defer import Deferred, inlineCallbacks, returnValue
+
+from ubuntuone.controlpanel.logger import setup_logging
+
+
+logger = setup_logging('replication_client')
+
+BOOKMARKS = 'bookmarks'
+CONTACTS = 'contacts'
+# we should get this list from somewhere else
+REPLICATIONS = set([BOOKMARKS, CONTACTS])
+
+
+class ReplicationError(Exception):
+ """A replication error."""
+
+
+class NoPairingRecord(ReplicationError):
+ """There is no pairing record."""
+
+
+class InvalidIdError(ReplicationError):
+ """The replication id is not valid."""
+
+
+class NotExcludedError(ReplicationError):
+ """The replication can not be replicated since is not excluded."""
+
+
+class AlreadyExcludedError(ReplicationError):
+ """The replication can not be excluded since is already excluded."""
+
+
+def get_replication_proxy(replication_module=None):
+ """Return a proxy to the replication client."""
+ d = Deferred()
+ if replication_module is None:
+ # delay import in case DC is not installed at module import time
+ # Unable to import 'desktopcouch.application.replication_services'
+ # pylint: disable=F0401
+ from desktopcouch.application.replication_services \
+ import ubuntuone as replication_module
+ try:
+ result = replication_module.ReplicationExclusion()
+ except ValueError:
+ d.errback(NoPairingRecord())
+ else:
+ d.callback(result)
+
+ return d
+
+
+@inlineCallbacks
+def get_replications():
+ """Retrieve the list of replications."""
+ yield get_replication_proxy()
+ returnValue(REPLICATIONS)
+
+
+@inlineCallbacks
+def get_exclusions():
+ """Retrieve the list of exclusions."""
+ proxy = yield get_replication_proxy()
+ result = proxy.all_exclusions()
+ returnValue(result)
+
+
+@inlineCallbacks
+def replicate(replication_id):
+ """Remove replication_id from the exclusions list."""
+ replications = yield get_replications()
+ if replication_id not in replications:
+ raise InvalidIdError(replication_id)
+
+ exclusions = yield get_exclusions()
+ if replication_id not in exclusions:
+ raise NotExcludedError(replication_id)
+
+ proxy = yield get_replication_proxy()
+ yield proxy.replicate(replication_id)
+
+
+@inlineCallbacks
+def exclude(replication_id):
+ """Add replication_id to the exclusions list."""
+ replications = yield get_replications()
+ if replication_id not in replications:
+ raise InvalidIdError(replication_id)
+
+ exclusions = yield get_exclusions()
+ if replication_id in exclusions:
+ raise AlreadyExcludedError(replication_id)
+
+ proxy = yield get_replication_proxy()
+ yield proxy.exclude(replication_id)
=== modified file 'ubuntuone/controlpanel/tests/__init__.py'
--- ubuntuone/controlpanel/tests/__init__.py 2010-12-20 16:11:13 +0000
+++ ubuntuone/controlpanel/tests/__init__.py 2011-01-06 20:36:09 +0000
@@ -24,9 +24,157 @@
TOKEN = {u'consumer_key': u'xQ7xDAz',
u'consumer_secret': u'KzCJWCTNbbntwfyCKKjomJDzlgqxLy',
u'token_name': u'test',
- u'token': u'GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo',
+ u'token': u'ABCDEF01234-localtoken',
u'token_secret': u'qFYImEtlczPbsCnYyuwLoPDlPEnvNcIktZphPQklAWrvyfFMV'}
+SAMPLE_ACCOUNT_JSON = """
+{
+ "username": "andrewpz",
+ "openid": "https://login.launchpad.net/+id/abcdefg",
+ "first_name": "Andrew P.",
+ "last_name": "Zoilo",
+ "couchdb": {
+ "host": "https://couchdb.one.ubuntu.com",
+ "root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
+ "dbpath": "u/abc/def/12345"
+ },
+ "couchdb_root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
+ "email": "andrewpz@protocultura.net",%s
+ "nickname": "Andrew P. Zoilo",
+ "id": 12345,
+ "subscription": {
+ "upgrade_available": false,
+ "description": "Paid Plan, 50 GB of storage",
+ "trial": false,
+ "started": "2010-03-24T18:38:38Z",
+ "is_paid": true,
+ "expires": null,
+ "qty": 1,
+ "price": 0.0,
+ "currency": null,
+ "id": 654321,
+ "name": "50 GB"
+ }
+}
+"""
+
+CURRENT_PLAN = "Ubuntu One Basic (2 GB) + 1 x 20-Pack with 20 GB (monthly)"
+SAMPLE_CURRENT_PLAN = '\n "current_plan": "%s",' % CURRENT_PLAN
+
+SAMPLE_ACCOUNT_NO_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % ''
+SAMPLE_ACCOUNT_WITH_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % SAMPLE_CURRENT_PLAN
+
+
+SAMPLE_QUOTA_JSON = """
+{
+ "total": 53687091200,
+ "used": 2350345156
+}
+"""
+
+EXPECTED_ACCOUNT_INFO = {
+ "quota_used": "2350345156",
+ "quota_total": "53687091200",
+ "type": "Paid Plan, 50 GB of storage",
+ "name": "Andrew P. Zoilo",
+ "email": "andrewpz@protocultura.net",
+}
+
+EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN = {
+ "quota_used": "2350345156",
+ "quota_total": "53687091200",
+ "type": CURRENT_PLAN,
+ "name": "Andrew P. Zoilo",
+ "email": "andrewpz@protocultura.net",
+}
+
+SAMPLE_DEVICES_JSON = """
+[
+ {
+ "token": "ABCDEF01234token",
+ "description": "Ubuntu One @ darkstar",
+ "kind": "Computer"
+ },
+ {
+ "token": "ABCDEF01234-localtoken",
+ "description": "Ubuntu One @ localhost",
+ "kind": "Computer"
+ },
+ {
+ "kind": "Phone",
+ "description": "Nokia E65",
+ "id": 1000
+ }
+]
+"""
+
+EXPECTED_DEVICES_INFO = [
+ {
+ "device_id": "ComputerABCDEF01234token",
+ "name": "Ubuntu One @ darkstar",
+ "type": "Computer",
+ "is_local": '',
+ "configurable": '',
+ },
+ {
+ 'is_local': 'True',
+ 'configurable': 'True',
+ 'device_id': 'ComputerABCDEF01234-localtoken',
+ 'limit_bandwidth': '',
+ 'max_download_speed': '-1',
+ 'max_upload_speed': '-1',
+ 'name': 'Ubuntu One @ localhost',
+ 'type': 'Computer'
+ },
+ {
+ "device_id": "Phone1000",
+ "name": "Nokia E65",
+ "type": "Phone",
+ "configurable": '',
+ "is_local": '',
+ },
+]
+
+SAMPLE_FOLDERS = [
+ {u'generation': u'2', u'node_id': u'341da068-81d8-437a-8f75-5bb9d86455ba',
+ u'path': u'/home/tester/Public', u'subscribed': u'True',
+ u'suggested_path': u'~/Public',
+ u'type': u'UDF', u'volume_id': u'9ea892f8-15fa-4201-bdbf-8de99fa5f588'},
+ {u'generation': u'', u'node_id': u'11fbc86c-0d7a-49f5-ae83-8402caf66c6a',
+ u'path': u'/home/tester/Documents', u'subscribed': u'',
+ u'suggested_path': u'~/Documents',
+ u'type': u'UDF', u'volume_id': u'2db262f5-a151-4c19-969c-bb5ced753c61'},
+ {u'generation': u'24', u'node_id': u'9ee0e130-a7c7-4d76-a5e3-5df506221b48',
+ u'path': u'/home/tester/Pictures/Photos', u'subscribed': u'True',
+ u'suggested_path': u'~/Pictures/Photos',
+ u'type': u'UDF', u'volume_id': u'1deb2874-3d28-46ae-9999-d5f48de9f460'},
+]
+
+SAMPLE_SHARES = [
+ {u'accepted': u'True', u'access_level': u'View',
+ u'free_bytes': u'39892622746', u'generation': u'2704',
+ u'name': u're', u'node_id': u'c483f419-ed28-490a-825d-a8c074e2d795',
+ u'other_username': u'otheruser', u'other_visible_name': u'Other User',
+ u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User',
+ u'type': u'Share', u'volume_id': u'4a1b263b-a2b3-4f66-9e66-4cd18050810d'},
+ {u'accepted': u'True', u'access_level': u'Modify',
+ u'free_bytes': u'39892622746', u'generation': u'2704',
+ u'name': u'do', u'node_id': u'84544ea4-aefe-4f91-9bb9-ed7b0a805baf',
+ u'other_username': u'otheruser', u'other_visible_name': u'Other User',
+ u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User',
+ u'type': u'Share', u'volume_id': u'7d130dfe-98b2-4bd5-8708-9eeba9838ac0'},
+]
+
+SAMPLE_SHARED = [
+ {u'accepted': u'True', u'access_level': u'View',
+ u'free_bytes': u'', u'generation': u'',
+ u'name': u'bar', u'node_id': u'31e47530-9448-4f03-b4dc-4154fdf35225',
+ u'other_username': u'otheruser', u'other_visible_name': u'Other User',
+ u'path': u'/home/tester/Ubuntu One/bar',
+ u'type': u'Shared',
+ u'volume_id': u'79584900-517f-4dff-b2f3-20e8c1e79365'},
+]
+
class TestCase(BaseTestCase):
"""Basics for testing."""
=== modified file 'ubuntuone/controlpanel/tests/test_backend.py'
--- ubuntuone/controlpanel/tests/test_backend.py 2010-12-23 18:20:56 +0000
+++ ubuntuone/controlpanel/tests/test_backend.py 2011-01-06 20:36:09 +0000
@@ -25,9 +25,9 @@
from twisted.internet.defer import inlineCallbacks
from ubuntuone.devtools.handlers import MementoHandler
-from ubuntuone.controlpanel import backend
-from ubuntuone.controlpanel.backend import (ACCOUNT_API,
- DEVICES_API, DEVICE_REMOVE_API, QUOTA_API,
+from ubuntuone.controlpanel import backend, replication_client
+from ubuntuone.controlpanel.backend import (bool_str,
+ ACCOUNT_API, DEVICES_API, DEVICE_REMOVE_API, QUOTA_API,
FILE_SYNC_DISABLED,
FILE_SYNC_DISCONNECTED,
FILE_SYNC_ERROR,
@@ -37,160 +37,21 @@
FILE_SYNC_UNKNOWN,
MSG_KEY, STATUS_KEY,
)
-
-from ubuntuone.controlpanel.tests import TestCase
+from ubuntuone.controlpanel.tests import (TestCase,
+ EXPECTED_ACCOUNT_INFO,
+ EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN,
+ EXPECTED_DEVICES_INFO,
+ SAMPLE_ACCOUNT_NO_CURRENT_PLAN,
+ SAMPLE_ACCOUNT_WITH_CURRENT_PLAN,
+ SAMPLE_DEVICES_JSON,
+ SAMPLE_FOLDERS,
+ SAMPLE_QUOTA_JSON,
+ SAMPLE_SHARED,
+ SAMPLE_SHARES,
+ TOKEN,
+)
from ubuntuone.controlpanel.webclient import WebClientError
-SAMPLE_CREDENTIALS = {"token": "ABC1234DEF"}
-
-SAMPLE_ACCOUNT_JSON = """
-{
- "username": "andrewpz",
- "openid": "https://login.launchpad.net/+id/abcdefg",
- "first_name": "Andrew P.",
- "last_name": "Zoilo",
- "couchdb": {
- "host": "https://couchdb.one.ubuntu.com",
- "root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
- "dbpath": "u/abc/def/12345"
- },
- "couchdb_root": "https://couchdb.one.ubuntu.com/u/abc/def/12345",
- "email": "andrewpz@protocultura.net",%s
- "nickname": "Andrew P. Zoilo",
- "id": 12345,
- "subscription": {
- "upgrade_available": false,
- "description": "Paid Plan, 50 GB of storage",
- "trial": false,
- "started": "2010-03-24T18:38:38Z",
- "is_paid": true,
- "expires": null,
- "qty": 1,
- "price": 0.0,
- "currency": null,
- "id": 654321,
- "name": "50 GB"
- }
-}
-"""
-
-CURRENT_PLAN = "Ubuntu One Basic (2 GB) + 1 x 20-Pack with 20 GB (monthly)"
-SAMPLE_CURRENT_PLAN = '\n "current_plan": "%s",' % CURRENT_PLAN
-
-SAMPLE_ACCOUNT_NO_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % ''
-SAMPLE_ACCOUNT_WITH_CURRENT_PLAN = SAMPLE_ACCOUNT_JSON % SAMPLE_CURRENT_PLAN
-
-
-SAMPLE_QUOTA_JSON = """
-{
- "total": 53687091200,
- "used": 2350345156
-}
-"""
-
-EXPECTED_ACCOUNT_INFO = {
- "quota_used": "2350345156",
- "quota_total": "53687091200",
- "type": "Paid Plan, 50 GB of storage",
- "name": "Andrew P. Zoilo",
- "email": "andrewpz@protocultura.net",
-}
-
-EXPECTED_ACCOUNT_INFO_WITH_CURRENT_PLAN = {
- "quota_used": "2350345156",
- "quota_total": "53687091200",
- "type": CURRENT_PLAN,
- "name": "Andrew P. Zoilo",
- "email": "andrewpz@protocultura.net",
-}
-
-SAMPLE_DEVICES_JSON = """
-[
- {
- "token": "ABCDEF01234token",
- "description": "Ubuntu One @ darkstar",
- "kind": "Computer"
- },
- {
- "token": "ABC1234DEF",
- "description": "Ubuntu One @ localhost",
- "kind": "Computer"
- },
- {
- "kind": "Phone",
- "description": "Nokia E65",
- "id": 1000
- }
-]
-"""
-
-EXPECTED_DEVICES_INFO = [
- {
- "device_id": "ComputerABCDEF01234token",
- "name": "Ubuntu One @ darkstar",
- "type": "Computer",
- "is_local": '',
- "configurable": '',
- },
- {
- 'is_local': 'True',
- 'configurable': 'True',
- 'device_id': 'ComputerABC1234DEF',
- 'limit_bandwidth': '',
- 'max_download_speed': '-1',
- 'max_upload_speed': '-1',
- 'name': 'Ubuntu One @ localhost',
- 'type': 'Computer'
- },
- {
- "device_id": "Phone1000",
- "name": "Nokia E65",
- "type": "Phone",
- "configurable": '',
- "is_local": '',
- },
-]
-
-SAMPLE_FOLDERS = [
- {u'generation': u'2', u'node_id': u'341da068-81d8-437a-8f75-5bb9d86455ba',
- u'path': u'/home/tester/Public', u'subscribed': u'True',
- u'suggested_path': u'~/Public',
- u'type': u'UDF', u'volume_id': u'9ea892f8-15fa-4201-bdbf-8de99fa5f588'},
- {u'generation': u'', u'node_id': u'11fbc86c-0d7a-49f5-ae83-8402caf66c6a',
- u'path': u'/home/tester/Documents', u'subscribed': u'',
- u'suggested_path': u'~/Documents',
- u'type': u'UDF', u'volume_id': u'2db262f5-a151-4c19-969c-bb5ced753c61'},
- {u'generation': u'24', u'node_id': u'9ee0e130-a7c7-4d76-a5e3-5df506221b48',
- u'path': u'/home/tester/Pictures/Photos', u'subscribed': u'True',
- u'suggested_path': u'~/Pictures/Photos',
- u'type': u'UDF', u'volume_id': u'1deb2874-3d28-46ae-9999-d5f48de9f460'},
-]
-
-SAMPLE_SHARES = [
- {u'accepted': u'True', u'access_level': u'View',
- u'free_bytes': u'39892622746', u'generation': u'2704',
- u'name': u're', u'node_id': u'c483f419-ed28-490a-825d-a8c074e2d795',
- u'other_username': u'otheruser', u'other_visible_name': u'Other User',
- u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User',
- u'type': u'Share', u'volume_id': u'4a1b263b-a2b3-4f66-9e66-4cd18050810d'},
- {u'accepted': u'True', u'access_level': u'Modify',
- u'free_bytes': u'39892622746', u'generation': u'2704',
- u'name': u'do', u'node_id': u'84544ea4-aefe-4f91-9bb9-ed7b0a805baf',
- u'other_username': u'otheruser', u'other_visible_name': u'Other User',
- u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User',
- u'type': u'Share', u'volume_id': u'7d130dfe-98b2-4bd5-8708-9eeba9838ac0'},
-]
-
-SAMPLE_SHARED = [
- {u'accepted': u'True', u'access_level': u'View',
- u'free_bytes': u'', u'generation': u'',
- u'name': u'bar', u'node_id': u'31e47530-9448-4f03-b4dc-4154fdf35225',
- u'other_username': u'otheruser', u'other_visible_name': u'Other User',
- u'path': u'/home/tester/Ubuntu One/bar',
- u'type': u'Shared',
- u'volume_id': u'79584900-517f-4dff-b2f3-20e8c1e79365'},
-]
-
class MockWebClient(object):
"""A mock webclient."""
@@ -213,7 +74,7 @@
class MockDBusClient(object):
"""A mock dbus_client module."""
- creds = SAMPLE_CREDENTIALS
+ creds = TOKEN
throttling = False
limits = {"download": -1, "upload": -1}
file_sync = True
@@ -292,6 +153,36 @@
return SAMPLE_SHARED
+class MockReplicationClient(object):
+ """A mock replication_client module."""
+
+ BOOKMARKS = 'awesome'
+ CONTACTS = 'legendary'
+
+ replications = set([BOOKMARKS, CONTACTS, 'other'])
+ exclusions = set([CONTACTS])
+
+ def get_replications(self):
+ """Grab the list of replications in this machine."""
+ return MockReplicationClient.replications
+
+ def get_exclusions(self):
+ """Grab the list of exclusions in this machine."""
+ return MockReplicationClient.exclusions
+
+ def replicate(self, replication_id):
+ """Remove replication_id from the exclusions list."""
+ if replication_id not in MockReplicationClient.replications:
+ raise replication_client.ReplicationError(replication_id)
+ MockReplicationClient.exclusions.remove(replication_id)
+
+ def exclude(self, replication_id):
+ """Add replication_id to the exclusions list."""
+ if replication_id not in MockReplicationClient.replications:
+ raise replication_client.ReplicationError(replication_id)
+ MockReplicationClient.exclusions.add(replication_id)
+
+
class BackendBasicTestCase(TestCase):
"""Simple tests for the backend."""
@@ -301,13 +192,14 @@
super(BackendBasicTestCase, self).setUp()
self.patch(backend, "WebClient", MockWebClient)
self.patch(backend, "dbus_client", MockDBusClient())
- self.local_token = "Computer" + SAMPLE_CREDENTIALS["token"]
+ self.patch(backend, "replication_client", MockReplicationClient())
+ self.local_token = "Computer" + TOKEN["token"]
self.be = backend.ControlBackend()
self.memento = MementoHandler()
backend.logger.addHandler(self.memento)
- MockDBusClient.creds = SAMPLE_CREDENTIALS
+ MockDBusClient.creds = TOKEN
def test_backend_creation(self):
"""The backend instance is successfully created."""
@@ -317,7 +209,7 @@
def test_get_token(self):
"""The get_token method returns the right token."""
token = yield self.be.get_token()
- self.assertEqual(token, SAMPLE_CREDENTIALS["token"])
+ self.assertEqual(token, TOKEN["token"])
@inlineCallbacks
def test_device_is_local(self):
@@ -388,12 +280,12 @@
result = yield self.be.remove_device(device_id)
self.assertEqual(result, device_id)
# credentials were not cleared
- self.assertEqual(MockDBusClient.creds, SAMPLE_CREDENTIALS)
+ self.assertEqual(MockDBusClient.creds, TOKEN)
@inlineCallbacks
def test_remove_device_clear_credentials_if_local_device(self):
"""The remove_device method clears the credentials if is local."""
- apiurl = DEVICE_REMOVE_API % ('computer', SAMPLE_CREDENTIALS['token'])
+ apiurl = DEVICE_REMOVE_API % ('computer', TOKEN['token'])
# pylint: disable=E1101
self.be.wc.results[apiurl] = SAMPLE_DEVICES_JSON
yield self.be.remove_device(self.local_token)
@@ -487,10 +379,10 @@
"""The volume settings can be changed."""
fid = '0123-4567'
- yield self.be.change_volume_settings(fid, {'subscribed': True})
+ yield self.be.change_volume_settings(fid, {'subscribed': 'True'})
self.assertEqual(MockDBusClient.subscribed_folders, [fid])
- yield self.be.change_volume_settings(fid, {'subscribed': False})
+ yield self.be.change_volume_settings(fid, {'subscribed': ''})
self.assertEqual(MockDBusClient.subscribed_folders, [])
@inlineCallbacks
@@ -671,3 +563,64 @@
self.be.disable_files()
self.assertFalse(MockDBusClient.file_sync)
+
+
+class BackendReplicationsTestCase(BackendBasicTestCase):
+ """Replications tests for the backend."""
+
+ @inlineCallbacks
+ def test_replications_info(self):
+ """The replications_info method exercises its callback."""
+ result = yield self.be.replications_info()
+
+ # replications_info will use exclusions information
+ expected = []
+ for name in MockReplicationClient.replications:
+ enabled = bool_str(name not in MockReplicationClient.exclusions)
+ dependency = ''
+ if name == MockReplicationClient.BOOKMARKS:
+ dependency = backend.BOOKMARKS_PKG
+ elif name == MockReplicationClient.CONTACTS:
+ dependency = backend.CONTACTS_PKG
+
+ item = {'replication_id': name, 'name': name,
+ 'enabled': enabled, 'dependency': dependency}
+ expected.append(item)
+ self.assertEqual(sorted(expected), sorted(result))
+
+ @inlineCallbacks
+ def test_change_replication_settings(self):
+ """The replication settings can be changed."""
+ rid = '0123-4567'
+ MockReplicationClient.replications.add(rid)
+ self.addCleanup(lambda: MockReplicationClient.replications.remove(rid))
+
+ yield self.be.change_replication_settings(rid, {'enabled': ''})
+ self.assertIn(rid, MockReplicationClient.exclusions)
+
+ yield self.be.change_replication_settings(rid, {'enabled': 'True'})
+ self.assertNotIn(rid, MockReplicationClient.exclusions)
+
+ @inlineCallbacks
+ def test_change_replication_settings_not_in_replications(self):
+ """The settings can not be changed for an item not in replications."""
+ rid = '0123-4567'
+ assert rid not in MockReplicationClient.replications
+
+ d = self.be.change_replication_settings(rid, {'enabled': 'True'})
+ yield self.assertFailure(d, replication_client.ReplicationError)
+
+ d = self.be.change_replication_settings(rid, {'enabled': ''})
+ yield self.assertFailure(d, replication_client.ReplicationError)
+
+ @inlineCallbacks
+ def test_change_replication_settings_no_setting(self):
+ """The change replication settings does not fail on empty settings."""
+ rid = '0123-4567'
+ MockReplicationClient.replications.add(rid)
+ self.addCleanup(lambda: MockReplicationClient.replications.remove(rid))
+
+ prior = MockReplicationClient.exclusions.copy()
+ yield self.be.change_replication_settings(rid, {})
+
+ self.assertEqual(MockReplicationClient.exclusions, prior)
=== added file 'ubuntuone/controlpanel/tests/test_replication_client.py'
--- ubuntuone/controlpanel/tests/test_replication_client.py 1970-01-01 00:00:00 +0000
+++ ubuntuone/controlpanel/tests/test_replication_client.py 2011-01-06 20:36:09 +0000
@@ -0,0 +1,160 @@
+# -*- coding: utf-8 -*-
+
+# Authors: Natalia B. Bidart
+#
+# Copyright 2010 Canonical Ltd.
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 3, as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranties of
+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+# PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see .
+
+"""Tests for the DBus service when accessing desktopcouch replications."""
+
+from twisted.internet.defer import inlineCallbacks
+
+from ubuntuone.controlpanel import replication_client
+from ubuntuone.controlpanel.tests import TestCase
+
+EXCLUSIONS = set()
+
+
+class FakedReplication(object):
+ """Faked a DC replication exclusion."""
+
+ def __init__(self):
+ self.all_exclusions = lambda: EXCLUSIONS
+ self.replicate = EXCLUSIONS.remove
+ self.exclude = EXCLUSIONS.add
+
+
+class FakedReplicationModule(object):
+ """Faked a DC replication module."""
+
+ ReplicationExclusion = FakedReplication
+
+
+class ReplicationsTestCase(TestCase):
+ """Test for the replications client methods."""
+
+ def setUp(self):
+ super(ReplicationsTestCase, self).setUp()
+
+ orig_get_proxy = replication_client.get_replication_proxy
+
+ def get_proxy():
+ """Fake the proxy getter."""
+ return orig_get_proxy(replication_module=FakedReplicationModule())
+
+ self.patch(replication_client, 'get_replication_proxy', get_proxy)
+
+ def tearDown(self):
+ EXCLUSIONS.clear()
+ super(ReplicationsTestCase, self).tearDown()
+
+ @inlineCallbacks
+ def test_no_pairing_record(self):
+ """Handle ValueError from replication layer."""
+
+ def no_pairing_record(*args, **kwargs):
+ """Fail with ValueError."""
+ raise ValueError('No pairing record.')
+
+ self.patch(FakedReplicationModule, 'ReplicationExclusion',
+ no_pairing_record)
+
+ yield self.assertFailure(replication_client.get_replications(),
+ replication_client.NoPairingRecord)
+
+ @inlineCallbacks
+ def test_get_replications(self):
+ """Replications are correctly retrieved."""
+ result = yield replication_client.get_replications()
+ self.assertEqual(result, replication_client.REPLICATIONS)
+
+ @inlineCallbacks
+ def test_get_exclusions(self):
+ """Exclusions are correctly retrieved."""
+ replications = yield replication_client.get_replications()
+ for rep in replications:
+ yield replication_client.exclude(rep)
+
+ result = yield replication_client.get_exclusions()
+ self.assertEqual(result, replications)
+
+ @inlineCallbacks
+ def test_replicate(self):
+ """Replicate a service is correct."""
+ replications = yield replication_client.get_replications()
+ rid = list(replications)[0]
+ yield replication_client.exclude(rid)
+
+ yield replication_client.replicate(rid)
+ exclusions = yield replication_client.get_exclusions()
+ self.assertNotIn(rid, exclusions)
+
+ @inlineCallbacks
+ def test_replicate_name_not_in_replications(self):
+ """Replicate a service fails if not in replications."""
+ replications = yield replication_client.get_replications()
+ rid = 'not in replications'
+ assert rid not in replications
+
+ yield self.assertFailure(replication_client.replicate(rid),
+ replication_client.InvalidIdError)
+
+ @inlineCallbacks
+ def test_replicate_name_not_in_exclusions(self):
+ """Replicate a service fails if not in exclusions."""
+ replications = yield replication_client.get_replications()
+ rid = list(replications)[0]
+ assert rid in replications
+
+ exclusions = yield replication_client.get_exclusions()
+ assert rid not in exclusions
+
+ yield self.assertFailure(replication_client.replicate(rid),
+ replication_client.NotExcludedError)
+
+ @inlineCallbacks
+ def test_exclude(self):
+ """Excluding a service is correct."""
+ replications = yield replication_client.get_replications()
+ rid = list(replications)[0]
+ yield replication_client.exclude(rid)
+ yield replication_client.replicate(rid)
+
+ yield replication_client.exclude(rid)
+ exclusions = yield replication_client.get_exclusions()
+ self.assertIn(rid, exclusions)
+
+ @inlineCallbacks
+ def test_exclude_name_not_in_replications(self):
+ """Excluding a service fails if not in replications."""
+ replications = yield replication_client.get_replications()
+ rid = 'not in replications'
+ assert rid not in replications
+
+ yield self.assertFailure(replication_client.exclude(rid),
+ replication_client.InvalidIdError)
+
+ @inlineCallbacks
+ def test_exclude_name_in_exclusions(self):
+ """Excluding a service fails if already on exclusions."""
+ replications = yield replication_client.get_replications()
+ rid = list(replications)[0]
+ assert rid in replications
+
+ yield replication_client.exclude(rid)
+ exclusions = yield replication_client.get_exclusions()
+ assert rid in exclusions
+
+ yield self.assertFailure(replication_client.exclude(rid),
+ replication_client.AlreadyExcludedError)