~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: 2013-12-12 12:29:43 UTC
  • mfrom: (1.1.10)
  • Revision ID: package-import@ubuntu.com-20131212122943-mq90h82sv95yz0r7
Tags: 0.3.1-1
* New upstream release.
* debian/patches/dates.patch:
  - Keep fuzzy dates relative to today, thanks to Frédéric Brière for
    the bug report and the patch! (Closes: #722080).
* debian/patches/keyworks.patch:
  - Implement Keyword key in the .desktop file.
* debian/patches/manpages.patch:
  - Fix spelling errors in the man pages.
* debian/control:
  - Bump Standards-Version to 3.9.5, no changes required.
* debian/copyright:
  - Refresh copyright information.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# -*- coding: utf-8 -*-
2
2
# -----------------------------------------------------------------------------
3
 
# Gettings Things Gnome! - a personal organizer for the GNOME desktop
4
 
# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
 
3
# Getting Things GNOME! - a personal organizer for the GNOME desktop
 
4
# Copyright (c) 2008-2013 - Lionel Dricot & Bertrand Rousseau
5
5
#
6
6
# This program is free software: you can redistribute it and/or modify it under
7
7
# the terms of the GNU General Public License as published by the Free Software
23
23
"""
24
24
 
25
25
import os
26
 
import sys
27
26
import errno
28
27
import pickle
29
28
import threading
30
29
from collections import deque
31
30
 
32
31
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
 
32
from GTG.tools.keyring import Keyring
 
33
from GTG.core import CoreConfig
 
34
from GTG.tools.logger import Log
 
35
from GTG.tools.interruptible import _cancellation_point
37
36
 
38
37
PICKLE_BACKUP_NBR = 2
39
38
 
46
45
    A particular backend should redefine all the methods marked as such.
47
46
    '''
48
47
 
49
 
 
50
48
   ###########################################################################
51
49
   ### BACKEND INTERFACE #####################################################
52
50
   ###########################################################################
53
 
 
54
 
    #General description of the backend: these parameters are used
55
 
    #to show a description of the backend to the user when s/he is
56
 
    #considering adding it.
 
51
    # General description of the backend: these parameters are used
 
52
    # to show a description of the backend to the user when s/he is
 
53
    # considering adding it.
57
54
    # For an example, see the GTG/backends/backend_localfile.py file
58
 
    #_general_description has this format:
59
 
    #_general_description = {
 
55
    # _general_description has this format:
 
56
    # _general_description = {
60
57
    #    GenericBackend.BACKEND_NAME:       "backend_unique_identifier", \
61
58
    #    GenericBackend.BACKEND_HUMAN_NAME: _("Human friendly name"), \
62
59
    #    GenericBackend.BACKEND_AUTHORS:    ["First author", \
68
65
    # The complete list of constants and their meaning is given below.
69
66
    _general_description = {}
70
67
 
71
 
    #These are the parameters to configure a new backend of this type. A
 
68
    # These are the parameters to configure a new backend of this type. A
72
69
    # parameter has a name, a type and a default value.
73
70
    # For an example, see the GTG/backends/backend_localfile.py file
74
 
    #_static_parameters has this format:
75
 
    #_static_parameters = { \
 
71
    # _static_parameters has this format:
 
72
    # _static_parameters = { \
76
73
    #    "param1_name": { \
77
74
    #        GenericBackend.PARAM_TYPE: GenericBackend.TYPE_STRING,
78
75
    #        GenericBackend.PARAM_DEFAULT_VALUE: "my default value",
88
85
        '''
89
86
        Called each time it is enabled (including on backend creation).
90
87
        Please note that a class instance for each disabled backend *is*
91
 
        created, but it's not initialized. 
92
 
        Optional. 
 
88
        created, but it's not initialized.
 
89
        Optional.
93
90
        NOTE: make sure to call super().initialize()
94
91
        '''
95
92
        self._parameters[self.KEY_ENABLED] = True
96
93
        self._is_initialized = True
97
 
        #we signal that the backend has been enabled
 
94
        # we signal that the backend has been enabled
98
95
        self._signal_manager.backend_state_changed(self.get_id())
99
96
 
100
97
    def start_get_tasks(self):
121
118
    def remove_task(self, tid):
122
119
        ''' This function is called from GTG core whenever a task must be
123
120
        removed from the backend. Note that the task could be not present here.
124
 
        
 
121
 
125
122
        @param tid: the id of the task to delete
126
123
        '''
127
124
        pass
133
130
        This function is needed only in the default backend (XML localfile,
134
131
        currently).
135
132
        The xml parameter is an object containing GTG default tasks.
136
 
        
 
133
 
137
134
        @param xml: an xml object containing the default tasks.
138
135
        '''
139
136
        pass
140
137
 
141
 
    def quit(self, disable = False):
 
138
    def quit(self, disable=False):
142
139
        '''
143
140
        Called when GTG quits or the user wants to disable the backend.
144
 
        
 
141
 
145
142
        @param disable: If disable is True, the backend won't
146
143
                        be automatically loaded when GTG starts
147
144
        '''
149
146
            self._is_initialized = False
150
147
            if disable:
151
148
                self._parameters[self.KEY_ENABLED] = False
152
 
                #we signal that we have been disabled
 
149
                # we signal that we have been disabled
153
150
                self._signal_manager.backend_state_changed(self.get_id())
154
151
                self._signal_manager.backend_sync_ended(self.get_id())
155
 
            syncing_thread = threading.Thread(target = self.sync).run()
 
152
            threading.Thread(target=self.sync).run()
156
153
 
157
154
    def save_state(self):
158
155
        '''
169
166
   ###########################################################################
170
167
   ### CONSTANTS #############################################################
171
168
   ###########################################################################
172
 
    #BACKEND TYPE DESCRIPTION
 
169
    # BACKEND TYPE DESCRIPTION
173
170
    # Each backend must have a "_general_description" attribute, which
174
171
    # is a dictionary that holds the values for the following keys.
175
 
    BACKEND_NAME = "name" #the backend gtg internal name (doesn't change in
 
172
    BACKEND_NAME = "name"  # the backend gtg internal name (doesn't change in
176
173
                          # translations, *must be unique*)
177
 
    BACKEND_HUMAN_NAME = "human-friendly-name" #The name shown to the user
178
 
    BACKEND_DESCRIPTION = "description" #A short description of the backend
179
 
    BACKEND_AUTHORS = "authors" #a list of strings
 
174
    BACKEND_HUMAN_NAME = "human-friendly-name"  # The name shown to the user
 
175
    BACKEND_DESCRIPTION = "description"  # A short description of the backend
 
176
    BACKEND_AUTHORS = "authors"  # a list of strings
180
177
    BACKEND_TYPE = "type"
181
 
    #BACKEND_TYPE is one of:
 
178
    # BACKEND_TYPE is one of:
182
179
    TYPE_READWRITE = "readwrite"
183
180
    TYPE_READONLY = "readonly"
184
181
    TYPE_IMPORT = "import"
185
182
    TYPE_EXPORT = "export"
186
183
 
187
 
 
188
184
    #"static_parameters" is a dictionary of dictionaries, each of which
189
185
    # are a description of a parameter needed to configure the backend and
190
186
    # is identified in the outer dictionary by a key which is the name of the
191
187
    # parameter.
192
188
    # For an example, see the GTG/backends/backend_localfile.py file
193
189
    # Each dictionary contains the keys:
194
 
    PARAM_DEFAULT_VALUE = "default_value" # its default value
195
 
    PARAM_TYPE = "type"  
196
 
    #PARAM_TYPE is one of the following (changing this changes the way
 
190
    PARAM_DEFAULT_VALUE = "default_value"  # its default value
 
191
    PARAM_TYPE = "type"
 
192
    # PARAM_TYPE is one of the following (changing this changes the way
197
193
    # the user can configure the parameter)
198
 
    TYPE_PASSWORD = "password" #the real password is stored in the GNOME
 
194
    TYPE_PASSWORD = "password"  # the real password is stored in the GNOME
199
195
                               # keyring
200
196
                               # This is just a key to find it there
201
 
    TYPE_STRING = "string"  #generic string, nothing fancy is done
202
 
    TYPE_INT = "int"  #edit box can contain only integers
203
 
    TYPE_BOOL = "bool" #checkbox is shown
204
 
    TYPE_LIST_OF_STRINGS = "liststring" #list of strings. the "," character is
205
 
                                        # prohibited in strings
 
197
    TYPE_STRING = "string"  # generic string, nothing fancy is done
 
198
    TYPE_INT = "int"  # edit box can contain only integers
 
199
    TYPE_BOOL = "bool"  # checkbox is shown
 
200
    TYPE_LIST_OF_STRINGS = "liststring"  # list of strings. the "," character
 
201
                                         # is prohibited in strings
206
202
 
207
 
    #These parameters are common to all backends and necessary.
 
203
    # These parameters are common to all backends and necessary.
208
204
    # They will be added automatically to your _static_parameters list
209
 
    #NOTE: for now I'm disabling changing the default backend. Once it's all
 
205
    # NOTE: for now I'm disabling changing the default backend. Once it's all
210
206
    #      set up, we will see about that (invernizzi)
211
207
    KEY_DEFAULT_BACKEND = "Default"
212
 
    KEY_ENABLED = "Enabled"
 
208
    KEY_ENABLED = "enabled"
213
209
    KEY_HUMAN_NAME = BACKEND_HUMAN_NAME
214
210
    KEY_ATTACHED_TAGS = "attached-tags"
215
211
    KEY_USER = "user"
216
212
    KEY_PID = "pid"
217
 
    ALLTASKS_TAG = "gtg-tags-all" #NOTE: this has been moved here to avoid
 
213
    ALLTASKS_TAG = "gtg-tags-all"  # NOTE: this has been moved here to avoid
218
214
                                  #    circular imports. It's the same as in
219
215
                                  #    the CoreConfig class, because it's the
220
216
                                  #    same thing conceptually. It doesn't
221
217
                                  #    matter it the naming diverges.
222
218
 
223
 
    _static_parameters_obligatory = { \
224
 
                                    KEY_DEFAULT_BACKEND: { \
225
 
                                         PARAM_TYPE: TYPE_BOOL, \
226
 
                                         PARAM_DEFAULT_VALUE: False, \
227
 
                                    }, \
228
 
                                    KEY_HUMAN_NAME: { \
229
 
                                         PARAM_TYPE: TYPE_STRING, \
230
 
                                         PARAM_DEFAULT_VALUE: "", \
231
 
                                    }, \
232
 
                                    KEY_USER: { \
233
 
                                         PARAM_TYPE: TYPE_STRING, \
234
 
                                         PARAM_DEFAULT_VALUE: "", \
235
 
                                    }, \
236
 
                                    KEY_PID: { \
237
 
                                         PARAM_TYPE: TYPE_STRING, \
238
 
                                         PARAM_DEFAULT_VALUE: "", \
239
 
                                    }, \
240
 
                                    KEY_ENABLED: { \
241
 
                                         PARAM_TYPE: TYPE_BOOL, \
242
 
                                         PARAM_DEFAULT_VALUE: False, \
243
 
                                    }}
244
 
 
245
 
    _static_parameters_obligatory_for_rw = { \
246
 
                                    KEY_ATTACHED_TAGS: {\
247
 
                                         PARAM_TYPE: TYPE_LIST_OF_STRINGS, \
248
 
                                         PARAM_DEFAULT_VALUE: [ALLTASKS_TAG], \
249
 
                                    }}
250
 
    
251
 
    #Handy dictionary used in type conversion (from string to type)
 
219
    _static_parameters_obligatory = {
 
220
        KEY_DEFAULT_BACKEND: {
 
221
            PARAM_TYPE: TYPE_BOOL,
 
222
            PARAM_DEFAULT_VALUE: False,
 
223
        },
 
224
        KEY_HUMAN_NAME: {
 
225
            PARAM_TYPE: TYPE_STRING,
 
226
            PARAM_DEFAULT_VALUE: "",
 
227
        },
 
228
        KEY_USER: {
 
229
            PARAM_TYPE: TYPE_STRING,
 
230
            PARAM_DEFAULT_VALUE: "",
 
231
        },
 
232
        KEY_PID: {
 
233
            PARAM_TYPE: TYPE_STRING,
 
234
            PARAM_DEFAULT_VALUE: "",
 
235
        },
 
236
        KEY_ENABLED: {
 
237
            PARAM_TYPE: TYPE_BOOL,
 
238
            PARAM_DEFAULT_VALUE: False,
 
239
        }}
 
240
 
 
241
    _static_parameters_obligatory_for_rw = {
 
242
        KEY_ATTACHED_TAGS: {
 
243
            PARAM_TYPE: TYPE_LIST_OF_STRINGS,
 
244
            PARAM_DEFAULT_VALUE: [ALLTASKS_TAG],
 
245
        }}
 
246
 
 
247
    # Handy dictionary used in type conversion (from string to type)
252
248
    _type_converter = {TYPE_STRING: str,
253
249
                       TYPE_INT: int,
254
 
                      }
 
250
                       }
255
251
 
256
252
    @classmethod
257
253
    def _get_static_parameters(cls):
263
259
        '''
264
260
        temp_dic = cls._static_parameters_obligatory.copy()
265
261
        if cls._general_description[cls.BACKEND_TYPE] == \
266
 
                                                        cls.TYPE_READWRITE:
 
262
                cls.TYPE_READWRITE:
267
263
            for key, value in \
268
 
                      cls._static_parameters_obligatory_for_rw.iteritems():
 
264
                    cls._static_parameters_obligatory_for_rw.iteritems():
269
265
                temp_dic[key] = value
270
266
        for key, value in cls._static_parameters.iteritems():
271
267
            temp_dic[key] = value
272
 
        return temp_dic 
 
268
        return temp_dic
273
269
 
274
270
    def __init__(self, parameters):
275
271
        """
278
274
        want to check out the initialize() function.
279
275
        """
280
276
        if self.KEY_DEFAULT_BACKEND not in parameters:
281
 
            #if it's not specified, then this is the default backend
 
277
            # if it's not specified, then this is the default backend
282
278
            #(for retro-compatibility with the GTG 0.2 series)
283
279
            parameters[self.KEY_DEFAULT_BACKEND] = True
284
 
        #default backends should get all the tasks
 
280
        # default backends should get all the tasks
285
281
        if parameters[self.KEY_DEFAULT_BACKEND] or \
286
 
                (not self.KEY_ATTACHED_TAGS in parameters and \
287
 
                self._general_description[self.BACKEND_TYPE] \
288
 
                                        == self.TYPE_READWRITE):
 
282
                (not self.KEY_ATTACHED_TAGS in parameters and
 
283
                 self._general_description[self.BACKEND_TYPE]
 
284
                 == self.TYPE_READWRITE):
289
285
            parameters[self.KEY_ATTACHED_TAGS] = [self.ALLTASKS_TAG]
290
286
        self._parameters = parameters
291
287
        self._signal_manager = BackendSignals()
292
288
        self._is_initialized = False
293
 
        #if debugging mode is enabled, tasks should be saved as soon as they're
294
 
        # marked as modified. If in normal mode, we prefer speed over easier
295
 
        # debugging.
 
289
        # if debugging mode is enabled, tasks should be saved as soon as
 
290
        # they're marked as modified. If in normal mode, we prefer speed over
 
291
        # easier debugging.
296
292
        if Log.is_debugging_mode():
297
293
            self.timer_timestep = 5
298
294
        else:
299
 
            self.timer_timestep = 1 
 
295
            self.timer_timestep = 1
300
296
        self.to_set_timer = None
301
297
        self.please_quit = False
302
 
        self.cancellation_point = lambda: _cancellation_point(\
303
 
                                        lambda: self.please_quit)
 
298
        self.cancellation_point = lambda: _cancellation_point(
 
299
            lambda: self.please_quit)
304
300
        self.to_set = deque()
305
301
        self.to_remove = deque()
306
302
 
309
305
        Returns the list of tags which are handled by this backend
310
306
        '''
311
307
        if hasattr(self._parameters, self.KEY_DEFAULT_BACKEND) and \
312
 
                   self._parameters[self.KEY_DEFAULT_BACKEND]:
313
 
            #default backends should get all the tasks
314
 
            #NOTE: this shouldn't be needed, but it doesn't cost anything and it
315
 
            #      could avoid potential tasks losses.
 
308
                self._parameters[self.KEY_DEFAULT_BACKEND]:
 
309
            # default backends should get all the tasks
 
310
            # NOTE: this shouldn't be needed, but it doesn't cost anything and
 
311
            #      it could avoid potential tasks losses.
316
312
            return [self.ALLTASKS_TAG]
317
313
        try:
318
314
            return self._parameters[self.KEY_ATTACHED_TAGS]
402
398
                return False
403
399
            else:
404
400
                raise Exception("Unrecognized bool value '%s'" %
405
 
                                 param_type)
 
401
                                param_type)
406
402
        elif param_type == cls.TYPE_PASSWORD:
407
403
            if param_value == -1:
408
404
                return None
425
421
        @returns something: param_value casted to string
426
422
        '''
427
423
        if param_type == GenericBackend.TYPE_PASSWORD:
428
 
            if param_value == None:
 
424
            if param_value is None:
429
425
                return str(-1)
430
426
            else:
431
427
                return str(Keyring().set_password(
463
459
        @returns string: the "human name" of this backend
464
460
        '''
465
461
        if self.KEY_HUMAN_NAME in self._parameters and \
466
 
                    self._parameters[self.KEY_HUMAN_NAME] != "":
 
462
                self._parameters[self.KEY_HUMAN_NAME] != "":
467
463
            return self._parameters[self.KEY_HUMAN_NAME]
468
464
        else:
469
465
            return self.get_human_default_name()
475
471
        @param name: the new name
476
472
        '''
477
473
        self._parameters[self.KEY_HUMAN_NAME] = name
478
 
        #we signal the change
 
474
        # we signal the change
479
475
        self._signal_manager.backend_renamed(self.get_id())
480
476
 
481
477
    def is_enabled(self):
485
481
        @returns: bool
486
482
        '''
487
483
        return self.get_parameters()[GenericBackend.KEY_ENABLED] or \
488
 
                self.is_default()
 
484
            self.is_default()
489
485
 
490
486
    def is_default(self):
491
487
        '''
505
501
 
506
502
    def get_parameter_type(self, param_name):
507
503
        '''
508
 
        Given the name of a parameter, returns its type. If the parameter is one
509
 
        of the default ones, it does not have a type: in that case, it returns
510
 
        None
 
504
        Given the name of a parameter, returns its type. If the parameter is
 
505
         one of the default ones, it does not have a type: in that case, it
 
506
        returns None
511
507
 
512
508
        @param param_name: the name of the parameter
513
509
        @returns string: the type, or None
519
515
 
520
516
    def register_datastore(self, datastore):
521
517
        '''
522
 
        Setter function to inform the backend about the datastore that's loading
523
 
        it.
 
518
        Setter function to inform the backend about the datastore that's
 
519
        loading it.
524
520
 
525
521
        @param datastore: a Datastore
526
522
        '''
529
525
###############################################################################
530
526
### HELPER FUNCTIONS ##########################################################
531
527
###############################################################################
532
 
 
533
528
    def _store_pickled_file(self, path, data):
534
529
        '''
535
530
        A helper function to save some object in a file.
539
534
        @param data: the object
540
535
        '''
541
536
        path = os.path.join(CoreConfig().get_data_dir(), path)
542
 
        #mkdir -p
 
537
        # mkdir -p
543
538
        try:
544
539
            os.makedirs(os.path.dirname(path))
545
540
        except OSError, exception:
546
 
            if exception.errno != errno.EEXIST: 
 
541
            if exception.errno != errno.EEXIST:
547
542
                raise
548
543
 
549
544
        # Shift backups
550
545
        for i in range(PICKLE_BACKUP_NBR, 1, -1):
551
546
            destination = "%s.bak.%d" % (path, i)
552
 
            source = "%s.bak.%d" % (path, i-1)
 
547
            source = "%s.bak.%d" % (path, i - 1)
553
548
 
554
549
            if os.path.exists(destination):
555
550
                os.unlink(destination)
563
558
            if os.path.exists(path):
564
559
                os.rename(path, destination)
565
560
 
566
 
        #saving
 
561
        # saving
567
562
        with open(path, 'wb') as file:
568
563
                pickle.dump(data, file)
569
564
 
570
 
    def _load_pickled_file(self, path, default_value = None):
 
565
    def _load_pickled_file(self, path, default_value=None):
571
566
        '''
572
567
        A helper function to load some object from a file.
573
568
 
584
579
            try:
585
580
                return pickle.load(file)
586
581
            except Exception:
587
 
                Log.error("Pickle file for backend '%s' is damaged" % \
588
 
                    self.get_name())
 
582
                Log.error("Pickle file for backend '%s' is damaged" %
 
583
                          self.get_name())
589
584
 
590
585
        # Loading file failed, trying backups
591
 
        for i in range(1, PICKLE_BACKUP_NBR+1):
 
586
        for i in range(1, PICKLE_BACKUP_NBR + 1):
592
587
            backup_file = "%s.bak.%d" % (path, i)
593
588
            if os.path.exists(backup_file):
594
589
                with open(backup_file, 'r') as file:
595
590
                    try:
596
591
                        data = pickle.load(file)
597
 
                        Log.info("Succesfully restored backup #%d for '%s'" % \
598
 
                            (i, self.get_name()))
 
592
                        Log.info("Succesfully restored backup #%d for '%s'" %
 
593
                                (i, self.get_name()))
599
594
                        return data
600
595
                    except Exception:
601
 
                        Log.error("Backup #%d for '%s' is damaged as well" % \
602
 
                            (i, self.get_name()))
 
596
                        Log.error("Backup #%d for '%s' is damaged as well" %
 
597
                                 (i, self.get_name()))
603
598
 
604
599
        # Data could not be loaded, degrade to default data
605
 
        Log.error("There is no suitable backup for '%s', " \
606
 
            "loading default data" % self.get_name())
 
600
        Log.error("There is no suitable backup for '%s', "
 
601
                  "loading default data" % self.get_name())
607
602
        return default_value
608
603
 
609
 
 
610
604
    def _gtg_task_is_syncable_per_attached_tags(self, task):
611
605
        '''
612
606
        Helper function which checks if the given task satisfies the filtering
621
615
            return True
622
616
        for tag in task.get_tags_name():
623
617
            if tag in attached_tags:
624
 
                return  True
 
618
                return True
625
619
        return False
626
620
 
627
621
###############################################################################
628
622
### THREADING #################################################################
629
623
###############################################################################
630
 
 
631
624
    def __try_launch_setting_thread(self):
632
625
        '''
633
626
        Helper function to launch the setting thread, if it's not running.
634
627
        '''
635
 
        if self.to_set_timer == None and self.is_enabled():
636
 
            self.to_set_timer = threading.Timer(self.timer_timestep, \
637
 
                                        self.launch_setting_thread)
 
628
        if self.to_set_timer is None and self.is_enabled():
 
629
            self.to_set_timer = threading.Timer(self.timer_timestep,
 
630
                                                self.launch_setting_thread)
638
631
            self.to_set_timer.start()
639
632
 
640
 
    def launch_setting_thread(self, bypass_quit_request = False):
 
633
    def launch_setting_thread(self, bypass_quit_request=False):
641
634
        '''
642
635
        This function is launched as a separate thread. Its job is to perform
643
 
        the changes that have been issued from GTG core. 
 
636
        the changes that have been issued from GTG core.
644
637
        In particular, for each task in the self.to_set queue, a task
645
638
        has to be modified or to be created (if the tid is new), and for
646
639
        each task in the self.to_remove queue, a task has to be deleted
656
649
            except IndexError:
657
650
                break
658
651
            tid = task.get_id()
659
 
            if tid  not in self.to_remove:
 
652
            if tid not in self.to_remove:
660
653
                self.set_task(task)
661
654
 
662
655
        while not self.please_quit or bypass_quit_request:
665
658
            except IndexError:
666
659
                break
667
660
            self.remove_task(tid)
668
 
        #we release the weak lock
 
661
        # we release the weak lock
669
662
        self.to_set_timer = None
670
663
 
671
664
    def queue_set_task(self, task):
672
665
        ''' Save the task in the backend. In particular, it just enqueues the
673
666
        task in the self.to_set queue. A thread will shortly run to apply the
674
667
        requested changes.
675
 
        
 
668
 
676
669
        @param task: the task that should be saved
677
670
        '''
678
671
        tid = task.get_id()
683
676
    def queue_remove_task(self, tid):
684
677
        '''
685
678
        Queues task to be removed. In particular, it just enqueues the
686
 
        task in the self.to_remove queue. A thread will shortly run to apply the
687
 
        requested changes.
 
679
        task in the self.to_remove queue. A thread will shortly run to apply
 
680
        the requested changes.
688
681
 
689
682
        @param tid: The Task ID of the task to be removed
690
683
        '''
698
691
        Helper method. Forces the backend to perform all the pending changes.
699
692
        It is usually called upon quitting the backend.
700
693
        '''
701
 
        if self.to_set_timer != None:
 
694
        if self.to_set_timer is not None:
702
695
            self.please_quit = True
703
696
            try:
704
697
                self.to_set_timer.cancel()
708
701
                self.to_set_timer.join()
709
702
            except:
710
703
                pass
711
 
        self.launch_setting_thread(bypass_quit_request = True)
 
704
        self.launch_setting_thread(bypass_quit_request=True)
712
705
        self.save_state()