← Back to team overview

gtg team mailing list archive

[Merge] lp:~gtg-user/gtg/backends-first-merge into lp:gtg

 

Luca Invernizzi has proposed merging lp:~gtg-user/gtg/backends-first-merge into lp:gtg.

Requested reviews:
  Gtg developers (gtg)


This branch contains a series of things. I wanted to do separate merges, but unfortunately bazaar does not support partial merges, so it would have been a merge hell on my next merge.

- refactored signals for deleting a task. Now no phantoms are left if you delete. The FilteredTree emits "path-deleted" signals to update the task browser.

- new datastore with testset. It should have most of the features that I will need for the multi-backend feature

- refatored loading and saving backends
-- 
https://code.launchpad.net/~gtg-user/gtg/backends-first-merge/+merge/26433
Your team Gtg developers is requested to review the proposed merge of lp:~gtg-user/gtg/backends-first-merge into lp:gtg.
=== modified file 'GTG/backends/__init__.py'
--- GTG/backends/__init__.py	2010-03-01 01:55:12 +0000
+++ GTG/backends/__init__.py	2010-05-31 15:39:30 +0000
@@ -23,14 +23,130 @@
 (like on the hard disk or on the internet)
 and to read projects from this medium
 """
-#
-#Current backends are :
-#
-# localfile.py : store and read a local XML file
-#
-#
-#
-# this __init__.py should not be empty. It should list the available backends
-#
-#http://www.faqts.com/knowledge_base/view.phtml/aid/4221/fid/538
-#http://www.python.org/doc/2.1.3/tut/node8.html
+
+import sys
+import uuid
+import os.path
+
+from GTG.tools.logger            import Log
+from GTG.tools.borg              import Borg
+from GTG.backends.genericbackend import GenericBackend
+
+
+
+class BackendFactory(Borg):
+    '''
+    This class holds the information about the backend types.
+    Since it's about types, all information is static. The instantiated
+    backends are handled in the Datastore.
+    It is a Borg for what matters its only state (_backend_modules),
+    since it makes no sense of keeping multiple instances of this.
+    '''
+
+
+    BACKEND_PREFIX = "backend_"
+
+    def __init__(self):
+        """
+         Creates a dictionary of the currently available backend modules
+        """
+        super(BackendFactory, self).__init__()
+        if hasattr(self, "backend_modules"):
+            #This object has already been constructed
+            return
+        self.backend_modules = {}
+        #Look for backends in the GTG/backends dir
+        this_dir = os.path.dirname(__file__)
+        backend_files = filter(lambda f: f.endswith(".py") and     \
+               f[ : len(self.BACKEND_PREFIX)] == self.BACKEND_PREFIX , \
+               os.listdir(this_dir))
+        #Create module names
+        module_names = map(lambda f: f.replace(".py",""), backend_files)
+        Log.debug("Backends found: " + str(module_names))
+        #Load backend modules
+        for module_name in module_names:
+            extended_module_name = "GTG.backends." + module_name
+            try:
+                __import__(extended_module_name)
+            except ImportError, exception:
+                #Something is wrong with this backend, skipping
+                Log.debug("Backend %s could not be loaded: %s" % \
+                          (module_name, str(exception)))
+                continue
+            self.backend_modules[module_name] = sys.modules[extended_module_name]
+
+    def get_backend(self, backend_name):
+        '''
+        Returns the backend module for the backend matching 
+        backend_name. Else, returns none
+        '''
+        if backend_name in self.backend_modules:
+            return self.backend_modules[backend_name]
+        else:
+            Log.debug("Trying to load backend %s, but failed!" % backend_name)
+            return None
+
+    def get_all_backends(self):
+        '''
+        Returns a dictionary containing all the backends types
+        '''
+        return self.backend_modules
+
+    def get_new_backend_dict(self, backend_name):
+        '''
+        Constructs a new backend initialization dictionary. In more
+        exact terms, creates a dictionary, containing all the necessary
+        entries to initialize a backend.
+        '''
+        if not self.backend_modules.has_key(backend_name):
+            return None
+        dic = {}
+        module = self.get_backend(backend_name)
+        #Different pids are necessary to discern between backends of the same
+        # type
+        dic["pid"] = str(uuid.uuid4())
+        dic["module"] = module.Backend.get_name()
+        parameters = module.Backend.get_static_parameters()
+        #we all the parameters and their default values in dic
+        for param_name, param_dic in parameters.iteritems():
+            #FIXME: add type handling 
+            dic[param_name] = param_dic[GenericBackend.PARAM_DEFAULT_VALUE]
+        dic["backend"] = module.Backend(dic)
+        print dic
+        return dic
+
+    def restore_backend_from_xml(self, dic):
+        '''
+        Function restoring a backend from its xml description.
+        dic should be a dictionary containing at least the key 
+        - "module", with the module name
+        - "xmlobject", with its xml description.
+        Every other key is passed as-is to the backend, as parameter.
+
+        Returns the backend instance, or None is something goes wrong
+        '''
+        if not "module" in dic or not "xmlobject" in dic:
+            Log.debug ("Malformed backend configuration found! %s" % \
+                       dic)
+        module = self.get_backend(dic["module"])
+        if module == None:
+            Log.debug ("could not load module for backend %s" % \
+                       dic["module"])
+            return None
+        #we pop the xml object, as it will be redundant when the parameters
+        # are set directly in the dict
+        xp = dic.pop("xmlobject")
+        #Building the dictionary
+        parameters_specs = module.Backend.get_static_parameters()
+        dic["pid"] = str(xp.getAttribute("pid"))
+        for param_name, param_dic in parameters_specs.iteritems():
+            if xp.hasAttribute(param_name):
+                #we need to convert the parameter to the right format.
+                # we fetch the format from the static_parameters
+                param_type = param_dic[GenericBackend.PARAM_TYPE]
+                param_value = GenericBackend.cast_param_type_from_string( \
+                                    xp.getAttribute(param_name), param_type)
+                dic[param_name] = param_value
+        #We put the backend itself in the dict
+        dic["backend"] = module.Backend(dic)
+        return dic["backend"]

=== added file 'GTG/backends/backend_localfile.py'
--- GTG/backends/backend_localfile.py	1970-01-01 00:00:00 +0000
+++ GTG/backends/backend_localfile.py	2010-05-31 15:39:30 +0000
@@ -0,0 +1,258 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Gettings 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/>.
+# -----------------------------------------------------------------------------
+
+'''
+Localfile is a read/write backend that will store your tasks in an XML file
+This file will be in your $XDG_DATA_DIR/gtg folder.
+'''
+
+import os
+import uuid
+from collections import deque
+import threading
+
+from GTG.backends.genericbackend import GenericBackend
+from GTG.core                    import CoreConfig
+from GTG.tools                   import cleanxml, taskxml
+from GTG                         import _
+from GTG.tools.logger            import Log
+
+
+
+class Backend(GenericBackend):
+    
+
+    DEFAULT_PATH = CoreConfig.DATA_DIR #default path for filenames
+
+
+    #Description of the backend (mainly it's data we show the user, only the
+    # name is used internally. Please note that BACKEND_NAME and
+    # BACKEND_ICON_NAME should *not* be translated.
+    _general_description = { \
+        GenericBackend.BACKEND_NAME:       "backend_localfile", \
+        GenericBackend.BACKEND_HUMAN_NAME: _("Local File"), \
+        GenericBackend.BACKEND_ICON_NAME:  "backend_local_file", \
+        GenericBackend.BACKEND_AUTHORS:    ["Lionel Dricot", \
+                                            "Luca Invernizzi"], \
+        GenericBackend.BACKEND_TYPE:       GenericBackend.TYPE_READWRITE, \
+        GenericBackend.BACKEND_DESCRIPTION: \
+            _("Your tasks are saved in a text file (XML format). " + \
+              " This is the most basic and the default way " +   \
+              "for GTG to save your tasks."),\
+        }
+
+    #parameters to configure a new backend of this type.
+    #NOTE: should we always give back a different default filename? it can be
+    #      done, but I'd like to keep this backend simple, so that it can be
+    #      used as example (invernizzi)
+    _static_parameters = { \
+        "path": { \
+            GenericBackend.PARAM_TYPE: GenericBackend.TYPE_FILENAME, \
+            GenericBackend.PARAM_CONFIGURABLE: True, \
+            GenericBackend.PARAM_DEFAULT_VALUE: \
+                 os.path.join(DEFAULT_PATH, "gtg_tasks-%s.xml" %(uuid.uuid4()))
+        }}
+
+    def _get_default_filename_path(self, filename = None):
+        '''
+        Generates a default path with a random filename
+        @param filename: specify a filename
+        '''
+        if not filename:
+            filename = "gtg_tasks-%s.xml" % (uuid.uuid4())
+        return os.path.join(self.DEFAULT_PATH, filename)
+
+    def __init__(self, parameters):
+        """
+        Instantiates a new backend.
+
+        @param parameters: should match the dictionary returned in
+        get_parameters. Anyway, the backend should care if one expected
+        value is None or does not exist in the dictionary. 
+        @firstrun: only needed for the default backend. It should be
+        omitted for all other backends.
+        """
+        super(Backend, self).__init__(parameters)
+        self.tids = []
+        self.to_set_timer = None
+        self.to_set = deque()
+        self.to_remove = deque()
+
+
+        #####RETROCOMPATIBILIY
+        #NOTE: retrocompatibility. We convert "filename" to "path"
+        #      and we forget about "filename"
+        if "need_conversion" in parameters:
+            parameters["path"] = os.path.join(self.DEFAULT_PATH, \
+                                        parameters["need_conversion"])
+            del parameters["need_conversion"]
+        if not self.KEY_DEFAULT_BACKEND in parameters:
+            parameters[self.KEY_DEFAULT_BACKEND] = True
+        ####
+
+        #To be extra sure, if we are the default backend and we don't have a
+        #  path set, we generate one (shouldn't happen)
+        if parameters[self.KEY_DEFAULT_BACKEND] and \
+             "path" not in parameters:
+            parameters["path"] = self._get_default_filename_path() 
+            Log.debug("Path for default backend has not been found. Please" +\
+                      "report this bug")
+        self.doc, self.xmlproj = cleanxml.openxmlfile( \
+                                parameters["path"], "project")
+
+    def this_is_the_first_run(self, xml):
+        #Create the default tasks for the first run.
+        #We write the XML object in a file
+        self._parameters[self.KEY_DEFAULT_BACKEND] = True
+        cleanxml.savexml(self._parameters["path"], xml)
+        self.doc, self.xmlproj = cleanxml.openxmlfile(\
+                        self._parameters["path"], "project")
+        self._parameters[self.KEY_DEFAULT_BACKEND] = True
+
+    def start_get_tasks(self,push_task_func,task_factory_func):
+        '''
+        Once this function is launched, the backend can start pushing
+        tasks to gtg parameters.
+
+        @push_task_func: a function that takes a Task as parameter
+         and pushes it into GTG.
+        @task_factory_func: a function that takes a tid as parameter
+         and returns a Task object with the given tid. 
+
+        @return: start_get_tasks() might not return or finish
+        '''
+        tid_list = []
+        for node in self.xmlproj.childNodes:
+            tid = node.getAttribute("id")
+            if tid not in self.tids:
+                self.tids.append(tid)
+            task = task_factory_func(tid)
+            if task:
+                task = taskxml.task_from_xml(task, node)
+                push_task_func(task)
+            else:
+                print "tried to load task with the same tid"
+        #print "#### finishing pushing tasks"
+
+    def set_task(self, task):
+        ''' Save the task in the backend. '''
+        tid = task.get_id()
+        if task not in self.to_set and tid not in self.to_remove:
+            self.to_set.appendleft(task)
+            self.__try_launch_setting_thread()
+
+    def __try_launch_setting_thread(self):
+        '''
+        Helper function to launch the setting thread, if it's not running
+        '''
+        if self.to_set_timer == None:
+            self.to_set_timer = threading.Timer(10.0, \
+                                        self.launch_setting_thread)
+            self.to_set_timer.start()
+
+    def launch_setting_thread(self):
+        while True:
+            try:
+                task = self.to_set.pop()
+            except IndexError:
+                break
+            #time.sleep(4)
+            tid = task.get_id()
+            if tid not in self.tids and tid not in self.to_remove:
+                self.tids.append(tid)
+                self.__save_task(task)
+        while True:
+            try:
+                tid = self.to_remove.pop()
+            except IndexError:
+                break
+            self.__remove_task(tid)
+        #we release the weak lock
+        self.to_set_timer = None
+
+    def __save_task(self, task):
+            tid = task.get_id()
+            existing = None
+            #First, we find the existing task from the treenode
+            for node in self.xmlproj.childNodes:
+                if node.getAttribute("id") == tid:
+                    existing = node
+            t_xml = taskxml.task_to_xml(self.doc, task)
+            modified = False
+            #We then replace the existing node
+            if existing and t_xml:
+                #We will write only if the task has changed
+                if t_xml.toxml() != existing.toxml():
+                    self.xmlproj.replaceChild(t_xml, existing)
+                    modified = True
+            #If the node doesn't exist, we create it
+            # (it might not be the case in all backends
+            else:
+                self.xmlproj.appendChild(t_xml)
+                modified = True
+            #In this particular backend, we write all the tasks
+            #This is inherent to the XML file backend
+            if modified and self._parameters["path"] and self.doc :
+                cleanxml.savexml(self._parameters["path"], self.doc)
+
+    def remove_task(self, tid):
+        '''
+        Queues task to be removed.
+        @param tid: The Task ID of the task to be removed
+        '''
+        if tid not in self.to_remove:
+            self.to_remove.appendleft(tid)
+            self.__try_launch_setting_thread()
+            return None
+
+    def __remove_task(self, tid):
+        ''' Completely remove the task with ID = tid '''
+        for node in self.xmlproj.childNodes:
+            if node.getAttribute("id") == tid:
+                self.xmlproj.removeChild(node)
+                if tid in self.tids:
+                    self.tids.remove(tid)
+        cleanxml.savexml(self._parameters["path"], self.doc)
+
+    def sync(self):
+        '''
+        Forces the backend to sync all the pending tasks
+        '''
+        if self.to_set_timer != None:
+            try:
+                self.to_set_timer.cancel()
+                self.to_set_timer.join(20)
+            except:
+                pass
+        self.launch_setting_thread()
+
+    def quit(self, disable = False):
+        '''
+        Called when GTG quits or disconnects the backend.
+        '''
+        super(Backend, self).quit(disable)
+        self.sync()
+        cleanxml.savexml(self._parameters["path"], self.doc, backup=True)
+
+    def get_number_of_tasks(self):
+        '''
+        Returns the number of tasks stored in the backend. Doesn't need to be a
+        fast function, is called just for the UI
+        '''
+        return len(self.tids)

=== added file 'GTG/backends/backendsignals.py'
--- GTG/backends/backendsignals.py	1970-01-01 00:00:00 +0000
+++ GTG/backends/backendsignals.py	2010-05-31 15:39:30 +0000
@@ -0,0 +1,80 @@
+# -*- 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/>.
+# -----------------------------------------------------------------------------
+
+import gobject
+
+from GTG.tools.borg import Borg
+
+
+
+class BackendSignals(Borg):
+    '''
+    This class handles the signals that involve backends. 
+    In particular, it's a wrapper Borg class around a _BackendSignalsGObject
+    class, and all method of the wrapped class can be used as if they were part
+    of this class
+    '''
+
+
+    def __init__(self):
+        super(BackendSignals, self).__init__()
+        if not hasattr(self, "_gobject"):
+            self._gobject = BackendSignals._BackendSignalsGObject()
+
+    def __getattr__(self, attr):
+        return getattr(self._gobject, attr)
+
+    class _BackendSignalsGObject(gobject.GObject):
+
+        #signal name constants
+        BACKEND_STATE_TOGGLED = 'backend-state-toggled' #emitted when a
+                                                        #backend is
+                                                        #enabled or disabled
+        BACKEND_RENAMED = 'backend-renamed' #emitted when a backend is renamed
+        BACKEND_ADDED = 'backend-added'
+        BACKEND_REMOVED = 'backend-added' #when a backend is deleted
+
+        __string_signal__ = (gobject.SIGNAL_RUN_FIRST, \
+                         gobject.TYPE_NONE, (str, ))
+
+        __gsignals__ = {BACKEND_STATE_TOGGLED : __string_signal__, \
+                        BACKEND_RENAMED       : __string_signal__, \
+                        BACKEND_ADDED         : __string_signal__, \
+                        BACKEND_REMOVED       : __string_signal__}
+
+        ############# Signals #########
+        #connecting to signals is fine, but keep an eye if you should emit them.
+        #As a general rule, signals should only be emitted in the GenericBackend
+        #class
+        
+        def _emit_signal(self, signal, backend_id):
+            gobject.idle_add(self.emit, signal, backend_id)
+
+        def backend_state_changed(self, backend_id):
+            self._emit_signal(self.BACKEND_STATE_TOGGLED, backend_id)
+
+        def backend_renamed(self, backend_id):
+            self._emit_signal(self.BACKEND_RENAMED, backend_id)
+
+        def backend_added(self, backend_id):
+            self._emit_signal(self.BACKEND_ADDED, backend_id)
+
+        def backend_removed(self, backend_id):
+            self._emit_signal(self.BACKEND_REMOVED, backend_id)
+

=== added file 'GTG/backends/genericbackend.py'
--- GTG/backends/genericbackend.py	1970-01-01 00:00:00 +0000
+++ GTG/backends/genericbackend.py	2010-05-31 15:39:30 +0000
@@ -0,0 +1,372 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Gettings 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/>.
+# -----------------------------------------------------------------------------
+
+'''
+FIXME: document!
+'''
+
+from GTG.backends.backendsignals import BackendSignals
+from GTG.tools.logger import Log
+
+
+
+class GenericBackend(object):
+    '''
+    Base class for every backend. It's a little more than an interface which
+    methods have to be redefined in order for the backend to run.
+    '''
+
+
+    #BACKEND TYPE DESCRIPTION
+    #"_general_description" is a dictionary that holds the values for the
+    # following keys:
+    BACKEND_NAME = "name" #the backend gtg internal name (doesn't change in
+                          # translations, *must be unique*)
+    BACKEND_HUMAN_NAME = "human-friendly-name" #The name shown to the user
+    BACKEND_DESCRIPTION = "description" #A short description of the backend
+    BACKEND_ICON_NAME = "icon-name" #name of the icon in the theme (filename)
+    BACKEND_AUTHORS = "authors" #a list of strings
+    BACKEND_TYPE = "type"
+    #BACKEND_TYPE is one of:
+    TYPE_READWRITE = "readwrite"
+    TYPE_READONLY = "readonly"
+    TYPE_IMPORT = "import"
+    TYPE_EXPORT = "export"
+    _general_description = {}
+
+
+    #"static_parameters" is a dictionary of dictionaries, each of which
+    #representing a parameter needed to configure the backend.
+    #each "sub-dictionary" is identified by this a key representing its name.
+    #"static_parameters" will be part of the definition of each
+    #particular backend.
+    # Each dictionary contains the keys:
+    #PARAM_DESCRIPTION = "description" #short description (shown to the user
+                                      # during configuration)
+    PARAM_DEFAULT_VALUE = "default_value" # its default value
+    PARAM_CONFIGURABLE = "configurable" # True if the user should
+                                        # see and change this
+    PARAM_TYPE = "type"  
+    #PARAM_TYPE is one of the following (changing this changes the way
+    # the user can configure the parameter)
+    TYPE_FILENAME = "file" #a file chooser is added
+    TYPE_PASSWORD = "password" #the real password is stored in the GNOME
+                               # keyring
+                               # This is just a key to find it there
+    TYPE_STRING = "string"  #generic string, nothing fancy is done
+    TYPE_INT = "int"  #edit box can contain only integers
+    TYPE_BOOL = "bool" #checkbox is shown
+    _static_parameters = {}
+
+    def initialize(self):
+        '''
+        Called each time it is enabled again (including on backend creation).
+        Please note that a class instance for each disabled backend *is*
+        created, but it's not initialized. 
+        Optional. 
+        NOTE: make sure to call super().initialize()
+        '''
+        self._parameters[self.KEY_ENABLED] = True
+        #we signal that the backend has been enabled
+        self._signal_manager.backend_state_changed(self.get_id())
+
+    def start_get_tasks(self,push_task_func,task_factory_func):
+        '''
+        Once this function is launched, the backend can start pushing
+        tasks to gtg parameters.
+
+        @push_task_func: a function that takes a Task as parameter
+         and pushes it into GTG.
+        @task_factory_func: a function that takes a tid as parameter
+         and returns a Task object with the given pid. 
+
+        @return: start_get_tasks() might not return or finish
+        '''
+        raise NotImplemented()
+
+    def set_task(self, task):
+        '''
+        Save the task in the backend. If the task id is new for the 
+        backend, then a new task must be created
+        '''
+        raise NotImplemented()
+
+    def remove_task(self, tid):
+        ''' Completely remove the task with ID = tid '''
+        raise NotImplemented()
+
+    def has_task(self, tid):
+        '''Returns true if the backend has an internal idea 
+           of the task corresponding to the tid. False otherwise'''
+        raise NotImplemented()
+
+    def new_task_id(self):
+        '''
+        Returns an available ID for a new task so that a task with this ID
+        can be saved with set_task later.
+        '''
+        raise NotImplemented()
+
+    def this_is_the_first_run(self, xml):
+        '''
+        Steps to execute if it's the first time the backend is run. Optional.
+        '''
+        pass
+
+    def purge(self):
+        '''
+        Called when a backend will be removed from GTG. Useful for removing
+        configuration files. Optional.
+        '''
+        pass
+
+    def get_number_of_tasks(self):
+        '''
+        Returns the number of tasks stored in the backend. Doesn't need to be a
+        fast function, is called just for the UI
+        '''
+        raise NotImplemented()
+
+    def quit(self, disable = False):
+        '''
+        Called when GTG quits or disconnects the backend. Remember to execute
+        also this function when quitting. If disable is True, the backend won't
+        be automatically loaded at next GTG start
+        '''
+        if disable:
+            self._parameters[self.KEY_ENABLED] = False
+            #we signal that we have been disabled
+            self._signal_manager.backend_state_changed(self.get_id())
+        #sync attached tags with the stored parameter
+        if self._attached_tags:
+            self._parameters[self.KEY_ATTACHED_TAGS] = \
+                    reduce(lambda a, b: a + "," + b, self._attached_tags)
+        else:
+            self._parameters[self.KEY_ATTACHED_TAGS] = []
+
+###############################################################################
+###### You don't need to reimplement the functions below this line ############
+###############################################################################
+
+    #These parameters are common to all backends and necessary.
+    # They will be added automatically to your _static_parameters list
+    #NOTE: for now I'm disabling changing the default backend. Once it's all
+    #      set up, we will see about that (invernizzi)
+    KEY_DEFAULT_BACKEND = "Default"
+    KEY_ENABLED = "Enabled"
+    KEY_HUMAN_NAME = BACKEND_HUMAN_NAME
+    KEY_ATTACHED_TAGS = "tags"
+    KEY_USER = "user"
+    ALLTASKS_TAG = "gtg-tags-all"  #FIXME: moved here to avoid circular imports
+
+    _static_parameters_obligatory = { \
+                                    KEY_ATTACHED_TAGS: {\
+                                         PARAM_TYPE: TYPE_STRING, \
+                                         PARAM_DEFAULT_VALUE: ALLTASKS_TAG, \
+                                         PARAM_CONFIGURABLE: True, \
+                                    }, \
+                                    KEY_DEFAULT_BACKEND: { \
+                                         PARAM_TYPE: TYPE_BOOL, \
+                                         PARAM_DEFAULT_VALUE: False, \
+                                         PARAM_CONFIGURABLE: False, \
+                                    }, \
+                                    KEY_HUMAN_NAME: { \
+                                         PARAM_TYPE: TYPE_STRING, \
+                                         PARAM_DEFAULT_VALUE: "", \
+                                         PARAM_CONFIGURABLE: True, \
+                                    }, \
+                                    KEY_USER: { \
+                                         PARAM_TYPE: TYPE_STRING, \
+                                         PARAM_DEFAULT_VALUE: "", \
+                                         PARAM_CONFIGURABLE: True, \
+                                    }, \
+                                    KEY_ENABLED: { \
+                                         PARAM_TYPE: TYPE_BOOL, \
+                                         PARAM_DEFAULT_VALUE: False, \
+                                         PARAM_CONFIGURABLE: True, \
+                                    }}
+    
+    #Handy dictionary used in type conversion (from string to type)
+    _type_converter = {TYPE_FILENAME: str,
+                       TYPE_STRING: str,
+                       TYPE_INT: int}
+
+    @classmethod
+    def _get_static_parameters(cls):
+        '''
+        Helper method, used to obtain the full list of the static_parameters
+        (user configured and default ones)
+        '''
+        if hasattr(cls, "_static_parameters"):
+            temp_dic = cls._static_parameters.copy()
+            for key, value in cls._static_parameters_obligatory.iteritems():
+                temp_dic[key] = value
+            return temp_dic 
+        else:
+            raise NotImplemented("_static_parameters not implemented for " + \
+                                 "backend %s" % type(cls))
+
+    def __init__(self, parameters):
+        """
+        Instantiates a new backend. Please note that this is called also for
+        disabled backends. Those are not initialized, so you might want to check
+        out the initialize() function.
+        """
+        if self.KEY_DEFAULT_BACKEND not in parameters:
+            parameters[self.KEY_DEFAULT_BACKEND] = True
+        if parameters[self.KEY_DEFAULT_BACKEND] or \
+           not self.KEY_ATTACHED_TAGS in parameters:
+            self._attached_tags = [self.ALLTASKS_TAG]
+        else:
+            self._attached_tags = parameters[self.KEY_ATTACHED_TAGS].split(',')
+        self._parameters = parameters
+        self._signal_manager = BackendSignals()
+
+    def get_attached_tags(self):
+        '''
+        Returns the list of tags which are handled by this backend
+        '''
+        if hasattr(self._parameters, self.KEY_DEFAULT_BACKEND) and \
+                   self._parameters[self.KEY_DEFAULT_BACKEND]:
+            return [self.ALLTASKS_TAG]
+        return self._attached_tags
+
+    def set_attached_tags(self, tags):
+        '''
+        Changes the set of attached tags
+        '''
+        self._attached_tags = tags
+
+    @classmethod
+    def get_static_parameters(cls):
+        """
+        Returns a dictionary of parameters necessary to create a backend.
+        """
+        return cls._get_static_parameters()
+
+    def get_parameters(self):
+        """
+        Returns a dictionary of the current parameters.
+        """
+        return self._parameters
+
+    @classmethod
+    def get_name(cls):
+        """
+        Returns the name of the backend as it should be displayed in the UI
+        """
+        return cls._get_from_general_description(cls.BACKEND_NAME)
+
+    @classmethod
+    def get_description(cls):
+        """Returns a description of the backend"""
+        return cls._get_from_general_description(cls.BACKEND_DESCRIPTION)
+
+    @classmethod
+    def get_type(cls):
+        """Returns the backend type(readonly, r/w, import, export) """
+        return cls._get_from_general_description(cls.BACKEND_TYPE)
+
+    @classmethod
+    def get_authors(cls):
+        '''
+        returns the backend author(s)
+        '''
+        return cls._get_from_general_description(cls.BACKEND_AUTHORS)
+
+    @classmethod
+    def _get_from_general_description(cls, key):
+        '''
+        Helper method to extract values from cls._general_description.
+        Raises an exception if the key is missing (helpful for developers
+        adding new backends).
+        '''
+        if key in cls._general_description:
+            return cls._general_description[key]
+        else:
+            raise NotImplemented("Key %s is missing from " +\
+                    "'self._general_description' of a backend (%s). " +
+                    "Please add the corresponding value" % (key, type(cls)))
+
+    @classmethod
+    def cast_param_type_from_string(cls, param_value, param_type):
+        '''
+        Parameters are saved in a text format, so we have to cast them to the
+        appropriate type on loading. This function does exactly that.
+        '''
+        if param_type in cls._type_converter:
+            return cls._type_converter[param_type](param_value)
+        elif param_type == cls.TYPE_BOOL:
+            if param_value == "True":
+                return True
+            elif param_value == "False":
+                return False
+            else:
+                raise Exception("Unrecognized bool value '%s'" %
+                                 param_type)
+        else:
+            raise NotImplemented("I don't know what type is '%s'" %
+                                 param_type)
+
+    def get_id(self):
+        '''
+        returns the backends id, used in the datastore for indexing backends
+        '''
+        return self.get_name() + "@" + self._parameters["pid"]
+
+    @classmethod
+    def get_human_default_name(cls):
+        '''
+        returns the user friendly default backend name. 
+        '''
+        return cls._general_description[cls.BACKEND_HUMAN_NAME]
+
+    def get_human_name(self):
+        '''
+        returns the user customized backend name. If the user hasn't
+        customized it, returns the default one
+        '''
+        if self.KEY_HUMAN_NAME in self._parameters and \
+                    self._parameters[self.KEY_HUMAN_NAME] != "":
+            return self._parameters[self.KEY_HUMAN_NAME]
+        else:
+            return self.get_human_default_name()
+
+    def set_human_name(self, name):
+        '''
+        sets a custom name for the backend
+        '''
+        static_parameters = self.get_static_parameters()[self.KEY_HUMAN_NAME]
+        if static_parameters[self.PARAM_CONFIGURABLE]:
+            self._parameters[self.KEY_HUMAN_NAME] = name
+            #we signal the change
+            self._signal_manager.backend_renamed(self.get_id())
+
+    def is_enabled(self):
+        '''
+        Returns if the backend is enabled
+        '''
+        return self.get_parameters()[GenericBackend.KEY_ENABLED] or \
+               self.is_default()
+
+    def is_default(self):
+        '''
+        Returns if the backend is enabled
+        '''
+        return self.get_parameters()[GenericBackend.KEY_DEFAULT_BACKEND]

=== removed file 'GTG/backends/localfile.py'
--- GTG/backends/localfile.py	2010-04-26 23:12:57 +0000
+++ GTG/backends/localfile.py	1970-01-01 00:00:00 +0000
@@ -1,176 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Gettings 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/>.
-# -----------------------------------------------------------------------------
-
-'''
-Localfile is a read/write backend that will store your tasks in an XML file
-This file will be in your $XDG_DATA_DIR/gtg folder.
-'''
-
-import os
-import uuid
-
-from GTG.core  import CoreConfig
-from GTG.tools import cleanxml, taskxml
-
-def get_name():
-    """Returns the name of the backend as it should be displayed in the UI"""
-    return "Local File"
-
-def get_description():
-    """Returns a description of the backend"""
-    return "Your tasks are saved in an XML file located in your HOME folder"
-
-def get_parameters():
-    """
-    Returns a dictionary of parameters. Keys should be strings and
-    are the name of the parameter.
-    Values are string with value : string, password, int, bool
-    and are an information about the type of the parameter
-    Currently, only string is supported.
-    """
-    dic = {}
-    dic["filename"] = "string"
-    return dic
-
-def get_features():
-    """Returns a dict of features supported by this backend"""
-    return {}
-
-def get_type():
-    """Type is one of : readwrite, readonly, import, export"""
-    return "readwrite"
-
-class Backend:
-    def __init__(self, parameters, firstrunxml=None):
-        """
-        Instantiates a new backend.
-
-        @param parameters: should match the dictionary returned in
-         get_parameters. Anyway, the backend should care if one expected value is
-         None or does not exist in the dictionary.
-        @firstrun: only needed for the default backend. It should be omitted for
-         all other backends.
-        """
-        self.tids = []
-        self.pid = 1
-        if "filename" in parameters:
-            zefile = parameters["filename"]
-        #If zefile is None, we create a new file
-        else:
-            zefile = "%s.xml" %(uuid.uuid4())
-            parameters["filename"] = zefile
-        #For the day we want to open files somewhere else
-        default_folder = True
-        if default_folder:
-            self.zefile = os.path.join(CoreConfig.DATA_DIR, zefile)
-            self.filename = zefile
-        else:
-            self.zefile = zefile
-            self.filename = zefile
-        #Create the default tasks for the first run.
-        #We write the XML object in a file
-        if firstrunxml and not os.path.exists(zefile):
-            #shutil.copy(firstrunfile,self.zefile)
-            cleanxml.savexml(self.zefile, firstrunxml)
-        self.doc, self.xmlproj = cleanxml.openxmlfile(self.zefile, "project")
-
-    def start_get_tasks(self,push_task_func,task_factory_func):
-        '''
-        Once this function is launched, the backend can start pushing
-        tasks to gtg parameters.
-
-        @push_task_func: a function that takes a Task as parameter
-         and pushes it into GTG.
-        @task_factory_func: a function that takes a tid as parameter
-         and returns a Task object with the given pid. 
-
-        @return: start_get_tasks() might not return or finish
-        '''
-        tid_list = []
-        for node in self.xmlproj.childNodes:
-            #time.sleep(2)
-            tid = node.getAttribute("id")
-            if tid not in self.tids:
-                self.tids.append(tid)
-            task = task_factory_func(tid)
-            task = taskxml.task_from_xml(task,node)
-            push_task_func(task)
-        #print "#### finishing pushing tasks"
-
-    def set_task(self, task):
-        ''' Save the task in the backend '''
-        #time.sleep(4)
-        tid = task.get_id()
-        if tid not in self.tids:
-            self.tids.append(tid)
-        existing = None
-        #First, we find the existing task from the treenode
-        for node in self.xmlproj.childNodes:
-            if node.getAttribute("id") == tid:
-                existing = node
-        t_xml = taskxml.task_to_xml(self.doc, task)
-        modified = False
-        #We then replace the existing node
-        if existing and t_xml:
-            #We will write only if the task has changed
-            if t_xml.toxml() != existing.toxml():
-                self.xmlproj.replaceChild(t_xml, existing)
-                modified = True
-        #If the node doesn't exist, we create it
-        # (it might not be the case in all backends
-        else:
-            self.xmlproj.appendChild(t_xml)
-            modified = True
-        #In this particular backend, we write all the tasks
-        #This is inherent to the XML file backend
-        if modified and self.zefile and self.doc :
-            cleanxml.savexml(self.zefile, self.doc)
-        return None
-
-    def remove_task(self, tid):
-        ''' Completely remove the task with ID = tid '''
-        for node in self.xmlproj.childNodes:
-            if node.getAttribute("id") == tid:
-                self.xmlproj.removeChild(node)
-                if tid in self.tids:
-                    self.tids.remove(tid)
-        cleanxml.savexml(self.zefile, self.doc)
-
-    def new_task_id(self):
-        '''
-        Returns an available ID for a new task so that a task with this ID
-        can be saved with set_task later.
-        If None, then GTG will create a new ID by itself.
-        The ID cannot contain the character "@".
-        '''
-        k = 0
-        pid = self.pid
-        newid = "%s@%s" %(k, pid)
-        while str(newid) in self.tids:
-            k += 1
-            newid = "%s@%s" %(k, pid)
-        self.tids.append(newid)
-        return newid
-
-    def quit(self):
-        '''
-        Called when GTG quits or disconnects the backend.
-        (Subclasses might pass here)
-        '''
-        cleanxml.savexml(self.zefile, self.doc, backup=True)

=== modified file 'GTG/core/__init__.py'
--- GTG/core/__init__.py	2010-05-26 09:54:42 +0000
+++ GTG/core/__init__.py	2010-05-31 15:39:30 +0000
@@ -40,11 +40,15 @@
 
 #=== IMPORT ====================================================================
 import os
-from xdg.BaseDirectory import xdg_data_home, xdg_config_home
-from GTG.tools         import cleanxml
-from configobj         import ConfigObj
-
-from GTG.core          import firstrun_tasks
+from xdg.BaseDirectory           import xdg_data_home, xdg_config_home
+from configobj                   import ConfigObj
+
+import GTG
+from GTG.core                    import firstrun_tasks
+from GTG.tools                   import cleanxml
+from GTG.backends                import BackendFactory
+from GTG.tools.logger            import Log
+
 
 class CoreConfig:
     
@@ -60,6 +64,8 @@
     #DBUS
     BUSNAME = "org.GTG"
     BUSINTERFACE = "/org/GTG"
+    #TAGS
+    ALLTASKS_TAG = "gtg-tags-all"
 
     def __init__(self):
         if not os.path.exists(self.CONF_DIR):
@@ -81,96 +87,53 @@
         self.conf_dict = ConfigObj(self.CONF_DIR + self.CONF_FILE)
         self.task_conf_dict = ConfigObj(self.CONF_DIR + self.TASK_CONF_FILE)
     
-    def save_config(self):
+    def save(self):
+        ''' Saves the configuration of CoreConfig '''
         self.conf_dict.write()
         self.task_conf_dict.write()
-    
+
+    @staticmethod
+    def get_icons_directories():
+        '''
+        Returns the directories containing the icons
+        '''
+        return [GTG.DATA_DIR, os.path.join(GTG.DATA_DIR, "icons")]
+
     def get_backends_list(self):
-        backend_fn = []
-
-        # Check if config dir exists, if not create it
-        if not os.path.exists(self.DATA_DIR):
-            os.makedirs(self.DATA_DIR)
-
+        backends_dic = self._read_backend_configuration_file()
+
+        #Retrocompatibility: default backend has changed name
+        for dic in backends_dic:
+            if dic["module"] == "localfile":
+                dic["module"] = "backend_localfile"
+                dic["need_conversion"] = \
+                    dic["xmlobject"].getAttribute("filename")
+                
+        #Now that the backend list is build, we will construct them
+        for dic in backends_dic:
+            BackendFactory().restore_backend_from_xml(dic)
+        #If no backend available, we create a new using localfile. Xmlobject
+        # will be filled in by the backend
+        if len(backends_dic) == 0:
+            dic = BackendFactory().get_new_backend_dict( \
+                                                "backend_localfile")
+            dic["backend"].this_is_the_first_run(firstrun_tasks.populate())
+            backends_dic.append(dic)
+        return backends_dic
+
+    def _read_backend_configuration_file(self):
+        '''
+        Reads the file describing the current backend configuration (project.xml)
+        and returns a list of dictionaries, each containing: 
+         - the xml object defining the backend characteristics under
+              "xmlobject"
+         - the name of the backend under "module"
+        '''
         # Read configuration file, if it does not exist, create one
-        datafile = self.DATA_DIR + self.DATA_FILE
-        doc, configxml = cleanxml.openxmlfile(datafile,"config") #pylint: disable-msg=W0612
+        datafile = os.path.join(self.DATA_DIR, self.DATA_FILE)
+        doc, configxml = cleanxml.openxmlfile(datafile, "config")
         xmlproject = doc.getElementsByTagName("backend")
-        # collect configred backends
-        pid = 1
-        for xp in xmlproject:
-            dic = {}
-            #We have some retrocompatibility code
-            #A backend without the module attribute is pre-rev.105
-            #and is considered as "filename"
-            if xp.hasAttribute("module"):
-                dic["module"] = str(xp.getAttribute("module"))
-                dic["pid"] = str(xp.getAttribute("pid"))
-            #The following "else" could be removed later
-            else:
-                dic["module"] = "localfile"
-                dic["pid"] = str(pid)
-            
-            dic["xmlobject"] = xp
-            pid += 1
-            backend_fn.append(dic)
-                
-        firstrun = False
-        #If no backend available, we create a new using localfile
-        if len(backend_fn) == 0:
-            dic = {}
-            dic["module"] = "localfile"
-            dic["pid"] = "1"
-            backend_fn.append(dic)
-            firstrun = True
-            
-        #Now that the backend list is build, we will construct them
-        #Remember that b is a dictionnary
-        for b in backend_fn:
-            #We dynamically import modules needed
-            module_name = "GTG.backends.%s"%b["module"]
-            #FIXME : we should throw an error if the backend is not importable
-            module   = __import__(module_name)
-            module   = getattr(module, "backends")
-            classobj = getattr(module, b["module"])
-            b["parameters"] = classobj.get_parameters()
-            #If creating the default backend, we don't have the xmlobject yet
-            if "xmlobject" in b:
-                xp = b.pop("xmlobject")
-                #We will try to get the parameters
-                for key in b["parameters"]:
-                    if xp.hasAttribute(key):
-                        b[key] = str(xp.getAttribute(key))
-            if firstrun:
-                frx = firstrun_tasks.populate()
-                back = classobj.Backend(b,firstrunxml=frx)
-            else:
-                back = classobj.Backend(b)
-            #We put the backend itself in the dic
-            b["backend"] = back
-            
-        return backend_fn
-        
-
-    #If initial save, we don't close stuffs.
-    def save_datastore(self,ds,initial_save=False):
-        doc,xmlconfig = cleanxml.emptydoc("config")
-        for b in ds.get_all_backends():
-            param = b.get_parameters()
-            t_xml = doc.createElement("backend")
-            for key in param:
-                #We dont want parameters,backend,xmlobject
-                if key not in ["backend","parameters","xmlobject"]:
-                    t_xml.setAttribute(str(key),str(param[key]))
-            #Saving all the projects at close
-            xmlconfig.appendChild(t_xml)
-            if not initial_save:
-                b.quit()
-            
-        datafile = self.DATA_DIR + self.DATA_FILE
-        cleanxml.savexml(datafile,doc,backup=True)
-
-        #Saving the tagstore
-        if not initial_save:
-            ts = ds.get_tagstore()
-            ts.save()
+        # collect configured backends
+        return [{"xmlobject": xp, \
+                 "module": xp.getAttribute("module")} for xp in xmlproject]
+

=== modified file 'GTG/core/datastore.py'
--- GTG/core/datastore.py	2010-05-26 08:55:45 +0000
+++ GTG/core/datastore.py	2010-05-31 15:39:30 +0000
@@ -18,267 +18,495 @@
 # -----------------------------------------------------------------------------
 
 """
-datastore contains a list of TagSource objects, which are proxies between a backend and the datastore itself
+The DaataStore contains a list of TagSource objects, which are proxies
+between a backend and the datastore itself
 """
 
 import threading
-import gobject
-import time
-
-from GTG.core      import tagstore, requester
-from GTG.core.task import Task
-from GTG.core.tree import Tree
-
-
-#Only the datastore should access to the backend
-DEFAULT_BACKEND = "1"
-#If you want to debug a backend, it can be useful to disable the threads
-#Currently, it's python threads (and not idle_add, which is not useful)
-THREADING = True
-
-
-class DataStore:
-    """ A wrapper around a backend that provides an API for adding/removing tasks """
+import uuid
+import os.path
+from collections import deque
+
+from GTG.core                    import tagstore, requester
+from GTG.core.task               import Task
+from GTG.core.tree               import Tree
+from GTG.core                    import CoreConfig
+from GTG.tools.logger            import Log
+from GTG.backends.genericbackend import GenericBackend
+from GTG.tools                   import cleanxml
+from GTG.backends.backendsignals import BackendSignals
+
+
+class DataStore(object):
+    '''
+    A wrapper around all backends that is responsible for keeping the backend
+    instances. It can enable, disable, register and destroy backends, and acts
+    as interface between the backends and GTG core.
+    You should not interface yourself directly with the DataStore: use the
+    Requester instead (which also sends signals as you issue commands).
+    '''
+
+
     def __init__(self):
-        """ Initializes a DataStore object """
-        self.backends = {}
+        '''
+        Initializes a DataStore object
+        '''
+        self.backends = {} #dictionary {backend_name_string: Backend instance}
         self.open_tasks = Tree()
-#        self.closed_tasks = Tree()
         self.requester = requester.Requester(self)
         self.tagstore = tagstore.TagStore(self.requester)
-
-    def all_tasks(self):
-        """
+        self._backend_signals = BackendSignals()
+        self.mutex = threading.RLock()
+
+    ##########################################################################
+    ### Helper functions (get_ methods for Datastore embedded objects)
+    ##########################################################################
+
+    def get_tagstore(self):
+        '''
+        Helper function to obtain the Tagstore associated with this DataStore
+        @return GTG.core.tagstore.TagStore: the tagstore object
+        '''
+        return self.tagstore
+
+    def get_requester(self):
+        '''
+        Helper function to get the Requester associate with this DataStore
+        @returns GTG.core.requester.Requester: the requester associated with
+        this datastore
+        '''
+        return self.requester
+        
+    def get_tasks_tree(self):
+        '''
+        Helper function to get a Tree with all the tasks contained in this
+        Datastore
+        @returns GTG.core.tree.Tree: a task tree (the main one)
+        '''
+        return self.open_tasks
+
+    ##########################################################################
+    ### Tasks functions
+    ##########################################################################
+
+    def get_all_tasks(self):
+        '''
         Returns list of all keys of open tasks
-        """
+        @return a list of strings: a list of task ids
+        '''
         return self.open_tasks.get_all_keys()
 
     def has_task(self, tid):
-        """
+        '''
         Returns true if the tid is among the open or closed tasks for
         this DataStore, False otherwise.
-        param tid: Task ID to search for
-        """
-        return self.open_tasks.has_node(tid) #or self.closed_tasks.has_node(tid)
+        @param tid: Task ID to search for
+        @return bool: True if the task is present
+        '''
+        return self.open_tasks.has_node(tid)
 
     def get_task(self, tid):
-        """
+        '''
         Returns the internal task object for the given tid, or None if the
         tid is not present in this DataStore.
         @param tid: Task ID to retrieve
-        """
-        if tid:
-            if self.has_task(tid):
-                task = self.__internal_get_task(tid)
-            else:
-                #print "no task %s" %tid
-                task = None
-            return task
+        @returns GTG.core.task.Task or None:  whether the Task is present
+        or not
+        '''
+        if self.has_task(tid):
+            return self.open_tasks.get_node(tid)
         else:
-            print "get_task should take a tid"
+            Log.debug("requested non-existent task")
             return None
         
-    def __internal_get_task(self, tid):
-        return self.open_tasks.get_node(tid)
-#        if toreturn == None:
-#            self.closed_tasks.get_node(tid)
-        #else:
-            #print "error : this task doesn't exist in either tree"
-            #pass
-        #we return None if the task doesn't exist
-#        return toreturn
-
-    def delete_task(self, tid):
-        """
-        Deletes the given task entirely from this DataStore, and unlinks
-        it from the task's parent.
-        @return: True if task was deleted, or False if the tid was not
-         present in this DataStore.
-        """
-        if not tid or not self.has_task(tid):
-            return False
-
-        self.__internal_get_task(tid).delete()
-        uid, pid = tid.split('@') #pylint: disable-msg=W0612
-        back = self.backends[pid]
-        #Check that the task still exist. It might have been deleted
-        #by its parent a few line earlier :
-        if self.has_task(tid):
-            self.open_tasks.remove_node(tid)
-#            self.closed_tasks.remove_node(tid)
-        back.remove_task(tid)
-        return True
-            
-    def new_task(self,pid=None):
+    def task_factory(self, tid, newtask = False):
+        '''
+        Instantiates the given task id as a Task object.
+        @param tid: a task id. Must be unique
+        @param newtask: True if the task has never been seen before
+        @return Task: a Task instance
+        '''
+        return Task(tid, self.requester, newtask)
+
+    def new_task(self):
         """
         Creates a blank new task in this DataStore.
-        @param pid: (Optional) parent ID that this task should be a child of.
-         If not specified, the task will be a child of the default backend.
+        New task is created in all the backends that collect all tasks (among
+        them, the default backend). The default backend uses the same task id
+        in its own internal representation.
         @return: The task object that was created.
         """
-        if not pid:
-            pid = DEFAULT_BACKEND
-        newtid = self.backends[pid].new_task_id()
-        while self.has_task(newtid):
-            print "error : tid already exists"
-            newtid = self.backends[pid].new_task_id()
-        task = Task(newtid, self.requester,newtask=True)
+        task = self.task_factory(uuid.uuid4(), True)
         self.open_tasks.add_node(task)
-        task.set_sync_func(self.backends[pid].set_task,callsync=False)
         return task
-
-    def get_tagstore(self):
-        return self.tagstore
-
-    def get_requester(self):
-        return self.requester
-        
-    def get_tasks_tree(self):
-        """ return: Open tasks tree """
-        return self.open_tasks
-        
-    def push_task(self,task):
-        """
-        Adds the given task object as a node to the open tasks tree.
-        @param task: A valid task object
-        """
-        tid = task.get_id()
-        if self.has_task(tid):
-            print "pushing an existing task. We should care about modifications"
+        
+    def push_task(self, task, backend_capabilities = 'bypass for now'):
+        '''
+        Adds the given task object to the task tree. In other words, registers
+        the given task in the GTG task set.
+        @param task: A valid task object  (a GTG.core.task.Task)
+        @return bool: True if the task has been accepted
+        '''
+        # self.mutex.acquire()
+        if self.has_task(task.get_id()):
+            return False
+            #Work in progress here
+            loaded_task = self.get_task(task.get_id())
+            if loaded_task.get_modified() < task.get_modified:
+                print '''check backend capabilities and set the corresponding
+                attributes, including the modified_time.
+                '''
+                #self.mutex.release()
+            return False
         else:
-            uid, pid = tid.split('@')
             self.open_tasks.add_node(task)
             task.set_loaded()
-            task.set_sync_func(self.backends[pid].set_task,callsync=False)
-    
-    def task_factory(self,tid):
-        """
-        Instantiates the given task id as a Task object.
-        @param tid: The id of the task to instantiate
-        @return: The task object instantiated for tid
-        """
-        task = None
-        if self.has_task(tid):
-            print "error : tid already exists"
+            #self.mutex.release()
+            return True
+
+    ##########################################################################
+    ### Backends functions
+    ##########################################################################
+
+    def get_all_backends(self, disabled = False):
+        """ 
+        returns list of all registered backends for this DataStore.
+        @param disabled: If disabled is True, attaches also the list of disabled backends
+        @return list: a list of TaskSource objects
+        """
+        #NOTE: consider cashing this result for speed.
+        result = []
+        for backend in self.backends.itervalues():
+            if backend.is_enabled() or disabled:
+                result.append(backend)
+        return result
+
+    def get_backend(self, backend_id):
+        '''
+        Returns a backend given its id
+        @param backend_id: a backend id
+        @returns GTG.core.datastore.TaskSource or None: the requested backend,
+        or none
+        '''
+        if backend_id in self.backends:
+            return self.backends[backend_id]
         else:
-            task = Task(tid, self.requester, newtask=False)
-        return task
-            
+            return None
 
-    def register_backend(self, dic):
+    def register_backend(self, backend_dic):
         """
         Registers a TaskSource as a backend for this DataStore
-        @param dic: Dictionary object with a "backend" and "pid"
-         specified.  dic["pid"] should be the parent ID to use
-         with the backend specified in dic["backend"].
+        @param backend_dic: Dictionary object containing all the
+        parameters to initialize the backend (filename...). It should
+        also contain the backend class (under "backend"), and its unique
+        id (under "pid")
         """
-        if "backend" in dic:
-            pid = dic["pid"]
-            backend = dic["backend"]
-            source = TaskSource(backend, dic)
-            self.backends[pid] = source
-            #Filling the backend
-            #Doing this at start is more efficient than
-            #after the GUI is launched
-            source.start_get_tasks(self.push_task,self.task_factory)
+        if "backend" in backend_dic:
+            if "pid" not in backend_dic:
+                Log.debug("registering a backend without pid.")
+                return
+            backend = backend_dic["backend"]
+            #Checking that is a new backend
+            if backend.get_id() in self.backends:
+                Log.debug("registering already registered backend")
+                return
+            source = TaskSource(requester = self.requester,
+                                backend = backend,
+                                task_factory = self.task_factory,
+                                push_task = self.push_task,
+                                get_task = self.get_task)
+            self.backends[backend.get_id()] = source
+            #we notify that a new backend is present
+            self._backend_signals.backend_added(backend.get_id())
+            #saving the backend in the correct dictionary (backends for enabled
+            # backends, disabled_backends for the disabled ones)
+            if not GenericBackend.KEY_ENABLED in backend_dic:
+                #this is useful for retro-compatibility 
+                backend_dic[GenericBackend.KEY_ENABLED] = True
+            #if it's enabled, we initialize it
+            if source.is_enabled():
+                source.initialize(connect_signals = False)
+                #Filling the backend
+                #Doing this at start is more efficient than
+                #after the GUI is launched
+                source.start_get_tasks()
         else:
-            print "Register a dic without backend key:  BUG"
-
-    def unregister_backend(self, backend):
-        """ Unimplemented """
-        print "unregister backend %s not implemented" %backend
-
-    def get_all_backends(self):
-        """ returns list of all registered backends for this DataStore """
-        l = []
-        for key in self.backends:
-            l.append(self.backends[key])
-        return l
+            Log.debug("Tried to register a backend without a  pid")
+
+    def set_backend_enabled(self, backend_id, state):
+        """
+        The backend corresponding to backend_id is enabled or disabled
+        according to "state".
+        Disable:
+        Quits a backend and disables it (which means it won't be
+        automatically loaded next time GTG is started)
+        Enable:
+        Reloads a disabled backend. Backend must be already known by the
+        Datastore
+        @parma backend_id: a backend id
+        @param state: True to enable, False to disable
+        """
+        if backend_id in self.backends:
+            backend = self.backends[backend_id]
+            current_state = backend.is_enabled()
+            if current_state == True and state == False:
+                #we disable the backend
+                backend.quit(disable = True)
+            elif current_state == False and state == True:
+                backend.initialize()
+                self.flush_all_tasks(backend_id)
+
+    def remove_backend(self, backend_id):
+        '''
+        Removes a backend, and forgets it ever existed.
+        @param backend_id: a backend id
+        '''
+        if backend_id in self.backends:
+            backend = self.backends[backend_id]
+            if backend.is_enabled():
+                self.set_backend_enabled(backend_id, False)
+            backend.purge()
+            #we notify that the backend has been deleted
+            self._backend_signals.backend_removed(backend.get_id())
+            del self.backends[backend_id]
+
+    def backend_change_attached_tags(self, backend_id, tag_names):
+        '''
+        Changes the tags for which a backend should store a task
+        @param backend_id: a backend_id
+        @param tag_names: the new set of tags. This should not be a tag object,
+                          just the tag name.
+        '''
+        backend = self.backends[backend_id]
+        backend.set_attached_tags(tag_names)
+
+    def flush_all_tasks(self, backend_id):
+        '''
+        This function will cause all tasks to be checked against the backend
+        identified with backend_id. If tasks need to be added or removed, it
+        will be done here.
+        It has to be run after the creation of a new backend (or an alteration
+        of its "attached tags"), so that the tasks which are already loaded in 
+        the Tree will be saved in the proper backends
+        @param backend_id: a backend id
+        '''
+        def _internal_flush_all_tasks():
+            backend = self.backends[backend_id]
+            for task_id in self.requester.get_all_tasks_list():
+                if backend.should_task_id_be_stored(task_id):
+                    backend.set_task(None, task_id)
+                else:
+                    backend.remove_task(None, task_id)
+        t = threading.Thread(target = _internal_flush_all_tasks).start()
+        self.backends[backend_id].start_get_tasks()
+
+    def save(self, quit = False):
+        '''
+        Saves the backends parameters. 
+        @param quit: If quit is true, backends are shut down
+        '''
+        doc,xmlconfig = cleanxml.emptydoc("config")
+        #we ask all the backends to quit first.
+        if quit:
+            for b in self.get_all_backends():
+                #NOTE:we could do this in parallel. Maybe a quit and
+                #has_quit would be faster (invernizzi)
+                b.quit()
+        #we save the parameters
+        for b in self.get_all_backends(disabled = True):
+            param = b.get_parameters()
+            t_xml = doc.createElement("backend")
+            for key in param:
+                #We dont want parameters,backend,xmlobject
+                if key not in ["backend", "xmlobject"]:
+                    t_xml.setAttribute(str(key),str(param[key]))
+            #Saving all the projects at close
+            xmlconfig.appendChild(t_xml)
+            
+        datafile = os.path.join(CoreConfig.DATA_DIR, CoreConfig.DATA_FILE)
+        cleanxml.savexml(datafile,doc,backup=True)
+
+        #Saving the tagstore
+        ts = self.get_tagstore()
+        ts.save()
 
 class TaskSource():
-    """ transparent interface between the real backend and the datastore """
-    def __init__(self, backend, parameters):
+    '''
+    Transparent interface between the real backend and the DataStore.
+    Is in charge of connecting and disconnecting to signals
+    '''
+    def __init__(self, requester, backend, task_factory, push_task, get_task):
         """
         Instantiates a TaskSource object.
-        @param backend: (Required) Task Backend being wrapperized
-        @param parameters: Dictionary of custom parameters.
+        @param backend:  the backend being wrapped
+        @param task_factory: a function capable of generating tasks
+        @param push_task: a function to inject tasks into GTG
+        @param get_task: a function to obtain a Task object from a tid
         """
         self.backend = backend
-        self.dic = parameters
-        self.to_set = []
-        self.to_remove = []
-        self.lock = threading.Lock()
-        self.count_set = 0
+        self.req = requester
+        self.task_factory = task_factory
+        self.push_task = push_task
+        self.get_task = get_task
+        self.to_set = deque()
+        self.to_remove = deque()
+        self.task_filter = self.get_task_filter_for_backend()
+        self.set_task_handle = None
+        self.remove_task_handle = None
+        self.to_set_timer = None
         
-    def start_get_tasks(self,push_task,task_factory):
-        """
+    def start_get_tasks(self):
+        ''''
         Maps the TaskSource to the backend and starts threading.
-        This must be called before the DataStore is usable.
-        """
+        '''
         func = self.backend.start_get_tasks
-        t = threading.Thread(target=func,args=(push_task,task_factory))
+        t = threading.Thread(target=func,args=(self.push_task,\
+                                               self.task_factory))
         t.start()
-    
-    def set_task(self, task):
+        #FIXME: this should be postponed
+        self._connect_signals()
+
+    def get_task_filter_for_backend(self):
+        '''
+        Fiter that checks if the task should be stored in this backend.
+
+        @returns function: a function that accepts a task and returns True/False
+                 whether the task should be stored or not
+        '''
+        raw_filter = self.req.get_filter_bank().tags_intersection
+        return lambda task: raw_filter(task, \
+                              set(self.backend.get_attached_tags()))
+
+    def should_task_id_be_stored(self, task_id):
+        '''
+        Helper function:  Checks if a task should be stored in this backend
+        @param task_id: a task id
+        @returns bool: True if the task should be stored
+        '''
+        task = self.req.get_task(task_id)
+        return self.task_filter(task)
+
+    def set_task(self, sender, tid):
         """
         Updates the task in the DataStore.  Actually, it adds the task to a
         queue to be updated asynchronously.
+        @param sender: not used, any value will do.
         @param task: The Task object to be updated.
         """
-        tid = task.get_id()
-        if task not in self.to_set and tid not in self.to_remove:
-            self.to_set.append(task)
-        if self.lock.acquire(False):
-            func = self.setting_thread
-            t = threading.Thread(target=func)
-            t.start()
-#        else:
-#            print "cannot acquire lock : not a problem, just for debug purpose"
+        if self.should_task_id_be_stored(tid):
+            if tid not in self.to_set and tid not in self.to_remove:
+                self.to_set.appendleft(tid)
+                self.__try_launch_setting_thread()
+        else:
+            self.remove_task(None, tid)
             
-    def setting_thread(self):
-        """
+    def launch_setting_thread(self):
+        '''
         Operates the threads to set and remove tasks.
         Releases the lock when it is done.
-        """
-        try:
-            while len(self.to_set) > 0:
-                t = self.to_set.pop(0)
-                tid = t.get_id()
-                if tid not in self.to_remove:
-                    self.count_set += 1
-                    #print "saving task %s (%s saves)" %(tid,self.count_set)
-                    self.backend.set_task(t)
-            while len(self.to_remove) > 0:
-                tid = self.to_remove.pop(0)
-                self.backend.remove_task(tid)
-        finally:
-            self.lock.release()
+        '''
+        #FIXME: the lock should be general for all backends. Therefore, it
+        #should be handled in the datastore
+        while True:
+            try:
+                tid = self.to_set.pop()
+            except IndexError:
+                break
+            #we check that the task is not already marked for deletion
+            #and that it's still to be stored in this backend
+            #NOTE: no need to lock, we're reading
+            if tid not in self.to_remove and \
+                    self.should_task_id_be_stored(tid) and \
+                   self.req.has_task(tid):
+                task = self.req.get_task(tid)
+                self.backend.set_task(task)
+        while True:
+            try:
+                tid = self.to_remove.pop()
+            except IndexError:
+                break
+            self.backend.remove_task(tid)
+        #we release the weak lock
+        self.to_set_timer = None
     
-    def remove_task(self, tid):
-        """
+    def remove_task(self, sender, tid):
+        '''
         Queues task to be removed.
+        @param sender: not used, any value will do
         @param tid: The Task ID of the task to be removed
-        """
+        '''
         if tid not in self.to_remove:
-            self.to_remove.append(tid)
-        if self.lock.acquire(False):
-            func = self.setting_thread
-            t = threading.Thread(target=func)
-            t.start()
-    
-    def new_task_id(self):
-        """
-        returns a new ID created by the backend.
-        """
-        return self.backend.new_task_id()
-    
-    def quit(self):
-        """ Quits the backend """
-        self.backend.quit()
-        
-    #Those functions are only for TaskSource
-    def get_parameters(self):
-        """
-        Returns the parameters specified during creation of the DataStore
-        """
-        return self.dic
+            self.to_remove.appendleft(tid)
+            self.__try_launch_setting_thread()
+
+    def __try_launch_setting_thread(self):
+        '''
+        Helper function to launch the setting thread, if it's not running
+        '''
+        if self.to_set_timer == None:
+            self.to_set_timer = threading.Timer(10.0, \
+                                        self.launch_setting_thread)
+            self.to_set_timer.start()
+
+    def initialize(self, connect_signals = True):
+        '''
+        Initializes the backend and starts looking for signals.
+        @param connect_signals: if True, it starts listening for signals
+        '''
+        self.backend.initialize()
+        if connect_signals:
+            self._connect_signals()
+
+    def _connect_signals(self):
+        '''
+        Helper function to connect signals
+        '''
+        if not self.set_task_handle:
+            self.set_task_handle = self.req.connect('task-to-be-synced', \
+                                                    self.set_task)
+        if not self.remove_task_handle:
+            self.remove_task_handle = self.req.connect('task-deleted',\
+                                                   self.remove_task)
+
+    def _disconnect_signals(self):
+        '''
+        Helper function to disconnect signals
+        '''
+        if self.set_task_handle:
+            self.req.disconnect(self.set_task_handle)
+            self.set_task_handle = None
+        if  self.remove_task_handle:
+            self.req.disconnect(self.remove_task_handle)
+            self.remove_task_handle = None
+
+    def sync(self):
+        '''
+        Forces the TaskSource to sync all the pending tasks
+        '''
+        if self.to_set_timer != None:
+            try:
+                self.to_set_timer.cancel()
+                self.to_set_timer.join(20)
+            except:
+                pass
+        self.launch_setting_thread()
+
+    def quit(self, disable = False):
+        '''
+        Quits the backend and disconnect the signals
+        @param disable: if True, the backend is disabled.
+        '''
+        self._disconnect_signals()
+        self.sync()
+        self.backend.quit(disable)
+    
+    def __getattr__(self, attr):
+        '''
+        Delegates all the functions not defined here to the real backend
+        (standard python function)
+        @param attr: attribute to get
+        '''
+        if attr in self.__dict__: 
+            return self.__dict__[attr]
+        else:
+            return getattr(self.backend, attr)

=== modified file 'GTG/core/filteredtree.py'
--- GTG/core/filteredtree.py	2010-05-29 16:16:54 +0000
+++ GTG/core/filteredtree.py	2010-05-31 15:39:30 +0000
@@ -85,6 +85,8 @@
                                           gobject.TYPE_NONE, (str, )),
                     'task-deleted-inview': (gobject.SIGNAL_RUN_FIRST, \
                                             gobject.TYPE_NONE, (str, )),
+                    'path-deleted-inview': (gobject.SIGNAL_RUN_FIRST, \
+                                            gobject.TYPE_NONE, (str, )),
                     'task-modified-inview': (gobject.SIGNAL_RUN_FIRST, \
                                             gobject.TYPE_NONE, (str, )),}
 
@@ -201,7 +203,7 @@
             #we remove it
             if curdis:
 #                print "%s is removed" %tid
-                self.__remove_node(tid)
+                self.__task_deleted(None, tid)
             else:
 #                print "%s is modified, not to dis" %tid
                 self.emit("task-deleted-inview", tid)
@@ -654,6 +656,9 @@
                         self.__add_node(n,False)
     
     def __remove_node(self,tid):
+        paths = self.get_paths_for_node(self.get_node(tid))
+        for path in paths:
+            self.emit("path-deleted-inview", path)
         if tid in self.displayed_nodes:
             self.remove_count += 1
             self.__nodes_count -= 1

=== modified file 'GTG/core/filters_bank.py'
--- GTG/core/filters_bank.py	2010-05-23 00:52:33 +0000
+++ GTG/core/filters_bank.py	2010-05-31 15:39:30 +0000
@@ -176,6 +176,18 @@
         """ Filter of tasks which are closed """
         ret = task.get_status() in [Task.STA_DISMISSED, Task.STA_DONE]
         return ret
+
+    def tags_intersection(self, task, tags_to_match_set):
+        '''
+        Filter that checks if two task sets intersect.
+        @param task: a task object
+        @oaram tags_to_match_set: a *set* of tag names
+        '''
+        all_tasks_tag = self.req.get_alltag_tag().get_name()
+        if all_tasks_tag in tags_to_match_set:
+            return True
+        task_tags = set([t.get_name() for t in task.get_tags()])
+        return task_tags.intersection(tags_to_match_set)
         
     ##########################################
         

=== modified file 'GTG/core/requester.py'
--- GTG/core/requester.py	2010-05-20 11:42:48 +0000
+++ GTG/core/requester.py	2010-05-31 15:39:30 +0000
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 # -----------------------------------------------------------------------------
-# Gettings Things Gnome! - a personal organizer for the GNOME desktop
+# 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
@@ -39,18 +39,19 @@
     Multiple L{Requester}s can exist on the same datastore, so they should
     never have state of their own.
     """
-    __gsignals__ = {'task-added': (gobject.SIGNAL_RUN_FIRST, \
-                                    gobject.TYPE_NONE, (str, )),
-                    'task-deleted': (gobject.SIGNAL_RUN_FIRST, \
-                                    gobject.TYPE_NONE, (str, )),
-                    'task-modified': (gobject.SIGNAL_RUN_FIRST, \
-                                    gobject.TYPE_NONE, (str, )),
-                    'tag-added': (gobject.SIGNAL_RUN_FIRST, \
-                                    gobject.TYPE_NONE, (str, )),
-                    'tag-deleted': (gobject.SIGNAL_RUN_FIRST, \
-                                    gobject.TYPE_NONE, (str, )),
-                    'tag-modified': (gobject.SIGNAL_RUN_FIRST, \
-                                    gobject.TYPE_NONE, (str, ))}
+
+    __string_signal__ = (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (str, ))
+
+    __gsignals__ = {'task-added' : __string_signal__, \
+              'task-deleted'     : __string_signal__, \
+              'task-modified'    : __string_signal__, \
+              'task-to-be-synced': __string_signal__, \
+              'task-tagged'      : __string_signal__, \
+              'task-untagged'    : __string_signal__, \
+              'tag-added'        : __string_signal__, \
+              'tag-deleted'      : __string_signal__, \
+              'tag-path-deleted' : __string_signal__, \
+              'tag-modified'     : __string_signal__}
 
     def __init__(self, datastore):
         """Construct a L{Requester}."""
@@ -72,12 +73,23 @@
         self.counter_call += 1
         #print "signal task_modified %s (%s modifications)" %(tid,self.counter_call)
         gobject.idle_add(self.emit, "task-modified", tid)
-        
+
+    def _task_has_to_be_synced(self, tid):
+        #FIXME: is probably equivalent to task-modified (invernizzi)
+        gobject.idle_add(self.emit, "task-to-be-synced", tid)
+
+    def _task_deleted(self, tid):
+        #when this is emitted, task has *already* been deleted
+        gobject.idle_add(self.emit, "task-deleted", tid)
+
     def _tag_added(self,tagname):
         gobject.idle_add(self.emit, "tag-added", tagname)
 
     def _tag_modified(self,tagname):
         gobject.idle_add(self.emit, "tag-modified", tagname)
+
+    def _tag_path_deleted(self, path):
+        gobject.idle_add(self.emit, "tag-path-deleted", path)
         
     ############ Tasks Tree ######################
     # This is the main FilteredTree. You cannot apply filters
@@ -139,6 +151,10 @@
 
     ######### Filters bank #######################
     # Get the filter object for a given name
+
+    def get_filter_bank(self):
+        return self.filters
+
     def get_filter(self,filter_name):
         return self.filters.get_filter(filter_name)
     
@@ -175,7 +191,7 @@
         task = self.ds.get_task(tid)
         return task
 
-    def new_task(self, pid=None, tags=None, newtask=True):
+    def new_task(self, tags=None, newtask=True):
         """Create a new task.
 
         Note: this modifies the datastore.
@@ -188,31 +204,16 @@
             existed, C{False} if importing an existing task from a backend.
         @return: A task from the data store
         """
-        task = self.ds.new_task(pid=pid)
+        task = self.ds.new_task()
         if tags:
             for t in tags:
                 assert(isinstance(t, Tag) == False)
                 task.tag_added(t)
         return task
 
-    def delete_task(self, tid):
-        """Delete the task 'tid'.
-
-        Note: this modifies the datastore.
-
-        @param tid: The id of the task to be deleted.
-        """
-        #send the signal before actually deleting the task !
-        Log.debug("deleting task %s" % tid)
-        task = self.get_task(tid)
-        for tag in task.get_tags():
-            self.emit('tag-modified', tag.get_name())
-        self.emit('task-deleted', tid)
-        #return True
-        return self.ds.delete_task(tid)
-
     ############### Tags ##########################
     ###############################################
+
     def get_tag_tree(self):
         return self.ds.get_tagstore()
 
@@ -263,3 +264,27 @@
                 l.append(t.get_name())
         l.sort(cmp=lambda x, y: cmp(x.lower(),y.lower()))
         return l
+
+    ############## Backends #######################
+    ###############################################
+
+    def get_all_backends(self, disabled = False):
+        return self.ds.get_all_backends(disabled)
+
+    def register_backend(self, dic):
+        return self.ds.register_backend(dic)
+
+    def flush_all_tasks(self, backend_id):
+        return self.ds.flush_all_tasks(backend_id)
+
+    def get_backend(self, backend_id):
+        return self.ds.get_backend(backend_id)
+
+    def set_backend_enabled(self, backend_id, state):
+        return self.ds.set_backend_enabled(backend_id, state)
+
+    def remove_backend(self, backend_id):
+        return self.ds.remove_backend(backend_id)
+
+    def backend_change_attached_tags(self, backend_id, tags):
+        return self.ds.backend_change_attached_tags(backend_id, tags)

=== modified file 'GTG/core/tagstore.py'
--- GTG/core/tagstore.py	2010-05-28 23:47:15 +0000
+++ GTG/core/tagstore.py	2010-05-31 15:39:30 +0000
@@ -40,6 +40,7 @@
 # There's only one Tag store by user. It will store all the tag used
 # and their attribute.
 class TagStore(Tree):
+
     
     def __init__(self,requester):
         Tree.__init__(self)
@@ -48,7 +49,7 @@
         
         ### building the initial tags
         # Build the "all tasks tag"
-        self.alltag_tag = self.new_tag("gtg-tags-all")
+        self.alltag_tag = self.new_tag(CoreConfig.ALLTASKS_TAG)
         self.alltag_tag.set_attribute("special","all")
         self.alltag_tag.set_attribute("label","<span weight='bold'>%s</span>"\
                                              % _("All tasks"))
@@ -110,6 +111,14 @@
         if tagname[0] != "@":
             tagname = "@" + tagname
         return self.get_node(tagname)
+
+    def remove_tag(self, tag_name):
+        node = self.get_node(tag_name)
+        path = self.get_path_for_node(node)
+        print node
+        print path
+        self.req._tag_path_deleted(path)
+        self.remove_tag(tag_name)
         
     #FIXME : also add a new filter
     def rename_tag(self, oldname, newname):
@@ -137,7 +146,7 @@
                     tas = self.req.get_task(tid)
                     tas.rename_tag(oldname,newname)
                 #remove the old one
-                self.remove_node(oldname,otag)
+                self.remove_tag(oldname,otag)
                 
     def get_all_tags_name(self, attname=None, attvalue=None):
         """Return the name of all tags
@@ -302,9 +311,12 @@
     def add_task(self, tid):
         if tid not in self.tasks:
             self.tasks.append(tid)      
-    def remove_task(self,tid):
+
+    def remove_task(self, tid):
         if tid in self.tasks:
             self.tasks.remove(tid)          
+            self.req._tag_modified(self.get_name())
+
     def get_tasks(self):
         #return a copy of the list
         toreturn = self.tasks[:]

=== modified file 'GTG/core/task.py'
--- GTG/core/task.py	2010-05-23 00:52:33 +0000
+++ GTG/core/task.py	2010-05-31 15:39:30 +0000
@@ -50,7 +50,7 @@
         self.content = ""
         #self.content = \
         #    "<content>Press Escape or close this task to save it</content>"
-        self.sync_func = None
+        self.sync_functs = []
         self.title = _("My new task")
         #available status are: Active - Done - Dismiss - Note
         self.status = self.STA_ACTIVE
@@ -77,7 +77,6 @@
             self.loaded = True
             if signal:
                 self.req._task_loaded(self.tid)
-                #not sure the following is necessary
                 #self.req._task_modified(self.tid)
 
     def set_to_keep(self):
@@ -114,7 +113,7 @@
             self.title = title.strip('\t\n')
         else:
             self.title = "(no title task)"
-        #Avoid unecessary sync
+        #Avoid unnecessary sync
         if self.title != old_title:
             self.sync()
             return True
@@ -328,8 +327,7 @@
         """Add a newly created subtask to this task. Return the task added as
         a subtask
         """
-        uid, pid = self.get_id().split('@') #pylint: disable-msg=W0612
-        subt     = self.req.new_task(pid=pid, newtask=True)
+        subt     = self.req.new_task(newtask=True)
         #we use the inherited childrens
         self.add_child(subt.get_id())
         return subt
@@ -369,7 +367,7 @@
                 #FIXME: what about I want to move the child to a 
                 #       root node? We have to make sure that remove_parent
                 #       is called instead
-                self.req.delete_task(tid)
+                task.delete()
             self.sync()
             return True
         else:
@@ -463,31 +461,42 @@
     #This method is called by the datastore and should not be called directly
     #Use the requester
     def delete(self):
-        self.set_sync_func(None, callsync=False)
+        #we issue a delete for all the children
         for task in self.get_subtasks():
-            task.remove_parent(self.get_id())
-            self.req.delete_task(task.get_id())
+            #I think it's superfluous (invernizzi)
+            #task.remove_parent(self.get_id())
+            task.delete()
+        #we tell the parents we have to go
         for i in self.get_parents():
             task = self.req.get_task(i)
             task.remove_child(self.get_id())
+        #we tell the tags about the deletion
         for tagname in self.tags:
             tag = self.req.get_tag(tagname)
             tag.remove_task(self.get_id())
-        #then we remove effectively the task
-        #self.req.delete_task(self.get_id())
+        #then we signal the we are ready to be removed
+        self.req._task_deleted(self.get_id())
 
     #This is a callback. The "sync" function has to be set
-    def set_sync_func(self, sync, callsync=True):
-        self.sync_func = sync
-        #We call it immediatly to save stuffs that were set before this
+    def add_sync_func(self, sync, callsync = True):
+        if sync not in self.sync_functs:
+            self.sync_functs.append(sync)
+        #We call it immediately to save stuffs that were set before this
         if callsync and self.is_loaded():
             self.sync()
 
+    def remove_sync_func(self, sync):
+        if sync in self.sync_functs:
+            self.sync_functs.remove(sync)
+
     def sync(self):
         self._modified_update()
-        if self.sync_func and self.is_loaded():
-            self.sync_func(self)
-            self.call_modified()
+        if self.is_loaded():
+            self.req._task_has_to_be_synced(self.get_id())
+            #for func in self.sync_functs:
+            #    func(self)
+            if self.sync_functs != []:
+                self.call_modified()
     
     #This function send the modified signals for the tasks, 
     #parents and childrens       
@@ -502,6 +511,9 @@
             self.req._task_modified(p)
 
     def _modified_update(self):
+        '''
+        Updates the modified timestamp
+        '''
         self.modified = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
 
 
@@ -530,7 +542,7 @@
 
     def tag_added(self, tagname):
         """
-        Adds a tag. Does not add '@tag' to the contents. See insert_tag
+        Adds a tag. Does not add '@tag' to the contents. See add_tag
         """
         #print "tag %s added to task %s" %(tagname,self.get_id())
         t = tagname.encode("UTF-8")
@@ -541,6 +553,8 @@
         #Do not add the same tag twice
         if not t in self.tags:
             self.tags.append(t)
+            #we notify the backends
+            #self.req.tag_was_added_to_task(self, tagname)
             for child in self.get_subtasks():
                 if child.can_be_deleted:
                     child.add_tag(t)
@@ -578,6 +592,8 @@
         self.req._tag_modified(tagname)
         if tagname in self.tags:
             self.tags.remove(tagname)
+            #we notify the backends
+            #self.req.tag_was_removed_from_task(self, tagname)
             for child in self.get_subtasks():
                 if child.can_be_deleted:
                     child.remove_tag(tagname)

=== modified file 'GTG/gtg.py'
--- GTG/gtg.py	2010-05-26 09:54:42 +0000
+++ GTG/gtg.py	2010-05-31 15:39:30 +0000
@@ -51,17 +51,14 @@
 import os
 import dbus
 import logging
-import signal
-from contextlib import contextmanager
 
 #our own imports
-from GTG                     import _
-from GTG.viewmanager.manager import Manager
-from GTG.core.datastore      import DataStore
-from GTG.core                import CoreConfig
-from GTG.tools.logger        import Log
-from GTG.tools               import gtkcrashhandler
-from GTG                     import info
+from GTG                       import _
+from GTG.viewmanager.manager   import Manager
+from GTG.core.datastore        import DataStore
+from GTG.core                  import CoreConfig
+from GTG.tools.logger          import Log
+from GTG.tools.gtkcrashhandler import signal_catcher
 
 #=== OBJECTS ==================================================================
 
@@ -96,6 +93,23 @@
 #=== MAIN CLASS ===============================================================
 
 def main(options=None, args=None):
+    '''
+    Calling this starts the full GTG experience  ( :-D )
+    '''
+    config, ds, req = core_main_init(options, args)
+    # Launch task browser
+    manager = Manager(req, config)
+    #main loop
+    #To be more user friendly and get the logs of crashes, we show an apport
+    # hooked window upon crashes
+    with signal_catcher(manager.close_browser):
+        manager.main()
+    core_main_quit(config, ds)
+
+def core_main_init(options = None, args = None):
+    ''' 
+    Part of the main function prior to the UI initialization.
+    '''
     # Debugging subsystem initialization
     if options.debug:
         Log.setLevel(logging.DEBUG)
@@ -104,47 +118,30 @@
     config = CoreConfig()
     check_instance(config.DATA_DIR)
     backends_list = config.get_backends_list()
-
-    #initialize Apport hook for crash handling
-    gtkcrashhandler.initialize(app_name = "Getting Things GNOME!", message  =  \
-          "GTG" + info.VERSION + _(" has crashed. Please report the bug on <a "\
-          "href=\"http://bugs.edge.launchpad.net/gtg\";>our Launchpad page</a>."\
-          " If you have Apport installed, it will be started for you."),       \
-          use_apport = True)
-    
     # Load data store
     ds = DataStore()
-    
+    # Register backends 
     for backend_dic in backends_list:
         ds.register_backend(backend_dic)
-    
     #save directly the backends to be sure to write projects.xml
-    config.save_datastore(ds,initial_save=True)
+    ds.save(quit = False)
         
     # Launch task browser
     req = ds.get_requester()
-    manager = Manager(req, config)
- 
-    #we listen for signals from the system in order to save our configuration
-    # if GTG is forcefully terminated (e.g.: on shutdown).
-    @contextmanager
-    def signal_catcher():
-        #if TERM or ABORT are caught, we close the browser
-        for s in [signal.SIGABRT, signal.SIGTERM]:
-            signal.signal(s, lambda a,b: manager.close_browser())
-        yield
+    return config, ds, req
 
-    #main loop
-    with signal_catcher():
-        manager.main()
-      
+def core_main_quit(config, ds):
+    '''
+    Last bits of code executed in GTG, after the UI has been shut off. 
+    Currently, it's just saving everything.
+    '''
     # Ideally we should load window geometry configuration from a config.
     # backend like gconf at some point, and restore the appearance of the
     # application as the user last exited it.
-
+    #
     # Ending the application: we save configuration
-    config.save_config()
-    config.save_datastore(ds)
+    config.save()
+    ds.save(quit = True)
 
 #=== EXECUTION ================================================================
 

=== modified file 'GTG/taskbrowser/browser.py'
--- GTG/taskbrowser/browser.py	2010-05-28 23:47:15 +0000
+++ GTG/taskbrowser/browser.py	2010-05-31 15:39:30 +0000
@@ -25,7 +25,6 @@
 import pygtk
 pygtk.require('2.0')
 import gobject
-import os
 import gtk
 import locale
 import re
@@ -34,26 +33,23 @@
 
 #our own imports
 import GTG
-from GTG import info
-from GTG import _
-from GTG import ngettext
-from GTG.tools.logger                 import Log
-from GTG.core.task                    import Task
-#from GTG.core.tagstore                import Tag
-from GTG.taskbrowser                  import GnomeConfig
-from GTG.taskbrowser                  import tasktree
-#from GTG.taskbrowser.preferences      import PreferencesDialog
-from GTG.taskbrowser.tasktree         import TaskTreeModel,\
-                                             ActiveTaskTreeView,\
-                                             ClosedTaskTreeView
-from GTG.taskbrowser                  import tagtree
-from GTG.taskbrowser.tagtree          import TagTreeModel,\
-                                             TagTreeView
-from GTG.tools                        import openurl
-from GTG.tools.dates                  import strtodate,\
-                                             no_date,\
-                                             FuzzyDate, \
-                                             get_canonical_date
+from GTG                      import info
+from GTG.core                 import CoreConfig
+from GTG                      import _
+from GTG                      import ngettext
+from GTG.tools.logger         import Log
+from GTG.core.task            import Task
+#from GTG.core.tagstore       import Tag
+from GTG.taskbrowser          import GnomeConfig
+from GTG.taskbrowser          import tasktree
+from GTG.taskbrowser.tasktree import TaskTreeModel,\
+ActiveTaskTreeView,\
+ClosedTaskTreeView
+from GTG.taskbrowser          import tagtree
+from GTG.taskbrowser.tagtree  import TagTreeModel,\
+TagTreeView
+from GTG.tools                import openurl
+from GTG.tools.dates          import no_date,FuzzyDate, get_canonical_date
 #from GTG.tools                        import clipboard
 
 #=== MAIN CLASS ===============================================================
@@ -160,7 +156,7 @@
         self.priv['quick_add_cbs']            = []
 
     def _init_icon_theme(self):
-        icon_dirs = [GTG.DATA_DIR, os.path.join(GTG.DATA_DIR, "icons")]
+        icon_dirs = CoreConfig.get_icons_directories()
         for i in icon_dirs:
             gtk.icon_theme_get_default().prepend_search_path(i)
             gtk.window_set_default_icon_name("gtg")
@@ -323,6 +319,8 @@
                 self.on_nonworkviewtag_toggled,
             "on_preferences_activate":
                 self.open_preferences,
+            "on_edit_backends_activate":
+                self.open_edit_backends,
         }
         self.builder.connect_signals(SIGNAL_CONNECTIONS_DIC)
 
@@ -368,6 +366,11 @@
         # Connect requester signals to TreeModels
         self.req.connect("task-added", self.on_task_added) 
         self.req.connect("task-deleted", self.on_task_deleted)
+        #this causes changed be shouwn only on save
+        #tree = self.task_tree_model.get_tree()
+        #tree.connect("task-added-inview", self.on_task_added) 
+        #tree.connect("task-deleted-inview", self.on_task_deleted)
+
         
         # Connect signals from models
         self.task_modelsort.connect("row-has-child-toggled",\
@@ -442,9 +445,12 @@
 
 ### HELPER FUNCTIONS ########################################################
 
-    def open_preferences(self,widget):
-        self.vmanager.show_preferences(self.priv)
+    def open_preferences(self, widget):
+        self.vmanager.open_preferences(self.priv)
         
+    def open_edit_backends(self, widget):
+        self.vmanager.open_edit_backends()
+
     def quit(self,widget=None):
         self.vmanager.close_browser()
         

=== modified file 'GTG/taskbrowser/taskbrowser.glade'
--- GTG/taskbrowser/taskbrowser.glade	2010-05-22 22:41:44 +0000
+++ GTG/taskbrowser/taskbrowser.glade	2010-05-31 15:39:30 +0000
@@ -11,7 +11,6 @@
     <child>
       <object class="GtkVBox" id="master_vbox">
         <property name="visible">True</property>
-        <property name="orientation">vertical</property>
         <child>
           <object class="GtkMenuBar" id="browser_menu">
             <property name="visible">True</property>
@@ -154,6 +153,17 @@
                         <signal name="activate" handler="on_preferences_activate"/>
                       </object>
                     </child>
+                    <child>
+                      <object class="GtkImageMenuItem" id="backends_mi">
+                        <property name="label">_Backends</property>
+                        <property name="visible">True</property>
+                        <property name="use_underline">True</property>
+                        <property name="image">image4</property>
+                        <property name="use_stock">False</property>
+                        <property name="accel_group">accelgroup1</property>
+                        <signal name="activate" handler="on_edit_backends_activate"/>
+                      </object>
+                    </child>
                   </object>
                 </child>
               </object>
@@ -253,6 +263,7 @@
                         <property name="label">gtk-help</property>
                         <property name="visible">True</property>
                         <property name="tooltip_text" translatable="yes">Open GTG documentation in your web browser</property>
+                        <property name="use_underline">True</property>
                         <property name="use_stock">True</property>
                         <property name="accel_group">accelgroup1</property>
                         <signal name="activate" handler="on_documentation_clicked"/>
@@ -429,7 +440,6 @@
             <child>
               <object class="GtkVBox" id="sidebar_vbox">
                 <property name="width_request">75</property>
-                <property name="orientation">vertical</property>
                 <child>
                   <object class="GtkHBox" id="hbox4">
                     <property name="visible">True</property>
@@ -484,9 +494,9 @@
                   <object class="GtkNotebook" id="sidebar_notebook">
                     <property name="visible">True</property>
                     <property name="can_focus">True</property>
+                    <property name="tab_pos">bottom</property>
+                    <property name="show_tabs">False</property>
                     <property name="group_id">1</property>
-                    <property name="show_tabs">False</property>
-                    <property name="tab_pos">bottom</property>
                     <child>
                       <object class="GtkScrolledWindow" id="sidebar-scroll">
                         <property name="visible">True</property>
@@ -507,7 +517,6 @@
                       <packing>
                         <property name="tab_fill">False</property>
                       </packing>
-
                     </child>
                   </object>
                   <packing>
@@ -523,7 +532,6 @@
             <child>
               <object class="GtkVBox" id="main_vbox">
                 <property name="visible">True</property>
-                <property name="orientation">vertical</property>
                 <child>
                   <object class="GtkHBox" id="quickadd_pane">
                     <property name="visible">True</property>
@@ -567,14 +575,13 @@
                   <object class="GtkVPaned" id="vpaned1">
                     <property name="visible">True</property>
                     <property name="can_focus">True</property>
-                    <property name="orientation">vertical</property>
                     <child>
                       <object class="GtkNotebook" id="main_notebook">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
+                        <property name="tab_pos">bottom</property>
+                        <property name="show_tabs">False</property>
                         <property name="group_id">2</property>
-                        <property name="show_tabs">False</property>
-                        <property name="tab_pos">bottom</property>
                         <child>
                           <object class="GtkScrolledWindow" id="main_pane">
                             <property name="visible">True</property>
@@ -605,9 +612,9 @@
                       <object class="GtkNotebook" id="accessory_notebook">
                         <property name="visible">False</property>
                         <property name="can_focus">True</property>
+                        <property name="tab_pos">bottom</property>
+                        <property name="show_tabs">False</property>
                         <property name="group_id">2</property>
-                        <property name="show_tabs">False</property>
-                        <property name="tab_pos">bottom</property>
                         <child>
                           <placeholder/>
                         </child>
@@ -695,7 +702,6 @@
     <child internal-child="vbox">
       <object class="GtkVBox" id="about_dialog_vbox">
         <property name="visible">True</property>
-        <property name="orientation">vertical</property>
         <property name="spacing">2</property>
         <child internal-child="action_area">
           <object class="GtkHButtonBox" id="dialog-action_area2">
@@ -745,7 +751,6 @@
         <property name="visible">True</property>
         <property name="use_underline">True</property>
         <property name="use_stock">False</property>
-        <property name="submenu">schedule_for_context_menu</property>
       </object>
     </child>
     <child>
@@ -959,7 +964,6 @@
     <child internal-child="vbox">
       <object class="GtkVBox" id="addtag_dialog_vbox">
         <property name="visible">True</property>
-        <property name="orientation">vertical</property>
         <property name="spacing">2</property>
         <child>
           <object class="GtkLabel" id="addtag_label">
@@ -1062,4 +1066,8 @@
     <property name="pixel_size">16</property>
     <property name="icon_name">gtg-tag</property>
   </object>
+  <object class="GtkImage" id="image4">
+    <property name="visible">True</property>
+    <property name="stock">gtk-home</property>
+  </object>
 </interface>

=== modified file 'GTG/taskbrowser/tasktree.py'
--- GTG/taskbrowser/tasktree.py	2010-05-26 07:50:36 +0000
+++ GTG/taskbrowser/tasktree.py	2010-05-31 15:39:30 +0000
@@ -86,15 +86,18 @@
             self.tree = tree
         else:
             self.tree = self.req.get_main_tasks_tree()
-        self.tree.connect('task-added-inview',self.add_task)
-        self.tree.connect('task-deleted-inview',self.remove_task)
-        self.tree.connect('task-modified-inview',self.update_task)
+        self.tree.connect('task-added-inview', self.add_task)
+        self.tree.connect('path-deleted-inview', self.remove_task)
+        self.tree.connect('task-modified-inview', self.update_task)
         #need to get the GTK style for the inline preview of task content
         tempwin = gtk.Window()
         tempwin.realize()
         self.style = tempwin.get_style()
         tempwin.destroy()
 
+    def get_tree(self):
+        return self.tree
+
 ### TREE MODEL HELPER FUNCTIONS ###############################################
 
     def _count_active_subtasks_rec(self, task):
@@ -289,18 +292,18 @@
 #                       print "tasktree child toogled %s" %tid
                         self.row_has_child_toggled(par_path, par_iter)
 
-    def remove_task(self, sender, tid):
+    def remove_task(self, sender, string_path):
         #a task has been removed from the view. Therefore,
         # the widgets that represent it should be removed
-        Log.debug("tasktree remove_task %s" %tid)
-        node = self.tree.get_node(tid)
-        removed = False
-        node_paths = self.tree.get_paths_for_node(node)
-        for node_path in node_paths:
-            Log.debug("* tasktree REMOVE %s - %s " %(tid,node_path))
-            self.row_deleted(node_path)
-            removed = True
-        return removed
+
+        #we must rebuild the tuple (serialized as string
+        list_path = []
+        for c in string_path[1:-1].split(","):
+            if c:
+                list_path.append(int(c))
+        path = tuple(list_path)
+        self.row_deleted(path)
+        return True
                     
     def move_task(self, parent_tid, child_tid):
         """Moves the task identified by child_tid under

=== modified file 'GTG/tests/__init__.py'
--- GTG/tests/__init__.py	2010-05-29 13:02:14 +0000
+++ GTG/tests/__init__.py	2010-05-31 15:39:30 +0000
@@ -26,6 +26,8 @@
     test_taskviewserial,
     test_tree,
     test_apidocs,
+    test_backends,
+    test_datastore,
     )
 
 
@@ -35,4 +37,6 @@
         test_taskviewserial.test_suite(),
     	test_tree.test_suite(),
         test_apidocs.test_suite(),
+        test_backends.test_suite(),
+        test_datastore.test_suite(),
         ])

=== added file 'GTG/tests/test_backends.py'
--- GTG/tests/test_backends.py	1970-01-01 00:00:00 +0000
+++ GTG/tests/test_backends.py	2010-05-31 15:39:30 +0000
@@ -0,0 +1,183 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Gettings 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/>.
+# -----------------------------------------------------------------------------
+
+"""Tests for GTG backends.
+
+Some of these tests will generate files in
+xdg.BaseDirectory.xdg_data_home/gtg directory.
+"""
+
+# Standard imports
+import unittest
+import os
+import xdg
+
+# GTG imports
+from GTG.backends import backend_localfile as localfile
+from GTG.core import datastore
+from GTG.tools import cleanxml
+
+
+class GtgBackendsUniTests(unittest.TestCase):
+    """Tests for GTG backends."""
+
+    def __init__(self, test):
+        unittest.TestCase.__init__(self, test)
+        self.taskfile = ''
+        self.datafile = ''
+        self.taskpath = ''
+        self.datapath = ''
+
+    def test_localfile_get_name(self):
+        """Tests for localfile/get_name function :
+        - a string is expected.
+        """
+        res = localfile.Backend.get_name()
+        expectedres = "backend_localfile"
+        self.assertEqual(res, expectedres)
+
+    def test_localfile_get_description(self):
+        """Tests for localfile/get_description function :
+        - a string is expected.
+        """
+        res = localfile.Backend.get_description()
+        expectedres = "Your tasks are saved"
+        self.assertEqual(res[:len(expectedres)], expectedres)
+
+
+    def test_localfile_get_static_parameters(self):
+        """Tests for localfile/get_static_parameters function:
+        - a string is expected.
+        """
+        res = localfile.Backend.get_static_parameters()
+        self.assertEqual(res['path']['type'], "file")
+
+    def test_localfile_get_type(self):
+        """Tests for localfile/get_type function:
+        - a string is expected.
+        """
+        res = localfile.Backend.get_type()
+        expectedres = "readwrite"
+        self.assertEqual(res, expectedres)
+
+
+    def test_localfile_backend_method3(self):
+        """Tests for localfile/Backend/remove_task method:
+        - parse task file to check if task has been removed.
+        """
+        self.create_test_environment()
+        doc, configxml = cleanxml.openxmlfile(self.datapath, 'config')
+        xmlproject = doc.getElementsByTagName('backend')
+        for domobj in xmlproject:
+            dic = {}
+            if domobj.hasAttribute("module"):
+                dic["module"] = str(domobj.getAttribute("module"))
+                dic["pid"] = str(domobj.getAttribute("pid"))
+                dic["xmlobject"] = domobj
+                dic["path"] = self.taskpath
+        beobj = localfile.Backend(dic)
+        expectedres = True
+        beobj.remove_task("0@1")
+        beobj.quit()
+        dataline = open(self.taskpath, 'r').read()
+        print dataline
+        if "0@1" in dataline:
+            res = False
+        else:
+            res = True
+        expectedres = True
+        self.assertEqual(res, expectedres)
+
+#    def test_localfile_backend_method4(self):
+#        """Tests for localfile/Backend/get_task method:
+#        - Compares task titles to check if method works.
+#        """
+#        self.create_test_environment()
+#        doc, configxml = cleanxml.openxmlfile(self.datapath, 'config')
+#        xmlproject = doc.getElementsByTagName('backend')
+#        for domobj in xmlproject:
+#            dic = {}
+#            if domobj.hasAttribute("module"):
+#                dic["module"] = str(domobj.getAttribute("module"))
+#                dic["pid"] = str(domobj.getAttribute("pid"))
+#                dic["xmlobject"] = domobj
+#                dic["filename"] = self.taskfile
+#        beobj = localfile.Backend(dic)
+#        dstore = datastore.DataStore()
+#        newtask = dstore.new_task(tid="0@2", pid="1", newtask=True)
+#        beobj.get_task(newtask, "0@1")
+#        self.assertEqual(newtask.get_title(), u"Ceci est un test")
+
+#    def test_localfile_backend_method5(self):
+#        """Tests for localfile/Backend/set_task method:
+#        - parses task file to check if new task has been stored.
+#        """
+#        self.create_test_environment()
+#        doc, configxml = cleanxml.openxmlfile(self.datapath, 'config')
+#        xmlproject = doc.getElementsByTagName('backend')
+#        for domobj in xmlproject:
+#            dic = {}
+#            if domobj.hasAttribute("module"):
+#                dic["module"] = str(domobj.getAttribute("module"))
+#                dic["pid"] = str(domobj.getAttribute("pid"))
+#                dic["xmlobject"] = domobj
+#                dic["filename"] = self.taskfile
+#        beobj = localfile.Backend(dic)
+#        dstore = datastore.DataStore()
+#        newtask = dstore.new_task(tid="0@2", pid="1", newtask=True)
+#        beobj.set_task(newtask)
+#        dataline = open(self.taskpath, 'r').read()
+#        if "0@2" in dataline:
+#            res = True
+#        else:
+#            res = False
+#        expectedres = True
+#        self.assertEqual(res, expectedres)
+
+    def create_test_environment(self):
+        """Create the test environment"""
+        self.taskfile = 'test.xml'
+        self.datafile = 'projectstest.xml'
+        tasks = [
+            '<?xml version="1.0" ?>\n',
+            '<project>\n',
+            '\t<task id="0@1" status="Active" tags="">\n',
+            '\t\t<title>\n',
+            '\t\t\tCeci est un test\n',
+            '\t\t</title>\n',
+            '\t</task>\n',
+            '</project>\n',
+            ]
+        data = [
+            '<?xml version="1.0" ?>\n',
+            '<config>\n',
+            '\t<backend filename="test.xml" module="localfile" pid="1"/>\n',
+            '</config>\n',
+            ]
+        self.testdir = os.path.join(xdg.BaseDirectory.xdg_data_home, 'gtg')
+        if not os.path.exists(self.testdir):
+            os.makedirs(self.testdir)
+        self.taskpath = os.path.join(self.testdir, self.taskfile)
+        self.datapath = os.path.join(self.testdir, self.datafile)
+        open(self.taskpath, 'w').writelines(tasks)
+        open(self.datapath, 'w').writelines(data)
+
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromName(__name__)

=== added file 'GTG/tests/test_datastore.py'
--- GTG/tests/test_datastore.py	1970-01-01 00:00:00 +0000
+++ GTG/tests/test_datastore.py	2010-05-31 15:39:30 +0000
@@ -0,0 +1,348 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Gettings 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/>.
+# -----------------------------------------------------------------------------
+
+'''
+Tests for the datastore
+'''
+
+import unittest
+import uuid
+import random
+import time
+
+import GTG
+from GTG.core.datastore import DataStore
+from GTG.backends.genericbackend import GenericBackend
+from GTG.core import CoreConfig
+
+
+
+class TestDatastore(unittest.TestCase):
+    '''
+    Tests for the DataStore object.
+    '''
+
+    
+    def setUp(self):
+        '''
+        Creates the environment for the tests
+        @returns None
+        '''
+        self.datastore = DataStore()
+        self.requester = self.datastore.get_requester()
+
+    def test_task_factory(self):
+        '''
+        Test for the task_factory function
+        '''
+        #generate a Task with a random id
+        tid = str(uuid.uuid4())
+        task = self.datastore.task_factory(tid, newtask = True)
+        self.assertTrue(isinstance(task, GTG.core.task.Task))
+        self.assertEqual(task.get_id(), tid)
+        self.assertEqual(task.is_new(), True)
+        tid = str(uuid.uuid4())
+        task = self.datastore.task_factory(tid, newtask = False)
+        self.assertEqual(task.is_new(), False)
+
+    def test_new_task_and_has_task(self):
+        '''
+        Tests the new_task function
+        '''
+        task = self.datastore.new_task()
+        tid = task.get_id()
+        self.assertTrue(isinstance(tid, str))
+        self.assertTrue(tid != '')
+        self.assertTrue(task.is_new())
+        self.assertTrue(self.datastore.has_task(tid))
+        self.assertTrue(len(self.datastore.get_all_tasks()) == 1)
+
+    def test_get_all_tasks(self):
+        '''
+        Tests the get_all_tasks function
+        '''
+        task_ids = []
+        for i in xrange(1, 10):
+            task = self.datastore.new_task()
+            task_ids.append(task.get_id())
+            return_list =self.datastore.get_all_tasks()
+            self.assertEqual(len(return_list), i)
+            task_ids.sort()
+            return_list.sort()
+            self.assertEqual(task_ids, return_list)
+
+    def test_get_task(self):
+        '''
+        Tests the get_task function
+        '''
+        self.assertEqual(self.datastore.get_task(str(uuid.uuid4())), None)
+        task = self.datastore.new_task()
+        self.assertTrue(isinstance(self.datastore.get_task(task.get_id()),
+                                   GTG.core.task.Task))
+        self.assertEqual(self.datastore.get_task(task.get_id()), task)
+
+
+    def test_get_tagstore(self):
+        '''
+        Tests the get_tagstore function
+        '''
+        tagstore = self.datastore.get_tagstore()
+        self.assertTrue(isinstance(tagstore, GTG.core.tagstore.TagStore))
+
+    def test_get_requester(self):
+        '''
+        Tests the get_requester function
+        '''
+        requester = self.datastore.get_requester()
+        self.assertTrue(isinstance(requester, GTG.core.requester.Requester))
+
+    def test_get_tasks_tree(self):
+        '''
+        Tests the get_tasks_tree function
+        '''
+        tasks_tree = self.datastore.get_tasks_tree()
+        self.assertTrue(isinstance(tasks_tree, GTG.core.tree.Tree))
+
+    def test_push_task(self):
+        '''
+        Tests the push_task function
+        '''
+        task_ids = []
+        for i in xrange(1, 10):
+            tid = str(uuid.uuid4())
+            if tid not in task_ids:
+                task_ids.append(tid)
+            task = self.datastore.task_factory(tid)
+            return_value1 = self.datastore.push_task(task)
+            self.assertTrue(return_value1)
+            #we do it twice, but it should be pushed only once if it's
+            # working correctly (the second should be discarded)
+            return_value2 = self.datastore.push_task(task)
+            self.assertFalse(return_value2)
+            stored_tasks = self.datastore.get_all_tasks()
+            task_ids.sort()
+            stored_tasks.sort()
+            self.assertEqual(task_ids, stored_tasks)
+
+    def test_register_backend(self):
+        '''
+        Tests the register_backend function. It also tests the
+        get_all_backends and get_backend function as a side effect
+        '''
+        #create a simple backend dictionary
+        backend = FakeBackend(enabled = True)
+        tasks_in_backend_count = int(random.random() * 20)
+        for temp in xrange(0, tasks_in_backend_count):
+            backend.fake_add_random_task()
+        backend_dic = {'backend': backend, 'pid': 'a'}
+        self.datastore.register_backend(backend_dic)
+        all_backends = self.datastore.get_all_backends(disabled = True)
+        self.assertEqual(len(all_backends), 1)
+        registered_backend = self.datastore.get_backend(backend.get_id())
+        self.assertEqual(backend.get_id(), registered_backend.get_id())
+        self.assertTrue(isinstance(registered_backend, \
+                                   GTG.core.datastore.TaskSource))
+        self.assertTrue(registered_backend.is_enabled())
+        self.assertEqual(registered_backend.fake_get_initialized_count(), 1)
+        #we give some time for the backend to push all its tasks
+        time.sleep(1)
+        self.assertEqual(len(self.datastore.get_all_tasks()), \
+                         tasks_in_backend_count)
+
+        #same test, disabled backend
+        backend = FakeBackend(enabled = False)
+        for temp in xrange(1, int(random.random() * 20)):
+            backend.fake_add_random_task()
+        backend_dic = {'backend': backend, 'pid':'b'}
+        self.datastore.register_backend(backend_dic)
+        all_backends = self.datastore.get_all_backends(disabled = True)
+        self.assertEqual(len(all_backends), 2)
+        all_backends = self.datastore.get_all_backends(disabled = False)
+        self.assertEqual(len(all_backends), 1)
+        registered_backend = self.datastore.get_backend(backend.get_id())
+        self.assertEqual(backend.get_id(), registered_backend.get_id())
+        self.assertTrue(isinstance(registered_backend, \
+                                   GTG.core.datastore.TaskSource))
+        self.assertFalse(registered_backend.is_enabled())
+        self.assertEqual(registered_backend.fake_get_initialized_count(), 0)
+        #we give some time for the backend to push all its tasks (is
+        #shouldn't, since it's disabled, but we give time anyway
+        time.sleep(1)
+        self.assertEqual(len(self.datastore.get_all_tasks()), \
+                         tasks_in_backend_count)
+
+    def test_set_backend_enabled(self):
+        '''
+        Tests the set_backend_enabled function
+        '''
+        enabled_backend = FakeBackend(enabled = True)
+        disabled_backend = FakeBackend(enabled = False)
+        self.datastore.register_backend({'backend': enabled_backend, \
+                                'pid': str(uuid.uuid4()), \
+                                GenericBackend.KEY_DEFAULT_BACKEND: False})
+        self.datastore.register_backend({'backend': disabled_backend,\
+                                'pid': str(uuid.uuid4()), \
+                                GenericBackend.KEY_DEFAULT_BACKEND: False})
+        #enabling an enabled backend
+        self.datastore.set_backend_enabled(enabled_backend.get_id(), True)
+        self.assertEqual(enabled_backend.fake_get_initialized_count(), 1)
+        self.assertTrue(enabled_backend.is_enabled())
+        #disabling a disabled backend
+        self.datastore.set_backend_enabled(disabled_backend.get_id(), False)
+        self.assertEqual(disabled_backend.fake_get_initialized_count(), 0)
+        self.assertFalse(disabled_backend.is_enabled())
+        #disabling an enabled backend
+        self.datastore.set_backend_enabled(enabled_backend.get_id(), False)
+        self.assertEqual(enabled_backend.fake_get_initialized_count(), 1)
+        self.assertFalse(enabled_backend.is_enabled())
+        #enabling a disabled backend
+        self.datastore.set_backend_enabled(disabled_backend.get_id(), True)
+        self.assertEqual(disabled_backend.fake_get_initialized_count(), 1)
+        self.assertTrue(disabled_backend.is_enabled())
+            
+    def test_remove_backend(self):
+        '''
+        Tests the remove_backend function
+        '''
+        enabled_backend = FakeBackend(enabled = True)
+        disabled_backend = FakeBackend(enabled = False)
+        self.datastore.register_backend({'backend': enabled_backend, \
+                                'pid': str(uuid.uuid4()), \
+                                GenericBackend.KEY_DEFAULT_BACKEND: False})
+        self.datastore.register_backend({'backend': disabled_backend,\
+                                'pid': str(uuid.uuid4()), \
+                                GenericBackend.KEY_DEFAULT_BACKEND: False})
+        #removing an enabled backend
+        self.datastore.remove_backend(enabled_backend.get_id())
+        self.assertFalse(enabled_backend.is_enabled())
+        self.assertTrue(enabled_backend.fake_is_purged())
+        self.assertEqual( \
+            len(self.datastore.get_all_backends(disabled = True)), 1)
+        #removing a disabled backend
+        self.datastore.remove_backend(disabled_backend.get_id())
+        self.assertFalse(disabled_backend.is_enabled())
+        self.assertTrue(disabled_backend.fake_is_purged())
+        self.assertEqual( \
+            len(self.datastore.get_all_backends(disabled = True)), 0)
+
+    def test_flush_all_tasks(self):
+        '''
+        Tests the flush_all_tasks function
+        '''
+        #we add some tasks in the datastore
+        tasks_in_datastore_count = 10 #int(random.random() * 20)
+        for temp in xrange(0, tasks_in_datastore_count):
+            self.datastore.new_task()
+        datastore_stored_tids = self.datastore.get_all_tasks()
+        self.assertEqual(tasks_in_datastore_count, len(datastore_stored_tids))
+
+        #we enable a backend
+        backend = FakeBackend(enabled = True)
+        self.datastore.register_backend({'backend': backend, 'pid': 'a'})
+        #we wait for the signal storm to wear off
+        time.sleep(5)
+        #we sync
+        self.datastore.get_backend(backend.get_id()).sync()
+        #and we inject task in the backend
+        tasks_in_backend_count = 5 #int(random.random() * 20)
+        for temp in xrange(0, tasks_in_backend_count):
+            backend.fake_add_random_task()
+        backend_stored_tids = backend.fake_get_task_ids()
+        self.assertEqual(tasks_in_backend_count, len(backend_stored_tids))
+        self.datastore.flush_all_tasks(backend.get_id())
+        #we wait for the signal storm to wear off
+        time.sleep(2)
+        #we sync
+        self.datastore.get_backend(backend.get_id()).sync()
+        all_tasks_count = tasks_in_backend_count + tasks_in_datastore_count
+        new_datastore_stored_tids = self.datastore.get_all_tasks()
+        new_backend_stored_tids = backend.fake_get_task_ids()
+        self.assertEqual(len(new_backend_stored_tids), all_tasks_count)
+        self.assertEqual(len(new_datastore_stored_tids), all_tasks_count)
+        new_datastore_stored_tids.sort()
+        new_backend_stored_tids.sort()
+        self.assertEqual(new_backend_stored_tids, new_datastore_stored_tids)
+
+
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromTestCase(TestDatastore)
+
+
+
+class FakeBackend(object):
+    '''
+    Mimics the behavior of a simple backend. Just used for testing
+    '''
+
+    def __init__(self, enabled = True):
+        self.enabled = enabled
+        self.initialized_count = 0
+        self.tasks_ids = []
+        self.backend_id = str(uuid.uuid4())
+        self.purged = False
+
+    def is_enabled(self):
+        return self.enabled
+
+    def initialize(self):
+        self.initialized_count += 1
+        self.enabled = True
+
+    def set_task(self, task):
+        self.tasks_ids.append(task.get_id())
+
+    def has_task(self, task_id):
+        return task_id in self.tasks_ids
+
+    def remove_task(self, task_id):
+        self.tasks_ids.remove(task_id)
+
+    def get_id(self):
+        return self.backend_id
+
+    def start_get_tasks(self, push_task, task_factory):
+        for task_id in self.tasks_ids:
+            push_task(task_factory(task_id))
+
+    def quit(self, disabled = False):
+        self.enabled = not disabled
+
+    def purge(self):
+        self.purged = True
+
+    def get_attached_tags(self):
+        return [CoreConfig.ALLTASKS_TAG]
+    
+    ##########################################################################
+    # The following are used just for testing, they're not present inside a
+    # normal backend
+    ##########################################################################
+
+    def fake_get_initialized_count(self):
+        return self.initialized_count
+
+    def fake_get_task_ids(self):
+        return self.tasks_ids
+
+    def fake_add_random_task(self):
+        self.tasks_ids.append(str(uuid.uuid4()))
+
+    def fake_is_purged(self):
+        return self.purged

=== added file 'GTG/tools/borg.py'
--- GTG/tools/borg.py	1970-01-01 00:00:00 +0000
+++ GTG/tools/borg.py	2010-05-31 15:39:30 +0000
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Gettings 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/>.
+# -----------------------------------------------------------------------------
+
+
+
+class Borg(object):
+    """
+    This pattern ensures that all instances of a particular class share
+    the same state (just inherit this class to have it working)
+    """
+
+    _borg_state = {}
+    
+    def __init__(self):
+        self.__dict__ = self._borg_state
+
+

=== modified file 'GTG/tools/colors.py'
--- GTG/tools/colors.py	2009-12-06 22:33:19 +0000
+++ GTG/tools/colors.py	2010-05-31 15:39:30 +0000
@@ -20,7 +20,7 @@
 
 #Take list of Tags and give the background color that should be applied
 #The returned color might be None (in which case, the default is used)
-def background_color(tags, bgcolor=None):
+def background_color(tags, bgcolor = None):
     if not bgcolor:
         bgcolor = gtk.gdk.color_parse("#FFFFFF")
     # Compute color
@@ -51,3 +51,30 @@
         
         my_color = gtk.gdk.Color(red, green, blue).to_string()
     return my_color
+
+def get_colored_tag_markup(req, tag_name):
+    '''
+    Given a tag name, returns a string containing the markup to color the
+    tag name
+    '''
+    tag = req.get_tag(tag_name)
+    if tag is None:
+        #no task loaded with that tag, color cannot be taken
+        return tag_name
+    else:
+        tag_color = tag.get_attribute("color")
+        if tag_color:
+            return '<span color="%s">%s</span>' % (tag_color, tag_name)
+        else:
+            return tag_name
+
+def get_colored_tags_markup(req, tag_names):
+    '''
+    Calls get_colored_tag_markup for each tag_name in tag_names
+    '''
+    tag_markups = map(lambda t: get_colored_tag_markup(req, t), tag_names)
+    tags_txt = ""
+    if tag_markups:
+        #reduce crashes if applied to an empty list
+        tags_txt = reduce(lambda a, b: a + ", " + b, tag_markups)
+    return tags_txt

=== modified file 'GTG/tools/gtkcrashhandler.py'
--- GTG/tools/gtkcrashhandler.py	2010-05-29 14:00:26 +0000
+++ GTG/tools/gtkcrashhandler.py	2010-05-31 15:39:30 +0000
@@ -33,6 +33,12 @@
 import sys
 import os
 import time
+import signal
+from contextlib import contextmanager
+
+from GTG import info
+
+
 try:
     import pygtk
     pygtk.require("2.0") # not tested on earlier versions
@@ -297,3 +303,21 @@
             return "gtkcrashhandler.py should imported, not run"
     raise DoNotRunException()
 
+
+## We handle initialization directly here, since this module will be used as a
+#  singleton
+        #we listen for signals from the system in order to save our configuration
+        # if GTG is forcefully terminated (e.g.: on shutdown).
+@contextmanager
+def signal_catcher(callback):
+    #if TERM or ABORT are caught, we execute the callback function
+    for s in [signal.SIGABRT, signal.SIGTERM]:
+        signal.signal(s, lambda a,b: callback())
+    yield
+
+initialize(app_name = "Getting Things GNOME!",
+           message  =  "GTG" + info.VERSION + 
+           _(" has crashed. Please report the bug on <a "\
+             "href=\"http://bugs.edge.launchpad.net/gtg\";>our Launchpad page</a>."\
+             " If you have Apport installed, it will be started for you."),       \
+          use_apport = True)

=== modified file 'GTG/viewmanager/__init__.py'
--- GTG/viewmanager/__init__.py	2010-03-02 14:31:47 +0000
+++ GTG/viewmanager/__init__.py	2010-05-31 15:39:30 +0000
@@ -25,9 +25,12 @@
 
 import os
 
-from GTG import _
+
 
 class ViewConfig:
+
+
     current_rep = os.path.dirname(os.path.abspath(__file__))
     DELETE_GLADE_FILE  = os.path.join(current_rep, "deletion.glade")
     PREFERENCES_GLADE_FILE = os.path.join(current_rep, "preferences.glade")
+    BACKENDS_GLADE_FILE = os.path.join(current_rep, "backends_dialog.glade")

=== modified file 'GTG/viewmanager/delete_dialog.py'
--- GTG/viewmanager/delete_dialog.py	2010-02-28 13:10:00 +0000
+++ GTG/viewmanager/delete_dialog.py	2010-05-31 15:39:30 +0000
@@ -39,7 +39,11 @@
         """if we pass a tid as a parameter, we delete directly
         otherwise, we will look which tid is selected"""
         for tid in self.tids_todelete:
-            self.req.delete_task(tid)
+            task = self.req.get_task(tid)
+            if task:
+                task.delete()
+            else:
+                print "trying to delete task already deleted"
         self.tids_todelete = []
 
     def delete_tasks(self, tids=None):

=== modified file 'GTG/viewmanager/manager.py'
--- GTG/viewmanager/manager.py	2010-05-20 09:18:47 +0000
+++ GTG/viewmanager/manager.py	2010-05-31 15:39:30 +0000
@@ -26,15 +26,16 @@
 import gobject
 
 import GTG
-from GTG.viewmanager.delete_dialog import DeletionUI
-from GTG.taskbrowser.browser import TaskBrowser
-from GTG.taskeditor.editor            import TaskEditor
-from GTG.viewmanager.preferences      import PreferencesDialog
-from GTG.viewmanager.dbuswrapper import DBusTaskWrapper
-from GTG.tools                        import clipboard
-from GTG.core.plugins.engine          import PluginEngine
-from GTG.core.plugins.api             import PluginAPI
-from GTG.tools.logger                 import Log
+from GTG.viewmanager.delete_dialog   import DeletionUI
+from GTG.taskbrowser.browser         import TaskBrowser
+from GTG.taskeditor.editor           import TaskEditor
+from GTG.viewmanager.preferences     import PreferencesDialog
+#from GTG.viewmanager.backends_dialog import BackendsDialog
+from GTG.viewmanager.dbuswrapper     import DBusTaskWrapper
+from GTG.tools                       import clipboard
+from GTG.core.plugins.engine         import PluginEngine
+from GTG.core.plugins.api            import PluginAPI
+from GTG.tools.logger                import Log
 
 class Manager():
 
@@ -69,9 +70,10 @@
         #Deletion UI
         self.delete_dialog = None
         
-        #Preferences windows
-        # Initialize "Preferences" dialog
-        self.preferences = None
+        #Preferences and Backends windows
+        # Initialize  dialogs
+        self.preferences_dialog = None
+        self.edit_backends_dialog = None
         
         #DBus
         DBusTaskWrapper(self.req, self)
@@ -186,10 +188,16 @@
             
 ################ Others dialog ############################################
 
-    def show_preferences(self, config_priv, sender=None):
-        if not self.preferences:
-            self.preferences = PreferencesDialog(self.pengine, self.p_apis)
-        self.preferences.activate(config_priv)
+    def open_preferences(self, config_priv, sender=None):
+        if not self.preferences_dialog:
+            self.preferences_dialog = PreferencesDialog(self.pengine, self.p_apis)
+        self.preferences_dialog.activate(config_priv)
+
+    def open_edit_backends(self, sender = None):
+        pass
+#        if not self.edit_backends_dialog:
+#            self.edit_backends_dialog = BackendsDialog(self.req)
+#        self.edit_backends_dialog.activate()
         
     def ask_delete_tasks(self, tids):
         if not self.delete_dialog:

=== modified file 'GTG/viewmanager/preferences.glade'
--- GTG/viewmanager/preferences.glade	2010-04-29 09:23:34 +0000
+++ GTG/viewmanager/preferences.glade	2010-05-31 15:39:30 +0000
@@ -213,63 +213,10 @@
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkAlignment" id="prefs-alignment3">
-                    <property name="visible">True</property>
-                    <property name="top_padding">10</property>
-                    <property name="bottom_padding">10</property>
-                    <property name="left_padding">10</property>
-                    <property name="right_padding">10</property>
-                    <child>
-                      <object class="GtkVBox" id="prefs-vbox5">
-                        <property name="visible">True</property>
-                        <property name="spacing">6</property>
-                        <child>
-                          <object class="GtkLabel" id="prefs-label6">
-                            <property name="visible">True</property>
-                            <property name="xalign">0</property>
-                            <property name="label" translatable="yes">Task _Backends:</property>
-                            <property name="use_underline">True</property>
-                          </object>
-                          <packing>
-                            <property name="expand">False</property>
-                            <property name="position">0</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkScrolledWindow" id="prefs-scrolledwindow1">
-                            <property name="visible">True</property>
-                            <property name="sensitive">False</property>
-                            <property name="can_focus">True</property>
-                            <property name="hscrollbar_policy">never</property>
-                            <property name="vscrollbar_policy">automatic</property>
-                            <child>
-                              <object class="GtkTreeView" id="BackendTree">
-                                <property name="visible">True</property>
-                                <property name="sensitive">False</property>
-                                <property name="can_focus">True</property>
-                              </object>
-                            </child>
-                          </object>
-                          <packing>
-                            <property name="position">1</property>
-                          </packing>
-                        </child>
-                      </object>
-                    </child>
-                  </object>
-                  <packing>
-                    <property name="position">1</property>
-                  </packing>
+                  <placeholder/>
                 </child>
                 <child type="tab">
-                  <object class="GtkLabel" id="prefs-label2">
-                    <property name="visible">True</property>
-                    <property name="label" translatable="yes">Storage</property>
-                  </object>
-                  <packing>
-                    <property name="position">1</property>
-                    <property name="tab_fill">False</property>
-                  </packing>
+                  <placeholder/>
                 </child>
                 <child>
                   <object class="GtkAlignment" id="prefs-alignment4">

=== modified file 'scripts/debug.sh'
--- scripts/debug.sh	2010-05-19 23:21:09 +0000
+++ scripts/debug.sh	2010-05-31 15:39:30 +0000
@@ -3,15 +3,17 @@
 args=""
 set="default"
 norun=0
+profiling=0
 
 # Create execution-time data directory if needed
 mkdir -p tmp
 
 # Interpret arguments
-while getopts dns: o
+while getopts dnps: o
 do  case "$o" in
     d)   args="-d";;
     n)   norun=1;;
+    p)   profiling=1;;
     s)   set="$OPTARG";;
     [?]) echo >&2 "Usage: $0 [-s dataset] [-d] [-n]"
          exit 1;;
@@ -38,6 +40,10 @@
 fi
 
 if [ $norun -eq 0 ]; then
+    if [ $profiling -eq 0 ]; then
     ./gtg $args
+    else
+    python -m cProfile -o gtg.prof ./gtg
+    fi
 fi
 

=== added file 'scripts/profile_interpret.sh'
--- scripts/profile_interpret.sh	1970-01-01 00:00:00 +0000
+++ scripts/profile_interpret.sh	2010-05-31 15:39:30 +0000
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+import pstats
+p = pstats.Stats('gtg.prof')
+p.strip_dirs().sort_stats("cumulative").print_stats(20)