gtg-user team mailing list archive
-
gtg-user team
-
Mailing list archive
-
Message #00316
Re: [Merge] lp:~gtg-user/gtg/evolution-backend into lp:gtg
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 <invernizzi.l@xxxxxxxxx>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 <invernizzi.l@xxxxxxxxx>'
> -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 <invernizzi.l@xxxxxxxxx>
> -#
> -# 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 <invernizzi.l@xxxxxxxxx>
> -#
> -# 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 <invernizzi.l@xxxxxxxxx>
> -# - Paulo Cabido <paulo.cabido@xxxxxxxxx> (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 <invernizzi.l@xxxxxxxxx>
> -#
> -# 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 <invernizzi.l@xxxxxxxxx>
> -#
> -# 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 <invernizzi.l@xxxxxxxxx>
> -#
> -# 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 <invernizzi.l@xxxxxxxxx>
> -#
> -# 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 <invernizzi.l@xxxxxxxxx>
> -#
> -# 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 <invernizzi.l@xxxxxxxxx>
> -#
> -# 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 : gtg-user@xxxxxxxxxxxxxxxxxxx
> 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 users is subscribed to branch lp:~gtg-user/gtg/evolution-backend.
References