1
# -*- coding: utf-8 -*-
3
# Authors: Alejandro J. Cura <alecu@canonical.com>
4
# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
6
# Copyright 2010 Canonical Ltd.
8
# This program is free software: you can redistribute it and/or modify it
9
# under the terms of the GNU General Public License version 3, as published
10
# by the Free Software Foundation.
12
# This program is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranties of
14
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
15
# PURPOSE. See the GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License along
18
# with this program. If not, see <http://www.gnu.org/licenses/>.
20
"""Export the control backend thru DBus."""
25
from dbus.mainloop.glib import DBusGMainLoop
26
from dbus.service import method, signal
28
from twisted.python.failure import Failure
30
from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,
31
DBUS_PREFERENCES_IFACE, utils)
32
from ubuntuone.controlpanel.backend import ControlBackend
33
from ubuntuone.controlpanel.logger import setup_logging
36
logger = setup_logging('dbus_service')
39
def make_unicode(anything):
40
"""Transform 'anything' on an unicode."""
41
if not isinstance(anything, unicode):
42
anything = str(anything).decode('utf8', 'replace')
47
def error_handler(error):
48
"""Handle 'error' properly to be able to call a dbus error signal.
49
If 'error' is a Failure, then transform the exception in it to a error
50
dict. If 'error' is a regular Exception, transform it.
52
If 'error' is already a string-string dict, just pass it along. Build a
53
generic error dict in any other case.
57
if isinstance(error, Failure):
58
result = utils.failure_to_error_dict(error)
59
elif isinstance(error, Exception):
60
result = utils.exception_to_error_dict(error)
61
elif isinstance(error, dict):
62
# ensure that both keys and values are unicodes
63
result = dict(map(make_unicode, i) for i in error.iteritems())
65
msg = 'Got unexpected error argument %r' % error
66
result = {utils.ERROR_TYPE: 'UnknownError', utils.ERROR_MESSAGE: msg}
71
def transform_failure(f):
72
"""Decorator to apply to DBus error signals.
74
With this call, a Failure is transformed into a string-string dict.
78
"""Do the Failure transformation."""
79
error_dict = error_handler(error)
85
class ControlPanelBackend(dbus.service.Object):
86
"""Export the Control Panel backend thru DBus."""
88
def __init__(self, backend, *args, **kwargs):
89
"""Create this instance of the backend."""
90
super(ControlPanelBackend, self).__init__(*args, **kwargs)
91
self.backend = backend
92
logger.debug('ControlPanelBackend created with %r, %r', args, kwargs)
94
# pylint: disable=C0103
96
@method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
97
def account_info(self):
98
"""Find out the account info for the current logged in user."""
99
d = self.backend.account_info()
100
d.addCallback(self.AccountInfoReady)
101
d.addErrback(transform_failure(self.AccountInfoError))
103
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
104
def AccountInfoReady(self, info):
105
"""The info for the current user is available right now."""
107
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
108
def AccountInfoError(self, error):
109
"""The info for the current user is currently unavailable."""
113
@method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
114
def devices_info(self):
115
"""Find out the devices info for the logged in user."""
116
d = self.backend.devices_info()
117
d.addCallback(self.DevicesInfoReady)
118
d.addErrback(transform_failure(self.DevicesInfoError))
120
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")
121
def DevicesInfoReady(self, info):
122
"""The info for the devices is available right now."""
124
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
125
def DevicesInfoError(self, error):
126
"""The info for the devices is currently unavailable."""
130
@method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}")
131
def change_device_settings(self, token, settings):
132
"""Configure a given device."""
133
d = self.backend.change_device_settings(token, settings)
134
d.addCallback(self.DeviceSettingsChanged)
135
d.addErrback(transform_failure(self.DeviceSettingsChangeError))
137
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
138
def DeviceSettingsChanged(self, token):
139
"""The settings for the device were changed."""
141
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
142
def DeviceSettingsChangeError(self, error):
143
"""Problem changing settings for the device."""
147
@method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="s")
148
def remove_device(self, token):
149
"""Remove a given device."""
150
d = self.backend.remove_device(token)
151
d.addCallback(self.DeviceRemoved)
152
d.addErrback(transform_failure(self.DeviceRemovalError))
154
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
155
def DeviceRemoved(self, token):
156
"""The removal for the device was completed."""
158
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
159
def DeviceRemovalError(self, error):
160
"""Problem removing the device."""
164
@method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
165
def file_sync_status(self):
166
"""Get the status of the file sync service."""
167
d = self.backend.file_sync_status()
168
d.addCallback(lambda args: self.FileSyncStatusReady(*args))
169
d.addErrback(transform_failure(self.FileSyncStatusError))
171
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="bs")
172
def FileSyncStatusReady(self, enabled, status):
173
"""The new file sync status is available."""
175
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
176
def FileSyncStatusError(self, error):
177
"""Problem getting the file sync status."""
181
@method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
182
def volumes_info(self):
183
"""Find out the volumes info for the logged in user."""
184
d = self.backend.volumes_info()
185
d.addCallback(self.VolumesInfoReady)
186
d.addErrback(transform_failure(self.VolumesInfoError))
188
@utils.log_call(logger.info)
189
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")
190
def VolumesInfoReady(self, info):
191
"""The info for the volumes is available right now."""
193
@utils.log_call(logger.error)
194
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
195
def VolumesInfoError(self, error):
196
"""The info for the volumes is currently unavailable."""
200
@method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}")
201
def change_volume_settings(self, volume_id, settings):
202
"""Configure a given volume."""
203
d = self.backend.change_volume_settings(volume_id, settings)
204
d.addCallback(self.VolumeSettingsChanged)
205
d.addErrback(transform_failure(self.VolumeSettingsChangeError))
207
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
208
def VolumeSettingsChanged(self, token):
209
"""The settings for the volume were changed."""
211
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
212
def VolumeSettingsChangeError(self, error):
213
"""Problem changing settings for the volume."""
217
@method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
218
def query_bookmark_extension(self):
219
"""Check if the extension to sync bookmarks is installed."""
220
d = self.backend.query_bookmark_extension()
221
d.addCallback(self.QueryBookmarksResult)
222
d.addErrback(transform_failure(self.QueryBookmarksError))
224
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="b")
225
def QueryBookmarksResult(self, enabled):
226
"""The bookmark extension is or is not installed."""
228
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
229
def QueryBookmarksError(self, error):
230
"""Problem getting the status of the extension."""
234
@method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
235
def install_bookmarks_extension(self):
236
"""Install the extension to sync bookmarks."""
237
d = self.backend.install_bookmarks_extension()
238
d.addCallback(lambda _: self.InstallBookmarksSuccess())
239
d.addErrback(transform_failure(self.InstallBookmarksError))
241
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="")
242
def InstallBookmarksSuccess(self):
243
"""The extension to sync bookmarks has been installed."""
245
@signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
246
def InstallBookmarksError(self, error):
247
"""Problem installing the extension to sync bookmarks."""
251
"""Start the DBus mainloop."""
252
DBusGMainLoop(set_as_default=True)
256
"""Run the gobject main loop."""
257
loop = gobject.MainLoop()
261
def register_service():
262
"""Try to register DBus service for making sure we run only one instance.
264
Return True if succesfully registered, False if already running.
266
session_bus = dbus.SessionBus()
267
name = session_bus.request_name(DBUS_BUS_NAME,
268
dbus.bus.NAME_FLAG_DO_NOT_QUEUE)
269
return name != dbus.bus.REQUEST_NAME_REPLY_EXISTS
273
"""Build the DBus BusName."""
274
return dbus.service.BusName(DBUS_BUS_NAME, bus=dbus.SessionBus())
277
def publish_backend(backend=ControlBackend()):
278
"""Publish the backend on the DBus."""
279
return ControlPanelBackend(backend=backend,
280
object_path=DBUS_PREFERENCES_PATH,
281
bus_name=get_busname())
285
"""Hook the DBus listeners and start the main loop."""
287
if register_service():
291
print "Control panel backend already running."