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