~saurabhanandiit/gtg/exportFixed

« back to all changes in this revision

Viewing changes to GTG/backends/generictomboy.py

Merge of my work on liblarch newbase and all the backends ported to liblarch
(which mainly means porting the datastore).
One failing test, will check it.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
# -----------------------------------------------------------------------------
 
3
# Getting Things Gnome! - a personal organizer for the GNOME desktop
 
4
# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
 
5
#
 
6
# This program is free software: you can redistribute it and/or modify it under
 
7
# the terms of the GNU General Public License as published by the Free Software
 
8
# Foundation, either version 3 of the License, or (at your option) any later
 
9
# version.
 
10
#
 
11
# This program is distributed in the hope that it will be useful, but WITHOUT
 
12
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 
13
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 
14
# details.
 
15
#
 
16
# You should have received a copy of the GNU General Public License along with
 
17
# this program.  If not, see <http://www.gnu.org/licenses/>.
 
18
# -----------------------------------------------------------------------------
 
19
 
 
20
'''
 
21
Contains the Backend class for both Tomboy and Gnote
 
22
'''
 
23
#Note: To introspect tomboy, execute:
 
24
#    qdbus org.gnome.Tomboy /org/gnome/Tomboy/RemoteControl
 
25
 
 
26
import os
 
27
import threading
 
28
import uuid
 
29
import dbus
 
30
import datetime
 
31
import unicodedata
 
32
 
 
33
from GTG.tools.testingmode       import TestingMode
 
34
from GTG.tools.borg              import Borg
 
35
from GTG.backends.genericbackend import GenericBackend
 
36
from GTG.backends.backendsignals import BackendSignals
 
37
from GTG.backends.syncengine     import SyncEngine, SyncMeme
 
38
from GTG.tools.logger            import Log
 
39
from GTG.tools.watchdog          import Watchdog
 
40
from GTG.tools.interruptible     import interruptible
 
41
from GTG.tools.tags              import extract_tags_from_text
 
42
 
 
43
 
 
44
 
 
45
class GenericTomboy(GenericBackend):
 
46
    '''Backend class for Tomboy/Gnote'''
 
47
    
 
48
 
 
49
###############################################################################
 
50
### Backend standard methods ##################################################
 
51
###############################################################################
 
52
 
 
53
    def __init__(self, parameters):
 
54
        """
 
55
        See GenericBackend for an explanation of this function.
 
56
        """
 
57
        super(GenericTomboy, self).__init__(parameters)
 
58
        #loading the saved state of the synchronization, if any
 
59
        self.data_path = os.path.join('backends/tomboy/', \
 
60
                                      "sync_engine-" + self.get_id())
 
61
        self.sync_engine = self._load_pickled_file(self.data_path, \
 
62
                                                   SyncEngine())
 
63
        #if the backend is being tested, we connect to a different DBus
 
64
        # interface to avoid clashing with a running instance of Tomboy
 
65
        if TestingMode().get_testing_mode():
 
66
            #just used for testing purposes
 
67
            self.BUS_ADDRESS = \
 
68
                    self._parameters["use this fake connection instead"]
 
69
        else:
 
70
            self.BUS_ADDRESS = self._BUS_ADDRESS
 
71
        #we let some time pass before considering a tomboy task for importing,
 
72
        # as the user may still be editing it. Here, we store the Timer objects
 
73
        # that will execute after some time after each tomboy signal.
 
74
        #NOTE: I'm not sure if this is the case anymore (but it shouldn't hurt
 
75
        #      anyway). (invernizzi)
 
76
        self._tomboy_setting_timers = {}
 
77
        
 
78
    def initialize(self):
 
79
        '''
 
80
        See GenericBackend for an explanation of this function.
 
81
        Connects to the session bus and sets the callbacks for bus signals
 
82
        '''
 
83
        super(GenericTomboy, self).initialize()
 
84
        with self.DbusWatchdog(self):
 
85
            bus = dbus.SessionBus()
 
86
            bus.add_signal_receiver(self.on_note_saved,
 
87
                                    dbus_interface = self.BUS_ADDRESS[2],
 
88
                                    signal_name    = "NoteSaved")
 
89
            bus.add_signal_receiver(self.on_note_deleted,
 
90
                                    dbus_interface = self.BUS_ADDRESS[2],
 
91
                                    signal_name    = "NoteDeleted")
 
92
 
 
93
    @interruptible
 
94
    def start_get_tasks(self):
 
95
        '''
 
96
        See GenericBackend for an explanation of this function.
 
97
        Gets all the notes from Tomboy and sees if they must be added in GTG
 
98
        (and, if so, it adds them).
 
99
        '''
 
100
        with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
 
101
            with self.DbusWatchdog(self):
 
102
                tomboy_notes = [note_id for note_id in \
 
103
                                tomboy.ListAllNotes()]
 
104
        #adding the new ones
 
105
        for note in tomboy_notes:
 
106
            self.cancellation_point()
 
107
            self._process_tomboy_note(note)
 
108
        #checking if some notes have been deleted while GTG was not running
 
109
        stored_notes_ids = self.sync_engine.get_all_remote()
 
110
        for note in set(stored_notes_ids).difference(set(tomboy_notes)):
 
111
            self.on_note_deleted(note, None)
 
112
 
 
113
    def save_state(self):
 
114
        '''Saves the state of the synchronization'''
 
115
        self._store_pickled_file(self.data_path, self.sync_engine)
 
116
 
 
117
    def quit(self, disable = False):
 
118
        '''
 
119
        See GenericBackend for an explanation of this function.
 
120
        '''
 
121
        def quit_thread():
 
122
            while True:
 
123
                try:
 
124
                    [key, timer] = \
 
125
                        self._tomboy_setting_timers.iteritems().next()
 
126
                except StopIteration:
 
127
                    break
 
128
                timer.cancel()
 
129
                del self._tomboy_setting_timers[key]
 
130
        threading.Thread(target = quit_thread).start()
 
131
        super(GenericTomboy, self).quit(disable)
 
132
 
 
133
###############################################################################
 
134
### Something got removed #####################################################
 
135
###############################################################################
 
136
 
 
137
    @interruptible
 
138
    def on_note_deleted(self, note, something):
 
139
        '''
 
140
        Callback, executed when a tomboy note is deleted.
 
141
        Deletes the related GTG task.
 
142
 
 
143
        @param note: the id of the Tomboy note
 
144
        @param something: not used, here for signal callback compatibility
 
145
        '''
 
146
        with self.datastore.get_backend_mutex():
 
147
            self.cancellation_point()
 
148
            try:
 
149
                tid = self.sync_engine.get_local_id(note)
 
150
            except KeyError:
 
151
                return
 
152
            if self.datastore.has_task(tid):
 
153
                self.datastore.request_task_deletion(tid)
 
154
                self.break_relationship(remote_id = note)
 
155
 
 
156
    @interruptible
 
157
    def remove_task(self, tid):
 
158
        '''
 
159
        See GenericBackend for an explanation of this function.
 
160
        '''
 
161
        with self.datastore.get_backend_mutex():
 
162
            self.cancellation_point()
 
163
            try:
 
164
                note = self.sync_engine.get_remote_id(tid)
 
165
            except KeyError:
 
166
                return
 
167
            with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
 
168
                with self.DbusWatchdog(self):
 
169
                    if tomboy.NoteExists(note):
 
170
                        tomboy.DeleteNote(note)
 
171
                        self.break_relationship(local_id = tid)
 
172
 
 
173
    def _exec_lost_syncability(self, tid, note):
 
174
        '''
 
175
        Executed when a relationship between tasks loses its syncability
 
176
        property. See SyncEngine for an explanation of that.
 
177
        This function finds out which object (task/note) is the original one
 
178
        and which is the copy, and deletes the copy.
 
179
 
 
180
        @param tid: a GTG task tid
 
181
        @param note: a tomboy note id
 
182
        '''
 
183
        self.cancellation_point()
 
184
        meme = self.sync_engine.get_meme_from_remote_id(note)
 
185
        #First of all, the relationship is lost
 
186
        self.sync_engine.break_relationship(remote_id = note)
 
187
        if meme.get_origin() == "GTG":
 
188
            with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
 
189
                with self.DbusWatchdog(self):
 
190
                    tomboy.DeleteNote(note)
 
191
        else:
 
192
            self.datastore.request_task_deletion(tid)
 
193
 
 
194
###############################################################################
 
195
### Process tasks #############################################################
 
196
###############################################################################
 
197
 
 
198
    def _process_tomboy_note(self, note):
 
199
        '''
 
200
        Given a tomboy note, finds out if it must be synced to a GTG note and, 
 
201
        if so, it carries out the synchronization (by creating or updating a GTG
 
202
        task, or deleting itself if the related task has been deleted)
 
203
 
 
204
        @param note: a Tomboy note id
 
205
        '''
 
206
        with self.datastore.get_backend_mutex():
 
207
            self.cancellation_point()
 
208
            is_syncable = self._tomboy_note_is_syncable(note)
 
209
            with self.DbusWatchdog(self):
 
210
                action, tid = self.sync_engine.analyze_remote_id(note, \
 
211
                         self.datastore.has_task, \
 
212
                         self._tomboy_note_exists, is_syncable)
 
213
            Log.debug("processing tomboy (%s, %s)" % (action, is_syncable))
 
214
 
 
215
            if action == SyncEngine.ADD:
 
216
                tid = str(uuid.uuid4())
 
217
                task = self.datastore.task_factory(tid)
 
218
                self._populate_task(task, note)
 
219
                self.record_relationship(local_id = tid,\
 
220
                            remote_id = note, \
 
221
                            meme = SyncMeme(task.get_modified(),
 
222
                                            self.get_modified_for_note(note),
 
223
                                            self.get_id()))
 
224
                self.datastore.push_task(task)
 
225
 
 
226
            elif action == SyncEngine.UPDATE:
 
227
                task = self.datastore.get_task(tid)
 
228
                meme = self.sync_engine.get_meme_from_remote_id(note)
 
229
                newest = meme.which_is_newest(task.get_modified(),
 
230
                                     self.get_modified_for_note(note))
 
231
                if newest == "remote":
 
232
                    self._populate_task(task, note)
 
233
                    meme.set_local_last_modified(task.get_modified())
 
234
                    meme.set_remote_last_modified(\
 
235
                                        self.get_modified_for_note(note))
 
236
                    self.save_state()
 
237
 
 
238
            elif action == SyncEngine.REMOVE:
 
239
                with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
 
240
                    with self.DbusWatchdog(self):
 
241
                        tomboy.DeleteNote(note)
 
242
                    try:
 
243
                        self.sync_engine.break_relationship(remote_id = note)
 
244
                    except KeyError:
 
245
                        pass
 
246
 
 
247
            elif action == SyncEngine.LOST_SYNCABILITY:
 
248
                self._exec_lost_syncability(tid, note)
 
249
 
 
250
    @interruptible
 
251
    def set_task(self, task):
 
252
        '''
 
253
        See GenericBackend for an explanation of this function.
 
254
        '''
 
255
        self.cancellation_point()
 
256
        is_syncable = self._gtg_task_is_syncable_per_attached_tags(task)
 
257
        tid = task.get_id()
 
258
        with self.datastore.get_backend_mutex():
 
259
            with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
 
260
                with self.DbusWatchdog(self):
 
261
                    action, note = self.sync_engine.analyze_local_id(tid, \
 
262
                              self.datastore.has_task, tomboy.NoteExists, \
 
263
                                                            is_syncable)
 
264
                Log.debug("processing gtg (%s, %d)" % (action, is_syncable))
 
265
 
 
266
                if action == SyncEngine.ADD:
 
267
                    #GTG allows multiple tasks with the same name,
 
268
                    #Tomboy doesn't. we need to handle the renaming
 
269
                    #manually
 
270
                    title = task.get_title()
 
271
                    duplicate_counter = 1
 
272
                    with self.DbusWatchdog(self):
 
273
                        note = tomboy.CreateNamedNote(title)
 
274
                        while note == "":
 
275
                            duplicate_counter += 1
 
276
                            note = tomboy.CreateNamedNote(title + "(%d)" %
 
277
                                                      duplicate_counter)
 
278
                    if duplicate_counter != 1:
 
279
                        #if we needed to rename, we have to rename also
 
280
                        # the gtg task
 
281
                        task.set_title(title + " (%d)" % duplicate_counter)
 
282
 
 
283
                    self._populate_note(note, task)
 
284
                    self.record_relationship( \
 
285
                        local_id = tid, remote_id = note, \
 
286
                        meme = SyncMeme(task.get_modified(),
 
287
                                        self.get_modified_for_note(note),
 
288
                                        "GTG"))
 
289
 
 
290
                elif action == SyncEngine.UPDATE:
 
291
                    meme = self.sync_engine.get_meme_from_local_id(\
 
292
                                                        task.get_id())
 
293
                    newest = meme.which_is_newest(task.get_modified(),
 
294
                                         self.get_modified_for_note(note))
 
295
                    if newest == "local":
 
296
                        self._populate_note(note, task)
 
297
                        meme.set_local_last_modified(task.get_modified())
 
298
                        meme.set_remote_last_modified(\
 
299
                                            self.get_modified_for_note(note))
 
300
                        self.save_state()
 
301
 
 
302
                elif action == SyncEngine.REMOVE:
 
303
                    self.datastore.request_task_deletion(tid)
 
304
                    try:
 
305
                        self.sync_engine.break_relationship(local_id = tid)
 
306
                        self.save_state()
 
307
                    except KeyError:
 
308
                        pass
 
309
 
 
310
                elif action == SyncEngine.LOST_SYNCABILITY:
 
311
                    self._exec_lost_syncability(tid, note)
 
312
 
 
313
###############################################################################
 
314
### Helper methods ############################################################
 
315
###############################################################################
 
316
 
 
317
    @interruptible
 
318
    def on_note_saved(self,  note):
 
319
        '''
 
320
        Callback, executed when a tomboy note is saved by Tomboy itself.
 
321
        Updates the related GTG task (or creates one, if necessary).
 
322
 
 
323
        @param note: the id of the Tomboy note
 
324
        '''
 
325
        self.cancellation_point()
 
326
        #NOTE: we let some seconds pass before executing the real callback, as
 
327
        #      the editing of the Tomboy note may still be in progress
 
328
        @interruptible
 
329
        def _execute_on_note_saved(self, note):
 
330
            self.cancellation_point()
 
331
            try:
 
332
                del self._tomboy_setting_timers[note]
 
333
            except:
 
334
                pass
 
335
            self._process_tomboy_note(note)
 
336
            self.save_state()
 
337
 
 
338
        try:
 
339
            self._tomboy_setting_timers[note].cancel()
 
340
        except KeyError:
 
341
            pass
 
342
        finally:
 
343
            timer =threading.Timer(5, _execute_on_note_saved,
 
344
                                   args = (self, note))
 
345
            self._tomboy_setting_timers[note] = timer
 
346
            timer.start()
 
347
 
 
348
    def _tomboy_note_is_syncable(self, note):
 
349
        '''
 
350
        Returns True if this tomboy note should be synced into GTG tasks.
 
351
 
 
352
        @param note: the note id
 
353
        @returns Boolean
 
354
        '''
 
355
        attached_tags = self.get_attached_tags()
 
356
        if GenericBackend.ALLTASKS_TAG in attached_tags:
 
357
            return True
 
358
        with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
 
359
            with self.DbusWatchdog(self):
 
360
                content = tomboy.GetNoteContents(note)
 
361
            syncable = False
 
362
            for tag in attached_tags:
 
363
                try:
 
364
                    content.index(tag)
 
365
                    syncable = True
 
366
                    break
 
367
                except ValueError:
 
368
                    pass
 
369
            return syncable
 
370
 
 
371
    def _tomboy_note_exists(self, note):
 
372
        '''
 
373
        Returns True if  a tomboy note exists with the given id.
 
374
 
 
375
        @param note: the note id
 
376
        @returns Boolean
 
377
        '''
 
378
        with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
 
379
            with self.DbusWatchdog(self):
 
380
                return tomboy.NoteExists(note)
 
381
 
 
382
    def get_modified_for_note(self, note):
 
383
        '''
 
384
        Returns the modification time for the given note id.
 
385
 
 
386
        @param note: the note id
 
387
        @returns datetime.datetime
 
388
        '''
 
389
        with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
 
390
            with self.DbusWatchdog(self):
 
391
                return datetime.datetime.fromtimestamp( \
 
392
                                tomboy.GetNoteChangeDate(note))
 
393
 
 
394
    def _tomboy_split_title_and_text(self, content):
 
395
        '''
 
396
        Tomboy does not have a "getTitle" and "getText" functions to get the
 
397
        title and the text of a note separately. Instead, it has a getContent
 
398
        function, that returns both of them.
 
399
        This function splits up the output of getContent into a title string and
 
400
        a text string.
 
401
 
 
402
        @param content: a string, the result of a getContent call
 
403
        @returns list: a list composed by [title, text]
 
404
        '''
 
405
        try:
 
406
            end_of_title = content.index('\n')
 
407
        except ValueError:
 
408
            return content, unicode("")
 
409
        title = content[: end_of_title]
 
410
        if len(content) > end_of_title:
 
411
            return title, content[end_of_title +1 :]
 
412
        else:
 
413
            return title, unicode("")
 
414
 
 
415
    def _populate_task(self, task, note):
 
416
        '''
 
417
        Copies the content of a Tomboy note into a task.
 
418
 
 
419
        @param task: a GTG Task
 
420
        @param note: a Tomboy note
 
421
        '''
 
422
        #add tags objects (it's not enough to have @tag in the text to add a
 
423
        # tag
 
424
        with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
 
425
            with self.DbusWatchdog(self):
 
426
                content = tomboy.GetNoteContents(note)
 
427
        #update the tags list
 
428
        task.set_only_these_tags(extract_tags_from_text(content))
 
429
        #extract title and text
 
430
        [title, text] = self._tomboy_split_title_and_text(unicode(content))
 
431
        #Tomboy speaks unicode, we don't
 
432
        title = unicodedata.normalize('NFKD', title).encode('ascii', 'ignore')
 
433
        text = unicodedata.normalize('NFKD', text).encode('ascii', 'ignore')
 
434
        task.set_title(title)
 
435
        task.set_text(text)
 
436
        task.add_remote_id(self.get_id(), note)
 
437
 
 
438
    def _populate_note(self, note, task):
 
439
        '''
 
440
        Copies the content of a task into a Tomboy note.
 
441
 
 
442
        @param note: a Tomboy note
 
443
        @param task: a GTG Task
 
444
        '''
 
445
        title = task.get_title()
 
446
        tested_title = title
 
447
        duplicate_counter = 1
 
448
        with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
 
449
            with self.DbusWatchdog(self):
 
450
                tomboy.SetNoteContents(note, title + '\n' + \
 
451
                                       task.get_excerpt(strip_tags = False))
 
452
 
 
453
    def break_relationship(self, *args, **kwargs):
 
454
        '''
 
455
        Proxy method for SyncEngine.break_relationship, which also saves the
 
456
        state of the synchronization.
 
457
        '''
 
458
        #tomboy passes Dbus.String objects, which are not pickable. We convert
 
459
        # those to unicode
 
460
        kwargs["remote_id"] = unicode(kwargs["remote_id"])
 
461
        try:
 
462
            self.sync_engine.break_relationship(*args, **kwargs)
 
463
            #we try to save the state at each change in the sync_engine:
 
464
            #it's slower, but it should avoid widespread task
 
465
            #duplication
 
466
            self.save_state()
 
467
        except KeyError:
 
468
            pass
 
469
 
 
470
    def record_relationship(self, *args, **kwargs):
 
471
        '''
 
472
        Proxy method for SyncEngine.break_relationship, which also saves the
 
473
        state of the synchronization.
 
474
        '''
 
475
        #tomboy passes Dbus.String objects, which are not pickable. We convert
 
476
        # those to unicode
 
477
        kwargs["remote_id"] = unicode(kwargs["remote_id"])
 
478
 
 
479
        self.sync_engine.record_relationship(*args, **kwargs)
 
480
        #we try to save the state at each change in the sync_engine:
 
481
        #it's slower, but it should avoid widespread task
 
482
        #duplication
 
483
        self.save_state()
 
484
 
 
485
###############################################################################
 
486
### Connection handling #######################################################
 
487
###############################################################################
 
488
 
 
489
 
 
490
 
 
491
    class TomboyConnection(Borg):
 
492
        '''
 
493
        TomboyConnection creates a connection to TOMBOY via DBUS and
 
494
        handles all the possible exceptions.
 
495
        It is a class that can be used with a with statement.
 
496
        Example:
 
497
        with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
 
498
            #do something
 
499
        '''
 
500
 
 
501
 
 
502
        def __init__(self, backend, bus_name, bus_path, bus_interface):
 
503
            '''
 
504
            Sees if a TomboyConnection object already exists. If so, since we
 
505
            are inheriting from a Borg object, the initialization already took
 
506
            place.
 
507
            If not, it tries to connect to Tomboy via Dbus. If the connection
 
508
            is not possible, the user is notified about it.
 
509
 
 
510
            @param backend: a reference to a Backend
 
511
            @param bus_name: the DBUS address of Tomboy
 
512
            @param bus_path: the DBUS path of Tomboy RemoteControl
 
513
            @param bus_interface: the DBUS address of Tomboy RemoteControl 
 
514
            '''
 
515
            super(GenericTomboy.TomboyConnection, self).__init__()
 
516
            if hasattr(self, "tomboy_connection_is_ok") and \
 
517
                                self.tomboy_connection_is_ok:
 
518
                return
 
519
            self.backend = backend
 
520
            with GenericTomboy.DbusWatchdog(backend):
 
521
                bus = dbus.SessionBus()
 
522
                obj = bus.get_object(bus_name, bus_path)
 
523
                self.tomboy = dbus.Interface(obj, bus_interface)
 
524
            self.tomboy_connection_is_ok = True
 
525
 
 
526
        def __enter__(self):
 
527
            '''
 
528
            Returns the Tomboy connection
 
529
 
 
530
            @returns dbus.Interface
 
531
            '''
 
532
            return self.tomboy
 
533
 
 
534
        def __exit__(self, exception_type, value, traceback):
 
535
            '''
 
536
            Checks the state of the connection.
 
537
            If something went wrong for the connection, notifies the user.
 
538
 
 
539
            @param exception_type: the type of exception that occurred, or
 
540
                                   None
 
541
            @param value: the instance of the exception occurred, or None
 
542
            @param traceback: the traceback of the error
 
543
            @returns: False if some exception must be re-raised.
 
544
            '''
 
545
            if isinstance(value, dbus.DBusException):
 
546
                self.tomboy_connection_is_ok = False
 
547
                self.backend.quit(disable = True)
 
548
                BackendSignals().backend_failed(self.backend.get_id(), \
 
549
                            BackendSignals.ERRNO_DBUS)
 
550
            else:
 
551
                return False
 
552
            return True
 
553
 
 
554
 
 
555
 
 
556
    class DbusWatchdog(Watchdog):
 
557
        '''
 
558
        A simple watchdog to detect stale dbus connections
 
559
        '''
 
560
 
 
561
 
 
562
        def __init__(self, backend):
 
563
            '''
 
564
            Simple constructor, which sets _when_taking_too_long as the function
 
565
            to run when the connection is taking too long.
 
566
 
 
567
            @param backend: a Backend object
 
568
            '''
 
569
            self.backend = backend
 
570
            super(GenericTomboy.DbusWatchdog, self).__init__(3, \
 
571
                                    self._when_taking_too_long)
 
572
 
 
573
        def _when_taking_too_long(self):
 
574
            '''
 
575
            Function that is executed when the Dbus connection seems to be
 
576
            hanging. It disables the backend and signals the error to the user.
 
577
            '''
 
578
            Log.error("Dbus connection is taking too long for the Tomboy/Gnote"
 
579
                      "backend!")
 
580
            self.backend.quit(disable = True)
 
581
            BackendSignals().backend_failed(self.backend.get_id(), \
 
582
                            BackendSignals.ERRNO_DBUS)
 
583