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
1=== modified file 'CHANGELOG'
2--- CHANGELOG 2010-08-04 00:30:22 +0000
3+++ CHANGELOG 2010-08-13 23:44:44 +0000
4@@ -4,6 +4,7 @@
5 * Fixed bug with data consistency #579189, by Marko Kevac
6 * Added samba bugzilla to the bugzilla plugin, by Jelmer Vernoij
7 * Fixed bug #532392, a start date is later than a due date, by Volodymyr Floreskul
8+ * New Tomboy/Gnote backend, by Luca Invernizzi
9
10 2010-03-01 Getting Things GNOME! 0.2.2
11 * Autostart on login, by Luca Invernizzi
12
13=== added file 'GTG/backends/backend_gnote.py'
14--- GTG/backends/backend_gnote.py 1970-01-01 00:00:00 +0000
15+++ GTG/backends/backend_gnote.py 2010-08-13 23:44:44 +0000
16@@ -0,0 +1,61 @@
17+# -*- coding: utf-8 -*-
18+# -----------------------------------------------------------------------------
19+# Gettings Things Gnome! - a personal organizer for the GNOME desktop
20+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
21+#
22+# This program is free software: you can redistribute it and/or modify it under
23+# the terms of the GNU General Public License as published by the Free Software
24+# Foundation, either version 3 of the License, or (at your option) any later
25+# version.
26+#
27+# This program is distributed in the hope that it will be useful, but WITHOUT
28+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
29+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
30+# details.
31+#
32+# You should have received a copy of the GNU General Public License along with
33+# this program. If not, see <http://www.gnu.org/licenses/>.
34+# -----------------------------------------------------------------------------
35+
36+'''
37+The gnote backend. The actual backend is all in GenericTomboy, since it's
38+shared with the tomboy backend.
39+'''
40+#To introspect tomboy: qdbus org.gnome.Tomboy /org/gnome/Tomboy/RemoteControl
41+
42+from GTG.backends.genericbackend import GenericBackend
43+from GTG import _
44+from GTG.backends.generictomboy import GenericTomboy
45+
46+
47+
48+class Backend(GenericTomboy):
49+ '''
50+ A simple class that adds some description to the GenericTomboy class.
51+ It's done this way since Tomboy and Gnote backends have different
52+ descriptions and Dbus addresses but the same backend behind them.
53+ '''
54+
55+
56+ _general_description = { \
57+ GenericBackend.BACKEND_NAME: "backend_gnote", \
58+ GenericBackend.BACKEND_HUMAN_NAME: _("Gnote"), \
59+ GenericBackend.BACKEND_AUTHORS: ["Luca Invernizzi"], \
60+ GenericBackend.BACKEND_TYPE: GenericBackend.TYPE_READWRITE, \
61+ GenericBackend.BACKEND_DESCRIPTION: \
62+ _("This backend can synchronize all or part of your Gnote"
63+ " notes in GTG. If you decide it would be handy to"
64+ " have one of your notes in your TODO list, just tag it "
65+ "with the tag you have chosen (you'll configure it later"
66+ "), and it will appear in GTG."),\
67+ }
68+
69+ _static_parameters = { \
70+ GenericBackend.KEY_ATTACHED_TAGS: {\
71+ GenericBackend.PARAM_TYPE: GenericBackend.TYPE_LIST_OF_STRINGS, \
72+ GenericBackend.PARAM_DEFAULT_VALUE: ["@GTG-Gnote"]}, \
73+ }
74+
75+ _BUS_ADDRESS = ("org.gnome.Gnote",
76+ "/org/gnome/Gnote/RemoteControl",
77+ "org.gnome.Gnote.RemoteControl")
78
79=== added file 'GTG/backends/backend_tomboy.py'
80--- GTG/backends/backend_tomboy.py 1970-01-01 00:00:00 +0000
81+++ GTG/backends/backend_tomboy.py 2010-08-13 23:44:44 +0000
82@@ -0,0 +1,60 @@
83+# -*- coding: utf-8 -*-
84+# -----------------------------------------------------------------------------
85+# Getting Things Gnome! - a personal organizer for the GNOME desktop
86+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
87+#
88+# This program is free software: you can redistribute it and/or modify it under
89+# the terms of the GNU General Public License as published by the Free Software
90+# Foundation, either version 3 of the License, or (at your option) any later
91+# version.
92+#
93+# This program is distributed in the hope that it will be useful, but WITHOUT
94+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
95+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
96+# details.
97+#
98+# You should have received a copy of the GNU General Public License along with
99+# this program. If not, see <http://www.gnu.org/licenses/>.
100+# -----------------------------------------------------------------------------
101+
102+'''
103+The tomboy backend. The actual backend is all in GenericTomboy, since it's
104+shared with the Gnote backend.
105+'''
106+
107+from GTG.backends.genericbackend import GenericBackend
108+from GTG import _
109+from GTG.backends.generictomboy import GenericTomboy
110+
111+
112+
113+class Backend(GenericTomboy):
114+ '''
115+ A simple class that adds some description to the GenericTomboy class.
116+ It's done this way since Tomboy and Gnote backends have different
117+ descriptions and Dbus addresses but the same backend behind them.
118+ '''
119+
120+
121+ _general_description = { \
122+ GenericBackend.BACKEND_NAME: "backend_tomboy", \
123+ GenericBackend.BACKEND_HUMAN_NAME: _("Tomboy"), \
124+ GenericBackend.BACKEND_AUTHORS: ["Luca Invernizzi"], \
125+ GenericBackend.BACKEND_TYPE: GenericBackend.TYPE_READWRITE, \
126+ GenericBackend.BACKEND_DESCRIPTION: \
127+ _("This backend can synchronize all or part of your Tomboy"
128+ " notes in GTG. If you decide it would be handy to"
129+ " have one of your notes in your TODO list, just tag it "
130+ "with the tag you have chosen (you'll configure it later"
131+ "), and it will appear in GTG."),\
132+ }
133+
134+ _static_parameters = { \
135+ GenericBackend.KEY_ATTACHED_TAGS: {\
136+ GenericBackend.PARAM_TYPE: GenericBackend.TYPE_LIST_OF_STRINGS, \
137+ GenericBackend.PARAM_DEFAULT_VALUE: ["@GTG-Tomboy"]}, \
138+ }
139+
140+ _BUS_ADDRESS = ("org.gnome.Tomboy",
141+ "/org/gnome/Tomboy/RemoteControl",
142+ "org.gnome.Tomboy.RemoteControl")
143
144=== added file 'GTG/backends/generictomboy.py'
145--- GTG/backends/generictomboy.py 1970-01-01 00:00:00 +0000
146+++ GTG/backends/generictomboy.py 2010-08-13 23:44:44 +0000
147@@ -0,0 +1,583 @@
148+# -*- coding: utf-8 -*-
149+# -----------------------------------------------------------------------------
150+# Getting Things Gnome! - a personal organizer for the GNOME desktop
151+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
152+#
153+# This program is free software: you can redistribute it and/or modify it under
154+# the terms of the GNU General Public License as published by the Free Software
155+# Foundation, either version 3 of the License, or (at your option) any later
156+# version.
157+#
158+# This program is distributed in the hope that it will be useful, but WITHOUT
159+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
160+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
161+# details.
162+#
163+# You should have received a copy of the GNU General Public License along with
164+# this program. If not, see <http://www.gnu.org/licenses/>.
165+# -----------------------------------------------------------------------------
166+
167+'''
168+Contains the Backend class for both Tomboy and Gnote
169+'''
170+#Note: To introspect tomboy, execute:
171+# qdbus org.gnome.Tomboy /org/gnome/Tomboy/RemoteControl
172+
173+import os
174+import re
175+import threading
176+import uuid
177+import dbus
178+import datetime
179+
180+from GTG.tools.testingmode import TestingMode
181+from GTG.tools.borg import Borg
182+from GTG.backends.genericbackend import GenericBackend
183+from GTG.backends.backendsignals import BackendSignals
184+from GTG.backends.syncengine import SyncEngine, SyncMeme
185+from GTG.tools.logger import Log
186+from GTG.tools.watchdog import Watchdog
187+from GTG.tools.interruptible import interruptible
188+
189+
190+
191+class GenericTomboy(GenericBackend):
192+ '''Backend class for Tomboy/Gnote'''
193+
194+
195+###############################################################################
196+### Backend standard methods ##################################################
197+###############################################################################
198+
199+ def __init__(self, parameters):
200+ """
201+ See GenericBackend for an explanation of this function.
202+ """
203+ super(GenericTomboy, self).__init__(parameters)
204+ #loading the saved state of the synchronization, if any
205+ self.data_path = os.path.join('backends/tomboy/', \
206+ "sync_engine-" + self.get_id())
207+ self.sync_engine = self._load_pickled_file(self.data_path, \
208+ SyncEngine())
209+ #if the backend is being tested, we connect to a different DBus
210+ # interface to avoid clashing with a running instance of Tomboy
211+ if TestingMode().get_testing_mode():
212+ #just used for testing purposes
213+ self.BUS_ADDRESS = \
214+ self._parameters["use this fake connection instead"]
215+ else:
216+ self.BUS_ADDRESS = self._BUS_ADDRESS
217+ #we let some time pass before considering a tomboy task for importing,
218+ # as the user may still be editing it. Here, we store the Timer objects
219+ # that will execute after some time after each tomboy signal.
220+ #NOTE: I'm not sure if this is the case anymore (but it shouldn't hurt
221+ # anyway). (invernizzi)
222+ self._tomboy_setting_timers = {}
223+
224+ def initialize(self):
225+ '''
226+ See GenericBackend for an explanation of this function.
227+ Connects to the session bus and sets the callbacks for bus signals
228+ '''
229+ super(GenericTomboy, self).initialize()
230+ with self.DbusWatchdog(self):
231+ bus = dbus.SessionBus()
232+ bus.add_signal_receiver(self.on_note_saved,
233+ dbus_interface = self.BUS_ADDRESS[2],
234+ signal_name = "NoteSaved")
235+ bus.add_signal_receiver(self.on_note_deleted,
236+ dbus_interface = self.BUS_ADDRESS[2],
237+ signal_name = "NoteDeleted")
238+
239+ @interruptible
240+ def start_get_tasks(self):
241+ '''
242+ See GenericBackend for an explanation of this function.
243+ Gets all the notes from Tomboy and sees if they must be added in GTG
244+ (and, if so, it adds them).
245+ '''
246+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
247+ with self.DbusWatchdog(self):
248+ tomboy_notes = [str(note_id) for note_id in \
249+ tomboy.ListAllNotes()]
250+ #adding the new ones
251+ for note in tomboy_notes:
252+ self.cancellation_point()
253+ self._process_tomboy_note(note)
254+ #checking if some notes have been deleted while GTG was not running
255+ stored_notes_ids = self.sync_engine.get_all_remote()
256+ for note in set(stored_notes_ids).difference(set(tomboy_notes)):
257+ self.on_note_deleted(note, None)
258+
259+ def save_state(self):
260+ '''Saves the state of the synchronization'''
261+ self._store_pickled_file(self.data_path, self.sync_engine)
262+
263+ def quit(self, disable = False):
264+ '''
265+ See GenericBackend for an explanation of this function.
266+ '''
267+ def quit_thread():
268+ while True:
269+ try:
270+ [key, timer] = \
271+ self._tomboy_setting_timers.iteritems().next()
272+ except StopIteration:
273+ break
274+ timer.cancel()
275+ del self._tomboy_setting_timers[key]
276+ threading.Thread(target = quit_thread).start()
277+ super(GenericTomboy, self).quit(disable)
278+
279+###############################################################################
280+### Something got removed #####################################################
281+###############################################################################
282+
283+ @interruptible
284+ def on_note_deleted(self, note, something):
285+ '''
286+ Callback, executed when a tomboy note is deleted.
287+ Deletes the related GTG task.
288+
289+ @param note: the id of the Tomboy note
290+ @param something: not used, here for signal callback compatibility
291+ '''
292+ note = str(note)
293+ with self.datastore.get_backend_mutex():
294+ self.cancellation_point()
295+ try:
296+ tid = self.sync_engine.get_local_id(note)
297+ except KeyError:
298+ return
299+ if self.datastore.has_task(tid):
300+ self.datastore.request_task_deletion(tid)
301+ self.break_relationship(remote_id = note)
302+
303+ @interruptible
304+ def remove_task(self, tid):
305+ '''
306+ See GenericBackend for an explanation of this function.
307+ '''
308+ with self.datastore.get_backend_mutex():
309+ self.cancellation_point()
310+ try:
311+ note = self.sync_engine.get_remote_id(tid)
312+ except KeyError:
313+ return
314+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
315+ with self.DbusWatchdog(self):
316+ if tomboy.NoteExists(note):
317+ tomboy.DeleteNote(note)
318+ self.break_relationship(local_id = tid)
319+
320+ def _exec_lost_syncability(self, tid, note):
321+ '''
322+ Executed when a relationship between tasks loses its syncability
323+ property. See SyncEngine for an explanation of that.
324+ This function finds out which object (task/note) is the original one
325+ and which is the copy, and deletes the copy.
326+
327+ @param tid: a GTG task tid
328+ #param note: a tomboy note id
329+ '''
330+ self.cancellation_point()
331+ meme = self.sync_engine.get_meme_from_remote_id(note)
332+ #First of all, the relationship is lost
333+ self.sync_engine.break_relationship(remote_id = note)
334+ if meme.get_origin() == "GTG":
335+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
336+ with self.DbusWatchdog(self):
337+ tomboy.DeleteNote(note)
338+ else:
339+ self.datastore.request_task_deletion(tid)
340+
341+###############################################################################
342+### Process tasks #############################################################
343+###############################################################################
344+
345+ def _process_tomboy_note(self, note):
346+ '''
347+ Given a tomboy note, finds out if it must be synced to a GTG note and,
348+ if so, it carries out the synchronization (by creating or updating a GTG
349+ task, or deleting itself if the related task has been deleted)
350+
351+ @param note: a Tomboy note id
352+ '''
353+ with self.datastore.get_backend_mutex():
354+ self.cancellation_point()
355+ is_syncable = self._tomboy_note_is_syncable(note)
356+ with self.DbusWatchdog(self):
357+ action, tid = self.sync_engine.analyze_remote_id(note, \
358+ self.datastore.has_task, \
359+ self._tomboy_note_exists, is_syncable)
360+ Log.debug("processing tomboy (%s, %s)" % (action, is_syncable))
361+
362+ if action == SyncEngine.ADD:
363+ tid = str(uuid.uuid4())
364+ task = self.datastore.task_factory(tid)
365+ self._populate_task(task, note)
366+ self.record_relationship(local_id = tid,\
367+ remote_id = note, \
368+ meme = SyncMeme(task.get_modified(),
369+ self.get_modified_for_note(note),
370+ self.get_id()))
371+ self.datastore.push_task(task)
372+
373+ elif action == SyncEngine.UPDATE:
374+ task = self.datastore.get_task(tid)
375+ meme = self.sync_engine.get_meme_from_remote_id(note)
376+ newest = meme.which_is_newest(task.get_modified(),
377+ self.get_modified_for_note(note))
378+ if newest == "remote":
379+ self._populate_task(task, note)
380+ meme.set_local_last_modified(task.get_modified())
381+ meme.set_remote_last_modified(\
382+ self.get_modified_for_note(note))
383+ self.save_state()
384+
385+ elif action == SyncEngine.REMOVE:
386+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
387+ with self.DbusWatchdog(self):
388+ tomboy.DeleteNote(note)
389+ try:
390+ self.sync_engine.break_relationship(remote_id = note)
391+ except KeyError:
392+ pass
393+
394+ elif action == SyncEngine.LOST_SYNCABILITY:
395+ self._exec_lost_syncability(tid, note)
396+
397+ @interruptible
398+ def set_task(self, task):
399+ '''
400+ See GenericBackend for an explanation of this function.
401+ '''
402+ self.cancellation_point()
403+ is_syncable = self._gtg_task_is_syncable_per_attached_tags(task)
404+ tid = task.get_id()
405+ with self.datastore.get_backend_mutex():
406+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
407+ with self.DbusWatchdog(self):
408+ action, note = self.sync_engine.analyze_local_id(tid, \
409+ self.datastore.has_task, tomboy.NoteExists, \
410+ is_syncable)
411+ Log.debug("processing gtg (%s, %d)" % (action, is_syncable))
412+
413+ if action == SyncEngine.ADD:
414+ #GTG allows multiple tasks with the same name,
415+ #Tomboy doesn't. we need to handle the renaming
416+ #manually
417+ title = task.get_title()
418+ duplicate_counter = 1
419+ with self.DbusWatchdog(self):
420+ note = str(tomboy.CreateNamedNote(title))
421+ while note == "":
422+ duplicate_counter += 1
423+ note = tomboy.CreateNamedNote(title + "(%d)" %
424+ duplicate_counter)
425+ if duplicate_counter != 1:
426+ #if we needed to rename, we have to rename also
427+ # the gtg task
428+ task.set_title(title + " (%d)" % duplicate_counter)
429+
430+ self._populate_note(note, task)
431+ self.record_relationship( \
432+ local_id = tid, remote_id = note, \
433+ meme = SyncMeme(task.get_modified(),
434+ self.get_modified_for_note(note),
435+ "GTG"))
436+
437+ elif action == SyncEngine.UPDATE:
438+ meme = self.sync_engine.get_meme_from_local_id(\
439+ task.get_id())
440+ newest = meme.which_is_newest(task.get_modified(),
441+ self.get_modified_for_note(note))
442+ if newest == "local":
443+ self._populate_note(note, task)
444+ meme.set_local_last_modified(task.get_modified())
445+ meme.set_remote_last_modified(\
446+ self.get_modified_for_note(note))
447+ self.save_state()
448+
449+ elif action == SyncEngine.REMOVE:
450+ self.datastore.request_task_deletion(tid)
451+ try:
452+ self.sync_engine.break_relationship(local_id = tid)
453+ self.save_state()
454+ except KeyError:
455+ pass
456+
457+ elif action == SyncEngine.LOST_SYNCABILITY:
458+ self._exec_lost_syncability(tid, note)
459+
460+###############################################################################
461+### Helper methods ############################################################
462+###############################################################################
463+
464+ @interruptible
465+ def on_note_saved(self, note):
466+ '''
467+ Callback, executed when a tomboy note is saved by Tomboy itself.
468+ Updates the related GTG task (or creates one, if necessary).
469+
470+ @param note: the id of the Tomboy note
471+ '''
472+ note = str(note)
473+ self.cancellation_point()
474+ #NOTE: we let some seconds pass before executing the real callback, as
475+ # the editing of the Tomboy note may still be in progress
476+ @interruptible
477+ def _execute_on_note_saved(self, note):
478+ self.cancellation_point()
479+ try:
480+ del self._tomboy_setting_timers[note]
481+ except:
482+ pass
483+ self._process_tomboy_note(note)
484+ self.save_state()
485+
486+ try:
487+ self._tomboy_setting_timers[note].cancel()
488+ except KeyError:
489+ pass
490+ finally:
491+ timer =threading.Timer(5, _execute_on_note_saved,
492+ args = (self, note))
493+ self._tomboy_setting_timers[note] = timer
494+ timer.start()
495+
496+ def _tomboy_note_is_syncable(self, note):
497+ '''
498+ Returns True if this tomboy note should be synced into GTG tasks.
499+
500+ @param note: the note id
501+ @returns Boolean
502+ '''
503+ attached_tags = self.get_attached_tags()
504+ if GenericBackend.ALLTASKS_TAG in attached_tags:
505+ return True
506+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
507+ with self.DbusWatchdog(self):
508+ content = tomboy.GetNoteContents(note)
509+ syncable = False
510+ for tag in attached_tags:
511+ try:
512+ content.index(tag)
513+ syncable = True
514+ break
515+ except ValueError:
516+ pass
517+ return syncable
518+
519+ def _tomboy_note_exists(self, note):
520+ '''
521+ Returns True if a tomboy note exists with the given id.
522+
523+ @param note: the note id
524+ @returns Boolean
525+ '''
526+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
527+ with self.DbusWatchdog(self):
528+ return tomboy.NoteExists(note)
529+
530+ def get_modified_for_note(self, note):
531+ '''
532+ Returns the modification time for the given note id.
533+
534+ @param note: the note id
535+ @returns datetime.datetime
536+ '''
537+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
538+ with self.DbusWatchdog(self):
539+ return datetime.datetime.fromtimestamp( \
540+ tomboy.GetNoteChangeDate(note))
541+
542+ def _tomboy_split_title_and_text(self, content):
543+ '''
544+ Tomboy does not have a "getTitle" and "getText" functions to get the
545+ title and the text of a note separately. Instead, it has a getContent
546+ function, that returns both of them.
547+ This function splits up the output of getContent into a title string and
548+ a text string.
549+
550+ @param content: a string, the result of a getContent call
551+ @returns list: a list composed by [title, text]
552+ '''
553+ try:
554+ end_of_title = content.index('\n')
555+ except ValueError:
556+ return content, ""
557+ title = content[: end_of_title]
558+ if len(content) > end_of_title:
559+ return title, content[end_of_title +1 :]
560+ else:
561+ return title, ""
562+
563+ def _populate_task(self, task, note):
564+ '''
565+ Copies the content of a Tomboy note into a task.
566+
567+ @param task: a GTG Task
568+ @param note: a Tomboy note
569+ '''
570+ #add tags objects (it's not enough to have @tag in the text to add a
571+ # tag
572+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
573+ with self.DbusWatchdog(self):
574+ content = tomboy.GetNoteContents(note)
575+ #update the tags list
576+ matches = re.finditer("(?<![^|\s])(@\w+)", content)
577+ new_tags_list = [content[g.start() : g.end()] for g in matches]
578+ for tag in task.get_tags_name():
579+ try:
580+ new_tags_list.remove(tag)
581+ task.remove_tag(tag)
582+ except:
583+ task.add_tag(tag)
584+ for tag in new_tags_list:
585+ task.add_tag(tag)
586+ #extract title and text
587+ [title, text] = self._tomboy_split_title_and_text(content)
588+ task.set_title(title)
589+ task.set_text(text)
590+ task.add_remote_id(self.get_id(), note)
591+
592+ def _populate_note(self, note, task):
593+ '''
594+ Copies the content of a task into a Tomboy note.
595+
596+ @param note: a Tomboy note
597+ @param task: a GTG Task
598+ '''
599+ title = task.get_title()
600+ tested_title = title
601+ duplicate_counter = 1
602+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
603+ with self.DbusWatchdog(self):
604+ tomboy.SetNoteContents(note, title + '\n' + \
605+ task.get_excerpt(strip_tags = False))
606+
607+ def break_relationship(self, *args, **kwargs):
608+ '''
609+ Proxy method for SyncEngine.break_relationship, which also saves the
610+ state of the synchronization.
611+ '''
612+ try:
613+ self.sync_engine.break_relationship(*args, **kwargs)
614+ #we try to save the state at each change in the sync_engine:
615+ #it's slower, but it should avoid widespread task
616+ #duplication
617+ self.save_state()
618+ except KeyError:
619+ pass
620+
621+ def record_relationship(self, *args, **kwargs):
622+ '''
623+ Proxy method for SyncEngine.break_relationship, which also saves the
624+ state of the synchronization.
625+ '''
626+ self.sync_engine.record_relationship(*args, **kwargs)
627+ #we try to save the state at each change in the sync_engine:
628+ #it's slower, but it should avoid widespread task
629+ #duplication
630+ self.save_state()
631+
632+###############################################################################
633+### Connection handling #######################################################
634+###############################################################################
635+
636+
637+
638+ class TomboyConnection(Borg):
639+ '''
640+ TomboyConnection creates a connection to TOMBOY via DBUS and
641+ handles all the possible exceptions.
642+ It is a class that can be used with a with statement.
643+ Example:
644+ with self.TomboyConnection(self, *self.BUS_ADDRESS) as tomboy:
645+ #do something
646+ '''
647+
648+
649+ def __init__(self, backend, bus_name, bus_path, bus_interface):
650+ '''
651+ Sees if a TomboyConnection object already exists. If so, since we
652+ are inheriting from a Borg object, the initialization already took
653+ place.
654+ If not, it tries to connect to Tomboy via Dbus. If the connection
655+ is not possible, the user is notified about it.
656+
657+ @param backend: a reference to a Backend
658+ @param bus_name: the DBUS address of Tomboy
659+ @param bus_path: the DBUS path of Tomboy RemoteControl
660+ @param bus_interface: the DBUS address of Tomboy RemoteControl
661+ '''
662+ super(GenericTomboy.TomboyConnection, self).__init__()
663+ if hasattr(self, "tomboy_connection_is_ok") and \
664+ self.tomboy_connection_is_ok:
665+ return
666+ self.backend = backend
667+ with GenericTomboy.DbusWatchdog(backend):
668+ bus = dbus.SessionBus()
669+ obj = bus.get_object(bus_name, bus_path)
670+ self.tomboy = dbus.Interface(obj, bus_interface)
671+ self.tomboy_connection_is_ok = True
672+
673+ def __enter__(self):
674+ '''
675+ Returns the Tomboy connection
676+
677+ @returns dbus.Interface
678+ '''
679+ return self.tomboy
680+
681+ def __exit__(self, exception_type, value, traceback):
682+ '''
683+ Checks the state of the connection.
684+ If something went wrong for the connection, notifies the user.
685+
686+ @param exception_type: the type of exception that occurred, or
687+ None
688+ @param value: the instance of the exception occurred, or None
689+ @param traceback: the traceback of the error
690+ @returns: False if some exception must be re-raised.
691+ '''
692+ if isinstance(value, dbus.DBusException):
693+ self.tomboy_connection_is_ok = False
694+ self.backend.quit(disable = True)
695+ BackendSignals().backend_failed(self.backend.get_id(), \
696+ BackendSignals.ERRNO_DBUS)
697+ else:
698+ return False
699+ return True
700+
701+
702+
703+ class DbusWatchdog(Watchdog):
704+ '''
705+ A simple watchdog to detect stale dbus connections
706+ '''
707+
708+
709+ def __init__(self, backend):
710+ '''
711+ Simple constructor, which sets _when_taking_too_long as the function
712+ to run when the connection is taking too long.
713+
714+ @param backend: a Backend object
715+ '''
716+ self.backend = backend
717+ super(GenericTomboy.DbusWatchdog, self).__init__(3, \
718+ self._when_taking_too_long)
719+
720+ def _when_taking_too_long(self):
721+ '''
722+ Function that is executed when the Dbus connection seems to be
723+ hanging. It disables the backend and signals the error to the user.
724+ '''
725+ Log.error("Dbus connection is taking too long for the Tomboy/Gnote"
726+ "backend!")
727+ self.backend.quit(disable = True)
728+ BackendSignals().backend_failed(self.backend.get_id(), \
729+ BackendSignals.ERRNO_DBUS)
730+
731
732=== modified file 'GTG/gtk/browser/browser.py'
733--- GTG/gtk/browser/browser.py 2010-08-10 17:30:24 +0000
734+++ GTG/gtk/browser/browser.py 2010-08-13 23:44:44 +0000
735@@ -953,7 +953,9 @@
736 text = \
737 text.replace("%s%s:%s" % (spaces, attribute, args), "")
738 # Create the new task
739- task = self.req.new_task(tags=[t.get_name() for t in tags], newtask=True)
740+ task = self.req.new_task( newtask=True)
741+ for tag in tags:
742+ task.add_tag(tag.get_name())
743 if text != "":
744 task.set_title(text.strip())
745 task.set_to_keep()
746
747=== added file 'data/icons/hicolor/scalable/apps/backend_gnote.png'
748Binary 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
749=== added file 'data/icons/hicolor/scalable/apps/backend_tomboy.png'
750Binary 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: