Merge lp:~gtg-user/gtg/evolution-backend into lp:~gtg/gtg/old-trunk
- evolution-backend
- Merge into old-trunk
Status: | Merged |
---|---|
Merged at revision: | 880 |
Proposed branch: | lp:~gtg-user/gtg/evolution-backend |
Merge into: | lp:~gtg/gtg/old-trunk |
Diff against target: |
1185 lines (+420/-707) 11 files modified
GTG/backends/backend_evolution.py (+420/-0) GTG/plugins/evolution-sync.gtg-plugin (+0/-9) GTG/plugins/evolution_sync/__init__.py (+0/-20) GTG/plugins/evolution_sync/evolutionProxy.py (+0/-73) GTG/plugins/evolution_sync/evolutionSync.py (+0/-45) GTG/plugins/evolution_sync/evolutionTask.py (+0/-110) GTG/plugins/evolution_sync/genericProxy.py (+0/-34) GTG/plugins/evolution_sync/genericTask.py (+0/-68) GTG/plugins/evolution_sync/gtgProxy.py (+0/-49) GTG/plugins/evolution_sync/gtgTask.py (+0/-91) GTG/plugins/evolution_sync/syncEngine.py (+0/-208) |
To merge this branch: | bzr merge lp:~gtg-user/gtg/evolution-backend |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gtg developers | Pending | ||
Review via email: mp+33854@code.launchpad.net |
Commit message
Description of the change
Evolution backend (and removal of the plugin).
This is not at the level of the other backends because the evolution api is not complete (that is, evolution start_date and categories are not exposed). However, it's the best we can have for now.
With this merge, backends are covering all the synchronization plugins used to make.
Martina de la Cruz (tyamar) wrote : | # |
Luca Invernizzi (invernizzi) wrote : | # |
Hi Tina,
it's easy: you just have to download the code and execute it.
See http://
tar czf gtg_backup.tgz .local/share/gtg/ #just in case, backup of your gtg install (if any)
sudo aptitude install bzr
bzr branch lp:~gtg-user/gtg/all_the_backends_merge_requests gtg_backends
cd gtg_backends
./scripts/debug.sh -d #this lauches a debug version of GTG that doesn't touch your own GTG tasks
Then, if you find something that doesn't work, you open a bug and describe the problem. Thanks!
Martina de la Cruz (tyamar) wrote : | # |
Thanks!
-- Tina
On Mon, Aug 30, 2010 at 16:03, Luca Invernizzi <email address hidden>wrote:
> Hi Tina,
> it's easy: you just have to download the code and execute it.
> See http://
>
> tar czf gtg_backup.tgz .local/share/gtg/ #just in case, backup of your gtg
> install (if any)
> sudo aptitude install bzr
> bzr branch lp:~gtg-user/gtg/all_the_backends_merge_requests gtg_backends
> cd gtg_backends
> ./scripts/debug.sh -d #this lauches a debug version of GTG that doesn't
> touch your own GTG tasks
>
> Then, if you find something that doesn't work, you open a bug and describe
> the problem. Thanks!
> --
> https:/
> Your team Gtg users is subscribed to branch
> lp:~gtg-user/gtg/evolution-backend.
>
> _______
> Mailing list: https:/
> Post to : <email address hidden>
> Unsubscribe : https:/
> More help : https:/
>
- 878. By Luca Invernizzi
-
cherrypicking from my development branch
- 879. By Luca Invernizzi
-
cherrypicking from my development branch
Preview Diff
1 | === added file 'GTG/backends/backend_evolution.py' |
2 | --- GTG/backends/backend_evolution.py 1970-01-01 00:00:00 +0000 |
3 | +++ GTG/backends/backend_evolution.py 2010-09-03 23:47:40 +0000 |
4 | @@ -0,0 +1,420 @@ |
5 | +# -*- coding: utf-8 -*- |
6 | +# ----------------------------------------------------------------------------- |
7 | +# Getting Things Gnome! - a personal organizer for the GNOME desktop |
8 | +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
9 | +# |
10 | +# This program is free software: you can redistribute it and/or modify it under |
11 | +# the terms of the GNU General Public License as published by the Free Software |
12 | +# Foundation, either version 3 of the License, or (at your option) any later |
13 | +# version. |
14 | +# |
15 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
16 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
17 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
18 | +# details. |
19 | +# |
20 | +# You should have received a copy of the GNU General Public License along with |
21 | +# this program. If not, see <http://www.gnu.org/licenses/>. |
22 | +# ----------------------------------------------------------------------------- |
23 | + |
24 | +''' |
25 | +Backend for storing/loading tasks in Evolution Tasks |
26 | +''' |
27 | + |
28 | +import os |
29 | +import time |
30 | +import uuid |
31 | +import datetime |
32 | +import evolution |
33 | +from dateutil.tz import tzutc, tzlocal |
34 | + |
35 | +from GTG.backends.genericbackend import GenericBackend |
36 | +from GTG import _ |
37 | +from GTG.backends.syncengine import SyncEngine, SyncMeme |
38 | +from GTG.backends.periodicimportbackend import PeriodicImportBackend |
39 | +from GTG.tools.dates import RealDate, NoDate |
40 | +from GTG.core.task import Task |
41 | +from GTG.tools.interruptible import interruptible |
42 | +from GTG.tools.logger import Log |
43 | +from GTG.tools.tags import extract_tags_from_text |
44 | + |
45 | +#Dictionaries to translate GTG tasks in Evolution ones |
46 | +_GTG_TO_EVOLUTION_STATUS = \ |
47 | + {Task.STA_ACTIVE: evolution.ecal.ICAL_STATUS_CONFIRMED, |
48 | + Task.STA_DONE: evolution.ecal.ICAL_STATUS_COMPLETED, |
49 | + Task.STA_DISMISSED: evolution.ecal.ICAL_STATUS_CANCELLED} |
50 | + |
51 | +_EVOLUTION_TO_GTG_STATUS = \ |
52 | + {evolution.ecal.ICAL_STATUS_CONFIRMED: Task.STA_ACTIVE, |
53 | + evolution.ecal.ICAL_STATUS_DRAFT: Task.STA_ACTIVE, |
54 | + evolution.ecal.ICAL_STATUS_FINAL: Task.STA_ACTIVE, |
55 | + evolution.ecal.ICAL_STATUS_INPROCESS: Task.STA_ACTIVE, |
56 | + evolution.ecal.ICAL_STATUS_NEEDSACTION: Task.STA_ACTIVE, |
57 | + evolution.ecal.ICAL_STATUS_NONE: Task.STA_ACTIVE, |
58 | + evolution.ecal.ICAL_STATUS_TENTATIVE: Task.STA_ACTIVE, |
59 | + evolution.ecal.ICAL_STATUS_X: Task.STA_ACTIVE, |
60 | + evolution.ecal.ICAL_STATUS_COMPLETED: Task.STA_DONE, |
61 | + evolution.ecal.ICAL_STATUS_CANCELLED: Task.STA_DISMISSED} |
62 | + |
63 | + |
64 | + |
65 | +class Backend(PeriodicImportBackend): |
66 | + ''' |
67 | + Evolution backend |
68 | + ''' |
69 | + |
70 | + |
71 | + _general_description = { \ |
72 | + GenericBackend.BACKEND_NAME: "backend_evolution", \ |
73 | + GenericBackend.BACKEND_HUMAN_NAME: _("Evolution tasks"), \ |
74 | + GenericBackend.BACKEND_AUTHORS: ["Luca Invernizzi"], \ |
75 | + GenericBackend.BACKEND_TYPE: GenericBackend.TYPE_READWRITE, \ |
76 | + GenericBackend.BACKEND_DESCRIPTION: \ |
77 | + _("Lets you synchronize your GTG tasks with Evolution tasks"),\ |
78 | + } |
79 | + |
80 | + _static_parameters = { \ |
81 | + "period": { \ |
82 | + GenericBackend.PARAM_TYPE: GenericBackend.TYPE_INT, \ |
83 | + GenericBackend.PARAM_DEFAULT_VALUE: 10, }, |
84 | + "is-first-run": { \ |
85 | + GenericBackend.PARAM_TYPE: GenericBackend.TYPE_BOOL, \ |
86 | + GenericBackend.PARAM_DEFAULT_VALUE: True, }, |
87 | + } |
88 | + |
89 | +############################################################################### |
90 | +### Backend standard methods ################################################## |
91 | +############################################################################### |
92 | + |
93 | + def __init__(self, parameters): |
94 | + ''' |
95 | + See GenericBackend for an explanation of this function. |
96 | + Loads the saved state of the sync, if any |
97 | + ''' |
98 | + super(Backend, self).__init__(parameters) |
99 | + #loading the saved state of the synchronization, if any |
100 | + self.sync_engine_path = os.path.join('backends/evolution/', \ |
101 | + "sync_engine-" + self.get_id()) |
102 | + self.sync_engine = self._load_pickled_file(self.sync_engine_path, \ |
103 | + SyncEngine()) |
104 | + #sets up the connection to the evolution api |
105 | + task_personal = evolution.ecal.list_task_sources()[0][1] |
106 | + self._evolution_tasks = evolution.ecal.open_calendar_source( \ |
107 | + task_personal, |
108 | + evolution.ecal.CAL_SOURCE_TYPE_TODO) |
109 | + |
110 | + def do_periodic_import(self): |
111 | + """ |
112 | + See PeriodicImportBackend for an explanation of this function. |
113 | + """ |
114 | + stored_evolution_task_ids = set(self.sync_engine.get_all_remote()) |
115 | + current_evolution_task_ids = set([task.get_uid() \ |
116 | + for task in self._evolution_tasks.get_all_objects()]) |
117 | + #If it's the very first time the backend is run, it's possible that the |
118 | + # user already synced his tasks in some way (but we don't know that). |
119 | + # Therefore, we attempt to induce those tasks relationships matching the |
120 | + # titles. |
121 | + if self._parameters["is-first-run"]: |
122 | + gtg_titles_dic = {} |
123 | + for tid in self.datastore.get_all_tasks(): |
124 | + gtg_task = self.datastore.get_task(tid) |
125 | + if not self._gtg_task_is_syncable_per_attached_tags(gtg_task): |
126 | + continue |
127 | + gtg_title = gtg_task.get_title() |
128 | + if gtg_titles_dic.has_key(gtg_title): |
129 | + gtg_titles_dic[gtg_task.get_title()].append(tid) |
130 | + else: |
131 | + gtg_titles_dic[gtg_task.get_title()] = [tid] |
132 | + for evo_task_id in current_evolution_task_ids: |
133 | + evo_task = self._evo_get_task(evo_task_id) |
134 | + try: |
135 | + tids = gtg_titles_dic[evo_task.get_summary()] |
136 | + #we remove the tid, so that it can't be linked to two |
137 | + # different evolution tasks |
138 | + tid = tids.pop() |
139 | + gtg_task = self.datastore.get_task(tid) |
140 | + meme = SyncMeme(gtg_task.get_modified(), |
141 | + self._evo_get_modified(evo_task), |
142 | + "GTG") |
143 | + self.sync_engine.record_relationship( \ |
144 | + local_id = tid, |
145 | + remote_id = evo_task.get_uid(), |
146 | + meme = meme) |
147 | + except KeyError: |
148 | + pass |
149 | + #a first run has been completed successfully |
150 | + self._parameters["is-first-run"] = False |
151 | + |
152 | + for evo_task_id in current_evolution_task_ids: |
153 | + #Adding and updating |
154 | + self.cancellation_point() |
155 | + self._process_evo_task(evo_task_id) |
156 | + |
157 | + for evo_task_id in stored_evolution_task_ids.difference(\ |
158 | + current_evolution_task_ids): |
159 | + #Removing the old ones |
160 | + self.cancellation_point() |
161 | + tid = self.sync_engine.get_local_id(evo_task_id) |
162 | + self.datastore.request_task_deletion(tid) |
163 | + try: |
164 | + self.sync_engine.break_relationship(remote_id = \ |
165 | + evo_task_id) |
166 | + except KeyError: |
167 | + pass |
168 | + |
169 | + def save_state(self): |
170 | + ''' |
171 | + See GenericBackend for an explanation of this function. |
172 | + ''' |
173 | + self._store_pickled_file(self.sync_engine_path, self.sync_engine) |
174 | + |
175 | +############################################################################### |
176 | +### Process tasks ############################################################# |
177 | +############################################################################### |
178 | + |
179 | + @interruptible |
180 | + def remove_task(self, tid): |
181 | + ''' |
182 | + See GenericBackend for an explanation of this function. |
183 | + ''' |
184 | + try: |
185 | + evo_task_id = self.sync_engine.get_remote_id(tid) |
186 | + self._delete_evolution_task(self._evo_get_task(evo_task_id)) |
187 | + except KeyError: |
188 | + pass |
189 | + try: |
190 | + self.sync_engine.break_relationship(local_id = tid) |
191 | + except: |
192 | + pass |
193 | + |
194 | + @interruptible |
195 | + def set_task(self, task): |
196 | + ''' |
197 | + See GenericBackend for an explanation of this function. |
198 | + ''' |
199 | + tid = task.get_id() |
200 | + is_syncable = self._gtg_task_is_syncable_per_attached_tags(task) |
201 | + action, evo_task_id = self.sync_engine.analyze_local_id( |
202 | + tid, |
203 | + self.datastore.has_task, |
204 | + self._evo_has_task, |
205 | + is_syncable) |
206 | + Log.debug('GTG->Evo set task (%s, %s)' % (action, is_syncable)) |
207 | + |
208 | + if action == None: |
209 | + return |
210 | + |
211 | + if action == SyncEngine.ADD: |
212 | + evo_task = evolution.ecal.ECalComponent( \ |
213 | + ical = evolution.ecal.CAL_COMPONENT_TODO) |
214 | + with self.datastore.get_backend_mutex(): |
215 | + self._evolution_tasks.add_object(evo_task) |
216 | + self._populate_evo_task(task, evo_task) |
217 | + meme = SyncMeme(task.get_modified(), |
218 | + self._evo_get_modified(evo_task), |
219 | + "GTG") |
220 | + self.sync_engine.record_relationship( \ |
221 | + local_id = tid, remote_id = evo_task.get_uid(), meme = meme) |
222 | + |
223 | + elif action == SyncEngine.UPDATE: |
224 | + with self.datastore.get_backend_mutex(): |
225 | + evo_task = self._evo_get_task(evo_task_id) |
226 | + meme = self.sync_engine.get_meme_from_local_id(task.get_id()) |
227 | + newest = meme.which_is_newest(task.get_modified(), |
228 | + self._evo_get_modified(evo_task)) |
229 | + if newest == "local": |
230 | + self._populate_evo_task(task, evo_task) |
231 | + meme.set_remote_last_modified( \ |
232 | + self._evo_get_modified(evo_task)) |
233 | + meme.set_local_last_modified(task.get_modified()) |
234 | + else: |
235 | + #we skip saving the state |
236 | + return |
237 | + |
238 | + elif action == SyncEngine.REMOVE: |
239 | + self.datastore.request_task_deletion(tid) |
240 | + try: |
241 | + self.sync_engine.break_relationship(local_id = tid) |
242 | + except KeyError: |
243 | + pass |
244 | + |
245 | + elif action == SyncEngine.LOST_SYNCABILITY: |
246 | + evo_task = self._evo_get_task(evo_task_id) |
247 | + self._exec_lost_syncability(tid, evo_task) |
248 | + self.save_state() |
249 | + |
250 | + def _process_evo_task(self, evo_task_id): |
251 | + ''' |
252 | + Takes an evolution task id and carries out the necessary operations to |
253 | + refresh the sync state |
254 | + ''' |
255 | + self.cancellation_point() |
256 | + evo_task = self._evo_get_task(evo_task_id) |
257 | + is_syncable = self._evo_task_is_syncable(evo_task) |
258 | + action, tid = self.sync_engine.analyze_remote_id( \ |
259 | + evo_task_id, |
260 | + self.datastore.has_task, |
261 | + self._evo_has_task, |
262 | + is_syncable) |
263 | + Log.debug('GTG<-Evo set task (%s, %s)' % (action, is_syncable)) |
264 | + |
265 | + if action == SyncEngine.ADD: |
266 | + with self.datastore.get_backend_mutex(): |
267 | + tid = str(uuid.uuid4()) |
268 | + task = self.datastore.task_factory(tid) |
269 | + self._populate_task(task, evo_task) |
270 | + meme = SyncMeme(task.get_modified(), |
271 | + self._evo_get_modified(evo_task), |
272 | + "GTG") |
273 | + self.sync_engine.record_relationship(local_id = tid, |
274 | + remote_id = evo_task_id, |
275 | + meme = meme) |
276 | + self.datastore.push_task(task) |
277 | + |
278 | + elif action == SyncEngine.UPDATE: |
279 | + with self.datastore.get_backend_mutex(): |
280 | + task = self.datastore.get_task(tid) |
281 | + meme = self.sync_engine.get_meme_from_remote_id(evo_task_id) |
282 | + newest = meme.which_is_newest(task.get_modified(), |
283 | + self._evo_get_modified(evo_task)) |
284 | + if newest == "remote": |
285 | + self._populate_task(task, evo_task) |
286 | + meme.set_remote_last_modified( \ |
287 | + self._evo_get_modified(evo_task)) |
288 | + meme.set_local_last_modified(task.get_modified()) |
289 | + |
290 | + elif action == SyncEngine.REMOVE: |
291 | + return |
292 | + try: |
293 | + evo_task = self._evo_get_task(evo_task_id) |
294 | + self._delete_evolution_task(evo_task) |
295 | + self.sync_engine.break_relationship(remote_id = evo_task) |
296 | + except KeyError: |
297 | + pass |
298 | + |
299 | + elif action == SyncEngine.LOST_SYNCABILITY: |
300 | + self._exec_lost_syncability(tid, evo_task) |
301 | + self.save_state() |
302 | + |
303 | +############################################################################### |
304 | +### Helper methods ############################################################ |
305 | +############################################################################### |
306 | + |
307 | + def _evo_has_task(self, evo_task_id): |
308 | + '''Returns true if Evolution has that task''' |
309 | + return bool(self._evo_get_task(evo_task_id)) |
310 | + |
311 | + def _evo_get_task(self, evo_task_id): |
312 | + '''Returns an Evolution task, given its uid''' |
313 | + return self._evolution_tasks.get_object(evo_task_id, "") |
314 | + |
315 | + def _evo_get_modified(self, evo_task): |
316 | + '''Returns the modified time of an Evolution task''' |
317 | + return datetime.datetime.fromtimestamp(evo_task.get_modified()) |
318 | + |
319 | + def _delete_evolution_task(self, evo_task): |
320 | + '''Deletes an Evolution task, given the task object''' |
321 | + self._evolution_tasks.remove_object(evo_task) |
322 | + self._evolution_tasks.update_object(evo_task) |
323 | + |
324 | + def _populate_task(self, task, evo_task): |
325 | + ''' |
326 | + Updates the attributes of a GTG task copying the ones of an Evolution |
327 | + task |
328 | + ''' |
329 | + task.set_title(evo_task.get_summary()) |
330 | + text = evo_task.get_description() |
331 | + if text == None: |
332 | + text = "" |
333 | + task.set_text(text) |
334 | + due_date_timestamp = evo_task.get_due() |
335 | + if isinstance(due_date_timestamp, (int, float)): |
336 | + due_date = self.__date_from_evo_to_gtg(due_date_timestamp) |
337 | + else: |
338 | + due_date = NoDate() |
339 | + task.set_due_date(due_date) |
340 | + status = evo_task.get_status() |
341 | + if task.get_status() != _EVOLUTION_TO_GTG_STATUS[status]: |
342 | + task.set_status(_EVOLUTION_TO_GTG_STATUS[status]) |
343 | + task.set_only_these_tags(extract_tags_from_text(text)) |
344 | + |
345 | + def _populate_evo_task(self, task, evo_task): |
346 | + evo_task.set_summary(task.get_title()) |
347 | + text = task.get_excerpt(strip_tags = True, strip_subtasks = True) |
348 | + if evo_task.get_description() != text: |
349 | + evo_task.set_description(text) |
350 | + due_date = task.get_due_date() |
351 | + if isinstance(due_date, NoDate): |
352 | + evo_task.set_due(None) |
353 | + else: |
354 | + evo_task.set_due(self.__date_from_gtg_to_evo(due_date)) |
355 | + status = task.get_status() |
356 | + if _EVOLUTION_TO_GTG_STATUS[evo_task.get_status()] != status: |
357 | + evo_task.set_status(_GTG_TO_EVOLUTION_STATUS[status]) |
358 | + #this calls are sometime ignored by evolution. Doing it twice |
359 | + # is a hackish way to solve the problem. (TODO: send bug report) |
360 | + self._evolution_tasks.update_object(evo_task) |
361 | + self._evolution_tasks.update_object(evo_task) |
362 | + |
363 | + def _exec_lost_syncability(self, tid, evo_task): |
364 | + ''' |
365 | + Executed when a relationship between tasks loses its syncability |
366 | + property. See SyncEngine for an explanation of that. |
367 | + This function finds out which object is the original one |
368 | + and which is the copy, and deletes the copy. |
369 | + ''' |
370 | + meme = self.sync_engine.get_meme_from_local_id(tid) |
371 | + self.sync_engine.break_relationship(local_id = tid) |
372 | + if meme.get_origin() == "GTG": |
373 | + evo_task = self._evo_get_task(evo_task.get_uid()) |
374 | + self._delete_evolution_task(evo_task) |
375 | + else: |
376 | + self.datastore.request_task_deletion(tid) |
377 | + |
378 | + def _evo_task_is_syncable(self, evo_task): |
379 | + ''' |
380 | + Returns True if this Evolution task should be synced into GTG tasks. |
381 | + |
382 | + @param evo_task: an Evolution task |
383 | + @returns Boolean |
384 | + ''' |
385 | + attached_tags = set(self.get_attached_tags()) |
386 | + if GenericBackend.ALLTASKS_TAG in attached_tags: |
387 | + return True |
388 | + evo_tags = set(extract_tags_from_text(evo_task.get_description())) |
389 | + return evo_task.is_disjoint(attached_tags) |
390 | + |
391 | + def __date_from_evo_to_gtg(self, evo_date_timestamp): |
392 | + """ |
393 | + Converts an evolution date object into the format understood by GTG |
394 | + |
395 | + @param evo_date: an int, which represent time from epoch in UTC |
396 | + convention |
397 | + """ |
398 | + evo_datetime = datetime.datetime.fromtimestamp(evo_date_timestamp) |
399 | + #See self.__date_from_gtg_to_evo for an explanation |
400 | + evo_datetime = evo_datetime.replace(tzinfo = tzlocal()) |
401 | + gtg_datetime = evo_datetime.astimezone(tzutc()) |
402 | + #we strip timezone infos, as they're not used or expected in GTG |
403 | + gtg_datetime.replace(tzinfo = None) |
404 | + return RealDate(gtg_datetime.date()) |
405 | + |
406 | + def __date_from_gtg_to_evo(self, gtg_date): |
407 | + """ |
408 | + Converts a datetime.date object into the format understood by Evolution |
409 | + |
410 | + @param gtg_date: a GTG Date object |
411 | + """ |
412 | + #GTG thinks in local time, evolution in utc |
413 | + #to convert date objects between different timezones, we must convert |
414 | + #them to datetime objects |
415 | + gtg_datetime = datetime.datetime.combine(gtg_date.to_py_date(), datetime.time(0)) |
416 | + #We don't want to express GTG date into a UTC equivalent. Instead, we |
417 | + #want the *same* date in GTG and evolution. Therefore, we must not do |
418 | + #the conversion Local-> UTC (which would point to the same moment in |
419 | + #time in different conventions), but do the opposite conversion UTC-> |
420 | + #Local (which will refer to different points in time, but to the same |
421 | + #written date) |
422 | + gtg_datetime = gtg_datetime.replace(tzinfo = tzutc()) |
423 | + evo_datetime = gtg_datetime.astimezone(tzlocal()) |
424 | + return int(time.mktime(evo_datetime.timetuple())) |
425 | |
426 | === removed file 'GTG/plugins/evolution-sync.gtg-plugin' |
427 | --- GTG/plugins/evolution-sync.gtg-plugin 2010-07-29 10:36:24 +0000 |
428 | +++ GTG/plugins/evolution-sync.gtg-plugin 1970-01-01 00:00:00 +0000 |
429 | @@ -1,9 +0,0 @@ |
430 | -[GTG Plugin] |
431 | -Module=evolution_sync |
432 | -Name=Evolution |
433 | -Short-description="Plugin for synchronising GTG with Gnome Evolution Tasks." |
434 | -Description="""Plugin for synchronising GTG with Gnome Evolution Tasks""" |
435 | -Authors='Luca Invernizzi <invernizzi.l@gmail.com>' |
436 | -Version=0.1.1 |
437 | -Dependencies=evolution, |
438 | -Enabled=False |
439 | |
440 | === removed directory 'GTG/plugins/evolution_sync' |
441 | === removed file 'GTG/plugins/evolution_sync/__init__.py' |
442 | --- GTG/plugins/evolution_sync/__init__.py 2010-03-16 02:16:14 +0000 |
443 | +++ GTG/plugins/evolution_sync/__init__.py 1970-01-01 00:00:00 +0000 |
444 | @@ -1,20 +0,0 @@ |
445 | -# -*- coding: utf-8 -*- |
446 | -# Copyright (c) 2009 - Luca Invernizzi <invernizzi.l@gmail.com> |
447 | -# |
448 | -# This program is free software: you can redistribute it and/or modify it under |
449 | -# the terms of the GNU General Public License as published by the Free Software |
450 | -# Foundation, either version 3 of the License, or (at your option) any later |
451 | -# version. |
452 | -# |
453 | -# This program is distributed in the hope that it will be useful, but WITHOUT |
454 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
455 | -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
456 | -# details. |
457 | -# |
458 | -# You should have received a copy of the GNU General Public License along with |
459 | -# this program. If not, see <http://www.gnu.org/licenses/>. |
460 | - |
461 | -from GTG.plugins.evolution_sync.evolutionSync import EvolutionSync |
462 | - |
463 | -#needed to keep pyflakes quiet |
464 | -if False == True: EvolutionSync() |
465 | |
466 | === removed file 'GTG/plugins/evolution_sync/evolutionProxy.py' |
467 | --- GTG/plugins/evolution_sync/evolutionProxy.py 2010-03-16 02:16:14 +0000 |
468 | +++ GTG/plugins/evolution_sync/evolutionProxy.py 1970-01-01 00:00:00 +0000 |
469 | @@ -1,73 +0,0 @@ |
470 | -# -*- coding: utf-8 -*- |
471 | -# Copyright (c) 2009 - Luca Invernizzi <invernizzi.l@gmail.com> |
472 | -# |
473 | -# This program is free software: you can redistribute it and/or modify it under |
474 | -# the terms of the GNU General Public License as published by the Free Software |
475 | -# Foundation, either version 3 of the License, or (at your option) any later |
476 | -# version. |
477 | -# |
478 | -# This program is distributed in the hope that it will be useful, but WITHOUT |
479 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
480 | -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
481 | -# details. |
482 | -# |
483 | -# You should have received a copy of the GNU General Public License along with |
484 | -# this program. If not, see <http://www.gnu.org/licenses/>. |
485 | - |
486 | -import evolution |
487 | - |
488 | -from GTG.core.task import Task |
489 | -from GTG.plugins.evolution_sync.evolutionTask import EvolutionTask |
490 | -from GTG.plugins.evolution_sync.genericProxy import GenericProxy |
491 | - |
492 | - |
493 | -class EvolutionProxy(GenericProxy): |
494 | - |
495 | - __GTG_STATUSES = [Task.STA_ACTIVE, |
496 | - Task.STA_DONE, |
497 | - Task.STA_DISMISSED] |
498 | - |
499 | - __EVO_STATUSES = [evolution.ecal.ICAL_STATUS_CONFIRMED, |
500 | - evolution.ecal.ICAL_STATUS_COMPLETED, |
501 | - evolution.ecal.ICAL_STATUS_CANCELLED] |
502 | - |
503 | - def __init__(self): |
504 | - super(EvolutionProxy, self).__init__() |
505 | - |
506 | - def generateTaskList(self): |
507 | - task_personal = evolution.ecal.list_task_sources()[0][1] |
508 | - self._evolution_tasks = evolution.ecal.open_calendar_source(task_personal, |
509 | - evolution.ecal.CAL_SOURCE_TYPE_TODO) |
510 | - self._gtg_to_evo_status = dict(zip(self.__GTG_STATUSES, |
511 | - self.__EVO_STATUSES)) |
512 | - self._evo_to_gtg_status = dict(zip(self.__EVO_STATUSES, |
513 | - self.__GTG_STATUSES)) |
514 | - #Need to find a solution for the statuses GTG doesn't expect: |
515 | - for evo_status in [evolution.ecal.ICAL_STATUS_DRAFT, |
516 | - evolution.ecal.ICAL_STATUS_FINAL, |
517 | - evolution.ecal.ICAL_STATUS_INPROCESS, |
518 | - evolution.ecal.ICAL_STATUS_NEEDSACTION, |
519 | - evolution.ecal.ICAL_STATUS_NONE, |
520 | - evolution.ecal.ICAL_STATUS_TENTATIVE, |
521 | - evolution.ecal.ICAL_STATUS_X]: |
522 | - self._evo_to_gtg_status[evo_status] = Task.STA_ACTIVE |
523 | - self._tasks_list = [] |
524 | - for task in self._evolution_tasks.get_all_objects(): |
525 | - self._tasks_list.append(EvolutionTask(task, self)) |
526 | - |
527 | - def create_new_task(self, title): |
528 | - task = evolution.ecal.ECalComponent(ical=evolution.ecal.CAL_COMPONENT_TODO) |
529 | - self._evolution_tasks.add_object(task) |
530 | - new_task = EvolutionTask(task, self) |
531 | - new_task.title = title |
532 | - self._tasks_list.append(new_task) |
533 | - return new_task |
534 | - |
535 | - def delete_task(self, task): |
536 | - evo_task = task.get_evolution_task() |
537 | - self._evolution_tasks.remove_object(evo_task) |
538 | - self._evolution_tasks.update_object(evo_task) |
539 | - |
540 | - def update_task(self, task): |
541 | - evo_task = task.get_evolution_task() |
542 | - self._evolution_tasks.update_object(evo_task) |
543 | |
544 | === removed file 'GTG/plugins/evolution_sync/evolutionSync.py' |
545 | --- GTG/plugins/evolution_sync/evolutionSync.py 2010-03-16 02:16:14 +0000 |
546 | +++ GTG/plugins/evolution_sync/evolutionSync.py 1970-01-01 00:00:00 +0000 |
547 | @@ -1,45 +0,0 @@ |
548 | -# -*- coding: utf-8 -*- |
549 | -# Copyright (c) 2009 - Luca Invernizzi <invernizzi.l@gmail.com> |
550 | -# - Paulo Cabido <paulo.cabido@gmail.com> (example file) |
551 | -# |
552 | -# This program is free software: you can redistribute it and/or modify it under |
553 | -# the terms of the GNU General Public License as published by the Free Software |
554 | -# Foundation, either version 3 of the License, or (at your option) any later |
555 | -# version. |
556 | -# |
557 | -# This program is distributed in the hope that it will be useful, but WITHOUT |
558 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
559 | -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
560 | -# details. |
561 | -# |
562 | -# You should have received a copy of the GNU General Public License along with |
563 | -# this program. If not, see <http://www.gnu.org/licenses/>. |
564 | - |
565 | -import gtk |
566 | -from threading import Thread |
567 | - |
568 | -from GTG import _ |
569 | -from GTG.plugins.evolution_sync.syncEngine import SyncEngine |
570 | - |
571 | -class EvolutionSync: |
572 | - |
573 | - def __init__(self): |
574 | - #drop down menu |
575 | - self.menu_item = gtk.MenuItem(_("Synchronize with Evolution")) |
576 | - self.menu_item.connect('activate', self.onTesteMenu) |
577 | - |
578 | - def activate(self, plugin_api): |
579 | - self.plugin_api = plugin_api |
580 | - # add a menu item to the menu bar |
581 | - plugin_api.add_menu_item(self.menu_item) |
582 | - self.sync_engine = SyncEngine(self) |
583 | - |
584 | - def deactivate(self, plugin_api): |
585 | - plugin_api.remove_menu_item(self.menu_item) |
586 | - |
587 | - def onTesteMenu(self, widget): |
588 | - self.worker_thread = Thread(target = \ |
589 | - self.sync_engine.synchronize).start() |
590 | - |
591 | - def onTaskOpened(self, plugin_api): |
592 | - pass |
593 | |
594 | === removed file 'GTG/plugins/evolution_sync/evolutionTask.py' |
595 | --- GTG/plugins/evolution_sync/evolutionTask.py 2010-03-16 02:16:14 +0000 |
596 | +++ GTG/plugins/evolution_sync/evolutionTask.py 1970-01-01 00:00:00 +0000 |
597 | @@ -1,110 +0,0 @@ |
598 | -# -*- coding: utf-8 -*- |
599 | -# Copyright (c) 2009 - Luca Invernizzi <invernizzi.l@gmail.com> |
600 | -# |
601 | -# This program is free software: you can redistribute it and/or modify it under |
602 | -# the terms of the GNU General Public License as published by the Free Software |
603 | -# Foundation, either version 3 of the License, or (at your option) any later |
604 | -# version. |
605 | -# |
606 | -# This program is distributed in the hope that it will be useful, but WITHOUT |
607 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
608 | -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
609 | -# details. |
610 | -# |
611 | -# You should have received a copy of the GNU General Public License along with |
612 | -# this program. If not, see <http://www.gnu.org/licenses/>. |
613 | - |
614 | -import time |
615 | -import datetime |
616 | - |
617 | -from GTG.plugins.evolution_sync.genericTask import GenericTask |
618 | - |
619 | - |
620 | -class EvolutionTask(GenericTask): |
621 | - |
622 | - def __init__(self, evo_task, evolution_proxy): |
623 | - super(EvolutionTask, self).__init__(evolution_proxy) |
624 | - self._evo_task = evo_task |
625 | - |
626 | - def _get_title(self): |
627 | - return self._evo_task.get_summary() |
628 | - |
629 | - def _set_title(self, title): |
630 | - self._evo_task.set_summary(title) |
631 | - self.get_proxy().update_task(self) |
632 | - |
633 | - def _get_id(self): |
634 | - return self._evo_task.get_uid() |
635 | - |
636 | - def _get_tags(self): |
637 | - #We could use Evolution's "Categories" as tags |
638 | - raise NotImplementedError() |
639 | - |
640 | - def _set_tags(self, tags): |
641 | - raise NotImplementedError() |
642 | - self.get_proxy().update_task(self) |
643 | - |
644 | - def _get_text(self): |
645 | - desc = self._evo_task.get_description() |
646 | - if desc == None: |
647 | - return "" |
648 | - return desc |
649 | - |
650 | - def _set_text(self, text): |
651 | - self._evo_task.set_description(text) |
652 | - self.get_proxy().update_task(self) |
653 | - |
654 | - def _set_status(self, status): |
655 | - #Since Evolution's statuses are fare more than GTG's, |
656 | - # we need to check that the current status is not one of the various |
657 | - # statuses translated in the same gtg status, passed by argument. |
658 | - # This way, we avoid to force a status change when it's not needed |
659 | - # (and not wanted) |
660 | - current_status_in_gtg_terms = self.get_proxy()._evo_to_gtg_status[\ |
661 | - self._evo_task.get_status()] |
662 | - if current_status_in_gtg_terms != status: |
663 | - new_evo_status = self.get_proxy()._gtg_to_evo_status[status] |
664 | - self._evo_task.set_status(new_evo_status) |
665 | - self.get_proxy().update_task(self) |
666 | - |
667 | - def _get_status(self): |
668 | - status = self._evo_task.get_status() |
669 | - return self.get_proxy()._evo_to_gtg_status[status] |
670 | - |
671 | - def _get_due_date(self): |
672 | - due = self._evo_task.get_due() |
673 | - if isinstance(due, (int, float)): |
674 | - return self.__time_evo_to_date(due) |
675 | - |
676 | - def _set_due_date(self, due): |
677 | - if due == None: |
678 | - #TODO: I haven't find a way to reset the due date |
679 | - # We could copy the task, but that would lose all the attributes |
680 | - # currently not supported by the library used (and they're a lot) |
681 | - pass |
682 | - else: |
683 | - self._evo_task.set_due(self.__time_date_to_evo(due)) |
684 | - self.get_proxy().update_task(self) |
685 | - |
686 | - def _get_modified(self): |
687 | - return self.__time_evo_to_datetime(self._evo_task.get_modified()) |
688 | - |
689 | - def __time_evo_to_date(self, timestamp): |
690 | - return datetime.datetime.fromtimestamp(timestamp + time.timezone).date() |
691 | - |
692 | - def __time_evo_to_datetime(self, timestamp): |
693 | - return datetime.datetime.fromtimestamp(timestamp) |
694 | - |
695 | - def __time_datetime_to_evo(self, timeobject): |
696 | - return int(time.mktime(timeobject.timetuple())) |
697 | - |
698 | - def __time_date_to_evo(self, timeobject): |
699 | - #NOTE: need to substract the timezone to avoid the "one day before bug |
700 | - # (at the airport => no internet now, need to fill bug number in) |
701 | - #Explanation: gtg date format is converted to datetime in date/00:00 in |
702 | - # local time, and time.mktime considers that when converting to UNIX |
703 | - # time. Evolution, however, doesn't convert back to local time. |
704 | - return self.__time_datetime_to_evo(timeobject) - time.timezone |
705 | - |
706 | - def get_evolution_task(self): |
707 | - return self._evo_task |
708 | |
709 | === removed file 'GTG/plugins/evolution_sync/genericProxy.py' |
710 | --- GTG/plugins/evolution_sync/genericProxy.py 2010-01-31 10:16:17 +0000 |
711 | +++ GTG/plugins/evolution_sync/genericProxy.py 1970-01-01 00:00:00 +0000 |
712 | @@ -1,34 +0,0 @@ |
713 | -# -*- coding: utf-8 -*- |
714 | -# Copyright (c) 2009 - Luca Invernizzi <invernizzi.l@gmail.com> |
715 | -# |
716 | -# This program is free software: you can redistribute it and/or modify it under |
717 | -# the terms of the GNU General Public License as published by the Free Software |
718 | -# Foundation, either version 3 of the License, or (at your option) any later |
719 | -# version. |
720 | -# |
721 | -# This program is distributed in the hope that it will be useful, but WITHOUT |
722 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
723 | -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
724 | -# details. |
725 | -# |
726 | -# You should have received a copy of the GNU General Public License along with |
727 | -# this program. If not, see <http://www.gnu.org/licenses/>. |
728 | - |
729 | -class GenericProxy(object): |
730 | - |
731 | - def __init__(self): |
732 | - super(GenericProxy, self).__init__() |
733 | - self._tasks_list = [] |
734 | - |
735 | - def get_tasks_list(self): |
736 | - return self._tasks_list |
737 | - |
738 | - def generateTaskList(self): |
739 | - raise NotImplementedError() |
740 | - |
741 | - def create_new_task(self, title): |
742 | - raise NotImplementedError() |
743 | - |
744 | - def delete_task(self, task): |
745 | - raise NotImplementedError() |
746 | - |
747 | |
748 | === removed file 'GTG/plugins/evolution_sync/genericTask.py' |
749 | --- GTG/plugins/evolution_sync/genericTask.py 2010-01-31 10:16:17 +0000 |
750 | +++ GTG/plugins/evolution_sync/genericTask.py 1970-01-01 00:00:00 +0000 |
751 | @@ -1,68 +0,0 @@ |
752 | -# -*- coding: utf-8 -*- |
753 | -# Copyright (c) 2009 - Luca Invernizzi <invernizzi.l@gmail.com> |
754 | -# |
755 | -# This program is free software: you can redistribute it and/or modify it under |
756 | -# the terms of the GNU General Public License as published by the Free Software |
757 | -# Foundation, either version 3 of the License, or (at your option) any later |
758 | -# version. |
759 | -# |
760 | -# This program is distributed in the hope that it will be useful, but WITHOUT |
761 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
762 | -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
763 | -# details. |
764 | -# |
765 | -# You should have received a copy of the GNU General Public License along with |
766 | -# this program. If not, see <http://www.gnu.org/licenses/>. |
767 | - |
768 | - |
769 | -class GenericTask(object): |
770 | - """GenericTask is the abstract interface that represents a generic task.""" |
771 | - |
772 | - def __init__(self, proxy): |
773 | - self.__proxy = proxy |
774 | - |
775 | - def __str__(self): |
776 | - return "Task " + self.title + "(" + self.id + ")" |
777 | - |
778 | - def toString(self): |
779 | - return "Task:\n" + \ |
780 | - "\t - Title: " + self.title + "\n" + \ |
781 | - "\t - ID: " + self.id + "\n" + \ |
782 | - "\t - Modified: " + str(self.modified) + "\n" + \ |
783 | - "\t - Due: " + str(self.due_date) + "\n" |
784 | - |
785 | - def copy(self, task): |
786 | - #Minimizing the number of actions will allow a faster RTM plugin |
787 | - # (where GET is fast, but SET is slow) |
788 | - if self.title != task.title: |
789 | - self.title = task.title |
790 | - if self.text != task.text: |
791 | - self.text = task.text |
792 | - if self.status != task.status: |
793 | - self.status = task.status |
794 | - if self.due_date != task.due_date: |
795 | - self.due_date = task.due_date |
796 | - |
797 | - def get_proxy(self): |
798 | - return self.__proxy |
799 | - |
800 | - title = property(lambda self: self._get_title(), |
801 | - lambda self, arg: self._set_title(arg)) |
802 | - |
803 | - id = property(lambda self: self._get_id()) |
804 | - |
805 | - text = property(lambda self: self._get_text(), |
806 | - lambda self, arg: self._set_text(arg)) |
807 | - |
808 | - status = property(lambda self: self._get_status(), |
809 | - lambda self, arg: self._set_status(arg)) |
810 | - |
811 | - modified = property(lambda self: self._get_modified()) |
812 | - |
813 | - due_date = property(lambda self: self._get_due_date(), |
814 | - lambda self, arg: self._set_due_date(arg)) |
815 | - |
816 | - tags = property(lambda self: self._get_tags(), |
817 | - lambda self, arg: self._set_tags(arg)) |
818 | - |
819 | - self = property(lambda self: self) |
820 | |
821 | === removed file 'GTG/plugins/evolution_sync/gtgProxy.py' |
822 | --- GTG/plugins/evolution_sync/gtgProxy.py 2010-03-16 02:16:14 +0000 |
823 | +++ GTG/plugins/evolution_sync/gtgProxy.py 1970-01-01 00:00:00 +0000 |
824 | @@ -1,49 +0,0 @@ |
825 | -# -*- coding: utf-8 -*- |
826 | -# Copyright (c) 2009 - Luca Invernizzi <invernizzi.l@gmail.com> |
827 | -# |
828 | -# This program is free software: you can redistribute it and/or modify it under |
829 | -# the terms of the GNU General Public License as published by the Free Software |
830 | -# Foundation, either version 3 of the License, or (at your option) any later |
831 | -# version. |
832 | -# |
833 | -# This program is distributed in the hope that it will be useful, but WITHOUT |
834 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
835 | -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
836 | -# details. |
837 | -# |
838 | -# You should have received a copy of the GNU General Public License along with |
839 | -# this program. If not, see <http://www.gnu.org/licenses/>. |
840 | - |
841 | -from GTG.core.task import Task |
842 | -from GTG.plugins.evolution_sync.gtgTask import GtgTask |
843 | -from GTG.plugins.evolution_sync.genericProxy import GenericProxy |
844 | - |
845 | - |
846 | -class GtgProxy(GenericProxy): |
847 | - |
848 | - def __init__(self, plugin_api): |
849 | - super(GtgProxy, self).__init__() |
850 | - self.plugin_api = plugin_api |
851 | - self.requester = self.plugin_api.get_requester() |
852 | - |
853 | - def generateTaskList(self): |
854 | - self._tasks_list = [] |
855 | - requester = self.plugin_api.get_requester() |
856 | - statuses = [Task.STA_ACTIVE, Task.STA_DISMISSED, Task.STA_DONE] |
857 | - tasks = map(self.plugin_api.get_task, \ |
858 | - requester.get_tasks_list(status = statuses)) |
859 | - map(lambda task: self._tasks_list.append(GtgTask(task, \ |
860 | - self.plugin_api, self)), tasks) |
861 | - |
862 | - def create_new_task(self, title, never_seen_before = True): |
863 | - new_gtg_local_task = self.requester.new_task(newtask=never_seen_before) |
864 | - new_task = GtgTask(new_gtg_local_task, self.plugin_api, self) |
865 | - new_task.title = title |
866 | - self._tasks_list.append(new_task) |
867 | - return new_task |
868 | - |
869 | - def delete_task(self, task): |
870 | - #NOTE: delete_task wants the internal gtg id, not the uuid |
871 | - id = task.get_gtg_task().get_id() |
872 | - self.requester.delete_task(id) |
873 | - |
874 | |
875 | === removed file 'GTG/plugins/evolution_sync/gtgTask.py' |
876 | --- GTG/plugins/evolution_sync/gtgTask.py 2010-03-16 02:16:14 +0000 |
877 | +++ GTG/plugins/evolution_sync/gtgTask.py 1970-01-01 00:00:00 +0000 |
878 | @@ -1,91 +0,0 @@ |
879 | -# -*- coding: utf-8 -*- |
880 | -# Copyright (c) 2009 - Luca Invernizzi <invernizzi.l@gmail.com> |
881 | -# |
882 | -# This program is free software: you can redistribute it and/or modify it under |
883 | -# the terms of the GNU General Public License as published by the Free Software |
884 | -# Foundation, either version 3 of the License, or (at your option) any later |
885 | -# version. |
886 | -# |
887 | -# This program is distributed in the hope that it will be useful, but WITHOUT |
888 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
889 | -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
890 | -# details. |
891 | -# |
892 | -# You should have received a copy of the GNU General Public License along with |
893 | -# this program. If not, see <http://www.gnu.org/licenses/>. |
894 | - |
895 | -import datetime |
896 | - |
897 | -from GTG.tools.dates import NoDate, RealDate |
898 | -from GTG.plugins.evolution_sync.genericTask import GenericTask |
899 | - |
900 | -class GtgTask(GenericTask): |
901 | - |
902 | - def __init__(self, gtg_task, plugin_api, gtg_proxy): |
903 | - super(GtgTask, self).__init__(gtg_proxy) |
904 | - self._gtg_task = gtg_task |
905 | - self.plugin_api = plugin_api |
906 | - |
907 | - def _get_title(self): |
908 | - return self._gtg_task.get_title() |
909 | - |
910 | - def _set_title(self, title): |
911 | - self._gtg_task.set_title(title) |
912 | - |
913 | - def _get_id(self): |
914 | - return self._gtg_task.get_uuid() |
915 | - |
916 | - def _get_tags(self): |
917 | - raise NotImplemented() |
918 | - return self._gtg_task.get_tags() |
919 | - |
920 | - def _set_tags(self, tags): |
921 | - raise NotImplemented() |
922 | - #NOTE: isn't there a better mode than removing all tags? |
923 | - # need to add function in GTG/core/task.py |
924 | - old_tags = self.tags |
925 | - for tag in old_tags: |
926 | - self._gtg_task.remove_tag(tag) |
927 | - map(lambda tag: self._gtg_task.add_tag('@'+tag), tags) |
928 | - |
929 | - def _get_text(self): |
930 | - return self._gtg_task.get_excerpt() |
931 | - |
932 | - def _set_text(self, text): |
933 | - self._gtg_task.set_text(text) |
934 | - |
935 | - def _set_status(self, status): |
936 | - self._gtg_task.set_status(status) |
937 | - |
938 | - def _get_status(self): |
939 | - return self._gtg_task.get_status() |
940 | - |
941 | - def _get_due_date(self): |
942 | - due_date = self._gtg_task.get_due_date() |
943 | - if due_date == NoDate(): |
944 | - return None |
945 | - return due_date.to_py_date() |
946 | - |
947 | - def _set_due_date(self, due): |
948 | - if due == None: |
949 | - gtg_due = NoDate() |
950 | - else: |
951 | - gtg_due = RealDate(due) |
952 | - self._gtg_task.set_due_date(gtg_due) |
953 | - |
954 | - def _get_modified(self): |
955 | - modified = self._gtg_task.get_modified() |
956 | - if modified == None or modified == "": |
957 | - return None |
958 | - return self.__time_gtg_to_datetime(modified) |
959 | - |
960 | - def get_gtg_task(self): |
961 | - return self._gtg_task |
962 | - |
963 | - def __time_gtg_to_datetime(self, string): |
964 | - #FIXME: need to handle time with TIMEZONES! |
965 | - string = string.split('.')[0].split('Z')[0] |
966 | - if string.find('T') == -1: |
967 | - return datetime.datetime.strptime(string.split(".")[0], "%Y-%m-%d") |
968 | - return datetime.datetime.strptime(string.split(".")[0], \ |
969 | - "%Y-%m-%dT%H:%M:%S") |
970 | |
971 | === removed file 'GTG/plugins/evolution_sync/syncEngine.py' |
972 | --- GTG/plugins/evolution_sync/syncEngine.py 2010-03-16 02:16:14 +0000 |
973 | +++ GTG/plugins/evolution_sync/syncEngine.py 1970-01-01 00:00:00 +0000 |
974 | @@ -1,208 +0,0 @@ |
975 | -# -*- coding: utf-8 -*- |
976 | -# Copyright (c) 2009 - Luca Invernizzi <invernizzi.l@gmail.com> |
977 | -# |
978 | -# This program is free software: you can redistribute it and/or modify it under |
979 | -# the terms of the GNU General Public License as published by the Free Software |
980 | -# Foundation, either version 3 of the License, or (at your option) any later |
981 | -# version. |
982 | -# |
983 | -# This program is distributed in the hope that it will be useful, but WITHOUT |
984 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
985 | -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
986 | -# details. |
987 | -# |
988 | -# You should have received a copy of the GNU General Public License along with |
989 | -# this program. If not, see <http://www.gnu.org/licenses/>. |
990 | - |
991 | -import datetime |
992 | -from xdg.BaseDirectory import xdg_data_home |
993 | - |
994 | -from GTG.plugins.evolution_sync.gtgProxy import GtgProxy |
995 | -from GTG.plugins.evolution_sync.evolutionProxy import EvolutionProxy |
996 | - |
997 | -class TaskPair(object): |
998 | - def __init__(self, \ |
999 | - local_task, |
1000 | - remote_task): |
1001 | - self.local_id = local_task.id |
1002 | - self.remote_id = remote_task.id |
1003 | - self.__remote_synced_until = remote_task.modified |
1004 | - self.local_synced_until = local_task.modified |
1005 | - self.__remote_first_seen = datetime.datetime.now() |
1006 | - |
1007 | - self = property(lambda self: self) |
1008 | - |
1009 | - remote_synced_until = property (\ |
1010 | - lambda self: self.__get_remote_synced_until(),\ |
1011 | - lambda self, t: self.__set_remote_synced_until(t)) |
1012 | - |
1013 | - def __get_remote_synced_until(self): |
1014 | - if self.__remote_synced_until > self.__remote_first_seen: |
1015 | - return self.__remote_synced_until |
1016 | - else: |
1017 | - return self.__remote_first_seen |
1018 | - |
1019 | - def __set_remote_synced_until(self, datetime_object): |
1020 | - self.__remote_synced_until = datetime_object |
1021 | - |
1022 | - |
1023 | -class SyncEngine(object): |
1024 | - |
1025 | - def __init__(self, this_plugin): |
1026 | - super(SyncEngine, self).__init__() |
1027 | - self.this_plugin = this_plugin |
1028 | - self.plugin_api = this_plugin.plugin_api |
1029 | - self.local_proxy = GtgProxy(self.this_plugin.plugin_api) |
1030 | - self.remote_proxy = EvolutionProxy() |
1031 | - |
1032 | - def synchronize(self): |
1033 | - self.synchronizeWorker() |
1034 | - |
1035 | - def synchronizeWorker(self): |
1036 | - #Generate the two list of tasks from the local and remote task source |
1037 | - self.remote_proxy.generateTaskList() |
1038 | - self.local_proxy.generateTaskList() |
1039 | - remote_tasks = self.remote_proxy.get_tasks_list() |
1040 | - local_tasks = self.local_proxy.get_tasks_list() |
1041 | - |
1042 | - #Load the list of known links between tasks (tasks that are the same |
1043 | - # one saved in different task sources) |
1044 | - self.taskpairs = self.plugin_api.load_configuration_object( \ |
1045 | - "evolution-sync", \ |
1046 | - "taskpairs", \ |
1047 | - xdg_data_home) |
1048 | - if self.taskpairs == None: |
1049 | - #We have no previous knowledge of links, this must be the first |
1050 | - #attempt to synchronize. So, we try to infer some by |
1051 | - # linking tasks with the same title |
1052 | - self._link_same_title(local_tasks, remote_tasks) |
1053 | - |
1054 | - #We'll use some sets to see what is new and what was deleted |
1055 | - old_local_ids = set(map(lambda tp: tp.local_id, self.taskpairs)) |
1056 | - old_remote_ids = set(map(lambda tp: tp.remote_id, self.taskpairs)) |
1057 | - current_local_ids = set(map(lambda t: t.id, local_tasks)) |
1058 | - current_remote_ids = set(map(lambda t: t.id, remote_tasks)) |
1059 | - #Tasks that have been added |
1060 | - new_local_ids = current_local_ids.difference(old_local_ids) |
1061 | - new_remote_ids = current_remote_ids.difference(old_remote_ids) |
1062 | - #Tasks that have been deleted |
1063 | - deleted_local_ids = old_local_ids.difference(current_local_ids) |
1064 | - deleted_remote_ids = old_remote_ids.difference(current_remote_ids) |
1065 | - #Local tasks with the remote task still existing (could need to be |
1066 | - #updated) |
1067 | - updatable_local_ids = current_local_ids.difference(new_local_ids) |
1068 | - |
1069 | - #Add tasks to the remote proxy |
1070 | - [new_local_tasks, new_remote_tasks] = self._process_new_tasks(\ |
1071 | - new_local_ids,\ |
1072 | - local_tasks, \ |
1073 | - self.remote_proxy) |
1074 | - self._append_to_taskpairs(new_local_tasks, new_remote_tasks) |
1075 | - #Add tasks to the local proxy |
1076 | - [new_remote_tasks, new_local_tasks] = self._process_new_tasks(\ |
1077 | - new_remote_ids,\ |
1078 | - remote_tasks,\ |
1079 | - self.local_proxy) |
1080 | - self._append_to_taskpairs(new_local_tasks, new_remote_tasks) |
1081 | - |
1082 | - #Delete tasks from the remote proxy |
1083 | - taskpairs_deleted = filter(lambda tp: tp.local_id in deleted_local_ids,\ |
1084 | - self.taskpairs) |
1085 | - remote_ids_to_delete = map( lambda tp: tp.remote_id, taskpairs_deleted) |
1086 | - self._process_deleted_tasks(remote_ids_to_delete, remote_tasks,\ |
1087 | - self.remote_proxy) |
1088 | - map(lambda tp: self.taskpairs.remove(tp), taskpairs_deleted) |
1089 | - |
1090 | - #Delete tasks from the local proxy |
1091 | - taskpairs_deleted = filter(lambda tp: tp.remote_id in deleted_remote_ids,\ |
1092 | - self.taskpairs) |
1093 | - local_ids_to_delete = map( lambda tp: tp.local_id, taskpairs_deleted) |
1094 | - self._process_deleted_tasks(local_ids_to_delete, local_tasks, self.local_proxy) |
1095 | - map(lambda tp: self.taskpairs.remove(tp), taskpairs_deleted) |
1096 | - |
1097 | - #Update tasks |
1098 | - local_to_taskpair = self._list_to_dict(self.taskpairs, \ |
1099 | - "local_id", \ |
1100 | - "self") |
1101 | - local_id_to_task = self._list_to_dict(local_tasks, \ |
1102 | - "id", \ |
1103 | - "self") |
1104 | - remote_id_to_task = self._list_to_dict(remote_tasks, \ |
1105 | - "id", \ |
1106 | - "self") |
1107 | - for local_id in updatable_local_ids: |
1108 | - taskpair = local_to_taskpair[local_id] |
1109 | - local_task = local_id_to_task[local_id] |
1110 | - remote_task = remote_id_to_task[taskpair.remote_id] |
1111 | - local_was_updated = local_task.modified > \ |
1112 | - taskpair.local_synced_until |
1113 | - remote_was_updated = remote_task.modified > \ |
1114 | - taskpair.remote_synced_until |
1115 | - |
1116 | - if local_was_updated and remote_was_updated: |
1117 | - if local_task.modified > remote_task.modified: |
1118 | - remote_task.copy(local_task) |
1119 | - else: |
1120 | - #If the update time is the same one, we have to |
1121 | - # arbitrary decide which gets copied |
1122 | - local_task.copy(remote_task) |
1123 | - elif local_was_updated: |
1124 | - remote_task.copy(local_task) |
1125 | - elif remote_was_updated: |
1126 | - local_task.copy(remote_task) |
1127 | - |
1128 | - taskpair.remote_synced_until = remote_task.modified |
1129 | - taskpair.local_synced_until = local_task.modified |
1130 | - |
1131 | - #Lastly, save the list of known links |
1132 | - self.plugin_api.save_configuration_object( \ |
1133 | - "evolution-sync", \ |
1134 | - "taskpairs", \ |
1135 | - self.taskpairs, |
1136 | - xdg_data_home) |
1137 | - |
1138 | - def _append_to_taskpairs(self, local_tasks, remote_tasks): |
1139 | - for local, remote in zip(local_tasks, remote_tasks): |
1140 | - self.taskpairs.append(TaskPair( \ |
1141 | - local_task = local, |
1142 | - remote_task = remote)) |
1143 | - |
1144 | - def _task_ids_to_tasks(self, id_list, task_list): |
1145 | - #TODO: this is not the quickest way to do this |
1146 | - id_to_task = self._list_to_dict(task_list, "id", "self") |
1147 | - return map(lambda id: id_to_task[id], id_list) |
1148 | - |
1149 | - |
1150 | - |
1151 | - def _process_new_tasks(self, new_ids, all_tasks, proxy): |
1152 | - new_tasks = self._task_ids_to_tasks(new_ids, all_tasks) |
1153 | - created_tasks = [] |
1154 | - for task in new_tasks: |
1155 | - created_task = proxy.create_new_task(task.title) |
1156 | - created_task.copy(task) |
1157 | - created_tasks.append(created_task) |
1158 | - return new_tasks, created_tasks |
1159 | - |
1160 | - def _process_deleted_tasks(self, ids_to_remove, all_tasks, proxy): |
1161 | - tasks_to_remove = self._task_ids_to_tasks(ids_to_remove, all_tasks) |
1162 | - for task in tasks_to_remove: |
1163 | - proxy.delete_task(task) |
1164 | - |
1165 | - def _list_to_dict(self, source_list, fun1, fun2): |
1166 | - list1 = map(lambda elem: getattr(elem, fun1), source_list) |
1167 | - list2 = map(lambda elem: getattr(elem, fun2), source_list) |
1168 | - return dict(zip(list1, list2)) |
1169 | - |
1170 | - def _link_same_title(self, local_list, remote_list): |
1171 | - self.taskpairs = [] |
1172 | - local_title_to_task = self._list_to_dict(local_list, \ |
1173 | - "title", "self") |
1174 | - remote_title_to_task = self._list_to_dict(remote_list, \ |
1175 | - "title", "self") |
1176 | - local_titles = map(lambda t: t.title, local_list) |
1177 | - remote_titles = map(lambda t: t.title, remote_list) |
1178 | - common_titles = set(local_titles).intersection(set(remote_titles)) |
1179 | - for title in common_titles: |
1180 | - self.taskpairs.append(TaskPair( \ |
1181 | - local_task = local_title_to_task[title], |
1182 | - remote_task = remote_title_to_task[title])) |
1183 | |
1184 | === added file 'data/icons/hicolor/scalable/apps/backend_evolution.png' |
1185 | Binary files data/icons/hicolor/scalable/apps/backend_evolution.png 1970-01-01 00:00:00 +0000 and data/icons/hicolor/scalable/apps/backend_evolution.png 2010-09-03 23:47:40 +0000 differ |
This is the first time I've done something like this. How do I initiate this
for testing in my GTG?
-- Tina
On Thu, Aug 26, 2010 at 18:46, Luca Invernizzi <email address hidden>wrote:
> Luca Invernizzi has proposed merging lp:~gtg-user/gtg/evolution-backend /code.launchpad .net/~gtg- user/gtg/ evolution- backend/ +merge/ 33854 backend_ evolution. py' backend_ evolution. py 1970-01-01 00:00:00 +0000 backend_ evolution. py 2010-08-26 23:46:43 +0000 ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- www.gnu. org/licenses/>. ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- genericbackend import GenericBackend syncengine import SyncEngine, SyncMeme periodicimportb ackend import PeriodicImportB ackend interruptible import interruptible tags_from_ text EVOLUTION_ STATUS = \ ecal.ICAL_ STATUS_ CONFIRMED, ecal.ICAL_ STATUS_ COMPLETED, ecal.ICAL_ STATUS_ CANCELLED} TO_GTG_ STATUS = \ ecal.ICAL_ STATUS_ CONFIRMED: Task...
> into lp:gtg.
>
> Requested reviews:
> Gtg developers (gtg)
>
>
> Evolution backend (and removal of the plugin).
> This is not at the level of the other backends because the evolution api is
> not complete (that is, evolution start_date and categories are not exposed).
> However, it's the best we can have for now.
>
> With this merge, backends are covering all the synchronization plugins used
> to make.
> --
> https:/
> Your team Gtg users is subscribed to branch
> lp:~gtg-user/gtg/evolution-backend.
>
> === added file 'GTG/backends/
> --- GTG/backends/
> +++ GTG/backends/
> @@ -0,0 +1,348 @@
> +# -*- coding: utf-8 -*-
> +#
> -------
> +# Getting Things Gnome! - a personal organizer for the GNOME desktop
> +# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
> +#
> +# This program is free software: you can redistribute it and/or modify it
> under
> +# the terms of the GNU General Public License as published by the Free
> Software
> +# Foundation, either version 3 of the License, or (at your option) any
> later
> +# version.
> +#
> +# This program is distributed in the hope that it will be useful, but
> WITHOUT
> +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> FITNESS
> +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
> +# details.
> +#
> +# You should have received a copy of the GNU General Public License along
> with
> +# this program. If not, see <http://
> +#
> -------
> +
> +'''
> +Backend for storing/loading tasks in Evolution Tasks
> +'''
> +
> +import os
> +import time
> +import uuid
> +import datetime
> +import evolution
> +
> +from GTG.backends.
> +from GTG import _
> +from GTG.backends.
> +from GTG.backends.
> +from GTG.tools.dates import RealDate, NoDate
> +from GTG.core.task import Task
> +from GTG.tools.
> +from GTG.tools.logger import Log
> +from GTG.tools.tags import extract_
> +
> +#Dictionaries to translate GTG tasks in Evolution ones
> +_GTG_TO_
> + {Task.STA_ACTIVE: evolution.
> + Task.STA_DONE: evolution.
> + Task.STA_DISMISSED: evolution.
> +
> +_EVOLUTION_
> + {evolution.