class MemoryDB(Observable): def __init__(self): Observable.__init__(self) self.path = "" self.categories = [] self.category_id_counter = IdCounter() self.events = [] self.event_id_counter = IdCounter() self.displayed_period = None self.hidden_categories = [] self.save_disabled = False from timelinelib.time.gregoriantime import GregorianTimeType self.time_type = GregorianTimeType() self.readonly = False self.importing = False def get_time_type(self): return self.time_type def is_read_only(self): return self.readonly def set_readonly(self): self.readonly = True def supported_event_data(self): return ["description", "icon", "alert", "hyperlink", "progress"] def search(self, search_string): return generic_event_search(self.events, search_string) def get_events(self, time_period): def include_event(event): if not event.inside_period(time_period): return False return True return [e for e in self.events if include_event(e)] def get_all_events(self): return list(self.events) def get_first_event(self): if len(self.events) == 0: return None e = min(self.events, key=lambda e: e.time_period.start_time) return e def get_last_event(self): if len(self.events) == 0: return None e = max(self.events, key=lambda e: e.time_period.end_time) return e def save_event(self, event): if (event.category is not None and event.category not in self.categories): raise TimelineIOError("Event's category not in db.") if event not in self.events: if event.has_id(): raise TimelineIOError("Event with id %s not found in db." % event.id) self.events.append(event) event.set_id(self.event_id_counter.get_next()) if event.is_subevent(): self._register_subevent(event) self._save_if_not_disabled() self._notify(STATE_CHANGE_ANY) def _register_subevent(self, subevent): container_events = [ event for event in self.events if event.is_container() ] containers = {} for container in container_events: key = container.cid() containers[key] = container try: container = containers[subevent.cid()] container.register_subevent(subevent) except: id = subevent.cid() if id == 0: id = self._get_max_container_id(container_events) + 1 subevent.set_cid(id) name = "[%d]Container" % id container = Container(subevent.time_type, subevent.time_period.start_time, subevent.time_period.end_time, name) self.save_event(container) self._register_subevent(subevent) pass def _get_max_container_id(self, container_events): id = 0 for event in container_events: if id < event.cid(): id = event.cid() return id def _unregister_subevent(self, subevent): container_events = [ event for event in self.events if event.is_container() ] containers = {} for container in container_events: containers[container.cid()] = container try: container = containers[subevent.cid()] container.unregister_subevent(subevent) if len(container.events) == 0: self.events.remove(container) except: pass def delete_event(self, event_or_id): if isinstance(event_or_id, Event): event = event_or_id else: event = self.find_event_with_id(event_or_id) if event in self.events: if event.is_subevent(): self._unregister_subevent(event) if event.is_container(): for subevent in event.events: self.events.remove(subevent) self.events.remove(event) event.set_id(None) self._save_if_not_disabled() self._notify(STATE_CHANGE_ANY) else: raise TimelineIOError("Event not in db.") def get_categories(self): return list(self.categories) def get_containers(self): containers = [event for event in self.events if event.is_container()] return containers def save_category(self, category): if (category.parent is not None and category.parent not in self.categories): raise TimelineIOError("Parent category not in db.") self._ensure_no_circular_parent(category) if not category in self.categories: if self.importing: if not self._category_name_exists(category): self._append_category(category) else: self._append_category(category) self._save_if_not_disabled() self._notify(STATE_CHANGE_CATEGORY) def _category_name_exists(self, category): return self._get_category_by_name(category) is not None def _append_category(self, category): if category.has_id(): raise TimelineIOError("Category with id %s not found in db." % category.id) self.categories.append(category) category.set_id(self.event_id_counter.get_next()) def _get_category_by_name(self, category): for cat in self.categories: if cat.name == category.name: return cat def delete_category(self, category_or_id): if isinstance(category_or_id, Category): category = category_or_id else: category = self._find_category_with_id(category_or_id) if category in self.categories: if category in self.hidden_categories: self.hidden_categories.remove(category) self.categories.remove(category) category.set_id(None) # Loop to update parent attribute on children for cat in self.categories: if cat.parent == category: cat.parent = category.parent # Loop to update category for events for event in self.events: if event.category == category: event.category = category.parent self._save_if_not_disabled() self._notify(STATE_CHANGE_CATEGORY) else: raise TimelineIOError("Category not in db.") def load_view_properties(self, view_properties): view_properties.displayed_period = self.displayed_period for cat in self.categories: visible = cat not in self.hidden_categories view_properties.set_category_visible(cat, visible) def save_view_properties(self, view_properties): if view_properties.displayed_period is not None: if not view_properties.displayed_period.is_period(): raise TimelineIOError(_("Displayed period must be > 0.")) self.displayed_period = view_properties.displayed_period self.hidden_categories = [] for cat in self.categories: if not view_properties.is_category_visible(cat): self.hidden_categories.append(cat) self._save_if_not_disabled() def disable_save(self): self.save_disabled = True def enable_save(self, call_save=True): if self.save_disabled == True: self.save_disabled = False if call_save == True: self._save_if_not_disabled() def place_event_after_event(self, event_to_place, target_event): if (event_to_place == target_event): return self.events.remove(event_to_place) new_index = self.events.index(target_event) + 1 self.events.insert(new_index, event_to_place) def place_event_before_event(self, event_to_place, target_event): if (event_to_place == target_event): return self.events.remove(event_to_place) new_index = self.events.index(target_event) self.events.insert(new_index, event_to_place) def _ensure_no_circular_parent(self, cat): parent = cat.parent while parent is not None: if parent == cat: raise TimelineIOError("Circular category parent.") else: parent = parent.parent def find_event_with_id(self, id): for e in self.events: if e.id == id: return e return None def _find_category_with_id(self, id): for c in self.categories: if c.id == id: return c return None def _save_if_not_disabled(self): if self.save_disabled == False: self._save() def _get_displayed_period(self): """ Inheritors can call this method to get the displayed period used in load_view_properties and save_view_properties. """ return self.displayed_period def _set_displayed_period(self, period): """ Inheritors can call this method to set the displayed period used in load_view_properties and save_view_properties. """ self.displayed_period = period def _get_hidden_categories(self): """ Inheritors can call this method to get the hidden categories used in load_view_properties and save_view_properties. """ return self.hidden_categories def _set_hidden_categories(self, hidden_categories): """ Inheritors can call this method to set the hidden categories used in load_view_properties and save_view_properties. """ self.hidden_categories = [] for cat in hidden_categories: if cat not in self.categories: raise ValueError("Category '%s' not in db." % cat.name) self.hidden_categories.append(cat) def _save(self): """ Inheritors can override this method to save this db to persistent storage. Called whenever this db changes. """ pass
class IcsTimeline(Observable): def __init__(self, path): Observable.__init__(self) self.path = path self.event_id_counter = IdCounter() self.cals = [] self.import_timeline(self.path) def get_time_type(self): return GregorianTimeType() def is_read_only(self): return True def supported_event_data(self): return [] def search(self, search_string): return generic_event_search(self._get_events(), search_string) def get_events(self, time_period): def decider(event): return event.inside_period(time_period) return self._get_events(decider) def get_all_events(self): def decider(event): return True return self._get_events(decider) def get_first_event(self): events = self._get_events() if events: return min(events, key=lambda x: x.time_period.start_time) else: return None def get_last_event(self): events = self._get_events() if events: return max(events, key=lambda x: x.time_period.end_time) else: return None def save_event(self, event): pass def delete_event(self, event_or_id): pass def get_categories(self): return [] def save_category(self, category): pass def delete_category(self, category_or_id): pass def load_view_properties(self, view_properties): pass def save_view_properties(self, view_properties): pass def find_event_with_id(self, id): events = self._get_events() for e in events: if e.id == id: return e return None def _get_events(self, decider_fn=None): self.events = [] for cal in self.cals: for event in cal.walk("VEVENT"): start, end = extract_start_end(event) txt = "" if event.has_key("summary"): txt = event["summary"] elif event.has_key("description"): txt = event["description"] else: txt == "Unknown" e = Event(self.get_time_type(), start, end, txt) e.set_id(event["timeline_id"]) if event.has_key("description"): e.set_data("description", event["description"]) if decider_fn is None or decider_fn(e): self.events.append(e) return self.events def import_timeline(self, path): try: ics_file = open(path, "rb") try: file_contents = ics_file.read() try: cal = Calendar.from_ical(file_contents) for event in cal.walk("VEVENT"): event["timeline_id"] = self.event_id_counter.get_next() self.cals.append(cal) except Exception, pe: msg1 = _("Unable to read timeline data from '%s'.") msg2 = "\n\n" + ex_msg(pe) raise TimelineIOError((msg1 % abspath(path)) + msg2) finally: ics_file.close() except IOError, e: msg = _("Unable to read from file '%s'.") whole_msg = (msg + "\n\n%s") % (abspath(self.path), e) raise TimelineIOError(whole_msg)