~ubuntu-branches/ubuntu/trusty/gtg/trusty

« back to all changes in this revision

Viewing changes to GTG/backends/genericbackend.py

  • Committer: Package Import Robot
  • Author(s): Luca Falavigna
  • Date: 2012-04-10 23:08:21 UTC
  • mfrom: (1.1.8)
  • Revision ID: package-import@ubuntu.com-20120410230821-q6it7f4d7elut6pv
Tags: 0.2.9-1
* New upstream release (Closes: #668096).
  - Implement a search text box (Closes: #650279).
  - Window title reflects active tasks (LP: #537096).
  - Fix misbehaviours of the indicator applet (LP: #548836, #676353).
  - Fix crash when selecting notification area plugin twice (LP: #550321).
  - Fix sorting of tasks by date (LP: #556159).
  - Fix excessive delays at startup (LP: #558600).
  - Fix crash with dates having unknown values (LP: #561449).
  - Fix crash issued when pressing delete key (LP: #583103).
  - Keep notification plugin enabled after logoff (LP: #617257).
  - Fix Hamster plugin to work with recent Hamster versions (LP: #620313).
  - No longer use non-unicode strings (LP: #680632).
  - New RTM sync mechanism (LP: #753327).
  - Fix crashes while handling XML storage file (LP: #916474, #917634).
* debian/patches/*:
  - Drop all patches, they have been merged upstream.
* debian/patches/shebang.patch:
  - Fix shebang line.
* debian/patches/manpages.patch:
  - Fix some groff warnings in gtg_new_task man page
* debian/compat:
  - Bump compatibility level to 9.
* debian/control:
  - Bump X-Python-Version to >= 2.6.
  - Add python-liblarch and python-liblarch-gtk to Depends field.
  - Add python-cheetah, python-geoclue, python-gnomekeyring,
    python-launchpadlib and python-suds to Suggests field.
  - Bump Standards-Version to 3.9.3.
* debian/copyright:
  - Refresh copyright information.
  - Format now points to copyright-format site.
* debian/rules:
  - Make gtcli_bash_completion script executable.
* debian/watch:
  - Update watch file.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
# -----------------------------------------------------------------------------
 
3
# Gettings Things Gnome! - a personal organizer for the GNOME desktop
 
4
# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
 
5
#
 
6
# This program is free software: you can redistribute it and/or modify it under
 
7
# the terms of the GNU General Public License as published by the Free Software
 
8
# Foundation, either version 3 of the License, or (at your option) any later
 
9
# version.
 
10
#
 
11
# This program is distributed in the hope that it will be useful, but WITHOUT
 
12
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 
13
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 
14
# details.
 
15
#
 
16
# You should have received a copy of the GNU General Public License along with
 
17
# this program.  If not, see <http://www.gnu.org/licenses/>.
 
18
# -----------------------------------------------------------------------------
 
19
 
 
20
'''
 
21
This file contains the most generic representation of a backend, the
 
22
GenericBackend class
 
23
'''
 
24
 
 
25
import os
 
26
import sys
 
27
import errno
 
28
import pickle
 
29
import threading
 
30
from collections import deque
 
31
 
 
32
from GTG.backends.backendsignals import BackendSignals
 
33
from GTG.tools.keyring           import Keyring
 
34
from GTG.core                    import CoreConfig
 
35
from GTG.tools.logger            import Log
 
36
from GTG.tools.interruptible     import _cancellation_point
 
37
 
 
38
 
 
39
 
 
40
class GenericBackend(object):
 
41
    '''
 
42
    Base class for every backend.
 
43
    It defines the interface a backend must have and takes care of all the
 
44
    operations common to all backends.
 
45
    A particular backend should redefine all the methods marked as such.
 
46
    '''
 
47
 
 
48
 
 
49
   ###########################################################################
 
50
   ### BACKEND INTERFACE #####################################################
 
51
   ###########################################################################
 
52
 
 
53
    #General description of the backend: these parameters are used
 
54
    #to show a description of the backend to the user when s/he is
 
55
    #considering adding it.
 
56
    # For an example, see the GTG/backends/backend_localfile.py file
 
57
    #_general_description has this format:
 
58
    #_general_description = {
 
59
    #    GenericBackend.BACKEND_NAME:       "backend_unique_identifier", \
 
60
    #    GenericBackend.BACKEND_HUMAN_NAME: _("Human friendly name"), \
 
61
    #    GenericBackend.BACKEND_AUTHORS:    ["First author", \
 
62
    #                                        "Chuck Norris"], \
 
63
    #    GenericBackend.BACKEND_TYPE:       GenericBackend.TYPE_READWRITE, \
 
64
    #    GenericBackend.BACKEND_DESCRIPTION: \
 
65
    #        _("Short description of the backend"),\
 
66
    #    }
 
67
    # The complete list of constants and their meaning is given below.
 
68
    _general_description = {}
 
69
 
 
70
    #These are the parameters to configure a new backend of this type. A
 
71
    # parameter has a name, a type and a default value.
 
72
    # For an example, see the GTG/backends/backend_localfile.py file
 
73
    #_static_parameters has this format:
 
74
    #_static_parameters = { \
 
75
    #    "param1_name": { \
 
76
    #        GenericBackend.PARAM_TYPE: GenericBackend.TYPE_STRING,
 
77
    #        GenericBackend.PARAM_DEFAULT_VALUE: "my default value",
 
78
    #    },
 
79
    #    "param2_name": {
 
80
    #        GenericBackend.PARAM_TYPE: GenericBackend.TYPE_INT,
 
81
    #        GenericBackend.PARAM_DEFAULT_VALUE: 42,
 
82
    #        }}
 
83
    # The complete list of constants and their meaning is given below.
 
84
    _static_parameters = {}
 
85
 
 
86
    def initialize(self):
 
87
        '''
 
88
        Called each time it is enabled (including on backend creation).
 
89
        Please note that a class instance for each disabled backend *is*
 
90
        created, but it's not initialized. 
 
91
        Optional. 
 
92
        NOTE: make sure to call super().initialize()
 
93
        '''
 
94
        #NOTE: I'm disabling this since support for runtime checking of the
 
95
        #        presence of the necessary modules is disabled. (invernizzi)
 
96
#        for module_name in self.get_required_modules():
 
97
#            sys.modules[module_name]= __import__(module_name)
 
98
        self._parameters[self.KEY_ENABLED] = True
 
99
        self._is_initialized = True
 
100
        #we signal that the backend has been enabled
 
101
        self._signal_manager.backend_state_changed(self.get_id())
 
102
 
 
103
    def start_get_tasks(self):
 
104
        '''
 
105
        This function starts submitting the tasks from the backend into GTG
 
106
        core.
 
107
        It's run as a separate thread.
 
108
 
 
109
        @return: start_get_tasks() might not return or finish
 
110
        '''
 
111
        return
 
112
 
 
113
    def set_task(self, task):
 
114
        '''
 
115
        This function is called from GTG core whenever a task should be
 
116
        saved, either because it's a new one or it has been modified.
 
117
        If the task id is new for the backend, then a new task must be
 
118
        created. No special notification that the task is a new one is given.
 
119
 
 
120
        @param task: the task object to save
 
121
        '''
 
122
        pass
 
123
 
 
124
    def remove_task(self, tid):
 
125
        ''' This function is called from GTG core whenever a task must be
 
126
        removed from the backend. Note that the task could be not present here.
 
127
        
 
128
        @param tid: the id of the task to delete
 
129
        '''
 
130
        pass
 
131
 
 
132
    def this_is_the_first_run(self, xml):
 
133
        '''
 
134
        Optional, and almost surely not needed.
 
135
        Called upon the very first GTG startup.
 
136
        This function is needed only in the default backend (XML localfile,
 
137
        currently).
 
138
        The xml parameter is an object containing GTG default tasks.
 
139
        
 
140
        @param xml: an xml object containing the default tasks.
 
141
        '''
 
142
        pass
 
143
 
 
144
#NOTE: task counting is disabled in the UI, so I've disabled it here
 
145
#      (invernizzi)
 
146
#    def get_number_of_tasks(self):
 
147
#        '''
 
148
#        Returns the number of tasks stored in the backend. Doesn't need
 
149
#        to be a fast function, is called just for the UI
 
150
#        '''
 
151
#        raise NotImplemented()
 
152
 
 
153
#NOTE: I'm disabling this since support for runtime checking of the
 
154
#        presence of the necessary modules is disabled. (invernizzi)
 
155
#    @staticmethod
 
156
#    def get_required_modules():
 
157
#        return []
 
158
 
 
159
    def quit(self, disable = False):
 
160
        '''
 
161
        Called when GTG quits or the user wants to disable the backend.
 
162
        
 
163
        @param disable: If disable is True, the backend won't
 
164
                        be automatically loaded when GTG starts
 
165
        '''
 
166
        self._is_initialized = False
 
167
        if disable:
 
168
            self._parameters[self.KEY_ENABLED] = False
 
169
            #we signal that we have been disabled
 
170
            self._signal_manager.backend_state_changed(self.get_id())
 
171
            self._signal_manager.backend_sync_ended(self.get_id())
 
172
        syncing_thread = threading.Thread(target = self.sync).run()
 
173
 
 
174
    def save_state(self):
 
175
        '''
 
176
        It's the last function executed on a quitting backend, after the
 
177
        pending actions have been done.
 
178
        Useful to ensure that the state is saved in a consistent manner
 
179
        '''
 
180
        pass
 
181
 
 
182
###############################################################################
 
183
###### You don't need to reimplement the functions below this line ############
 
184
###############################################################################
 
185
 
 
186
   ###########################################################################
 
187
   ### CONSTANTS #############################################################
 
188
   ###########################################################################
 
189
    #BACKEND TYPE DESCRIPTION
 
190
    # Each backend must have a "_general_description" attribute, which
 
191
    # is a dictionary that holds the values for the following keys.
 
192
    BACKEND_NAME = "name" #the backend gtg internal name (doesn't change in
 
193
                          # translations, *must be unique*)
 
194
    BACKEND_HUMAN_NAME = "human-friendly-name" #The name shown to the user
 
195
    BACKEND_DESCRIPTION = "description" #A short description of the backend
 
196
    BACKEND_AUTHORS = "authors" #a list of strings
 
197
    BACKEND_TYPE = "type"
 
198
    #BACKEND_TYPE is one of:
 
199
    TYPE_READWRITE = "readwrite"
 
200
    TYPE_READONLY = "readonly"
 
201
    TYPE_IMPORT = "import"
 
202
    TYPE_EXPORT = "export"
 
203
 
 
204
 
 
205
    #"static_parameters" is a dictionary of dictionaries, each of which
 
206
    # are a description of a parameter needed to configure the backend and
 
207
    # is identified in the outer dictionary by a key which is the name of the
 
208
    # parameter.
 
209
    # For an example, see the GTG/backends/backend_localfile.py file
 
210
    # Each dictionary contains the keys:
 
211
    PARAM_DEFAULT_VALUE = "default_value" # its default value
 
212
    PARAM_TYPE = "type"  
 
213
    #PARAM_TYPE is one of the following (changing this changes the way
 
214
    # the user can configure the parameter)
 
215
    TYPE_PASSWORD = "password" #the real password is stored in the GNOME
 
216
                               # keyring
 
217
                               # This is just a key to find it there
 
218
    TYPE_STRING = "string"  #generic string, nothing fancy is done
 
219
    TYPE_INT = "int"  #edit box can contain only integers
 
220
    TYPE_BOOL = "bool" #checkbox is shown
 
221
    TYPE_LIST_OF_STRINGS = "liststring" #list of strings. the "," character is
 
222
                                        # prohibited in strings
 
223
 
 
224
    #These parameters are common to all backends and necessary.
 
225
    # They will be added automatically to your _static_parameters list
 
226
    #NOTE: for now I'm disabling changing the default backend. Once it's all
 
227
    #      set up, we will see about that (invernizzi)
 
228
    KEY_DEFAULT_BACKEND = "Default"
 
229
    KEY_ENABLED = "Enabled"
 
230
    KEY_HUMAN_NAME = BACKEND_HUMAN_NAME
 
231
    KEY_ATTACHED_TAGS = "attached-tags"
 
232
    KEY_USER = "user"
 
233
    KEY_PID = "pid"
 
234
    ALLTASKS_TAG = "gtg-tags-all" #NOTE: this has been moved here to avoid
 
235
                                  #    circular imports. It's the same as in
 
236
                                  #    the CoreConfig class, because it's the
 
237
                                  #    same thing conceptually. It doesn't
 
238
                                  #    matter it the naming diverges.
 
239
 
 
240
    _static_parameters_obligatory = { \
 
241
                                    KEY_DEFAULT_BACKEND: { \
 
242
                                         PARAM_TYPE: TYPE_BOOL, \
 
243
                                         PARAM_DEFAULT_VALUE: False, \
 
244
                                    }, \
 
245
                                    KEY_HUMAN_NAME: { \
 
246
                                         PARAM_TYPE: TYPE_STRING, \
 
247
                                         PARAM_DEFAULT_VALUE: "", \
 
248
                                    }, \
 
249
                                    KEY_USER: { \
 
250
                                         PARAM_TYPE: TYPE_STRING, \
 
251
                                         PARAM_DEFAULT_VALUE: "", \
 
252
                                    }, \
 
253
                                    KEY_PID: { \
 
254
                                         PARAM_TYPE: TYPE_STRING, \
 
255
                                         PARAM_DEFAULT_VALUE: "", \
 
256
                                    }, \
 
257
                                    KEY_ENABLED: { \
 
258
                                         PARAM_TYPE: TYPE_BOOL, \
 
259
                                         PARAM_DEFAULT_VALUE: False, \
 
260
                                    }}
 
261
 
 
262
    _static_parameters_obligatory_for_rw = { \
 
263
                                    KEY_ATTACHED_TAGS: {\
 
264
                                         PARAM_TYPE: TYPE_LIST_OF_STRINGS, \
 
265
                                         PARAM_DEFAULT_VALUE: [ALLTASKS_TAG], \
 
266
                                    }}
 
267
    
 
268
    #Handy dictionary used in type conversion (from string to type)
 
269
    _type_converter = {TYPE_STRING: str,
 
270
                       TYPE_INT: int,
 
271
                      }
 
272
 
 
273
    @classmethod
 
274
    def _get_static_parameters(cls):
 
275
        '''
 
276
        Helper method, used to obtain the full list of the static_parameters
 
277
        (user configured and default ones)
 
278
 
 
279
        @returns dict: the dict containing all the static parameters
 
280
        '''
 
281
        temp_dic = cls._static_parameters_obligatory.copy()
 
282
        if cls._general_description[cls.BACKEND_TYPE] == \
 
283
                                                        cls.TYPE_READWRITE:
 
284
            for key, value in \
 
285
                      cls._static_parameters_obligatory_for_rw.iteritems():
 
286
                temp_dic[key] = value
 
287
        for key, value in cls._static_parameters.iteritems():
 
288
            temp_dic[key] = value
 
289
        return temp_dic 
 
290
 
 
291
    def __init__(self, parameters):
 
292
        """
 
293
        Instantiates a new backend. Please note that this is called also
 
294
        for disabled backends. Those are not initialized, so you might
 
295
        want to check out the initialize() function.
 
296
        """
 
297
        if self.KEY_DEFAULT_BACKEND not in parameters:
 
298
            #if it's not specified, then this is the default backend
 
299
            #(for retro-compatibility with the GTG 0.2 series)
 
300
            parameters[self.KEY_DEFAULT_BACKEND] = True
 
301
        #default backends should get all the tasks
 
302
        if parameters[self.KEY_DEFAULT_BACKEND] or \
 
303
                (not self.KEY_ATTACHED_TAGS in parameters and \
 
304
                self._general_description[self.BACKEND_TYPE] \
 
305
                                        == self.TYPE_READWRITE):
 
306
            parameters[self.KEY_ATTACHED_TAGS] = [self.ALLTASKS_TAG]
 
307
        self._parameters = parameters
 
308
        self._signal_manager = BackendSignals()
 
309
        self._is_initialized = False
 
310
        #if debugging mode is enabled, tasks should be saved as soon as they're
 
311
        # marked as modified. If in normal mode, we prefer speed over easier
 
312
        # debugging.
 
313
        if Log.is_debugging_mode():
 
314
            self.timer_timestep = 5
 
315
        else:
 
316
            self.timer_timestep = 1 
 
317
        self.to_set_timer = None
 
318
        self.please_quit = False
 
319
        self.cancellation_point = lambda: _cancellation_point(\
 
320
                                        lambda: self.please_quit)
 
321
        self.to_set = deque()
 
322
        self.to_remove = deque()
 
323
 
 
324
    def get_attached_tags(self):
 
325
        '''
 
326
        Returns the list of tags which are handled by this backend
 
327
        '''
 
328
        if hasattr(self._parameters, self.KEY_DEFAULT_BACKEND) and \
 
329
                   self._parameters[self.KEY_DEFAULT_BACKEND]:
 
330
            #default backends should get all the tasks
 
331
            #NOTE: this shouldn't be needed, but it doesn't cost anything and it
 
332
            #      could avoid potential tasks losses.
 
333
            return [self.ALLTASKS_TAG]
 
334
        try:
 
335
            return self._parameters[self.KEY_ATTACHED_TAGS]
 
336
        except:
 
337
            return []
 
338
 
 
339
    def set_attached_tags(self, tags):
 
340
        '''
 
341
        Changes the set of attached tags
 
342
 
 
343
        @param tags: the new attached_tags set
 
344
        '''
 
345
        self._parameters[self.KEY_ATTACHED_TAGS] = tags
 
346
 
 
347
    @classmethod
 
348
    def get_static_parameters(cls):
 
349
        """
 
350
        Returns a dictionary of parameters necessary to create a backend.
 
351
        """
 
352
        return cls._get_static_parameters()
 
353
 
 
354
    def get_parameters(self):
 
355
        """
 
356
        Returns a dictionary of the current parameters.
 
357
        """
 
358
        return self._parameters
 
359
 
 
360
    def set_parameter(self, parameter, value):
 
361
        '''
 
362
        Change a parameter for this backend
 
363
 
 
364
        @param parameter: the parameter name
 
365
        @param value: the new value
 
366
        '''
 
367
        self._parameters[parameter] = value
 
368
 
 
369
    @classmethod
 
370
    def get_name(cls):
 
371
        """
 
372
        Returns the name of the backend as it should be displayed in the UI
 
373
        """
 
374
        return cls._get_from_general_description(cls.BACKEND_NAME)
 
375
 
 
376
    @classmethod
 
377
    def get_description(cls):
 
378
        """Returns a description of the backend"""
 
379
        return cls._get_from_general_description(cls.BACKEND_DESCRIPTION)
 
380
 
 
381
    @classmethod
 
382
    def get_type(cls):
 
383
        """Returns the backend type(readonly, r/w, import, export) """
 
384
        return cls._get_from_general_description(cls.BACKEND_TYPE)
 
385
 
 
386
    @classmethod
 
387
    def get_authors(cls):
 
388
        '''
 
389
        returns the backend author(s)
 
390
        '''
 
391
        return cls._get_from_general_description(cls.BACKEND_AUTHORS)
 
392
 
 
393
    @classmethod
 
394
    def _get_from_general_description(cls, key):
 
395
        '''
 
396
        Helper method to extract values from cls._general_description.
 
397
 
 
398
        @param key: the key to extract
 
399
        '''
 
400
        return cls._general_description[key]
 
401
 
 
402
    @classmethod
 
403
    def cast_param_type_from_string(cls, param_value, param_type):
 
404
        '''
 
405
        Parameters are saved in a text format, so we have to cast them to the
 
406
        appropriate type on loading. This function does exactly that.
 
407
 
 
408
        @param param_value: the actual value of the parameter, in a string
 
409
                            format
 
410
        @param param_type: the wanted type
 
411
        @returns something: the casted param_value
 
412
        '''
 
413
        if param_type in cls._type_converter:
 
414
            return cls._type_converter[param_type](param_value)
 
415
        elif param_type == cls.TYPE_BOOL:
 
416
            if param_value == "True":
 
417
                return True
 
418
            elif param_value == "False":
 
419
                return False
 
420
            else:
 
421
                raise Exception("Unrecognized bool value '%s'" %
 
422
                                 param_type)
 
423
        elif param_type == cls.TYPE_PASSWORD:
 
424
            if param_value == -1:
 
425
                return None
 
426
            return Keyring().get_password(int(param_value))
 
427
        elif param_type == cls.TYPE_LIST_OF_STRINGS:
 
428
            the_list = param_value.split(",")
 
429
            if not isinstance(the_list, list):
 
430
                the_list = [the_list]
 
431
            return the_list
 
432
        else:
 
433
            raise NotImplemented("I don't know what type is '%s'" %
 
434
                                 param_type)
 
435
 
 
436
    def cast_param_type_to_string(self, param_type, param_value):
 
437
        '''
 
438
        Inverse of cast_param_type_from_string
 
439
 
 
440
        @param param_value: the actual value of the parameter
 
441
        @param param_type: the type of the parameter (password...)
 
442
        @returns something: param_value casted to string
 
443
        '''
 
444
        if param_type == GenericBackend.TYPE_PASSWORD:
 
445
            if param_value == None:
 
446
                return str(-1)
 
447
            else:
 
448
                return str(Keyring().set_password(
 
449
                    "GTG stored password -" + self.get_id(), param_value))
 
450
        elif param_type == GenericBackend.TYPE_LIST_OF_STRINGS:
 
451
            if param_value == []:
 
452
                return ""
 
453
            return reduce(lambda a, b: a + "," + b, param_value)
 
454
        else:
 
455
            return str(param_value)
 
456
 
 
457
    def get_id(self):
 
458
        '''
 
459
        returns the backends id, used in the datastore for indexing backends
 
460
 
 
461
        @returns string: the backend id
 
462
        '''
 
463
        return self.get_name() + "@" + self._parameters["pid"]
 
464
 
 
465
    @classmethod
 
466
    def get_human_default_name(cls):
 
467
        '''
 
468
        returns the user friendly default backend name, without eventual user
 
469
        modifications.
 
470
 
 
471
        @returns string: the default "human name"
 
472
        '''
 
473
        return cls._general_description[cls.BACKEND_HUMAN_NAME]
 
474
 
 
475
    def get_human_name(self):
 
476
        '''
 
477
        returns the user customized backend name. If the user hasn't
 
478
        customized it, returns the default one.
 
479
 
 
480
        @returns string: the "human name" of this backend
 
481
        '''
 
482
        if self.KEY_HUMAN_NAME in self._parameters and \
 
483
                    self._parameters[self.KEY_HUMAN_NAME] != "":
 
484
            return self._parameters[self.KEY_HUMAN_NAME]
 
485
        else:
 
486
            return self.get_human_default_name()
 
487
 
 
488
    def set_human_name(self, name):
 
489
        '''
 
490
        sets a custom name for the backend
 
491
 
 
492
        @param name: the new name
 
493
        '''
 
494
        self._parameters[self.KEY_HUMAN_NAME] = name
 
495
        #we signal the change
 
496
        self._signal_manager.backend_renamed(self.get_id())
 
497
 
 
498
    def is_enabled(self):
 
499
        '''
 
500
        Returns if the backend is enabled
 
501
 
 
502
        @returns bool
 
503
        '''
 
504
        return self.get_parameters()[GenericBackend.KEY_ENABLED] or \
 
505
                self.is_default()
 
506
 
 
507
    def is_default(self):
 
508
        '''
 
509
        Returns if the backend is enabled
 
510
 
 
511
        @returns bool
 
512
        '''
 
513
        return self.get_parameters()[GenericBackend.KEY_DEFAULT_BACKEND]
 
514
 
 
515
    def is_initialized(self):
 
516
        '''
 
517
        Returns if the backend is up and running
 
518
 
 
519
        @returns is_initialized
 
520
        '''
 
521
        return self._is_initialized
 
522
 
 
523
    def get_parameter_type(self, param_name):
 
524
        '''
 
525
        Given the name of a parameter, returns its type. If the parameter is one
 
526
        of the default ones, it does not have a type: in that case, it returns
 
527
        None
 
528
 
 
529
        @param param_name: the name of the parameter
 
530
        @returns string: the type, or None
 
531
        '''
 
532
        try:
 
533
            return self.get_static_parameters()[param_name][self.PARAM_TYPE]
 
534
        except:
 
535
            return None
 
536
 
 
537
    def register_datastore(self, datastore):
 
538
        '''
 
539
        Setter function to inform the backend about the datastore that's loading
 
540
        it.
 
541
 
 
542
        @param datastore: a Datastore
 
543
        '''
 
544
        self.datastore = datastore
 
545
 
 
546
###############################################################################
 
547
### HELPER FUNCTIONS ##########################################################
 
548
###############################################################################
 
549
 
 
550
    def _store_pickled_file(self, path, data):
 
551
        '''
 
552
        A helper function to save some object in a file.
 
553
 
 
554
        @param path: a relative path. A good choice is
 
555
        "backend_name/object_name"
 
556
        @param data: the object
 
557
        '''
 
558
        path = os.path.join(CoreConfig().get_data_dir(), path)
 
559
        #mkdir -p
 
560
        try:
 
561
            os.makedirs(os.path.dirname(path))
 
562
        except OSError, exception:
 
563
            if exception.errno != errno.EEXIST: 
 
564
                raise
 
565
        #saving
 
566
        with open(path, 'wb') as file:
 
567
                pickle.dump(data, file)
 
568
 
 
569
    def _load_pickled_file(self, path, default_value = None):
 
570
        '''
 
571
        A helper function to load some object from a file.
 
572
 
 
573
        @param path: the relative path of the file
 
574
        @param default_value: the value to return if the file is missing or
 
575
        corrupt
 
576
        @returns object: the needed object, or default_value
 
577
        '''
 
578
        path = os.path.join(CoreConfig().get_data_dir(), path)
 
579
        if not os.path.exists(path):
 
580
            return default_value
 
581
        else:
 
582
            with open(path, 'r') as file:
 
583
                try:
 
584
                    return pickle.load(file)
 
585
                except pickle.PickleError:
 
586
                    Log.error("PICKLE ERROR")
 
587
                    return default_value
 
588
 
 
589
    def _gtg_task_is_syncable_per_attached_tags(self, task):
 
590
        '''
 
591
        Helper function which checks if the given task satisfies the filtering
 
592
        imposed by the tags attached to the backend.
 
593
        That means, if a user wants a backend to sync only tasks tagged @works,
 
594
        this function should be used to check if that is verified.
 
595
 
 
596
        @returns bool: True if the task should be synced
 
597
        '''
 
598
        attached_tags = self.get_attached_tags()
 
599
        if GenericBackend.ALLTASKS_TAG in attached_tags:
 
600
            return True
 
601
        for tag in task.get_tags_name():
 
602
            if tag in attached_tags:
 
603
                return  True
 
604
        return False
 
605
 
 
606
###############################################################################
 
607
### THREADING #################################################################
 
608
###############################################################################
 
609
 
 
610
    def __try_launch_setting_thread(self):
 
611
        '''
 
612
        Helper function to launch the setting thread, if it's not running.
 
613
        '''
 
614
        if self.to_set_timer == None and self.is_enabled():
 
615
            self.to_set_timer = threading.Timer(self.timer_timestep, \
 
616
                                        self.launch_setting_thread)
 
617
            self.to_set_timer.start()
 
618
 
 
619
    def launch_setting_thread(self, bypass_quit_request = False):
 
620
        '''
 
621
        This function is launched as a separate thread. Its job is to perform
 
622
        the changes that have been issued from GTG core. 
 
623
        In particular, for each task in the self.to_set queue, a task
 
624
        has to be modified or to be created (if the tid is new), and for
 
625
        each task in the self.to_remove queue, a task has to be deleted
 
626
 
 
627
        @param bypass_quit_request: if True, the thread should not be stopped
 
628
                                    even if asked by self.please_quit = True.
 
629
                                    It's used when the backend quits, to finish
 
630
                                    syncing all pending tasks
 
631
        '''
 
632
        while not self.please_quit or bypass_quit_request:
 
633
            try:
 
634
                task = self.to_set.pop()
 
635
            except IndexError:
 
636
                break
 
637
            tid = task.get_id()
 
638
            if tid  not in self.to_remove:
 
639
                self.set_task(task)
 
640
 
 
641
        while not self.please_quit or bypass_quit_request:
 
642
            try:
 
643
                tid = self.to_remove.pop()
 
644
            except IndexError:
 
645
                break
 
646
            self.remove_task(tid)
 
647
        #we release the weak lock
 
648
        self.to_set_timer = None
 
649
 
 
650
    def queue_set_task(self, task):
 
651
        ''' Save the task in the backend. In particular, it just enqueues the
 
652
        task in the self.to_set queue. A thread will shortly run to apply the
 
653
        requested changes.
 
654
        
 
655
        @param task: the task that should be saved
 
656
        '''
 
657
        tid = task.get_id()
 
658
        if task not in self.to_set and tid not in self.to_remove:
 
659
            self.to_set.appendleft(task)
 
660
            self.__try_launch_setting_thread()
 
661
 
 
662
    def queue_remove_task(self, tid):
 
663
        '''
 
664
        Queues task to be removed. In particular, it just enqueues the
 
665
        task in the self.to_remove queue. A thread will shortly run to apply the
 
666
        requested changes.
 
667
 
 
668
        @param tid: The Task ID of the task to be removed
 
669
        '''
 
670
        if tid not in self.to_remove:
 
671
            self.to_remove.appendleft(tid)
 
672
            self.__try_launch_setting_thread()
 
673
            return None
 
674
 
 
675
    def sync(self):
 
676
        '''
 
677
        Helper method. Forces the backend to perform all the pending changes.
 
678
        It is usually called upon quitting the backend.
 
679
        '''
 
680
        if self.to_set_timer != None:
 
681
            self.please_quit = True
 
682
            try:
 
683
                self.to_set_timer.cancel()
 
684
            except:
 
685
                pass
 
686
            try:
 
687
                self.to_set_timer.join()
 
688
            except:
 
689
                pass
 
690
        self.launch_setting_thread(bypass_quit_request = True)
 
691
        self.save_state()
 
692
 
 
693