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 (community) Approve
Gtg developers Pending
Review via email: mp+28257@code.launchpad.net

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 :

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

review: Approve
lp:~gtg-user/gtg/tomboy-backend updated
824. By Bryce Harrington

If there aren't any unscheduled items, don't print them

825. By Luca Invernizzi

Multiple backends core features merge:
  - using signals instead of callback function to save tasks
  - superclass for all backends added, managing anything that's common to
    them
  - localfile baceknd updated (with tests)

  Unit testing now doesn't clutter the ~/.local/share directroy anymore

826. By Luca Invernizzi

small fix for tests (forgot to remove unused code)

827. By Lionel Dricot

Working on liblarch. It progress and it looks like it will be really good

828. By Luca Invernizzi

small fix in delete_dialog

829. By Lionel Dricot

liblarch progress

830. By Lionel Dricot

forgot some more liblarch love

831. By Lionel Dricot

only 21 todos left in liblarch before porting GTG to it

832. By Lionel Dricot

Very interesting! Starting from a unit test and fighting to find an unrelated
bug that was still afecting the test.

833. By Lionel Dricot

liblarch progress

834. By Luca Invernizzi

 small fix in tagstore (checking if a task object is not None)

835. By Lionel Dricot

liblarch test suite nearly finished

836. By Lionel Dricot

liblarch is now fully covered. Lets start liblarch-gtk now

837. By Bryce Harrington

Fix merge conflict

838. By Bryce Harrington

Add ability to postpone tasks by tag

839. By Bryce Harrington

Merge branch by Volodymyr Floreskul to move due date when start date changed

840. By Luca Invernizzi

small fixes in filteredtree

841. By Bryce Harrington

ghost position error messages should be debug data.

There are already several error messages about this to stdout before
hitting this code, so user is already notified. These prints display a
number of lines of task ids which won't be useful to the user.

842. By Bryce Harrington

Clean up some more messy debug messages

This leaves a one line message to display to the user, the rest is
displayed only if --debug is specified. The information it provides is
really only interesting for debug purposes (and probably of limited use
even there), so this reduces the spewage to stdout when these errors are
present.

843. By Bryce Harrington

'old paths are' is debugging output, move to Logger

844. By Bryce Harrington

Allow for sensible default values if none provided.

This change allows anonymize_task_file.py to be called with no args, and
it does the sensible default - read the user's regular tasks xml file
and anonymize it to a temporary file.

845. By Bryce Harrington

Increase column width for ids now that ids can be uuids

846. By Luca Invernizzi

Disabling the gtk crash handler

847. By Bertrand Rousseau <rousseau@biscuit>

Add short description to plugins. Edit the preference window aspects.

848. By Luca Invernizzi

Better phrasing in "Learn how to use the QuickAdd Entry" by Chris Johnston

849. By Luca Invernizzi

Typo in notification area plugin description (fixing bug #599832)

850. By Bryce Harrington

sp.

851. By Bryce Harrington

Include children of tags recursively. Fixes regression #592445

When a tag is selected, the browser displays tasks that belong to that
tag or its children, but not its grandchildren. We change the
functionality to be recursive so it'll include all descendant tags of
the given tag.

852. By Bryce Harrington

The tests should not be disabled in trunk.

I've verified all the tests still pass fine on my system. I guess they
were disabled simply for focusing on testing liblarch, but this is bad,
since it gives people false impressions that code committed to trunk is
okay, when it might not be.

Also, please do not disable tests without leaving some comment as to
*why* they are being disabled.

853. By Bryce Harrington

Fixup grammar

854. By Bryce Harrington

Add a --no-crash-handler option to disable apport, to be used by debug.sh

(Looks like someone temporarily disabled apport anyway, but not sure
why, so left it disabled for now.)

855. By Bryce Harrington

Also select whatever task cursor is over when right clicking

This better mimics nautilus' behavior, better matching expectations for
GNOME users.

856. By Bryce Harrington <bryce@salisbury>

Fix grammar

857. By Luca Invernizzi

Anonymizer script uses reasonable defaults (by bryce)

858. By Bryce Harrington

Add a simple boot performance test.

This adds a -b option to gtg which causes it to exit immediately after
completing the first iteration of the main loop, thus permitting a way
to reliably measure initialization time.

scripts/debug.sh is also updated to also support a -b option, which it
passes to gtg. This permits doing boot-up time profiling like this:

  scripts/debug.sh -b -p

Of course, that's not interesting since there's only 5 tasks in the
default data set. But you can use any data set in gtg/test/data/, for
instance, the infamous 'bryce' dataset:

  scripts/debug.sh -b -p -s bryce

859. By Bryce Harrington

Flesh out man page with recently added options

860. By Bryce Harrington

Include mention of the code documentation available online

861. By Bryce Harrington

Add coding guidelines for commented out code

862. By Bryce Harrington

Fixed crash traceback that's shown when pressing 'delete' key

Patch from Jeff Oliver (LP: #583103) Thanks!

863. By Bryce Harrington

Namespace gtg's D-BUS service name (LP: #582453)

864. By Bryce Harrington

Add a -v / --version option to show current version

865. By Bryce Harrington

Add ability to put lengthy text bodies into generated tasks

866. By Bryce Harrington

Be more consistent with date formatting

If no tasks were scheduled for a day, the date would be formatted
differently than if there was a task scheduled. E.g.:

...
Fri 8-20 13 1
2010-08-21 0 0
Sun 8-22 2 0
Mon 8-23 16 0
Tue 8-24 4 0
2010-08-25 0 0

867. By Luca Invernizzi

Accented tags are accepted in the quick-add field (by Jonathan Barnoud)
Fixing bug LP #615519

868. By Luca Invernizzi

UnitTests are automatically loaded at run time in GTG/tests/__init__.py.

Before, we had to add all the tests that we've written by hand in the
test_suite function, and that lead to a lot of merge conflicts. As tests
are now automatically added, there's no need to edit that file anymore.

869. By Luca Invernizzi

Solved bug in quickadd: when using the "tags:" option, it wasn't actually
inserting the tag text into the task content, but only attaching the Tag object
to the task. Some code elsewhere was adding the text upon task opening in the
Editor.
(This was causing weird behaviour in the backends, otherwise it would be hard
 to spot).

870. By Luca Invernizzi

a

871. By Luca Invernizzi

a

872. By Luca Invernizzi

a

873. By Luca Invernizzi

a

874. By Luca Invernizzi

a

875. By Luca Invernizzi

a

876. By Luca Invernizzi

a

877. By Luca Invernizzi

a

878. By Luca Invernizzi

a

879. By Luca Invernizzi

a

880. By Luca Invernizzi

a

881. By Luca Invernizzi

Changelog changed

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:43:03 +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:43:03 +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:43:03 +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:43:03 +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:43:03 +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:43:03 +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:43:03 +0000 differ

Subscribers

People subscribed via source and target branches

to status/vote changes: