~ubuntu-branches/ubuntu/quantal/ubuntuone-control-panel/quantal-proposed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# -*- coding: utf-8 -*-

# Authors: Alejandro J. Cura <alecu@canonical.com>
# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
#
# 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 <http://www.gnu.org/licenses/>.

"""Export the control backend thru DBus."""

import dbus.service
import gobject

from dbus.mainloop.glib import DBusGMainLoop
from dbus.service import method, signal

from twisted.python.failure import Failure

from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,
    DBUS_PREFERENCES_IFACE, utils)
from ubuntuone.controlpanel.backend import ControlBackend
from ubuntuone.controlpanel.logger import setup_logging


logger = setup_logging('dbus_service')


def make_unicode(anything):
    """Transform 'anything' on an unicode."""
    if not isinstance(anything, unicode):
        anything = str(anything).decode('utf8', 'replace')

    return anything


def error_handler(error):
    """Handle 'error' properly to be able to call a dbus error signal.
     If 'error' is a Failure, then transform the exception in it to a error
    dict. If 'error' is a regular Exception, transform it.

    If 'error' is already a string-string dict, just pass it along. Build a
    generic error dict in any other case.

    """
    result = {}
    if isinstance(error, Failure):
        result = utils.failure_to_error_dict(error)
    elif isinstance(error, Exception):
        result = utils.exception_to_error_dict(error)
    elif isinstance(error, dict):
        # ensure that both keys and values are unicodes
        result = dict(map(make_unicode, i) for i in error.iteritems())
    else:
        msg = 'Got unexpected error argument %r' % error
        result = {utils.ERROR_TYPE: 'UnknownError', utils.ERROR_MESSAGE: msg}

    return result


def transform_failure(f):
    """Decorator to apply to DBus error signals.

    With this call, a Failure is transformed into a string-string dict.

    """
    def inner(error, *a):
        """Do the Failure transformation."""
        error_dict = error_handler(error)
        return f(error_dict)

    return inner


class ControlPanelBackend(dbus.service.Object):
    """Export the Control Panel backend thru DBus."""

    def __init__(self, backend, *args, **kwargs):
        """Create this instance of the backend."""
        super(ControlPanelBackend, self).__init__(*args, **kwargs)
        self.backend = backend
        logger.debug('ControlPanelBackend created with %r, %r', args, kwargs)

    # pylint: disable=C0103

    @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
    def account_info(self):
        """Find out the account info for the current logged in user."""
        d = self.backend.account_info()
        d.addCallback(self.AccountInfoReady)
        d.addErrback(transform_failure(self.AccountInfoError))

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
    def AccountInfoReady(self, info):
        """The info for the current user is available right now."""

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
    def AccountInfoError(self, error):
        """The info for the current user is currently unavailable."""

    #---

    @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
    def devices_info(self):
        """Find out the devices info for the logged in user."""
        d = self.backend.devices_info()
        d.addCallback(self.DevicesInfoReady)
        d.addErrback(transform_failure(self.DevicesInfoError))

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")
    def DevicesInfoReady(self, info):
        """The info for the devices is available right now."""

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
    def DevicesInfoError(self, error):
        """The info for the devices is currently unavailable."""

    #---

    @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}")
    def change_device_settings(self, token, settings):
        """Configure a given device."""
        d = self.backend.change_device_settings(token, settings)
        d.addCallback(self.DeviceSettingsChanged)
        d.addErrback(transform_failure(self.DeviceSettingsChangeError))

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
    def DeviceSettingsChanged(self, token):
        """The settings for the device were changed."""

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
    def DeviceSettingsChangeError(self, error):
        """Problem changing settings for the device."""

    #---

    @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="s")
    def remove_device(self, token):
        """Remove a given device."""
        d = self.backend.remove_device(token)
        d.addCallback(self.DeviceRemoved)
        d.addErrback(transform_failure(self.DeviceRemovalError))

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
    def DeviceRemoved(self, token):
        """The removal for the device was completed."""

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
    def DeviceRemovalError(self, error):
        """Problem removing the device."""

    #---

    @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
    def file_sync_status(self):
        """Get the status of the file sync service."""
        d = self.backend.file_sync_status()
        d.addCallback(lambda args: self.FileSyncStatusReady(*args))
        d.addErrback(transform_failure(self.FileSyncStatusError))

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="bs")
    def FileSyncStatusReady(self, enabled, status):
        """The new file sync status is available."""

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
    def FileSyncStatusError(self, error):
        """Problem getting the file sync status."""

    #---

    @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
    def volumes_info(self):
        """Find out the volumes info for the logged in user."""
        d = self.backend.volumes_info()
        d.addCallback(self.VolumesInfoReady)
        d.addErrback(transform_failure(self.VolumesInfoError))

    @utils.log_call(logger.info)
    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="aa{ss}")
    def VolumesInfoReady(self, info):
        """The info for the volumes is available right now."""

    @utils.log_call(logger.error)
    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
    def VolumesInfoError(self, error):
        """The info for the volumes is currently unavailable."""

    #---

    @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="sa{ss}")
    def change_volume_settings(self, volume_id, settings):
        """Configure a given volume."""
        d = self.backend.change_volume_settings(volume_id, settings)
        d.addCallback(self.VolumeSettingsChanged)
        d.addErrback(transform_failure(self.VolumeSettingsChangeError))

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="s")
    def VolumeSettingsChanged(self, token):
        """The settings for the volume were changed."""

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
    def VolumeSettingsChangeError(self, error):
        """Problem changing settings for the volume."""

    #---

    @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()
        d.addCallback(self.QueryBookmarksResult)
        d.addErrback(transform_failure(self.QueryBookmarksError))

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="b")
    def QueryBookmarksResult(self, enabled):
        """The bookmark extension is or is not installed."""

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
    def QueryBookmarksError(self, error):
        """Problem getting the status of the extension."""

    #---

    @method(dbus_interface=DBUS_PREFERENCES_IFACE, in_signature="")
    def install_bookmarks_extension(self):
        """Install the extension to sync bookmarks."""
        d = self.backend.install_bookmarks_extension()
        d.addCallback(lambda _: self.InstallBookmarksSuccess())
        d.addErrback(transform_failure(self.InstallBookmarksError))

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="")
    def InstallBookmarksSuccess(self):
        """The extension to sync bookmarks has been installed."""

    @signal(dbus_interface=DBUS_PREFERENCES_IFACE, signature="a{ss}")
    def InstallBookmarksError(self, error):
        """Problem installing the extension to sync bookmarks."""


def init_mainloop():
    """Start the DBus mainloop."""
    DBusGMainLoop(set_as_default=True)


def run_mainloop():
    """Run the gobject main loop."""
    loop = gobject.MainLoop()
    loop.run()


def register_service():
    """Try to register DBus service for making sure we run only one instance.

    Return True if succesfully registered, False if already running.
    """
    session_bus = dbus.SessionBus()
    name = session_bus.request_name(DBUS_BUS_NAME,
                                    dbus.bus.NAME_FLAG_DO_NOT_QUEUE)
    return name != dbus.bus.REQUEST_NAME_REPLY_EXISTS


def get_busname():
    """Build the DBus BusName."""
    return dbus.service.BusName(DBUS_BUS_NAME, bus=dbus.SessionBus())


def publish_backend(backend=ControlBackend()):
    """Publish the backend on the DBus."""
    return ControlPanelBackend(backend=backend,
                               object_path=DBUS_PREFERENCES_PATH,
                               bus_name=get_busname())


def main():
    """Hook the DBus listeners and start the main loop."""
    init_mainloop()
    if register_service():
        publish_backend()
        run_mainloop()
    else:
        print "Control panel backend already running."