GTG

Merge lp:~gtg-user/gtg/tomboy-backend into lp:~gtg/gtg/old-trunk

Proposed by Luca Invernizzi
Status: Superseded
Proposed branch: lp:~gtg-user/gtg/tomboy-backend
Merge into: lp:~gtg/gtg/old-trunk
Prerequisite: lp:~gtg-user/gtg/multibackends-halfgsoc_merge
Diff against target: 750 lines (+708/-1)
5 files modified
CHANGELOG (+1/-0)
GTG/backends/backend_gnote.py (+61/-0)
GTG/backends/backend_tomboy.py (+60/-0)
GTG/backends/generictomboy.py (+583/-0)
GTG/gtk/browser/browser.py (+3/-1)
To merge this branch: bzr merge lp:~gtg-user/gtg/tomboy-backend
Reviewer Review Type Date Requested Status
Lionel Dricot Pending
Gtg developers Pending
Review via email: mp+32641@code.launchpad.net

This proposal supersedes a proposal from 2010-06-23.

This proposal has been superseded by a proposal from 2010-08-13.

Description of the change

The tomboy backend. Any tomboy note matching a particular tag will be inserted (r/w) in GTG.
It handles gracefully the change of the "attached tags", that are the tags to which the backend is looking for. The r/w synchronization engine is included.

It's complemented with a series of testcases and exception handling (when tomboy is put under stress - ~500 notes on my laptop - it begins to drop connections on dbus).

This is just to show the code for a review. I'll need to review the docstrings and use this backend for a couple of weeks before considering it stable - it works pretty well so far-.

To post a comment you must log in.
Revision history for this message
Lionel Dricot (ploum-deactivatedaccount) wrote : Posted in a previous version of this proposal

Not touching anything in GTG so, obviously, you can merge it. Very good work !

review: Approve
lp:~gtg-user/gtg/tomboy-backend updated
882. By Luca Invernizzi

cherripicking files

883. By Luca Invernizzi

cherripicking files

884. By Luca Invernizzi

cherrypicking from my development branch

885. By Luca Invernizzi

merge w/ trunk

886. By Luca Invernizzi

small typo in docs:

887. By Luca Invernizzi

merge w/ trunk

888. By Luca Invernizzi

cherrypicking from my development branch

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'CHANGELOG'
--- CHANGELOG 2010-08-04 00:30:22 +0000
+++ CHANGELOG 2010-08-13 23:44:44 +0000
@@ -4,6 +4,7 @@
4 * Fixed bug with data consistency #579189, by Marko Kevac4 * Fixed bug with data consistency #579189, by Marko Kevac
5 * Added samba bugzilla to the bugzilla plugin, by Jelmer Vernoij5 * Added samba bugzilla to the bugzilla plugin, by Jelmer Vernoij
6 * Fixed bug #532392, a start date is later than a due date, by Volodymyr Floreskul6 * Fixed bug #532392, a start date is later than a due date, by Volodymyr Floreskul
7 * New Tomboy/Gnote backend, by Luca Invernizzi
78
82010-03-01 Getting Things GNOME! 0.2.292010-03-01 Getting Things GNOME! 0.2.2
9 * Autostart on login, by Luca Invernizzi10 * Autostart on login, by Luca Invernizzi
1011
=== added file 'GTG/backends/backend_gnote.py'
--- GTG/backends/backend_gnote.py 1970-01-01 00:00:00 +0000
+++ GTG/backends/backend_gnote.py 2010-08-13 23:44:44 +0000
@@ -0,0 +1,61 @@
1# -*- coding: utf-8 -*-
2# -----------------------------------------------------------------------------
3# Gettings 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'''
21The gnote backend. The actual backend is all in GenericTomboy, since it's
22shared with the tomboy backend.
23'''
24#To introspect tomboy: qdbus org.gnome.Tomboy /org/gnome/Tomboy/RemoteControl
25
26from GTG.backends.genericbackend import GenericBackend
27from GTG import _
28from GTG.backends.generictomboy import GenericTomboy
29
30
31
32class Backend(GenericTomboy):
33 '''
34 A simple class that adds some description to the GenericTomboy class.
35 It's done this way since Tomboy and Gnote backends have different
36 descriptions and Dbus addresses but the same backend behind them.
37 '''
38
39
40 _general_description = { \
41 GenericBackend.BACKEND_NAME: "backend_gnote", \
42 GenericBackend.BACKEND_HUMAN_NAME: _("Gnote"), \
43 GenericBackend.BACKEND_AUTHORS: ["Luca Invernizzi"], \
44 GenericBackend.BACKEND_TYPE: GenericBackend.TYPE_READWRITE, \
45 GenericBackend.BACKEND_DESCRIPTION: \
46 _("This backend can synchronize all or part of your Gnote"
47 " notes in GTG. If you decide it would be handy to"
48 " have one of your notes in your TODO list, just tag it "
49 "with the tag you have chosen (you'll configure it later"
50 "), and it will appear in GTG."),\
51 }
52
53 _static_parameters = { \
54 GenericBackend.KEY_ATTACHED_TAGS: {\
55 GenericBackend.PARAM_TYPE: GenericBackend.TYPE_LIST_OF_STRINGS, \
56 GenericBackend.PARAM_DEFAULT_VALUE: ["@GTG-Gnote"]}, \
57 }
58
59 _BUS_ADDRESS = ("org.gnome.Gnote",
60 "/org/gnome/Gnote/RemoteControl",
61 "org.gnome.Gnote.RemoteControl")
062
=== added file 'GTG/backends/backend_tomboy.py'
--- GTG/backends/backend_tomboy.py 1970-01-01 00:00:00 +0000
+++ GTG/backends/backend_tomboy.py 2010-08-13 23:44:44 +0000
@@ -0,0 +1,60 @@
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'''
21The tomboy backend. The actual backend is all in GenericTomboy, since it's
22shared with the Gnote backend.
23'''
24
25from GTG.backends.genericbackend import GenericBackend
26from GTG import _
27from GTG.backends.generictomboy import GenericTomboy
28
29
30
31class Backend(GenericTomboy):
32 '''
33 A simple class that adds some description to the GenericTomboy class.
34 It's done this way since Tomboy and Gnote backends have different
35 descriptions and Dbus addresses but the same backend behind them.
36 '''
37
38
39 _general_description = { \
40 GenericBackend.BACKEND_NAME: "backend_tomboy", \
41 GenericBackend.BACKEND_HUMAN_NAME: _("Tomboy"), \
42 GenericBackend.BACKEND_AUTHORS: ["Luca Invernizzi"], \
43 GenericBackend.BACKEND_TYPE: GenericBackend.TYPE_READWRITE, \
44 GenericBackend.BACKEND_DESCRIPTION: \
45 _("This backend can synchronize all or part of your Tomboy"
46 " notes in GTG. If you decide it would be handy to"
47 " have one of your notes in your TODO list, just tag it "
48 "with the tag you have chosen (you'll configure it later"
49 "), and it will appear in GTG."),\
50 }
51
52 _static_parameters = { \
53 GenericBackend.KEY_ATTACHED_TAGS: {\
54 GenericBackend.PARAM_TYPE: GenericBackend.TYPE_LIST_OF_STRINGS, \
55 GenericBackend.PARAM_DEFAULT_VALUE: ["@GTG-Tomboy"]}, \
56 }
57
58 _BUS_ADDRESS = ("org.gnome.Tomboy",
59 "/org/gnome/Tomboy/RemoteControl",
60 "org.gnome.Tomboy.RemoteControl")
061
=== added file 'GTG/backends/generictomboy.py'
--- GTG/backends/generictomboy.py 1970-01-01 00:00:00 +0000
+++ GTG/backends/generictomboy.py 2010-08-13 23:44:44 +0000
@@ -0,0 +1,583 @@
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'''
21Contains 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
26import os
27import re
28import threading
29import uuid
30import dbus
31import datetime
32
33from GTG.tools.testingmode import TestingMode
34from GTG.tools.borg import Borg
35from GTG.backends.genericbackend import GenericBackend
36from GTG.backends.backendsignals import BackendSignals
37from GTG.backends.syncengine import SyncEngine, SyncMeme
38from GTG.tools.logger import Log
39from GTG.tools.watchdog import Watchdog
40from GTG.tools.interruptible import interruptible
41
42
43
44class GenericTomboy(GenericBackend):
45 '''Backend class for Tomboy/Gnote'''
46
47
48###############################################################################
49### Backend standard methods ##################################################
50###############################################################################
51
52 def __init__(self, parameters):
53 """
54 See GenericBackend for an explanation of this function.
55 """
56 super(GenericTomboy, self).__init__(parameters)
57 #loading the saved state of the synchronization, if any
58 self.data_path = os.path.join('backends/tomboy/', \
59 "sync_engine-" + self.get_id())
60 self.sync_engine = self._load_pickled_file(self.data_path, \
61 SyncEngine())
62 #if the backend is being tested, we connect to a different DBus
63 # interface to avoid clashing with a running instance of Tomboy
64 if TestingMode().get_testing_mode():
65 #just used for testing purposes
66 self.BUS_ADDRESS = \
67 self._parameters["use this fake connection instead"]
68 else:
69 self.BUS_ADDRESS = self._BUS_ADDRESS
70 #we let some time pass before considering a tomboy task for importing,
71 # as the user may still be editing it. Here, we store the Timer objects
72 # that will execute after some time after each tomboy signal.
73 #NOTE: I'm not sure if this is the case anymore (but it shouldn't hurt
74 # anyway). (invernizzi)
75 self._tomboy_setting_timers = {}
76
77 def initialize(self):
78 '''
79 See GenericBackend for an explanation of this function.
80 Connects to the session bus and sets the callbacks for bus signals
81 '''
82 super(GenericTomboy, self).initialize()
83 with self.DbusWatchdog(self):
84 bus = dbus.SessionBus()
85 bus.add_signal_receiver(self.on_note_saved,
86 dbus_interface = self.BUS_ADDRESS[2],
87 signal_name = "NoteSaved")
88 bus.add_signal_receiver(self.on_note_deleted,
89 dbus_interface = self.BUS_ADDRESS[2],
90 signal_name = "NoteDeleted")
91
92 @interruptible
93 def start_get_tasks(self):
94 '''
95 See GenericBackend for an explanation of this function.
96 Gets all the notes from Tomboy and sees if they must be added in GTG
97 (and, if so, it adds them).
98 '''
99 with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
100 with self.DbusWatchdog(self):
101 tomboy_notes = [str(note_id) for note_id in \
102 tomboy.ListAllNotes()]
103 #adding the new ones
104 for note in tomboy_notes:
105 self.cancellation_point()
106 self._process_tomboy_note(note)
107 #checking if some notes have been deleted while GTG was not running
108 stored_notes_ids = self.sync_engine.get_all_remote()
109 for note in set(stored_notes_ids).difference(set(tomboy_notes)):
110 self.on_note_deleted(note, None)
111
112 def save_state(self):
113 '''Saves the state of the synchronization'''
114 self._store_pickled_file(self.data_path, self.sync_engine)
115
116 def quit(self, disable = False):
117 '''
118 See GenericBackend for an explanation of this function.
119 '''
120 def quit_thread():
121 while True:
122 try:
123 [key, timer] = \
124 self._tomboy_setting_timers.iteritems().next()
125 except StopIteration:
126 break
127 timer.cancel()
128 del self._tomboy_setting_timers[key]
129 threading.Thread(target = quit_thread).start()
130 super(GenericTomboy, self).quit(disable)
131
132###############################################################################
133### Something got removed #####################################################
134###############################################################################
135
136 @interruptible
137 def on_note_deleted(self, note, something):
138 '''
139 Callback, executed when a tomboy note is deleted.
140 Deletes the related GTG task.
141
142 @param note: the id of the Tomboy note
143 @param something: not used, here for signal callback compatibility
144 '''
145 note = str(note)
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 = str(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 note = str(note)
326 self.cancellation_point()
327 #NOTE: we let some seconds pass before executing the real callback, as
328 # the editing of the Tomboy note may still be in progress
329 @interruptible
330 def _execute_on_note_saved(self, note):
331 self.cancellation_point()
332 try:
333 del self._tomboy_setting_timers[note]
334 except:
335 pass
336 self._process_tomboy_note(note)
337 self.save_state()
338
339 try:
340 self._tomboy_setting_timers[note].cancel()
341 except KeyError:
342 pass
343 finally:
344 timer =threading.Timer(5, _execute_on_note_saved,
345 args = (self, note))
346 self._tomboy_setting_timers[note] = timer
347 timer.start()
348
349 def _tomboy_note_is_syncable(self, note):
350 '''
351 Returns True if this tomboy note should be synced into GTG tasks.
352
353 @param note: the note id
354 @returns Boolean
355 '''
356 attached_tags = self.get_attached_tags()
357 if GenericBackend.ALLTASKS_TAG in attached_tags:
358 return True
359 with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
360 with self.DbusWatchdog(self):
361 content = tomboy.GetNoteContents(note)
362 syncable = False
363 for tag in attached_tags:
364 try:
365 content.index(tag)
366 syncable = True
367 break
368 except ValueError:
369 pass
370 return syncable
371
372 def _tomboy_note_exists(self, note):
373 '''
374 Returns True if a tomboy note exists with the given id.
375
376 @param note: the note id
377 @returns Boolean
378 '''
379 with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
380 with self.DbusWatchdog(self):
381 return tomboy.NoteExists(note)
382
383 def get_modified_for_note(self, note):
384 '''
385 Returns the modification time for the given note id.
386
387 @param note: the note id
388 @returns datetime.datetime
389 '''
390 with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
391 with self.DbusWatchdog(self):
392 return datetime.datetime.fromtimestamp( \
393 tomboy.GetNoteChangeDate(note))
394
395 def _tomboy_split_title_and_text(self, content):
396 '''
397 Tomboy does not have a "getTitle" and "getText" functions to get the
398 title and the text of a note separately. Instead, it has a getContent
399 function, that returns both of them.
400 This function splits up the output of getContent into a title string and
401 a text string.
402
403 @param content: a string, the result of a getContent call
404 @returns list: a list composed by [title, text]
405 '''
406 try:
407 end_of_title = content.index('\n')
408 except ValueError:
409 return content, ""
410 title = content[: end_of_title]
411 if len(content) > end_of_title:
412 return title, content[end_of_title +1 :]
413 else:
414 return title, ""
415
416 def _populate_task(self, task, note):
417 '''
418 Copies the content of a Tomboy note into a task.
419
420 @param task: a GTG Task
421 @param note: a Tomboy note
422 '''
423 #add tags objects (it's not enough to have @tag in the text to add a
424 # tag
425 with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
426 with self.DbusWatchdog(self):
427 content = tomboy.GetNoteContents(note)
428 #update the tags list
429 matches = re.finditer("(?<![^|\s])(@\w+)", content)
430 new_tags_list = [content[g.start() : g.end()] for g in matches]
431 for tag in task.get_tags_name():
432 try:
433 new_tags_list.remove(tag)
434 task.remove_tag(tag)
435 except:
436 task.add_tag(tag)
437 for tag in new_tags_list:
438 task.add_tag(tag)
439 #extract title and text
440 [title, text] = self._tomboy_split_title_and_text(content)
441 task.set_title(title)
442 task.set_text(text)
443 task.add_remote_id(self.get_id(), note)
444
445 def _populate_note(self, note, task):
446 '''
447 Copies the content of a task into a Tomboy note.
448
449 @param note: a Tomboy note
450 @param task: a GTG Task
451 '''
452 title = task.get_title()
453 tested_title = title
454 duplicate_counter = 1
455 with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
456 with self.DbusWatchdog(self):
457 tomboy.SetNoteContents(note, title + '\n' + \
458 task.get_excerpt(strip_tags = False))
459
460 def break_relationship(self, *args, **kwargs):
461 '''
462 Proxy method for SyncEngine.break_relationship, which also saves the
463 state of the synchronization.
464 '''
465 try:
466 self.sync_engine.break_relationship(*args, **kwargs)
467 #we try to save the state at each change in the sync_engine:
468 #it's slower, but it should avoid widespread task
469 #duplication
470 self.save_state()
471 except KeyError:
472 pass
473
474 def record_relationship(self, *args, **kwargs):
475 '''
476 Proxy method for SyncEngine.break_relationship, which also saves the
477 state of the synchronization.
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
0584
=== modified file 'GTG/gtk/browser/browser.py'
--- GTG/gtk/browser/browser.py 2010-08-10 17:30:24 +0000
+++ GTG/gtk/browser/browser.py 2010-08-13 23:44:44 +0000
@@ -953,7 +953,9 @@
953 text = \953 text = \
954 text.replace("%s%s:%s" % (spaces, attribute, args), "")954 text.replace("%s%s:%s" % (spaces, attribute, args), "")
955 # Create the new task955 # Create the new task
956 task = self.req.new_task(tags=[t.get_name() for t in tags], newtask=True)956 task = self.req.new_task( newtask=True)
957 for tag in tags:
958 task.add_tag(tag.get_name())
957 if text != "":959 if text != "":
958 task.set_title(text.strip())960 task.set_title(text.strip())
959 task.set_to_keep()961 task.set_to_keep()
960962
=== added file 'data/icons/hicolor/scalable/apps/backend_gnote.png'
961Binary files data/icons/hicolor/scalable/apps/backend_gnote.png 1970-01-01 00:00:00 +0000 and data/icons/hicolor/scalable/apps/backend_gnote.png 2010-08-13 23:44:44 +0000 differ963Binary files data/icons/hicolor/scalable/apps/backend_gnote.png 1970-01-01 00:00:00 +0000 and data/icons/hicolor/scalable/apps/backend_gnote.png 2010-08-13 23:44:44 +0000 differ
=== added file 'data/icons/hicolor/scalable/apps/backend_tomboy.png'
962Binary files data/icons/hicolor/scalable/apps/backend_tomboy.png 1970-01-01 00:00:00 +0000 and data/icons/hicolor/scalable/apps/backend_tomboy.png 2010-08-13 23:44:44 +0000 differ964Binary files data/icons/hicolor/scalable/apps/backend_tomboy.png 1970-01-01 00:00:00 +0000 and data/icons/hicolor/scalable/apps/backend_tomboy.png 2010-08-13 23:44:44 +0000 differ

Subscribers

People subscribed via source and target branches

to status/vote changes: