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 protected]>wrote: > Luca Invernizzi has proposed merging lp:~gtg-user/gtg/evolution-backend > 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://code.launchpad.net/~gtg-user/gtg/evolution-backend/+merge/33854 > Your team Gtg users is subscribed to branch > lp:~gtg-user/gtg/evolution-backend. > > === added file 'GTG/backends/backend_evolution.py' > --- GTG/backends/backend_evolution.py 1970-01-01 00:00:00 +0000 > +++ GTG/backends/backend_evolution.py 2010-08-26 23:46:43 +0000 > @@ -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://www.gnu.org/licenses/>. > +# > ----------------------------------------------------------------------------- > + > +''' > +Backend for storing/loading tasks in Evolution Tasks > +''' > + > +import os > +import time > +import uuid > +import datetime > +import evolution > + > +from GTG.backends.genericbackend import GenericBackend > +from GTG import _ > +from GTG.backends.syncengine import SyncEngine, SyncMeme > +from GTG.backends.periodicimportbackend import PeriodicImportBackend > +from GTG.tools.dates import RealDate, NoDate > +from GTG.core.task import Task > +from GTG.tools.interruptible import interruptible > +from GTG.tools.logger import Log > +from GTG.tools.tags import extract_tags_from_text > + > +#Dictionaries to translate GTG tasks in Evolution ones > +_GTG_TO_EVOLUTION_STATUS = \ > + {Task.STA_ACTIVE: evolution.ecal.ICAL_STATUS_CONFIRMED, > + Task.STA_DONE: evolution.ecal.ICAL_STATUS_COMPLETED, > + Task.STA_DISMISSED: evolution.ecal.ICAL_STATUS_CANCELLED} > + > +_EVOLUTION_TO_GTG_STATUS = \ > + {evolution.ecal.ICAL_STATUS_CONFIRMED: Task.STA_ACTIVE, > + evolution.ecal.ICAL_STATUS_DRAFT: Task.STA_ACTIVE, > + evolution.ecal.ICAL_STATUS_FINAL: Task.STA_ACTIVE, > + evolution.ecal.ICAL_STATUS_INPROCESS: Task.STA_ACTIVE, > + evolution.ecal.ICAL_STATUS_NEEDSACTION: Task.STA_ACTIVE, > + evolution.ecal.ICAL_STATUS_NONE: Task.STA_ACTIVE, > + evolution.ecal.ICAL_STATUS_TENTATIVE: Task.STA_ACTIVE, > + evolution.ecal.ICAL_STATUS_X: Task.STA_ACTIVE, > + evolution.ecal.ICAL_STATUS_COMPLETED: Task.STA_DONE, > + evolution.ecal.ICAL_STATUS_CANCELLED: Task.STA_DISMISSED} > + > + > + > +class Backend(PeriodicImportBackend): > + ''' > + Evolution backend > + ''' > + > + > + _general_description = { \ > + GenericBackend.BACKEND_NAME: "backend_evolution", \ > + GenericBackend.BACKEND_HUMAN_NAME: _("Evolution tasks"), \ > + GenericBackend.BACKEND_AUTHORS: ["Luca Invernizzi"], \ > + GenericBackend.BACKEND_TYPE: GenericBackend.TYPE_READWRITE, > \ > + GenericBackend.BACKEND_DESCRIPTION: \ > + _("Lets you synchronize your GTG tasks with Evolution > tasks"),\ > + } > + > + _static_parameters = { \ > + "period": { \ > + GenericBackend.PARAM_TYPE: GenericBackend.TYPE_INT, \ > + GenericBackend.PARAM_DEFAULT_VALUE: 10, }, > + } > + > > +############################################################################### > +### Backend standard methods > ################################################## > > +############################################################################### > + > + def __init__(self, parameters): > + ''' > + See GenericBackend for an explanation of this function. > + Loads the saved state of the sync, if any > + ''' > + super(Backend, self).__init__(parameters) > + #loading the saved state of the synchronization, if any > + self.sync_engine_path = os.path.join('backends/evolution/', \ > + "sync_engine-" + self.get_id()) > + self.sync_engine = self._load_pickled_file(self.sync_engine_path, > \ > + SyncEngine()) > + #sets up the connection to the evolution api > + task_personal = evolution.ecal.list_task_sources()[0][1] > + self._evolution_tasks = evolution.ecal.open_calendar_source( \ > + task_personal, > + evolution.ecal.CAL_SOURCE_TYPE_TODO) > + > + def do_periodic_import(self): > + """ > + See PeriodicImportBackend for an explanation of this function. > + """ > + stored_evolution_task_ids = set(self.sync_engine.get_all_remote()) > + current_evolution_task_ids = set([task.get_uid() \ > + for task in self._evolution_tasks.get_all_objects()]) > + for evo_task_id in current_evolution_task_ids: > + #Adding and updating > + self.cancellation_point() > + self._process_evo_task(evo_task_id) > + > + for evo_task_id in stored_evolution_task_ids.difference(\ > + current_evolution_task_ids): > + #Removing the old ones > + self.cancellation_point() > + tid = self.sync_engine.get_local_id(evo_task_id) > + self.datastore.request_task_deletion(tid) > + try: > + self.sync_engine.break_relationship(remote_id = \ > + evo_task_id) > + except KeyError: > + pass > + > + def save_state(self): > + ''' > + See GenericBackend for an explanation of this function. > + ''' > + self._store_pickled_file(self.sync_engine_path, self.sync_engine) > + > > +############################################################################### > +### Process tasks > ############################################################# > > +############################################################################### > + > + @interruptible > + def remove_task(self, tid): > + ''' > + See GenericBackend for an explanation of this function. > + ''' > + try: > + evo_task_id = self.sync_engine.get_remote_id(tid) > + self._delete_evolution_task(self._evo_get_task(evo_task_id)) > + except KeyError: > + pass > + try: > + self.sync_engine.break_relationship(local_id = tid) > + except: > + pass > + > + @interruptible > + def set_task(self, task): > + ''' > + See GenericBackend for an explanation of this function. > + ''' > + tid = task.get_id() > + is_syncable = self._gtg_task_is_syncable_per_attached_tags(task) > + action, evo_task_id = self.sync_engine.analyze_local_id( > + tid, > + self.datastore.has_task, > + self._evo_has_task, > + is_syncable) > + Log.debug('GTG->Evo set task (%s, %s)' % (action, is_syncable)) > + > + if action == None: > + return > + > + if action == SyncEngine.ADD: > + evo_task = evolution.ecal.ECalComponent( \ > + ical = evolution.ecal.CAL_COMPONENT_TODO) > + self._evolution_tasks.add_object(evo_task) > + self._populate_evo_task(task, evo_task) > + meme = SyncMeme(task.get_modified(), > + self._evo_get_modified(evo_task), > + "GTG") > + self.sync_engine.record_relationship( \ > + local_id = tid, remote_id = evo_task.get_uid(), meme = > meme) > + > + elif action == SyncEngine.UPDATE: > + evo_task = self._evo_get_task(evo_task_id) > + meme = self.sync_engine.get_meme_from_local_id(task.get_id()) > + newest = meme.which_is_newest(task.get_modified(), > + self._evo_get_modified(evo_task)) > + if newest == "local": > + self._populate_evo_task(task, evo_task) > + meme.set_remote_last_modified( \ > + self._evo_get_modified(evo_task)) > + meme.set_local_last_modified(task.get_modified()) > + else: > + #we skip saving the state > + return > + > + elif action == SyncEngine.REMOVE: > + self.datastore.request_task_deletion(tid) > + try: > + self.sync_engine.break_relationship(local_id = tid) > + except KeyError: > + pass > + > + elif action == SyncEngine.LOST_SYNCABILITY: > + evo_task = self._evo_get_task(evo_task_id) > + self._exec_lost_syncability(tid, evo_task) > + self.save_state() > + > + def _process_evo_task(self, evo_task_id): > + ''' > + Takes an evolution task id and carries out the necessary > operations to > + refresh the sync state > + ''' > + self.cancellation_point() > + evo_task = self._evo_get_task(evo_task_id) > + is_syncable = self._evo_task_is_syncable(evo_task) > + action, tid = self.sync_engine.analyze_remote_id( \ > + evo_task_id, > + self.datastore.has_task, > + self._evo_has_task, > + is_syncable) > + Log.debug('GTG<-Evo set task (%s, %s)' % (action, is_syncable)) > + > + if action == SyncEngine.ADD: > + tid = str(uuid.uuid4()) > + task = self.datastore.task_factory(tid) > + self._populate_task(task, evo_task) > + meme = SyncMeme(task.get_modified(), > + self._evo_get_modified(evo_task), > + "GTG") > + self.sync_engine.record_relationship(local_id = tid, > + remote_id = evo_task_id, > + meme = meme) > + self.datastore.push_task(task) > + > + elif action == SyncEngine.UPDATE: > + task = self.datastore.get_task(tid) > + meme = self.sync_engine.get_meme_from_remote_id(evo_task_id) > + newest = meme.which_is_newest(task.get_modified(), > + self._evo_get_modified(evo_task)) > + if newest == "remote": > + with self.datastore.get_backend_mutex(): > + self._populate_task(task, evo_task) > + meme.set_remote_last_modified( \ > + self._evo_get_modified(evo_task)) > + meme.set_local_last_modified(task.get_modified()) > + > + elif action == SyncEngine.REMOVE: > + return > + try: > + evo_task = self._evo_get_task(evo_task_id) > + self._delete_evolution_task(evo_task) > + self.sync_engine.break_relationship(remote_id = evo_task) > + except KeyError: > + pass > + > + elif action == SyncEngine.LOST_SYNCABILITY: > + self._exec_lost_syncability(tid, evo_task) > + self.save_state() > + > > +############################################################################### > +### Helper methods > ############################################################ > > +############################################################################### > + > + def _evo_has_task(self, evo_task_id): > + '''Returns true if Evolution has that task''' > + return bool(self._evo_get_task(evo_task_id)) > + > + def _evo_get_task(self, evo_task_id): > + '''Returns an Evolution task, given its uid''' > + return self._evolution_tasks.get_object(evo_task_id, "") > + > + def _evo_get_modified(self, evo_task): > + '''Returns the modified time of an Evolution task''' > + return datetime.datetime.fromtimestamp(evo_task.get_modified()) > + > + def _delete_evolution_task(self, evo_task): > + '''Deletes an Evolution task, given the task object''' > + self._evolution_tasks.remove_object(evo_task) > + self._evolution_tasks.update_object(evo_task) > + > + def _populate_task(self, task, evo_task): > + ''' > + Updates the attributes of a GTG task copying the ones of an > Evolution > + task > + ''' > + task.set_title(evo_task.get_summary()) > + text = evo_task.get_description() > + if text == None: > + text = "" > + task.set_text(text) > + due_date_timestamp = evo_task.get_due() > + if isinstance(due_date_timestamp, (int, float)): > + datetime_due_date = RealDate(datetime.datetime.fromtimestamp(\ > + due_date_timestamp + time.timezone).date()) > + else: > + datetime_due_date = NoDate() > + task.set_due_date(datetime_due_date) > + status = evo_task.get_status() > + if task.get_status() != _EVOLUTION_TO_GTG_STATUS[status]: > + task.set_status(_EVOLUTION_TO_GTG_STATUS[status]) > + task.set_only_these_tags(extract_tags_from_text(text)) > + > + def _populate_evo_task(self, task, evo_task): > + evo_task.set_summary(task.get_title()) > + text = task.get_excerpt(strip_tags = True, strip_subtasks = True) > + if evo_task.get_description() != text: > + evo_task.set_description(text) > + due_date = task.get_due_date() > + if isinstance(due_date, NoDate): > + #FIXME: how to unset due dates > + evo_task.set_due(None) > + print "DUE DATE UNSETTING UNSUPPORTED" > + else: > + #FIXME: this is strange - review > + due_date_for_evo = int(time.mktime(\ > + due_date.to_py_date().timetuple()) - \ > + time.timezone) - time.timezone > + evo_task.set_due(due_date_for_evo) > + status = task.get_status() > + if _EVOLUTION_TO_GTG_STATUS[evo_task.get_status()] != status: > + evo_task.set_status(_GTG_TO_EVOLUTION_STATUS[status]) > + self._evolution_tasks.update_object(evo_task) > + > + def _exec_lost_syncability(self, tid, evo_task): > + ''' > + Executed when a relationship between tasks loses its syncability > + property. See SyncEngine for an explanation of that. > + This function finds out which object is the original one > + and which is the copy, and deletes the copy. > + ''' > + meme = self.sync_engine.get_meme_from_local_id(tid) > + self.sync_engine.break_relationship(local_id = tid) > + if meme.get_origin() == "GTG": > + evo_task = self._evo_get_task(evo_task.get_uid()) > + self._delete_evolution_task(evo_task) > + else: > + self.datastore.request_task_deletion(tid) > + > + def _evo_task_is_syncable(self, evo_task): > + ''' > + Returns True if this Evolution task should be synced into GTG > tasks. > + > + @param evo_task: an Evolution task > + @returns Boolean > + ''' > + attached_tags = set(self.get_attached_tags()) > + if GenericBackend.ALLTASKS_TAG in attached_tags: > + return True > + evo_tags = set(extract_tags_from_text(evo_task.get_description())) > + return evo_task.is_disjoint(attached_tags) > + > > === removed file 'GTG/plugins/evolution-sync.gtg-plugin' > --- GTG/plugins/evolution-sync.gtg-plugin 2010-07-29 10:36:24 +0000 > +++ GTG/plugins/evolution-sync.gtg-plugin 1970-01-01 00:00:00 +0000 > @@ -1,9 +0,0 @@ > -[GTG Plugin] > -Module=evolution_sync > -Name=Evolution > -Short-description="Plugin for synchronising GTG with Gnome Evolution > Tasks." > -Description="""Plugin for synchronising GTG with Gnome Evolution Tasks""" > -Authors='Luca Invernizzi <[email protected]>' > -Version=0.1.1 > -Dependencies=evolution, > -Enabled=False > > === removed directory 'GTG/plugins/evolution_sync' > === removed file 'GTG/plugins/evolution_sync/__init__.py' > --- GTG/plugins/evolution_sync/__init__.py 2010-03-16 02:16:14 +0000 > +++ GTG/plugins/evolution_sync/__init__.py 1970-01-01 00:00:00 +0000 > @@ -1,20 +0,0 @@ > -# -*- coding: utf-8 -*- > -# Copyright (c) 2009 - Luca Invernizzi <[email protected]> > -# > -# 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://www.gnu.org/licenses/>. > - > -from GTG.plugins.evolution_sync.evolutionSync import EvolutionSync > - > -#needed to keep pyflakes quiet > -if False == True: EvolutionSync() > > === removed file 'GTG/plugins/evolution_sync/evolutionProxy.py' > --- GTG/plugins/evolution_sync/evolutionProxy.py 2010-03-16 02:16:14 > +0000 > +++ GTG/plugins/evolution_sync/evolutionProxy.py 1970-01-01 00:00:00 > +0000 > @@ -1,73 +0,0 @@ > -# -*- coding: utf-8 -*- > -# Copyright (c) 2009 - Luca Invernizzi <[email protected]> > -# > -# 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://www.gnu.org/licenses/>. > - > -import evolution > - > -from GTG.core.task import Task > -from GTG.plugins.evolution_sync.evolutionTask import EvolutionTask > -from GTG.plugins.evolution_sync.genericProxy import GenericProxy > - > - > -class EvolutionProxy(GenericProxy): > - > - __GTG_STATUSES = [Task.STA_ACTIVE, > - Task.STA_DONE, > - Task.STA_DISMISSED] > - > - __EVO_STATUSES = [evolution.ecal.ICAL_STATUS_CONFIRMED, > - evolution.ecal.ICAL_STATUS_COMPLETED, > - evolution.ecal.ICAL_STATUS_CANCELLED] > - > - def __init__(self): > - super(EvolutionProxy, self).__init__() > - > - def generateTaskList(self): > - task_personal = evolution.ecal.list_task_sources()[0][1] > - self._evolution_tasks = > evolution.ecal.open_calendar_source(task_personal, > - evolution.ecal.CAL_SOURCE_TYPE_TODO) > - self._gtg_to_evo_status = dict(zip(self.__GTG_STATUSES, > - self.__EVO_STATUSES)) > - self._evo_to_gtg_status = dict(zip(self.__EVO_STATUSES, > - self.__GTG_STATUSES)) > - #Need to find a solution for the statuses GTG doesn't expect: > - for evo_status in [evolution.ecal.ICAL_STATUS_DRAFT, > - evolution.ecal.ICAL_STATUS_FINAL, > - evolution.ecal.ICAL_STATUS_INPROCESS, > - evolution.ecal.ICAL_STATUS_NEEDSACTION, > - evolution.ecal.ICAL_STATUS_NONE, > - evolution.ecal.ICAL_STATUS_TENTATIVE, > - evolution.ecal.ICAL_STATUS_X]: > - self._evo_to_gtg_status[evo_status] = Task.STA_ACTIVE > - self._tasks_list = [] > - for task in self._evolution_tasks.get_all_objects(): > - self._tasks_list.append(EvolutionTask(task, self)) > - > - def create_new_task(self, title): > - task = > evolution.ecal.ECalComponent(ical=evolution.ecal.CAL_COMPONENT_TODO) > - self._evolution_tasks.add_object(task) > - new_task = EvolutionTask(task, self) > - new_task.title = title > - self._tasks_list.append(new_task) > - return new_task > - > - def delete_task(self, task): > - evo_task = task.get_evolution_task() > - self._evolution_tasks.remove_object(evo_task) > - self._evolution_tasks.update_object(evo_task) > - > - def update_task(self, task): > - evo_task = task.get_evolution_task() > - self._evolution_tasks.update_object(evo_task) > > === removed file 'GTG/plugins/evolution_sync/evolutionSync.py' > --- GTG/plugins/evolution_sync/evolutionSync.py 2010-03-16 02:16:14 +0000 > +++ GTG/plugins/evolution_sync/evolutionSync.py 1970-01-01 00:00:00 +0000 > @@ -1,45 +0,0 @@ > -# -*- coding: utf-8 -*- > -# Copyright (c) 2009 - Luca Invernizzi <[email protected]> > -# - Paulo Cabido <[email protected]> (example > file) > -# > -# 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://www.gnu.org/licenses/>. > - > -import gtk > -from threading import Thread > - > -from GTG import _ > -from GTG.plugins.evolution_sync.syncEngine import SyncEngine > - > -class EvolutionSync: > - > - def __init__(self): > - #drop down menu > - self.menu_item = gtk.MenuItem(_("Synchronize with Evolution")) > - self.menu_item.connect('activate', self.onTesteMenu) > - > - def activate(self, plugin_api): > - self.plugin_api = plugin_api > - # add a menu item to the menu bar > - plugin_api.add_menu_item(self.menu_item) > - self.sync_engine = SyncEngine(self) > - > - def deactivate(self, plugin_api): > - plugin_api.remove_menu_item(self.menu_item) > - > - def onTesteMenu(self, widget): > - self.worker_thread = Thread(target = \ > - self.sync_engine.synchronize).start() > - > - def onTaskOpened(self, plugin_api): > - pass > > === removed file 'GTG/plugins/evolution_sync/evolutionTask.py' > --- GTG/plugins/evolution_sync/evolutionTask.py 2010-03-16 02:16:14 +0000 > +++ GTG/plugins/evolution_sync/evolutionTask.py 1970-01-01 00:00:00 +0000 > @@ -1,110 +0,0 @@ > -# -*- coding: utf-8 -*- > -# Copyright (c) 2009 - Luca Invernizzi <[email protected]> > -# > -# 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://www.gnu.org/licenses/>. > - > -import time > -import datetime > - > -from GTG.plugins.evolution_sync.genericTask import GenericTask > - > - > -class EvolutionTask(GenericTask): > - > - def __init__(self, evo_task, evolution_proxy): > - super(EvolutionTask, self).__init__(evolution_proxy) > - self._evo_task = evo_task > - > - def _get_title(self): > - return self._evo_task.get_summary() > - > - def _set_title(self, title): > - self._evo_task.set_summary(title) > - self.get_proxy().update_task(self) > - > - def _get_id(self): > - return self._evo_task.get_uid() > - > - def _get_tags(self): > - #We could use Evolution's "Categories" as tags > - raise NotImplementedError() > - > - def _set_tags(self, tags): > - raise NotImplementedError() > - self.get_proxy().update_task(self) > - > - def _get_text(self): > - desc = self._evo_task.get_description() > - if desc == None: > - return "" > - return desc > - > - def _set_text(self, text): > - self._evo_task.set_description(text) > - self.get_proxy().update_task(self) > - > - def _set_status(self, status): > - #Since Evolution's statuses are fare more than GTG's, > - # we need to check that the current status is not one of the > various > - # statuses translated in the same gtg status, passed by argument. > - # This way, we avoid to force a status change when it's not needed > - # (and not wanted) > - current_status_in_gtg_terms = > self.get_proxy()._evo_to_gtg_status[\ > - > self._evo_task.get_status()] > - if current_status_in_gtg_terms != status: > - new_evo_status = self.get_proxy()._gtg_to_evo_status[status] > - self._evo_task.set_status(new_evo_status) > - self.get_proxy().update_task(self) > - > - def _get_status(self): > - status = self._evo_task.get_status() > - return self.get_proxy()._evo_to_gtg_status[status] > - > - def _get_due_date(self): > - due = self._evo_task.get_due() > - if isinstance(due, (int, float)): > - return self.__time_evo_to_date(due) > - > - def _set_due_date(self, due): > - if due == None: > - #TODO: I haven't find a way to reset the due date > - # We could copy the task, but that would lose all the > attributes > - # currently not supported by the library used (and they're a > lot) > - pass > - else: > - self._evo_task.set_due(self.__time_date_to_evo(due)) > - self.get_proxy().update_task(self) > - > - def _get_modified(self): > - return self.__time_evo_to_datetime(self._evo_task.get_modified()) > - > - def __time_evo_to_date(self, timestamp): > - return datetime.datetime.fromtimestamp(timestamp + > time.timezone).date() > - > - def __time_evo_to_datetime(self, timestamp): > - return datetime.datetime.fromtimestamp(timestamp) > - > - def __time_datetime_to_evo(self, timeobject): > - return int(time.mktime(timeobject.timetuple())) > - > - def __time_date_to_evo(self, timeobject): > - #NOTE: need to substract the timezone to avoid the "one day before > bug > - # (at the airport => no internet now, need to fill bug number in) > - #Explanation: gtg date format is converted to datetime in > date/00:00 in > - # local time, and time.mktime considers that when converting to > UNIX > - # time. Evolution, however, doesn't convert back to local time. > - return self.__time_datetime_to_evo(timeobject) - time.timezone > - > - def get_evolution_task(self): > - return self._evo_task > > === removed file 'GTG/plugins/evolution_sync/genericProxy.py' > --- GTG/plugins/evolution_sync/genericProxy.py 2010-01-31 10:16:17 +0000 > +++ GTG/plugins/evolution_sync/genericProxy.py 1970-01-01 00:00:00 +0000 > @@ -1,34 +0,0 @@ > -# -*- coding: utf-8 -*- > -# Copyright (c) 2009 - Luca Invernizzi <[email protected]> > -# > -# 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://www.gnu.org/licenses/>. > - > -class GenericProxy(object): > - > - def __init__(self): > - super(GenericProxy, self).__init__() > - self._tasks_list = [] > - > - def get_tasks_list(self): > - return self._tasks_list > - > - def generateTaskList(self): > - raise NotImplementedError() > - > - def create_new_task(self, title): > - raise NotImplementedError() > - > - def delete_task(self, task): > - raise NotImplementedError() > - > > === removed file 'GTG/plugins/evolution_sync/genericTask.py' > --- GTG/plugins/evolution_sync/genericTask.py 2010-01-31 10:16:17 +0000 > +++ GTG/plugins/evolution_sync/genericTask.py 1970-01-01 00:00:00 +0000 > @@ -1,68 +0,0 @@ > -# -*- coding: utf-8 -*- > -# Copyright (c) 2009 - Luca Invernizzi <[email protected]> > -# > -# 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://www.gnu.org/licenses/>. > - > - > -class GenericTask(object): > - """GenericTask is the abstract interface that represents a generic > task.""" > - > - def __init__(self, proxy): > - self.__proxy = proxy > - > - def __str__(self): > - return "Task " + self.title + "(" + self.id + ")" > - > - def toString(self): > - return "Task:\n" + \ > - "\t - Title: " + self.title + "\n" + \ > - "\t - ID: " + self.id + "\n" + \ > - "\t - Modified: " + str(self.modified) + "\n" + \ > - "\t - Due: " + str(self.due_date) + "\n" > - > - def copy(self, task): > - #Minimizing the number of actions will allow a faster RTM plugin > - # (where GET is fast, but SET is slow) > - if self.title != task.title: > - self.title = task.title > - if self.text != task.text: > - self.text = task.text > - if self.status != task.status: > - self.status = task.status > - if self.due_date != task.due_date: > - self.due_date = task.due_date > - > - def get_proxy(self): > - return self.__proxy > - > - title = property(lambda self: self._get_title(), > - lambda self, arg: self._set_title(arg)) > - > - id = property(lambda self: self._get_id()) > - > - text = property(lambda self: self._get_text(), > - lambda self, arg: self._set_text(arg)) > - > - status = property(lambda self: self._get_status(), > - lambda self, arg: self._set_status(arg)) > - > - modified = property(lambda self: self._get_modified()) > - > - due_date = property(lambda self: self._get_due_date(), > - lambda self, arg: self._set_due_date(arg)) > - > - tags = property(lambda self: self._get_tags(), > - lambda self, arg: self._set_tags(arg)) > - > - self = property(lambda self: self) > > === removed file 'GTG/plugins/evolution_sync/gtgProxy.py' > --- GTG/plugins/evolution_sync/gtgProxy.py 2010-03-16 02:16:14 +0000 > +++ GTG/plugins/evolution_sync/gtgProxy.py 1970-01-01 00:00:00 +0000 > @@ -1,49 +0,0 @@ > -# -*- coding: utf-8 -*- > -# Copyright (c) 2009 - Luca Invernizzi <[email protected]> > -# > -# 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://www.gnu.org/licenses/>. > - > -from GTG.core.task import Task > -from GTG.plugins.evolution_sync.gtgTask import GtgTask > -from GTG.plugins.evolution_sync.genericProxy import GenericProxy > - > - > -class GtgProxy(GenericProxy): > - > - def __init__(self, plugin_api): > - super(GtgProxy, self).__init__() > - self.plugin_api = plugin_api > - self.requester = self.plugin_api.get_requester() > - > - def generateTaskList(self): > - self._tasks_list = [] > - requester = self.plugin_api.get_requester() > - statuses = [Task.STA_ACTIVE, Task.STA_DISMISSED, Task.STA_DONE] > - tasks = map(self.plugin_api.get_task, \ > - requester.get_tasks_list(status = statuses)) > - map(lambda task: self._tasks_list.append(GtgTask(task, \ > - self.plugin_api, self)), tasks) > - > - def create_new_task(self, title, never_seen_before = True): > - new_gtg_local_task = > self.requester.new_task(newtask=never_seen_before) > - new_task = GtgTask(new_gtg_local_task, self.plugin_api, self) > - new_task.title = title > - self._tasks_list.append(new_task) > - return new_task > - > - def delete_task(self, task): > - #NOTE: delete_task wants the internal gtg id, not the uuid > - id = task.get_gtg_task().get_id() > - self.requester.delete_task(id) > - > > === removed file 'GTG/plugins/evolution_sync/gtgTask.py' > --- GTG/plugins/evolution_sync/gtgTask.py 2010-03-16 02:16:14 +0000 > +++ GTG/plugins/evolution_sync/gtgTask.py 1970-01-01 00:00:00 +0000 > @@ -1,91 +0,0 @@ > -# -*- coding: utf-8 -*- > -# Copyright (c) 2009 - Luca Invernizzi <[email protected]> > -# > -# 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://www.gnu.org/licenses/>. > - > -import datetime > - > -from GTG.tools.dates import NoDate, RealDate > -from GTG.plugins.evolution_sync.genericTask import GenericTask > - > -class GtgTask(GenericTask): > - > - def __init__(self, gtg_task, plugin_api, gtg_proxy): > - super(GtgTask, self).__init__(gtg_proxy) > - self._gtg_task = gtg_task > - self.plugin_api = plugin_api > - > - def _get_title(self): > - return self._gtg_task.get_title() > - > - def _set_title(self, title): > - self._gtg_task.set_title(title) > - > - def _get_id(self): > - return self._gtg_task.get_uuid() > - > - def _get_tags(self): > - raise NotImplemented() > - return self._gtg_task.get_tags() > - > - def _set_tags(self, tags): > - raise NotImplemented() > - #NOTE: isn't there a better mode than removing all tags? > - # need to add function in GTG/core/task.py > - old_tags = self.tags > - for tag in old_tags: > - self._gtg_task.remove_tag(tag) > - map(lambda tag: self._gtg_task.add_tag('@'+tag), tags) > - > - def _get_text(self): > - return self._gtg_task.get_excerpt() > - > - def _set_text(self, text): > - self._gtg_task.set_text(text) > - > - def _set_status(self, status): > - self._gtg_task.set_status(status) > - > - def _get_status(self): > - return self._gtg_task.get_status() > - > - def _get_due_date(self): > - due_date = self._gtg_task.get_due_date() > - if due_date == NoDate(): > - return None > - return due_date.to_py_date() > - > - def _set_due_date(self, due): > - if due == None: > - gtg_due = NoDate() > - else: > - gtg_due = RealDate(due) > - self._gtg_task.set_due_date(gtg_due) > - > - def _get_modified(self): > - modified = self._gtg_task.get_modified() > - if modified == None or modified == "": > - return None > - return self.__time_gtg_to_datetime(modified) > - > - def get_gtg_task(self): > - return self._gtg_task > - > - def __time_gtg_to_datetime(self, string): > - #FIXME: need to handle time with TIMEZONES! > - string = string.split('.')[0].split('Z')[0] > - if string.find('T') == -1: > - return datetime.datetime.strptime(string.split(".")[0], > "%Y-%m-%d") > - return datetime.datetime.strptime(string.split(".")[0], \ > - "%Y-%m-%dT%H:%M:%S") > > === removed file 'GTG/plugins/evolution_sync/syncEngine.py' > --- GTG/plugins/evolution_sync/syncEngine.py 2010-03-16 02:16:14 +0000 > +++ GTG/plugins/evolution_sync/syncEngine.py 1970-01-01 00:00:00 +0000 > @@ -1,208 +0,0 @@ > -# -*- coding: utf-8 -*- > -# Copyright (c) 2009 - Luca Invernizzi <[email protected]> > -# > -# 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://www.gnu.org/licenses/>. > - > -import datetime > -from xdg.BaseDirectory import xdg_data_home > - > -from GTG.plugins.evolution_sync.gtgProxy import GtgProxy > -from GTG.plugins.evolution_sync.evolutionProxy import EvolutionProxy > - > -class TaskPair(object): > - def __init__(self, \ > - local_task, > - remote_task): > - self.local_id = local_task.id > - self.remote_id = remote_task.id > - self.__remote_synced_until = remote_task.modified > - self.local_synced_until = local_task.modified > - self.__remote_first_seen = datetime.datetime.now() > - > - self = property(lambda self: self) > - > - remote_synced_until = property (\ > - lambda self: self.__get_remote_synced_until(),\ > - lambda self, t: self.__set_remote_synced_until(t)) > - > - def __get_remote_synced_until(self): > - if self.__remote_synced_until > self.__remote_first_seen: > - return self.__remote_synced_until > - else: > - return self.__remote_first_seen > - > - def __set_remote_synced_until(self, datetime_object): > - self.__remote_synced_until = datetime_object > - > - > -class SyncEngine(object): > - > - def __init__(self, this_plugin): > - super(SyncEngine, self).__init__() > - self.this_plugin = this_plugin > - self.plugin_api = this_plugin.plugin_api > - self.local_proxy = GtgProxy(self.this_plugin.plugin_api) > - self.remote_proxy = EvolutionProxy() > - > - def synchronize(self): > - self.synchronizeWorker() > - > - def synchronizeWorker(self): > - #Generate the two list of tasks from the local and remote task > source > - self.remote_proxy.generateTaskList() > - self.local_proxy.generateTaskList() > - remote_tasks = self.remote_proxy.get_tasks_list() > - local_tasks = self.local_proxy.get_tasks_list() > - > - #Load the list of known links between tasks (tasks that are the > same > - # one saved in different task sources) > - self.taskpairs = self.plugin_api.load_configuration_object( \ > - "evolution-sync", \ > - "taskpairs", \ > - xdg_data_home) > - if self.taskpairs == None: > - #We have no previous knowledge of links, this must be the first > - #attempt to synchronize. So, we try to infer some by > - # linking tasks with the same title > - self._link_same_title(local_tasks, remote_tasks) > - > - #We'll use some sets to see what is new and what was deleted > - old_local_ids = set(map(lambda tp: tp.local_id, self.taskpairs)) > - old_remote_ids = set(map(lambda tp: tp.remote_id, self.taskpairs)) > - current_local_ids = set(map(lambda t: t.id, local_tasks)) > - current_remote_ids = set(map(lambda t: t.id, remote_tasks)) > - #Tasks that have been added > - new_local_ids = current_local_ids.difference(old_local_ids) > - new_remote_ids = current_remote_ids.difference(old_remote_ids) > - #Tasks that have been deleted > - deleted_local_ids = old_local_ids.difference(current_local_ids) > - deleted_remote_ids = old_remote_ids.difference(current_remote_ids) > - #Local tasks with the remote task still existing (could need to be > - #updated) > - updatable_local_ids = current_local_ids.difference(new_local_ids) > - > - #Add tasks to the remote proxy > - [new_local_tasks, new_remote_tasks] = self._process_new_tasks(\ > - new_local_ids,\ > - local_tasks, \ > - self.remote_proxy) > - self._append_to_taskpairs(new_local_tasks, new_remote_tasks) > - #Add tasks to the local proxy > - [new_remote_tasks, new_local_tasks] = self._process_new_tasks(\ > - new_remote_ids,\ > - remote_tasks,\ > - self.local_proxy) > - self._append_to_taskpairs(new_local_tasks, new_remote_tasks) > - > - #Delete tasks from the remote proxy > - taskpairs_deleted = filter(lambda tp: tp.local_id in > deleted_local_ids,\ > - self.taskpairs) > - remote_ids_to_delete = map( lambda tp: tp.remote_id, > taskpairs_deleted) > - self._process_deleted_tasks(remote_ids_to_delete, remote_tasks,\ > - self.remote_proxy) > - map(lambda tp: self.taskpairs.remove(tp), taskpairs_deleted) > - > - #Delete tasks from the local proxy > - taskpairs_deleted = filter(lambda tp: tp.remote_id in > deleted_remote_ids,\ > - self.taskpairs) > - local_ids_to_delete = map( lambda tp: tp.local_id, > taskpairs_deleted) > - self._process_deleted_tasks(local_ids_to_delete, local_tasks, > self.local_proxy) > - map(lambda tp: self.taskpairs.remove(tp), taskpairs_deleted) > - > - #Update tasks > - local_to_taskpair = self._list_to_dict(self.taskpairs, \ > - "local_id", \ > - "self") > - local_id_to_task = self._list_to_dict(local_tasks, \ > - "id", \ > - "self") > - remote_id_to_task = self._list_to_dict(remote_tasks, \ > - "id", \ > - "self") > - for local_id in updatable_local_ids: > - taskpair = local_to_taskpair[local_id] > - local_task = local_id_to_task[local_id] > - remote_task = remote_id_to_task[taskpair.remote_id] > - local_was_updated = local_task.modified > \ > - taskpair.local_synced_until > - remote_was_updated = remote_task.modified > \ > - taskpair.remote_synced_until > - > - if local_was_updated and remote_was_updated: > - if local_task.modified > remote_task.modified: > - remote_task.copy(local_task) > - else: > - #If the update time is the same one, we have to > - # arbitrary decide which gets copied > - local_task.copy(remote_task) > - elif local_was_updated: > - remote_task.copy(local_task) > - elif remote_was_updated: > - local_task.copy(remote_task) > - > - taskpair.remote_synced_until = remote_task.modified > - taskpair.local_synced_until = local_task.modified > - > - #Lastly, save the list of known links > - self.plugin_api.save_configuration_object( \ > - "evolution-sync", \ > - "taskpairs", \ > - self.taskpairs, > - xdg_data_home) > - > - def _append_to_taskpairs(self, local_tasks, remote_tasks): > - for local, remote in zip(local_tasks, remote_tasks): > - self.taskpairs.append(TaskPair( \ > - local_task = local, > - remote_task = remote)) > - > - def _task_ids_to_tasks(self, id_list, task_list): > - #TODO: this is not the quickest way to do this > - id_to_task = self._list_to_dict(task_list, "id", "self") > - return map(lambda id: id_to_task[id], id_list) > - > - > - > - def _process_new_tasks(self, new_ids, all_tasks, proxy): > - new_tasks = self._task_ids_to_tasks(new_ids, all_tasks) > - created_tasks = [] > - for task in new_tasks: > - created_task = proxy.create_new_task(task.title) > - created_task.copy(task) > - created_tasks.append(created_task) > - return new_tasks, created_tasks > - > - def _process_deleted_tasks(self, ids_to_remove, all_tasks, proxy): > - tasks_to_remove = self._task_ids_to_tasks(ids_to_remove, > all_tasks) > - for task in tasks_to_remove: > - proxy.delete_task(task) > - > - def _list_to_dict(self, source_list, fun1, fun2): > - list1 = map(lambda elem: getattr(elem, fun1), source_list) > - list2 = map(lambda elem: getattr(elem, fun2), source_list) > - return dict(zip(list1, list2)) > - > - def _link_same_title(self, local_list, remote_list): > - self.taskpairs = [] > - local_title_to_task = self._list_to_dict(local_list, \ > - "title", "self") > - remote_title_to_task = self._list_to_dict(remote_list, \ > - "title", "self") > - local_titles = map(lambda t: t.title, local_list) > - remote_titles = map(lambda t: t.title, remote_list) > - common_titles = set(local_titles).intersection(set(remote_titles)) > - for title in common_titles: > - self.taskpairs.append(TaskPair( \ > - local_task = local_title_to_task[title], > - remote_task = > remote_title_to_task[title])) > > === added file 'data/icons/hicolor/scalable/apps/backend_evolution.png' > 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-08-26 > 23:46:43 +0000 differ > > _______________________________________________ > Mailing list: https://launchpad.net/~gtg-user > Post to : [email protected] > Unsubscribe : https://launchpad.net/~gtg-user > More help : https://help.launchpad.net/ListHelp > > -- https://code.launchpad.net/~gtg-user/gtg/evolution-backend/+merge/33854 Your team Gtg developers is requested to review the proposed merge of lp:~gtg-user/gtg/evolution-backend into lp:gtg. _______________________________________________ Mailing list: https://launchpad.net/~gtg Post to : [email protected] Unsubscribe : https://launchpad.net/~gtg More help : https://help.launchpad.net/ListHelp

