1
# -*- coding: utf-8 -*-
2
# -----------------------------------------------------------------------------
3
# Getting Things Gnome! - a personal organizer for the GNOME desktop
4
# Copyright (c) 2008-2011 - Lionel Dricot & Bertrand Rousseau
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
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
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
# -----------------------------------------------------------------------------
25
""" Synchronized queue for processing requests"""
27
def __init__(self, callback):
28
""" Initialize synchronized queue.
30
@param callback - function for processing requests"""
34
self.callback = callback
35
self._lock = threading.Lock()
37
def push(self, *element):
38
""" Add a new element to the queue.
40
Schedule its processing if it is not already.
43
self._queue.append(element)
45
if self._handler is None:
46
self._handler = gobject.idle_add(self.callback)
49
def priority_push(self, *element):
50
""" Add a new element to the queue.
52
Schedule its processing if it is not already.
53
vip element are in a priority queue. They will be processed first
54
(this comment was actually written in Berlin Airport, after having
55
to wait in an economy class queue)"""
57
self._vip_queue.append(element)
59
if self._handler is None:
60
self._handler = gobject.idle_add(self.callback)
64
""" Return elements to process
66
At the moment, it returns just one element. In the future more
67
elements may be better to return (to speed it up).
69
If there is no request left, disable processing. """
72
if len(self._vip_queue) > 0:
73
toreturn = [self._vip_queue.pop(0)]
74
elif len(self._queue) > 0:
75
toreturn = [self._queue.pop(0)]
79
if len(self._queue) == 0 and len(self._vip_queue) == 0 and\
80
self._handler is not None:
81
gobject.source_remove(self._handler)
87
""" Tree which stores and handle all requests """
90
""" Initialize MainTree.
92
@param root - the "root" node which contains all nodes
96
self.pending_relationships = []
100
self.root_id = 'root'
101
self.root = TreeNode(self.root_id)
102
self.root.set_tree(self)
104
self._queue = SyncQueue(self._process_queue)
105
self._origin_thread = threading.current_thread()
108
return "<Tree: root = '%s'>" % self.root
111
""" Return root node """
114
####### INTERFACE FOR CALLBACKS ###############################################
115
def register_callback(self, event, func):
116
""" Store function and return unique key which can be used to
117
unregister the callback later """
119
if not self.__cllbcks.has_key(event):
120
self.__cllbcks[event] = {}
122
callbacks = self.__cllbcks[event]
124
while callbacks.has_key(key):
127
callbacks[key] = func
130
def deregister_callback(self, event, key):
131
""" Remove the callback identifed by key (from register_cllbck) """
133
del self.__cllbcks[event][key]
137
def _callback(self, event, node_id):
138
""" Inform others about the event """
139
#We copy the dict to not loop on it while it could be modified
140
dic = dict(self.__cllbcks.get(event, {}))
141
for func in dic.itervalues():
144
####### INTERFACE FOR HANDLING REQUESTS #######################################
145
def add_node(self, node, parent_id=None, high_priority=False):
146
self._external_request(self._add_node, high_priority, node, parent_id)
148
def remove_node(self, node_id, recursive=False):
149
self._external_request(self._remove_node, True, node_id, recursive)
151
def modify_node(self, node_id):
152
self._external_request(self._modify_node, False, node_id)
154
def new_relationship(self, parent_id, child_id):
155
self._external_request(self._new_relationship, False, parent_id, child_id)
157
def break_relationship(self, parent_id, child_id):
158
self._external_request(self._break_relationship, False, parent_id, child_id)
160
def _external_request(self, request_type, vip, *args):
161
""" Put the reqest into queue and in the main thread handle it """
163
self._queue.priority_push(request_type, *args)
165
self._queue.push(request_type, *args)
167
if self._origin_thread == threading.current_thread():
168
self._process_queue()
170
def _process_queue(self):
171
""" Process requests from queue """
172
for action in self._queue.process():
176
# return True to process other requests as well
179
def refresh_all(self):
180
""" Refresh all nodes """
181
for node_id in self.nodes.keys():
182
self.modify_node(node_id)
184
####### IMPLEMENTATION OF HANDLING REQUESTS ###################################
185
def _create_relationship(self, parent_id, child_id):
186
""" Create relationship without any checks """
187
parent = self.nodes[parent_id]
188
child = self.nodes[child_id]
190
if child_id not in parent.children:
191
parent.children.append(child_id)
193
if parent_id not in child.parents:
194
child.parents.append(parent_id)
196
if child_id in self.root.children:
197
self.root.children.remove(child_id)
199
def _destroy_relationship(self, parent_id, child_id):
200
""" Destroy relationship without any checks """
201
parent = self.nodes[parent_id]
202
child = self.nodes[child_id]
204
if child_id in parent.children:
205
parent.children.remove(child_id)
207
if parent_id in child.parents:
208
child.parents.remove(parent_id)
210
def _is_circular_relation(self, parent_id, child_id):
211
""" Would the new relation be circular?
213
Go over every possible ancestors. If one of them is child_id,
214
this would be circular relation.
218
ancestors = [parent_id]
219
while ancestors != []:
220
node_id = ancestors.pop(0)
221
if node_id == child_id:
224
if node_id not in self.nodes:
227
for ancestor_id in self.nodes[node_id].parents:
228
if ancestor_id not in visited:
229
ancestors.append(ancestor_id)
233
def _add_node(self, node, parent_id):
234
""" Add a node to the tree
236
@param node - node to be added
237
@param parent_id - parent to add or it will be add to root
239
node_id = node.get_id()
240
if node_id in self.nodes:
241
print "Error: Node '%s' already exists" % node_id
245
for relationship in node.pending_relationships:
246
if relationship not in self.pending_relationships:
247
self.pending_relationships.append(relationship)
248
node.pending_relationships = []
250
self.nodes[node_id] = node
253
parents_to_refresh = []
254
children_to_refresh = []
256
# Build pending relationships
257
for rel_parent_id, rel_child_id in list(self.pending_relationships):
259
if rel_child_id == node_id and rel_parent_id in self.nodes:
260
if not self._is_circular_relation(rel_parent_id, node_id):
261
self._create_relationship(rel_parent_id, node_id)
263
parents_to_refresh.append(rel_parent_id)
265
print "Error: Detected pending circular relationship", \
266
rel_parent_id, rel_child_id
267
self.pending_relationships.remove((rel_parent_id, rel_child_id))
270
if rel_parent_id == node_id and rel_child_id in self.nodes:
271
if not self._is_circular_relation(node_id, rel_child_id):
272
self._create_relationship(node_id, rel_child_id)
273
children_to_refresh.append(rel_child_id)
275
print "Error: Detected pending circular relationship", \
276
rel_parent_id, rel_child_id
277
self.pending_relationships.remove((rel_parent_id, rel_child_id))
279
# Build relationship with given parent
280
if parent_id is not None:
281
if self._is_circular_relation(parent_id, node_id):
282
raise Exception('Creating circular relationship between %s and %s' % \
283
(parent_id, node_id))
284
if parent_id in self.nodes:
285
self._create_relationship(parent_id, node_id)
287
parents_to_refresh.append(parent_id)
289
self.pending_relationships.append((parent_id, node_id))
291
# Add at least to root
293
self.root.children.append(node_id)
297
#FIXME: this callback is very slow with treemodelsort
298
for parent_id in parents_to_refresh:
299
self._callback("node-modified", parent_id)
301
#FIXME: this callback is very slow with treemodelsort
302
self._callback("node-added", node_id)
304
#this callback is really fast. No problem
305
for child_id in children_to_refresh:
306
#FIXME: why parent_id? this should be a bug!
307
#removing this doesn't affect the tests. Why is it useful?
308
self._callback("node-modified", child_id)
310
def _remove_node(self, node_id, recursive=False):
311
""" Remove node from tree """
313
if node_id not in self.nodes:
314
print "*** Warning *** Trying to remove a non-existing node"
317
# Do not remove root node
321
# Remove pending relationships with this node
322
for relation in list(self.pending_relationships):
323
if node_id in relation:
324
self.pending_relationships.remove(relation)
326
node = self.nodes[node_id]
329
for parent_id in node.parents:
330
self._destroy_relationship(parent_id, node_id)
331
self._callback('node-modified', parent_id)
334
for child_id in list(node.children):
336
self._remove_node(child_id, True)
338
self._destroy_relationship(node_id, child_id)
339
self._callback('node-modified', child_id)
340
if self.nodes[child_id].parents == []:
341
self.root.children.append(child_id)
343
if node_id in self.root.children:
344
self.root.children.remove(node_id)
346
self.nodes.pop(node_id)
347
self._callback('node-deleted', node_id)
349
def _modify_node(self, node_id):
350
""" Force update of a node """
351
if node_id != self.root_id and node_id in self.nodes:
352
self._callback('node-modified', node_id)
354
def _new_relationship(self, parent_id, child_id):
355
""" Creates a new relationship
357
This method is used mainly from TreeNode"""
359
if (parent_id, child_id) in self.pending_relationships:
360
self.pending_relationships.remove((parent_id, child_id))
362
if not parent_id or not child_id or parent_id == child_id:
365
if parent_id not in self.nodes or child_id not in self.nodes:
366
self.pending_relationships.append((parent_id, child_id))
369
if self._is_circular_relation(parent_id, child_id):
370
self._destroy_relationship(parent_id, child_id)
371
raise Exception('Cannot build circular relationship between %s and %s' % (parent_id, child_id))
374
self._create_relationship(parent_id, child_id)
376
# Remove from root when having a new relationship
377
if child_id in self.root.children:
378
self.root.children.remove(child_id)
380
self._callback('node-modified', parent_id)
381
self._callback('node-modified', child_id)
383
def _break_relationship(self, parent_id, child_id):
384
""" Remove a relationship
386
This method is used mainly from TreeNode """
387
for rel_parent, rel_child in list(self.pending_relationships):
388
if rel_parent == parent_id and rel_child == child_id:
389
self.pending_relationships.remove((rel_parent, rel_child))
391
if not parent_id or not child_id or parent_id == child_id:
394
if parent_id not in self.nodes or child_id not in self.nodes:
397
self._destroy_relationship(parent_id, child_id)
399
# Move to root if beak the last parent
400
if self.nodes[child_id].get_parents() == []:
401
self.root.add_child(child_id)
403
self._callback('node-modified', parent_id)
404
self._callback('node-modified', child_id)
407
####### INTERFACE FOR READING STATE OF TREE ###################################
408
def has_node(self, node_id):
409
""" Is this node_id in this tree? """
410
return node_id in self.nodes
412
def get_node(self, node_id=None):
413
""" Return node of tree or root node of this tree """
414
if node_id in self.nodes:
415
return self.nodes[node_id]
416
elif node_id == self.root_id or node_id is None:
419
raise ValueError("Node %s is not in the tree" % node_id)
421
def get_node_for_path(self, path):
422
""" Convert path into node_id
424
@return node_id if path is valid, None otherwise
426
if not path or path == ():
429
if path in self.get_paths_for_node(node_id):
435
def get_paths_for_node(self, node_id):
436
""" Get all paths for node_id """
437
if not node_id or node_id == self.root_id:
439
elif node_id in self.nodes:
440
node = self.nodes[node_id]
441
if node.has_parent():
443
for parent_id in node.get_parents():
444
if parent_id not in self.nodes:
446
for path in self.get_paths_for_node(parent_id):
447
paths.append(path + (node_id,))
452
raise ValueError("Cannot get path for non existing node %s" % node_id)
454
def get_all_nodes(self):
455
""" Return list of all nodes in this tree """
456
return self.nodes.keys()
458
def next_node(self, node_id, parent_id=None):
459
""" Return the next sibling node or None if there is none
461
@param node_id - we look for siblings of this node
462
@param parent_id - specify which siblings should be used,
463
if task has more parents. If None, random parent will be used
466
raise ValueError('node_id should be different than None')
468
node = self.get_node(node_id)
469
parents_id = node.get_parents()
470
if len(parents_id) == 0:
472
elif parent_id in parents_id:
475
parid = parents_id[0]
477
parent = self.get_node(parid)
479
raise ValueError('Parent does not exist')
481
index = parent.get_child_index(node_id)
483
error = 'children are : %s\n' %parent.get_children()
484
error += 'node %s is not a child of %s' %(node_id,parid)
485
raise IndexError(error)
487
if parent.get_n_children() > index+1:
488
return parent.get_nth_child(index+1)
492
def print_tree(self, string=False):
493
output = self.root_id + "\n"
494
stack = [(" ", child_id) for child_id in reversed(self.root.children)]
497
prefix, node_id = stack.pop()
498
output += prefix + node_id + "\n"
500
for child_id in reversed(self.nodes[node_id].get_children()):
501
stack.append((prefix, child_id))
509
""" Object just for a single node in Tree """
510
# FIXME maybe add a lock which prevents changing root at the wrong moment,
511
# updating children, etc
513
def __init__(self, node_id, parent=None):
516
@param node_id - unique identifier of node (str)
517
@param parent - node_id of parent
519
self.node_id = node_id
525
self.pending_relationships = []
528
self.add_parent(parent)
531
return "<TreeNode: '%s'>" % (self.node_id)
534
""" Return node_id """
538
""" Force to update node (because it has changed) """
540
self.tree.modify_node(self.node_id)
542
def set_tree(self, tree):
543
""" Set tree which is should contain this node.
545
This method should be called only from MainTree. It is not
546
part of public interface. """
550
""" Return associated tree with this node """
553
def new_relationship(self, parent_id, child_id):
554
""" Create new relationship or save it for later if there is no tree """
556
self.tree.new_relationship(parent_id, child_id)
558
self.pending_relationships.append((parent_id, child_id))
560
####### Parents ###############################################################
561
def add_parent(self, parent_id):
562
""" Add a new parent """
563
if parent_id not in self.parents:
564
self.parents.append(parent_id)
565
self.new_relationship(parent_id, self.node_id)
567
def set_parent(self, parent_id):
568
""" Remove other parents and set this parent as only parent """
569
is_already_parent_flag = False
570
for node_id in self.parents:
571
if node_id != parent_id:
572
self.remove_parent(node_id)
574
is_already_parent_flag = True
575
if parent_id and not is_already_parent_flag:
576
self.add_parent(parent_id)
578
def remove_parent(self, parent_id):
579
""" Remove parent """
580
if parent_id in self.parents:
581
self.parents.remove(parent_id)
582
self.tree.break_relationship(parent_id, self.node_id)
584
def has_parent(self, parent_id=None):
585
""" Has parent/parents?
587
@param parent_id - None => has any parent?
588
not None => has this parent?
591
return self.tree.has_node(parent_id) and parent_id in self.parents
593
return len(self.parents) > 0
595
def get_parents(self):
596
""" Return parents of node """
599
for parent_id in self.parents:
600
if self.tree.has_node(parent_id):
601
parents.append(parent_id)
605
####### Children ##############################################################
606
def add_child(self, child_id):
607
""" Add a children to node """
608
if child_id not in self.children:
609
self.children.append(child_id)
610
self.new_relationship(self.node_id, child_id)
612
print "%s was already in children of %s" % (child_id, self.node_id)
614
def has_child(self, child_id=None):
615
""" Has child/children?
617
@param child_id - None => has any child?
618
not None => has this child?
621
return child_id in self.children
623
return bool(self.children)
625
def get_children(self):
626
""" Return children of nodes """
629
for child_id in self.children:
630
if self.tree.has_node(child_id):
631
children.append(child_id)
635
def get_n_children(self):
636
""" Return count of children """
637
return len(self.get_children())
639
def get_nth_child(self, index):
640
""" Return nth child """
642
return self.children[index]
644
raise ValueError("Requested non-existing child")
646
def get_child_index(self, node_id):
647
if node_id in self.children:
648
return self.children.index(node_id)