~saurabhanandiit/gtg/exportFixed

« back to all changes in this revision

Viewing changes to GTG/backends/backend_localfile.py

Merge of my work on liblarch newbase and all the backends ported to liblarch
(which mainly means porting the datastore).
One failing test, will check it.

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
'''
21
21
Localfile is a read/write backend that will store your tasks in an XML file
22
22
This file will be in your $XDG_DATA_DIR/gtg folder.
 
23
 
 
24
This backend contains comments that are meant as a reference, in case someone
 
25
wants to write a backend.
23
26
'''
24
27
 
25
28
import os
29
32
from GTG.core                    import CoreConfig
30
33
from GTG.tools                   import cleanxml, taskxml
31
34
from GTG                         import _
 
35
from GTG.tools.logger            import Log
32
36
 
33
37
 
34
38
 
35
39
class Backend(GenericBackend):
 
40
    '''
 
41
    Localfile backend, which stores your tasks in a XML file in the standard
 
42
    XDG_DATA_DIR/gtg folder (the path is configurable).
 
43
    An instance of this class is used as the default backend for GTG.
 
44
    This backend loads all the tasks stored in the localfile after it's enabled,
 
45
    and from that point on just writes the changes to the file: it does not
 
46
    listen for eventual file changes
 
47
    '''
36
48
    
37
49
 
38
50
    DEFAULT_PATH = CoreConfig().get_data_dir() #default path for filenames
39
51
 
40
52
 
41
 
    #Description of the backend (mainly it's data we show the user, only the
42
 
    # name is used internally. Please note that BACKEND_NAME and
43
 
    # BACKEND_ICON_NAME should *not* be translated.
 
53
    #General description of the backend: these are used to show a description of
 
54
    # the backend to the user when s/he is considering adding it.
 
55
    # BACKEND_NAME is the name of the backend used internally (it must be
 
56
    # unique).
 
57
    #Please note that BACKEND_NAME and BACKEND_ICON_NAME should *not* be
 
58
    #translated.
44
59
    _general_description = { \
45
60
        GenericBackend.BACKEND_NAME:       "backend_localfile", \
46
61
        GenericBackend.BACKEND_HUMAN_NAME: _("Local File"), \
53
68
              "for GTG to save your tasks."),\
54
69
        }
55
70
 
56
 
    #parameters to configure a new backend of this type.
57
 
    #NOTE: should we always give back a different default filename? it can be
58
 
    #      done, but I'd like to keep this backend simple, so that it can be
59
 
    #      used as example (invernizzi)
 
71
    #These are the parameters to configure a new backend of this type. A
 
72
    # parameter has a name, a type and a default value.
 
73
    # Here, we define a parameter "path", which is a string, and has a default
 
74
    # value as a random file in the default path
 
75
    #NOTE: to keep this simple, the filename default path is the same until GTG
 
76
    #      is restarted. I consider this a minor annoyance, and we can avoid
 
77
    #      coding the change of the path each time a backend is
 
78
    #      created (invernizzi)
60
79
    _static_parameters = { \
61
80
        "path": { \
62
81
            GenericBackend.PARAM_TYPE: GenericBackend.TYPE_STRING, \
64
83
                 os.path.join(DEFAULT_PATH, "gtg_tasks-%s.xml" %(uuid.uuid4()))
65
84
        }}
66
85
 
67
 
    def _get_default_filename_path(self, filename = None):
68
 
        '''
69
 
        Generates a default path with a random filename
70
 
        @param filename: specify a filename
71
 
        '''
72
 
        if not filename:
73
 
            filename = "gtg_tasks-%s.xml" % (uuid.uuid4())
74
 
        return os.path.join(self.DEFAULT_PATH, filename)
75
 
 
76
86
    def __init__(self, parameters):
77
87
        """
78
88
        Instantiates a new backend.
79
89
 
80
 
        @param parameters: should match the dictionary returned in
81
 
        get_parameters. Anyway, the backend should care if one expected
82
 
        value is None or does not exist in the dictionary. 
83
 
        @firstrun: only needed for the default backend. It should be
84
 
        omitted for all other backends.
 
90
        @param parameters: A dictionary of parameters, generated from
 
91
        _static_parameters. A few parameters are added to those, the list of
 
92
        these is in the "DefaultBackend" class, look for the KEY_* constants.
 
93
    
 
94
        The backend should take care if one expected value is None or
 
95
        does not exist in the dictionary.
85
96
        """
86
97
        super(Backend, self).__init__(parameters)
87
 
        self.tids = []
 
98
        self.tids = [] #we keep the list of loaded task ids here
88
99
        #####RETROCOMPATIBILIY
89
 
        #NOTE: retrocompatibility. We convert "filename" to "path"
90
 
        #      and we forget about "filename"
 
100
        #NOTE: retrocompatibility from the 0.2 series to 0.3.
 
101
        # We convert "filename" to "path and we forget about "filename "
91
102
        if "need_conversion" in parameters:
92
103
            parameters["path"] = os.path.join(self.DEFAULT_PATH, \
93
104
                                        parameters["need_conversion"])
99
110
                                self._parameters["path"], "project")
100
111
 
101
112
    def initialize(self):
 
113
        """This is called when a backend is enabled"""
102
114
        super(Backend, self).initialize()
103
115
        self.doc, self.xmlproj = cleanxml.openxmlfile( \
104
116
                                self._parameters["path"], "project")
105
117
 
106
118
    def this_is_the_first_run(self, xml):
107
 
        #Create the default tasks for the first run.
108
 
        #We write the XML object in a file
 
119
        """
 
120
        Called upon the very first GTG startup.
 
121
        This function is needed only in this backend, because it can be used as
 
122
        default one.
 
123
        The xml parameter is an object containing GTG default tasks. It will be
 
124
        saved to a file, and the backend will be set as default.
 
125
        @param xml: an xml object containing the default tasks.
 
126
        """
109
127
        self._parameters[self.KEY_DEFAULT_BACKEND] = True
110
128
        cleanxml.savexml(self._parameters["path"], xml)
111
129
        self.doc, self.xmlproj = cleanxml.openxmlfile(\
112
130
                        self._parameters["path"], "project")
113
 
        self._parameters[self.KEY_DEFAULT_BACKEND] = True
114
131
 
115
132
    def start_get_tasks(self):
116
133
        '''
117
 
        Once this function is launched, the backend can start pushing
118
 
        tasks to gtg parameters.
119
 
        
 
134
        This function starts submitting the tasks from the XML file into GTG core.
 
135
        It's run as a separate thread.
 
136
                
120
137
        @return: start_get_tasks() might not return or finish
121
138
        '''
122
139
        tid_list = []
130
147
                self.datastore.push_task(task)
131
148
 
132
149
    def set_task(self, task):
133
 
            tid = task.get_id()
134
 
            existing = None
135
 
            #First, we find the existing task from the treenode
136
 
            for node in self.xmlproj.childNodes:
137
 
                if node.getAttribute("id") == tid:
138
 
                    existing = node
139
 
            t_xml = taskxml.task_to_xml(self.doc, task)
140
 
            modified = False
141
 
            #We then replace the existing node
142
 
            if existing and t_xml:
143
 
                #We will write only if the task has changed
144
 
                if t_xml.toxml() != existing.toxml():
145
 
                    self.xmlproj.replaceChild(t_xml, existing)
146
 
                    modified = True
147
 
            #If the node doesn't exist, we create it
148
 
            # (it might not be the case in all backends
149
 
            else:
150
 
                self.xmlproj.appendChild(t_xml)
 
150
        '''
 
151
        This function is called from GTG core whenever a task should be
 
152
        saved, either because it's a new one or it has been modified.
 
153
        This function will look into the loaded XML object if the task is
 
154
        present, and if it's not, it will create it. Then, it will save the
 
155
        task data in the XML object.
 
156
 
 
157
        @param task: the task object to save
 
158
        '''
 
159
        tid = task.get_id()
 
160
        #We create an XML representation of the task
 
161
        t_xml = taskxml.task_to_xml(self.doc, task)
 
162
 
 
163
        #we find if the task exists in the XML treenode.
 
164
        existing = None
 
165
        for node in self.xmlproj.childNodes:
 
166
            if node.getAttribute("id") == tid:
 
167
                existing = node
 
168
 
 
169
        modified = False
 
170
        #We then replace the existing node
 
171
        if existing and t_xml:
 
172
            #We will write only if the task has changed
 
173
            if t_xml.toxml() != existing.toxml():
 
174
                self.xmlproj.replaceChild(t_xml, existing)
151
175
                modified = True
152
 
            #In this particular backend, we write all the tasks
153
 
            #This is inherent to the XML file backend
154
 
            if modified and self._parameters["path"] and self.doc :
155
 
                cleanxml.savexml(self._parameters["path"], self.doc)
 
176
        #If the node doesn't exist, we create it
 
177
        else:
 
178
            self.xmlproj.appendChild(t_xml)
 
179
            modified = True
 
180
 
 
181
        #if the XML object has changed, we save it to file
 
182
        if modified and self._parameters["path"] and self.doc :
 
183
            cleanxml.savexml(self._parameters["path"], self.doc)
156
184
 
157
185
    def remove_task(self, tid):
158
 
        ''' Completely remove the task with ID = tid '''
 
186
        ''' This function is called from GTG core whenever a task must be
 
187
        removed from the backend. Note that the task could be not present here.
 
188
        
 
189
        @param tid: the id of the task to delete
 
190
        '''
 
191
        modified = False
159
192
        for node in self.xmlproj.childNodes:
160
193
            if node.getAttribute("id") == tid:
 
194
                modified = True
161
195
                self.xmlproj.removeChild(node)
162
196
                if tid in self.tids:
163
197
                    self.tids.remove(tid)
164
 
        cleanxml.savexml(self._parameters["path"], self.doc)
165
 
 
166
 
 
167
 
    def quit(self, disable = False):
168
 
        '''
169
 
        Called when GTG quits or disconnects the backend.
170
 
        '''
171
 
        super(Backend, self).quit(disable)
172
 
 
173
 
    def save_state(self):
174
 
        cleanxml.savexml(self._parameters["path"], self.doc, backup=True)
175
 
 
176
 
    def get_number_of_tasks(self):
177
 
        '''
178
 
        Returns the number of tasks stored in the backend. Doesn't need to be a
179
 
        fast function, is called just for the UI
180
 
        '''
181
 
        return len(self.tids)
 
198
 
 
199
        #We save the XML file only if it's necessary
 
200
        if modified:
 
201
            cleanxml.savexml(self._parameters["path"], self.doc)
 
202
 
 
203
#NOTE: This is not used currently. Therefore, I'm disabling it (invernizzi)
 
204
#    def get_number_of_tasks(self):
 
205
#        '''
 
206
#        Returns the number of tasks stored in the backend. Doesn't need to be a
 
207
#        fast function, is called just for the UI
 
208
#        '''
 
209
#        return len(self.tids)