31
31
from collections import deque
33
from GTG.core import requester
34
from GTG.core.task import Task
35
from GTG.core.tag import Tag
36
from GTG.core import CoreConfig
37
from GTG.core.treefactory import TreeFactory
38
from GTG.tools.logger import Log
33
from GTG.core import requester
34
from GTG.core.task import Task
35
from GTG.core.tag import Tag
36
from GTG.core import CoreConfig
37
from GTG.core.treefactory import TreeFactory
38
from GTG.tools.logger import Log
39
39
from GTG.backends.genericbackend import GenericBackend
40
from GTG.tools import cleanxml
40
from GTG.tools import cleanxml
41
41
from GTG.backends.backendsignals import BackendSignals
42
from GTG.tools.borg import Borg
43
from GTG.core.search import parse_search_query, search_filter, InvalidQuery
42
from GTG.tools.borg import Borg
43
from GTG.core.search import parse_search_query, search_filter, InvalidQuery
46
46
class DataStore(object):
48
48
A wrapper around all backends that is responsible for keeping the backend
49
49
instances. It can enable, disable, register and destroy backends, and acts
50
50
as interface between the backends and GTG core.
51
51
You should not interface yourself directly with the DataStore: use the
52
52
Requester instead (which also sends signals as you issue commands).
55
55
def __init__(self, global_conf=CoreConfig()):
57
57
Initializes a DataStore object
59
59
self.backends = {} #dictionary {backend_name_string: Backend instance}
60
60
self.treefactory = TreeFactory()
61
61
self.__tasks = self.treefactory.get_tasks_tree()
79
79
##########################################################################
80
80
### Helper functions (get_ methods for Datastore embedded objects)
81
81
##########################################################################
83
82
def get_tagstore(self):
85
84
Helper function to obtain the Tagstore associated with this DataStore
87
86
@return GTG.core.tagstore.TagStore: the tagstore object
89
88
return self.__tagstore
91
90
def get_requester(self):
93
92
Helper function to get the Requester associate with this DataStore
95
94
@returns GTG.core.requester.Requester: the requester associated with
98
97
return self.requester
100
99
def get_tasks_tree(self):
102
101
Helper function to get a Tree with all the tasks contained in this
105
104
@returns GTG.core.tree.Tree: a task tree (the main one)
107
106
return self.__tasks
109
108
##########################################################################
110
109
### Tags functions
111
110
##########################################################################
113
111
def _add_new_tag(self, name, tag, filter_func, parameters, parent_id=None):
114
112
""" Add tag into a tree """
115
113
name = name.encode("UTF-8")
155
153
parameters, parent_id = CoreConfig.SEARCH_TAG)
156
154
Log.debug("*** view added %s ***" % name)
159
157
def remove_tag(self, name):
160
158
""" Removes a tag from the tagtree """
161
159
if self.__tagstore.has_node(name):
173
171
NOTE: Implementation for regular tasks must be much more robust.
174
172
You have to replace all occurences of tag name in tasks descriptions,
175
173
their parameters and backend settings (synchronize only certain tags).
177
175
Have a fun with implementing it!
179
177
tag = self.get_tag(oldname)
269
267
xmlroot.appendChild(t_xml)
270
268
already_saved.append(tagname)
272
270
cleanxml.savexml(self.tagfile, doc)
274
272
##########################################################################
275
273
### Tasks functions
276
274
##########################################################################
278
275
def get_all_tasks(self):
280
277
Returns list of all keys of open tasks
282
279
@return a list of strings: a list of task ids
284
281
return self.__tasks.get_main_view().get_all_nodes()
286
283
def has_task(self, tid):
288
285
Returns true if the tid is among the open or closed tasks for
289
286
this DataStore, False otherwise.
291
288
@param tid: Task ID to search for
292
289
@return bool: True if the task is present
294
291
return self.__tasks.has_node(tid)
296
293
def get_task(self, tid):
298
295
Returns the internal task object for the given tid, or None if the
299
296
tid is not present in this DataStore.
301
298
@param tid: Task ID to retrieve
302
299
@returns GTG.core.task.Task or None: whether the Task is present
305
302
if self.has_task(tid):
306
303
return self.__tasks.get_node(tid)
313
310
def task_factory(self, tid, newtask=False):
315
312
Instantiates the given task id as a Task object.
317
314
@param tid: a task id. Must be unique
318
315
@param newtask: True if the task has never been seen before
319
316
@return Task: a Task instance
321
318
return Task(tid, self.requester, newtask)
323
320
def new_task(self):
336
333
def push_task(self, task):
338
335
Adds the given task object to the task tree. In other words, registers
339
336
the given task in the GTG task set.
340
337
This function is used in mutual exclusion: only a backend at a time is
359
357
##########################################################################
360
358
### Backends functions
361
359
##########################################################################
363
360
def get_all_backends(self, disabled=False):
365
362
returns list of all registered backends for this DataStore.
367
@param disabled: If disabled is True, attaches also the list of disabled backends
364
@param disabled: If disabled is True, attaches also the list of
368
366
@return list: a list of TaskSource objects
376
374
def get_backend(self, backend_id):
378
376
Returns a backend given its id.
380
378
@param backend_id: a backend id
381
379
@returns GTG.core.datastore.TaskSource or None: the requested backend,
384
382
if backend_id in self.backends:
385
383
return self.backends[backend_id]
433
431
Log.error("Tried to register a backend without a pid")
435
433
def _activate_non_default_backends(self, sender=None):
437
435
Non-default backends have to wait until the default loads before
438
436
being activated. This function is called after the first default
439
437
backend has loaded all its tasks.
441
439
@param sender: not used, just here for signal compatibility
443
441
if self.is_default_backend_loaded:
444
442
Log.debug("spurious call")
450
448
self._backend_startup(backend)
452
450
def _backend_startup(self, backend):
454
452
Helper function to launch a thread that starts a backend.
456
454
@param backend: the backend object
458
457
def __backend_startup(self, backend):
460
459
Helper function to start a backend
462
461
@param backend: the backend object
464
463
backend.initialize()
465
464
backend.start_get_tasks()
466
465
self.flush_all_tasks(backend.get_id())
503
502
def remove_backend(self, backend_id):
505
504
Removes a backend, and forgets it ever existed.
507
506
@param backend_id: a backend id
509
508
if backend_id in self.backends:
510
509
backend = self.backends[backend_id]
511
510
if backend.is_enabled():
512
511
self.set_backend_enabled(backend_id, False)
513
512
#FIXME: to keep things simple, backends are not notified that they
514
513
# are completely removed (they think they're just
515
# deactivated). We should add a "purge" call to backend to let
516
# them know that they're removed, so that they can remove all
517
# the various files they've created. (invernizzi)
514
# deactivated). We should add a "purge" call to backend to
515
# let them know that they're removed, so that they can
516
# remove all the various files they've created. (invernizzi)
519
518
#we notify that the backend has been deleted
520
519
self._backend_signals.backend_removed(backend.get_id())
521
520
del self.backends[backend_id]
523
522
def backend_change_attached_tags(self, backend_id, tag_names):
525
524
Changes the tags for which a backend should store a task
527
526
@param backend_id: a backend_id
528
527
@param tag_names: the new set of tags. This should not be a tag object,
529
528
just the tag name.
531
530
backend = self.backends[backend_id]
532
531
backend.set_attached_tags(tag_names)
534
533
def flush_all_tasks(self, backend_id):
536
535
This function will cause all tasks to be checked against the backend
537
536
identified with backend_id. If tasks need to be added or removed, it
538
537
will be done here.
541
540
the Tree will be saved in the proper backends
543
542
@param backend_id: a backend id
545
545
def _internal_flush_all_tasks():
546
546
backend = self.backends[backend_id]
547
547
for task_id in self.get_all_tasks():
553
553
self.backends[backend_id].start_get_tasks()
555
555
def save(self, quit=False):
557
557
Saves the backends parameters.
559
559
@param quit: If quit is true, backends are shut down
562
562
self.start_get_tasks_thread.join()
563
563
except Exception:
590
590
t_xml.setAttribute(str(key), value)
591
591
#Saving all the projects at close
592
592
xmlconfig.appendChild(t_xml)
593
datafile = os.path.join(CoreConfig().get_data_dir(), CoreConfig.DATA_FILE)
593
datadir = CoreConfig().get_data_dir()
594
datafile = os.path.join(datadir, CoreConfig.DATA_FILE)
594
595
cleanxml.savexml(datafile, doc, backup=True)
595
596
#Saving the tagstore
596
597
self.save_tagtree()
598
599
def request_task_deletion(self, tid):
600
601
This is a proxy function to request a task deletion from a backend
602
603
@param tid: the tid of the task to remove
604
605
self.requester.delete_task(tid)
606
607
def get_backend_mutex(self):
608
609
Returns the mutex object used by backends to avoid modifying a task
609
610
at the same time.
611
612
@returns: threading.Lock
613
614
return self._backend_mutex
616
617
class TaskSource():
618
619
Transparent interface between the real backend and the DataStore.
619
620
Is in charge of connecting and disconnecting to signals
622
623
def __init__(self, requester, backend, datastore):
653
654
BackendSignals().default_backend_loaded()
655
656
def get_task_filter_for_backend(self):
657
658
Filter that checks if the task should be stored in this backend.
659
@returns function: a function that accepts a task and returns True/False
660
whether the task should be stored or not
660
@returns function: a function that accepts a task and returns
661
True/False whether the task should be stored or not
662
664
def backend_filter(req, task, parameters):
664
Filter that checks if two tags sets intersect. It is used to check if a
665
task should be stored inside a backend
666
Filter that checks if two tags sets intersect. It is used to check
667
if a task should be stored inside a backend
666
668
@param task: a task object
667
@oaram tags_to_match_set: a *set* of tag names
669
@param tags_to_match_set: a *set* of tag names
670
672
tags_to_match_set = parameters['tags']
680
682
{"tags": set(self.backend.get_attached_tags())})
682
684
def should_task_id_be_stored(self, task_id):
684
686
Helper function: Checks if a task should be stored in this backend
686
688
@param task_id: a task id
687
689
@returns bool: True if the task should be stored
689
691
#task = self.req.get_task(task_id)
690
692
#FIXME: it will be a lot easier to add, instead,
691
693
# a filter to a tree and check that this task is well in the tree
708
710
self.queue_remove_task(tid, path)
710
712
def launch_setting_thread(self, bypass_please_quit=False):
712
714
Operates the threads to set and remove tasks.
713
715
Releases the lock when it is done.
715
@param bypass_please_quit: if True, the self.please_quit "quit condition"
716
is ignored. Currently, it's turned to true
717
after the quit condition has been issued, to
718
execute eventual pending operations.
717
@param bypass_please_quit: if True, the self.please_quit
718
"quit condition" is ignored. Currently,
719
it's turned to true after the quit
720
condition has been issued, to execute
721
eventual pending operations.
720
723
while not self.please_quit or bypass_please_quit:
722
725
tid = self.to_set.pop()
740
743
self.to_set_timer = None
742
745
def queue_remove_task(self, tid, path=None):
744
747
Queues task to be removed.
746
749
@param sender: not used, any value will do
747
750
@param tid: The Task ID of the task to be removed
749
752
if tid not in self.to_remove:
750
753
self.to_remove.appendleft(tid)
751
754
self.__try_launch_setting_thread()
753
756
def __try_launch_setting_thread(self):
755
758
Helper function to launch the setting thread, if it's not running
757
760
if self.to_set_timer == None and not self.please_quit:
758
761
self.to_set_timer = threading.Timer(self.timer_timestep, \
759
762
self.launch_setting_thread)
761
764
self.to_set_timer.start()
763
766
def initialize(self, connect_signals=True):
765
768
Initializes the backend and starts looking for signals.
767
770
@param connect_signals: if True, it starts listening for signals
769
772
self.backend.initialize()
770
773
if connect_signals:
771
774
self._connect_signals()
773
776
def _connect_signals(self):
775
778
Helper function to connect signals
777
780
if not self.add_task_handle:
778
self.add_task_handle = self.tasktree.register_cllbck('node-added', \
781
self.add_task_handle = self.tasktree.register_cllbck(
782
'node-added', self.queue_set_task)
780
783
if not self.set_task_handle:
781
self.set_task_handle = self.tasktree.register_cllbck('node-modified', \
784
self.set_task_handle = self.tasktree.register_cllbck(
785
'node-modified', self.queue_set_task)
783
786
if not self.remove_task_handle:
784
self.remove_task_handle = self.tasktree.register_cllbck('node-deleted',\
785
self.queue_remove_task)
787
self.remove_task_handle = self.tasktree.register_cllbck(
788
'node-deleted', self.queue_remove_task)
787
790
def _disconnect_signals(self):
789
792
Helper function to disconnect signals
791
794
if self.add_task_handle:
792
self.tasktree.deregister_cllbck('node-added', self.set_task_handle)
795
self.tasktree.deregister_cllbck('node-added',
796
self.set_task_handle)
793
797
self.add_task_handle = None
794
798
if self.set_task_handle:
795
self.tasktree.deregister_cllbck('node-modified', self.set_task_handle)
799
self.tasktree.deregister_cllbck('node-modified',
800
self.set_task_handle)
796
801
self.set_task_handle = None
797
802
if self.remove_task_handle:
798
self.tasktree.deregister_cllbck('node-deleted', self.remove_task_handle)
803
self.tasktree.deregister_cllbck('node-deleted',
804
self.remove_task_handle)
799
805
self.remove_task_handle = None
803
809
Forces the TaskSource to sync all the pending tasks
806
812
self.to_set_timer.cancel()
807
813
except Exception:
817
823
self.launch_setting_thread(bypass_please_quit=True)
819
825
def quit(self, disable=False):
821
827
Quits the backend and disconnect the signals
823
829
@param disable: if True, the backend is disabled.
825
831
self._disconnect_signals()
826
832
self.please_quit = True
828
834
self.backend.quit(disable)
830
836
def __getattr__(self, attr):
832
838
Delegates all the functions not defined here to the real backend
833
839
(standard python function)
835
841
@param attr: attribute to get
837
843
if attr in self.__dict__:
838
844
return self.__dict__[attr]
843
849
class FilteredDataStore(Borg):
845
851
This class acts as an interface to the Datastore.
846
852
It is used to hide most of the methods of the Datastore.
847
853
The backends can safely use the remaining methods.
850
856
def __init__(self, datastore):
851
857
super(FilteredDataStore, self).__init__()