Package Gnumed :: Package timelinelib :: Package canvas :: Package data :: Module db
[frames] | no frames]

Source Code for Module Gnumed.timelinelib.canvas.data.db

  1  # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018  Rickard Lindberg, Roger Lindberg 
  2  # 
  3  # This file is part of Timeline. 
  4  # 
  5  # Timeline is free software: you can redistribute it and/or modify 
  6  # it under the terms of the GNU General Public License as published by 
  7  # the Free Software Foundation, either version 3 of the License, or 
  8  # (at your option) any later version. 
  9  # 
 10  # Timeline is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  # GNU General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU General Public License 
 16  # along with Timeline.  If not, see <http://www.gnu.org/licenses/>. 
 17   
 18   
 19  import collections 
 20  import contextlib 
 21   
 22  from timelinelib.calendar.gregorian.timetype import GregorianTimeType 
 23  from timelinelib.canvas.data.exceptions import TimelineIOError 
 24  from timelinelib.canvas.data.immutable import ImmutableDB 
 25  from timelinelib.canvas.data import Category 
 26  from timelinelib.canvas.data import Container 
 27  from timelinelib.canvas.data import Era 
 28  from timelinelib.canvas.data import Eras 
 29  from timelinelib.canvas.data import Event 
 30  from timelinelib.canvas.data import Milestone 
 31  from timelinelib.canvas.data import Subevent 
 32  from timelinelib.canvas.data import TimePeriod 
 33  from timelinelib.canvas.data.transactions import Transactions 
 34  from timelinelib.general.observer import Observable 
 35   
 36   
 37  # A category was added, edited, or deleted 
 38  STATE_CHANGE_CATEGORY = 1 
 39  # Something happened that changed the state of the timeline 
 40  STATE_CHANGE_ANY = 2 
41 42 43 -class MemoryDB(Observable):
44
45 - def __init__(self):
46 Observable.__init__(self) 47 self._id_counter = 0 48 self._transactions = Transactions(ImmutableDB()) 49 self._transactions.listen_for_any(self._transaction_committed) 50 self.path = "" 51 self.displayed_period = None 52 self._hidden_category_ids = [] 53 self.time_type = GregorianTimeType() 54 self.saved_now = self.time_type.now() 55 self.readonly = False 56 self._save_callback = None 57 self._should_lock = False 58 self._current_query = None
59
60 - def new_category(self, **kwargs):
61 return self._create_wrapper(Category, **kwargs)
62
63 - def new_milestone(self, **kwargs):
64 return self._create_wrapper(Milestone, **kwargs)
65
66 - def new_era(self, **kwargs):
67 return self._create_wrapper(Era, **kwargs)
68
69 - def new_event(self, **kwargs):
70 return self._create_wrapper(Event, **kwargs)
71
72 - def new_container(self, **kwargs):
73 return self._create_wrapper(Container, **kwargs)
74
75 - def new_subevent(self, **kwargs):
76 return self._create_wrapper(Subevent, **kwargs)
77
78 - def _create_wrapper(self, wrapper_class, **kwargs):
79 wrapper = wrapper_class(db=self) 80 if hasattr(wrapper, "time_period") and "time_period" not in kwargs: 81 now = self.time_type.now() 82 kwargs["time_period"] = TimePeriod(now, now) 83 if "container" in kwargs: 84 container = kwargs.pop("container") 85 else: 86 container = None 87 for name, value in kwargs.iteritems(): 88 setattr(wrapper, name, value) 89 if container is not None: 90 wrapper.container = container 91 return wrapper
92
93 - def next_id(self):
94 self._id_counter += 1 95 return self._id_counter
96
97 - def transaction(self, name):
98 return self._transactions.new(name)
99
100 - def clear_transactions(self):
101 self._transactions.clear()
102
103 - def transactions_status(self):
104 return self._transactions.status
105
106 - def display_in_canvas(self, canvas):
107 canvas.set_timeline(self)
108
109 - def is_saved(self):
110 return self._save_callback is not None
111
112 - def get_should_lock(self):
113 return self._should_lock
114
115 - def set_should_lock(self, should_lock):
116 self._should_lock = should_lock
117
118 - def register_save_callback(self, callback):
119 self._save_callback = callback
120
121 - def get_time_type(self):
122 return self.time_type
123
124 - def set_time_type(self, time_type):
125 self.time_type = time_type 126 if time_type is not None: 127 self.saved_now = time_type.now()
128
129 - def is_read_only(self):
130 return self.readonly
131
132 - def set_readonly(self):
133 self.readonly = True 134 self._notify(STATE_CHANGE_ANY)
135
136 - def search(self, search_string):
137 return _generic_event_search(self.get_all_events(), search_string)
138
139 - def get_events(self, time_period):
140 return self._get_events(lambda immutable_event: 141 immutable_event.time_period.inside_period(time_period) 142 )
143
144 - def get_all_events(self):
145 return self._get_events(lambda immutable_event: True)
146
147 - def get_max_sort_order(self):
148 max = -1 149 for id_, immutable_value in self._transactions.value.milestones: 150 sort_order = immutable_value["sort_order"] 151 if sort_order > max: 152 max = sort_order 153 for id_, immutable_value in self._transactions.value.events: 154 sort_order = immutable_value["sort_order"] 155 if sort_order > max: 156 max = sort_order 157 return max
158
159 - def _get_events(self, criteria_fn):
160 with self._query() as query: 161 return ( 162 self._get_milestones(criteria_fn) + 163 sort_events(self.get_containers() + [ 164 query.get_event(id_) 165 for id_, immutable_event 166 in self._transactions.value.events 167 if criteria_fn(immutable_event) 168 ]) 169 )
170
171 - def _get_milestones(self, criteria_fn):
172 with self._query() as query: 173 return [ 174 query.get_milestone(id_) 175 for id_, immutable_milestone 176 in self._transactions.value.milestones 177 if criteria_fn(immutable_milestone) 178 ]
179
180 - def get_first_event(self):
181 if len(self._transactions.value.events) == 0: 182 return None 183 else: 184 id_, immutable_event = min( 185 self._transactions.value.events, 186 key=lambda (id_, immutable_event): 187 immutable_event.time_period.start_time 188 ) 189 with self._query() as query: 190 return query.get_event(id_)
191
192 - def get_last_event(self):
193 if len(self._transactions.value.events) == 0: 194 return None 195 else: 196 id_, immutable_event = max( 197 self._transactions.value.events, 198 key=lambda (id_, immutable_event): 199 immutable_event.time_period.end_time 200 ) 201 with self._query() as query: 202 return query.get_event(id_)
203
204 - def save_events(self, events):
205 try: 206 with self.transaction("Save events"): 207 for event in events: 208 event.db = self 209 event.save() 210 except Exception as e: 211 raise TimelineIOError("Saving event failed: %s" % e)
212
213 - def save_event(self, event):
214 self.save_events([event])
215
216 - def delete_event(self, event_or_id):
217 try: 218 if isinstance(event_or_id, Event): 219 event = event_or_id 220 else: 221 event = self.find_event_with_id(event_or_id) 222 event.db = self 223 event.delete() 224 except Exception as e: 225 raise TimelineIOError("Deleting event failed: %s" % e)
226
227 - def get_all_eras(self):
228 return self._get_eras().get_all()
229
230 - def get_all_periods(self):
231 return self._get_eras().get_all_periods()
232
233 - def _get_eras(self):
234 with self._query() as query: 235 return Eras( 236 now_func=self.time_type.now, 237 eras=[ 238 query.get_era(id_) 239 for id_, immutable_era 240 in self._transactions.value.eras 241 ] 242 )
243
244 - def save_era(self, era):
245 try: 246 era.db = self 247 era.save() 248 except Exception as e: 249 raise TimelineIOError("Saving Era failed: %s" % e)
250
251 - def delete_era(self, era):
252 try: 253 era.db = self 254 era.delete() 255 except Exception as e: 256 raise TimelineIOError("Deleting Era failed: %s" % e)
257
258 - def get_categories(self):
259 with self._query() as query: 260 return [ 261 query.get_category(id_) 262 for id_, immutable_category 263 in self._transactions.value.categories 264 ]
265
266 - def get_containers(self):
267 with self._query() as query: 268 return [ 269 query.get_container(id_) 270 for id_, immutable_container 271 in self._transactions.value.containers 272 ]
273
274 - def save_category(self, category):
275 try: 276 category.db = self 277 category.save() 278 except Exception as e: 279 raise TimelineIOError("Saving category failed: %s" % e)
280
281 - def get_category_by_name(self, name):
282 with self._query() as query: 283 for id_, immutable_category in self._transactions.value.categories: 284 if immutable_category.name == name: 285 return query.get_category(id_) 286 return None
287
288 - def get_category_by_id(self, id_):
289 for category in self.get_categories(): 290 if category.id == id_: 291 return category
292
293 - def delete_category(self, category_or_id):
294 try: 295 if isinstance(category_or_id, Category): 296 category = category_or_id 297 else: 298 with self._query() as query: 299 category = query.get_category(category_or_id) 300 if category.id in self._hidden_category_ids: 301 self._hidden_category_ids.remove(category.id) 302 category.db = self 303 category.delete() 304 except Exception as e: 305 raise TimelineIOError("Deleting category failed: %s" % e)
306
307 - def get_saved_now(self):
308 return self.saved_now
309
310 - def set_saved_now(self, time):
311 self.saved_now = time 312 self.time_type.set_saved_now(time)
313
314 - def load_view_properties(self, view_properties):
315 view_properties.displayed_period = self.displayed_period 316 for cat in self.get_categories(): 317 visible = cat.id not in self._hidden_category_ids 318 view_properties.set_category_visible(cat, visible)
319
320 - def save_view_properties(self, view_properties):
321 if view_properties.displayed_period is not None: 322 if not view_properties.displayed_period.is_period(): 323 raise TimelineIOError(_("Displayed period must be > 0.")) 324 self.displayed_period = view_properties.displayed_period 325 self._hidden_category_ids = [] 326 for cat in self.get_categories(): 327 if not view_properties.is_category_visible(cat): 328 self._hidden_category_ids.append(cat.id) 329 self._save()
330
331 - def place_event_after_event(self, event_to_place, target_event):
332 self._place_event( 333 lambda index_to_place, index_target: index_to_place < index_target, 334 event_to_place.id, 335 target_event.id 336 )
337
338 - def place_event_before_event(self, event_to_place, target_event):
339 self._place_event( 340 lambda index_to_place, index_target: index_to_place > index_target, 341 event_to_place.id, 342 target_event.id 343 )
344
345 - def _place_event(self, validate_index, id_to_place, id_target):
346 all_events = [ 347 event 348 for event 349 in self.get_all_events() 350 if not event.is_subevent() 351 ] 352 for events in self._get_event_lists(all_events): 353 if self._move(events, validate_index, id_to_place, id_target): 354 with self.transaction("Move event"): 355 EventSorter().save_sort_order(all_events) 356 return
357
358 - def _get_event_lists(self, all_events):
359 yield all_events 360 for event in all_events: 361 if event.is_container(): 362 yield event.subevents
363
364 - def _move(self, events, validate_index, id_to_place, id_target):
365 index_to_place = None 366 index_target = None 367 for index, event in enumerate(events): 368 if event.id == id_to_place: 369 index_to_place = index 370 if event.id == id_target: 371 index_target = index 372 if index_to_place is None: 373 return False 374 if index_target is None: 375 return False 376 if validate_index(index_to_place, index_target): 377 events.insert(index_target, events.pop(index_to_place)) 378 return True 379 return False
380
381 - def undo(self):
382 index = self._get_undo_index() 383 if index is not None: 384 self._transactions.move(index)
385
386 - def redo(self):
387 index = self._get_redo_index() 388 if index is not None: 389 self._transactions.move(index)
390
391 - def undo_enabled(self):
392 return not self.is_read_only() and self._get_undo_index() is not None
393
394 - def redo_enabled(self):
395 return not self.is_read_only() and self._get_redo_index() is not None
396
397 - def _get_undo_index(self):
398 index, is_in_transaction, history = self._transactions.status 399 if index > 0: 400 return index - 1 401 else: 402 return None
403
404 - def _get_redo_index(self):
405 index, is_in_transaction, history = self._transactions.status 406 if index < len(history) - 1: 407 return index + 1 408 else: 409 return None
410
411 - def find_event_with_ids(self, ids):
412 with self._query() as query: 413 return [self.find_event_with_id(id_) for id_ in ids]
414
415 - def find_event_with_id(self, event_id):
416 with self._query() as query: 417 if query.event_exists(event_id): 418 return query.get_event(event_id) 419 if query.container_exists(event_id): 420 return query.get_container(event_id) 421 if query.milestone_exists(event_id): 422 return query.get_milestone(event_id)
423
424 - def _transaction_committed(self):
425 self._save() 426 self._notify(STATE_CHANGE_ANY)
427
428 - def _save(self):
429 if self._save_callback is not None: 430 self._save_callback()
431
432 - def get_displayed_period(self):
433 """ 434 Inheritors can call this method to get the displayed period used in 435 load_view_properties and save_view_properties. 436 """ 437 return self.displayed_period
438
439 - def set_displayed_period(self, period):
440 """ 441 Inheritors can call this method to set the displayed period used in 442 load_view_properties and save_view_properties. 443 """ 444 self.displayed_period = period
445
446 - def get_hidden_categories(self):
447 with self._query() as query: 448 return [ 449 query.get_category(id_) 450 for id_ 451 in self._hidden_category_ids 452 ]
453
454 - def set_hidden_categories(self, hidden_categories):
455 self._hidden_category_ids = [] 456 for cat in hidden_categories: 457 if cat.id not in self._transactions.value.categories: 458 raise ValueError("Category '%s' not in db." % cat.get_name()) 459 self._hidden_category_ids.append(cat.id)
460
461 - def import_db(self, db):
462 if self.get_time_type() != db.get_time_type(): 463 raise Exception("Import failed: time type does not match") 464 with self.transaction("Import events"): 465 for event in db.get_all_events(): 466 self._import_item(event)
467
468 - def _import_item(self, item):
469 if item.is_subevent(): 470 # Sub events are handled when importing the container 471 return 472 new_item = item.duplicate(target_db=self) 473 new_item.category = self._import_category(item.category) 474 new_item.save() 475 if item.is_container(): 476 for subevent in item.subevents: 477 new_sub = subevent.duplicate(target_db=self) 478 new_sub.category = self._import_category(subevent.category) 479 new_sub.container = new_item 480 new_sub.save()
481
482 - def _import_category(self, category):
483 if category is None: 484 return None 485 elif self._has_category_with_name(category.get_name()): 486 return self.get_category_by_name(category.get_name()) 487 else: 488 new_cat = category.duplicate(target_db=self) 489 new_cat.parent = self._import_category(category.parent) 490 new_cat.save() 491 return new_cat
492
493 - def _has_category_with_name(self, name):
494 for category in self.get_categories(): 495 if category.get_name() == name: 496 return True 497 return False
498
499 - def compress(self):
500 with self.transaction("Compress events"): 501 self._set_events_order_from_rows(self._place_events_on_rows())
502
503 - def _set_events_order_from_rows(self, rows):
504 event_sorter = EventSorter() 505 for key in sorted(rows.keys()): 506 event_sorter.save_sort_order(rows[key])
507
508 - def _place_events_on_rows(self):
509 rows = collections.defaultdict(lambda: []) 510 for event in self._length_sort(): 511 inx = 0 512 while True: 513 if self._fits_on_row(rows[inx], event): 514 event.r = inx 515 rows[inx].append(event) 516 break 517 inx += 1 518 return rows
519
520 - def _length_sort(self):
521 all_events = self.get_all_events() 522 reordered_events = [ 523 event 524 for event 525 in all_events 526 if not event.is_subevent() 527 ] 528 reordered_events = self._sort_by_length(reordered_events) 529 return reordered_events
530
531 - def _sort_by_length(self, events):
532 return sorted(events, key=self._event_length, reverse=True)
533
534 - def _event_length(self, evt):
535 return evt.get_time_period().delta()
536
537 - def _fits_on_row(self, row_events, event):
538 for ev in row_events: 539 if ev.overlaps(event): 540 return False 541 return True
542 543 @contextlib.contextmanager
544 - def _query(self):
545 need_to_create_query = self._current_query is None 546 if need_to_create_query: 547 self._current_query = Query(self, self._transactions.value) 548 try: 549 yield self._current_query 550 finally: 551 if need_to_create_query: 552 self._current_query = None
553
554 555 -class Query(object):
556
557 - def __init__(self, db, immutable_db):
558 self._db = db 559 self._immutable_db = immutable_db 560 self._wrappers = {}
561
562 - def container_exists(self, id_):
563 return id_ in self._immutable_db.containers
564
565 - def event_exists(self, id_):
566 return id_ in self._immutable_db.events
567
568 - def milestone_exists(self, id_):
569 return id_ in self._immutable_db.milestones
570
571 - def get_category(self, id_):
572 if id_ not in self._wrappers: 573 self._wrappers[id_] = self._create_category_wrapper(id_) 574 return self._wrappers[id_]
575
576 - def get_container(self, id_):
577 if id_ not in self._wrappers: 578 self._wrappers[id_] = self._create_container_wrapper(id_) 579 self._load_subevents(self._wrappers[id_]) 580 return self._wrappers[id_]
581
582 - def get_event(self, id_):
583 if id_ not in self._wrappers: 584 self._wrappers[id_] = self._create_event_wrapper(id_) 585 immutable_event = self._immutable_db.events.get(id_) 586 if immutable_event.container_id is not None: 587 # Loading the container will load and populate all subevents 588 self.get_container(immutable_event.container_id) 589 return self._wrappers[id_]
590
591 - def get_milestone(self, id_):
592 if id_ not in self._wrappers: 593 self._wrappers[id_] = self._create_milestone_wrapper(id_) 594 return self._wrappers[id_]
595
596 - def get_era(self, id_):
597 if id_ not in self._wrappers: 598 self._wrappers[id_] = self._create_era_wrapper(id_) 599 return self._wrappers[id_]
600
601 - def _load_subevents(self, container):
602 for subevent_id, immutable_event in self._immutable_db.events: 603 if immutable_event.container_id == container.id: 604 self.get_event(subevent_id).container = container
605
606 - def _create_category_wrapper(self, id_):
607 immutable_category = self._immutable_db.categories.get(id_) 608 wrapper = Category( 609 db=self._db, 610 id_=id_, 611 immutable_value=immutable_category, 612 ) 613 wrapper.parent = self._get_maybe_category(immutable_category.parent_id) 614 return wrapper
615
616 - def _create_container_wrapper(self, id_):
617 immutable_container = self._immutable_db.containers.get(id_) 618 wrapper = Container( 619 db=self._db, 620 id_=id_, 621 immutable_value=immutable_container, 622 ) 623 wrapper.category = self._get_maybe_category(immutable_container.category_id) 624 return wrapper
625
626 - def _create_event_wrapper(self, id_):
627 immutable_event = self._immutable_db.events.get(id_) 628 if immutable_event.container_id is None: 629 klass = Event 630 else: 631 klass = Subevent 632 wrapper = klass( 633 db=self._db, 634 id_=id_, 635 immutable_value=immutable_event, 636 ) 637 wrapper.category = self._get_maybe_category(immutable_event.category_id) 638 return wrapper
639
640 - def _create_milestone_wrapper(self, id_):
641 immutable_milestone = self._immutable_db.milestones.get(id_) 642 wrapper = Milestone( 643 db=self._db, 644 id_=id_, 645 immutable_value=immutable_milestone, 646 ) 647 wrapper.category = self._get_maybe_category(immutable_milestone.category_id) 648 return wrapper
649
650 - def _create_era_wrapper(self, id_):
651 immutable_era = self._immutable_db.eras.get(id_) 652 return Era( 653 db=self._db, 654 id_=id_, 655 immutable_value=immutable_era, 656 )
657
658 - def _get_maybe_category(self, category_id):
659 if category_id is None: 660 return None 661 else: 662 return self.get_category(category_id)
663
664 665 -class EventSorter(object):
666
667 - def __init__(self):
668 self._sort_order = 0
669
670 - def save_sort_order(self, events):
671 for event in events: 672 if event.is_container(): 673 self.save_sort_order(event.subevents) 674 else: 675 if event.sort_order != self._sort_order: 676 event.sort_order = self._sort_order 677 event.save() 678 self._sort_order += 1
679
680 681 -def sort_events(events):
682 return sorted(events, key=lambda event: event.sort_order)
683
684 685 -def _generic_event_search(events, search_string):
686 def match(event): 687 target = search_string.lower() 688 description = event.get_data("description") 689 if description is None: 690 description = "" 691 else: 692 description = description.lower() 693 return target in event.get_text().lower() or target in description
694 def mean_time(event): 695 return event.mean_time() 696 matches = [event for event in events if match(event)] 697 matches.sort(key=mean_time) 698 return matches 699