Package Gnumed :: Package timelinelib :: Package db :: Package backends :: Module memory |
[frames] | no frames]

# Source Code for Module Gnumed.timelinelib.db.backends.memory

      1  # Copyright (C) 2009, 2010, 2011  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  """
     20  Implementation of timeline database that stores all data in memory.
     21
     22  MemoryDB can be used as a base class for other timeline databases that wish to
     23  store all data in memory and also want to save the data to persistent storage
     24  whenever it changes in memory. Initially data can be read from persistent
     25  storage into memory.
     26
     27  MemoryDB is not suitable as a base class for timeline databases that need to
     28  query persistent storage to retrieve data.
     29  """
     30
     31
     32  from timelinelib.db.exceptions import TimelineIOError
     33  from timelinelib.db.objects import Category
     34  from timelinelib.db.objects import Container
     35  from timelinelib.db.objects import Event
     36  from timelinelib.db.observer import Observable
     37  from timelinelib.db.observer import STATE_CHANGE_ANY
     38  from timelinelib.db.observer import STATE_CHANGE_CATEGORY
     39  from timelinelib.db.search import generic_event_search
     40  from timelinelib.db.utils import IdCounter
     41
     42
     43 -class MemoryDB(Observable):
     44
     45 -    def __init__(self):
     46          Observable.__init__(self)
     47          self.path = ""
     48          self.categories = []
     49          self.category_id_counter = IdCounter()
     50          self.events = []
     51          self.event_id_counter = IdCounter()
     52          self.displayed_period = None
     53          self.hidden_categories = []
     54          self.save_disabled = False
     55          from timelinelib.time.pytime import PyTimeType
     56          self.time_type = PyTimeType()
     57
     58 -    def get_time_type(self):
     59          return self.time_type
     60
     61 -    def is_read_only(self):
     62          return False
     63
     64 -    def supported_event_data(self):
     65          return ["description", "icon", "alert", "hyperlink"]
     66
     67 -    def search(self, search_string):
     68          return generic_event_search(self.events, search_string)
     69
     70 -    def get_events(self, time_period):
     71          def include_event(event):
     72              if not event.inside_period(time_period):
     73                  return False
     74              return True
     75          return [e for e in self.events if include_event(e)]
     76
     77 -    def get_all_events(self):
     78          return list(self.events)
     79
     80 -    def get_first_event(self):
     81          if len(self.events) == 0:
     82              return None
     83          e = min(self.events, key=lambda e: e.time_period.start_time)
     84          return e
     85
     86 -    def get_last_event(self):
     87          if len(self.events) == 0:
     88              return None
     89          e = max(self.events, key=lambda e: e.time_period.end_time)
     90          return e
     91
     92 -    def save_event(self, event):
     93          if (event.category is not None and
     94              event.category not in self.categories):
     95              raise TimelineIOError("Event's category not in db.")
     96          if event not in self.events:
     97              if event.has_id():
     98                  raise TimelineIOError("Event with id %s not found in db." %
     99                                        event.id)
    100              self.events.append(event)
    101              event.set_id(self.event_id_counter.get_next())
    102              if event.is_subevent():
    103                  self._register_subevent(event)
    104          self._save_if_not_disabled()
    105          self._notify(STATE_CHANGE_ANY)
    106
    107 -    def _register_subevent(self, subevent):
    108          container_events = [event for event in self.events
    109                              if event.is_container()]
    110          containers = {}
    111          for container in container_events:
    112              key = container.cid()
    113              containers[key] = container
    114          try:
    115              container = containers[subevent.cid()]
    116              container.register_subevent(subevent)
    117          except:
    118              id = subevent.cid()
    119              if id == 0:
    120                  id = self._get_max_container_id(container_events) + 1
    121                  subevent.set_cid(id)
    122              name = "[%d]Container" % id
    123              container = Container(subevent.time_type,
    124                                    subevent.time_period.start_time,
    125                                    subevent.time_period.end_time, name)
    126              self.save_event(container)
    127              self._register_subevent(subevent)
    128              pass
    129
    130 -    def _get_max_container_id(self, container_events):
    131          id = 0
    132          for event in container_events:
    133              if id < event.cid():
    134                  id = event.cid()
    135          return id
    136
    137 -    def _unregister_subevent(self, subevent):
    138          container_events = [event for event in self.events
    139                              if event.is_container()]
    140          containers = {}
    141          for container in container_events:
    142              containers[container.cid()] = container
    143          try:
    144              container = containers[subevent.cid()]
    145              container.unregister_subevent(subevent)
    146              if len(container.events) == 0:
    147                  self.events.remove(container)
    148          except:
    149              pass
    150
    151 -    def delete_event(self, event_or_id):
    152          if isinstance(event_or_id, Event):
    153              event = event_or_id
    154          else:
    155              event = self.find_event_with_id(event_or_id)
    156          if event in self.events:
    157              if event.is_subevent():
    158                  self._unregister_subevent(event)
    159              if event.is_container():
    160                  for subevent in event.events:
    161                      self.events.remove(subevent)
    162              self.events.remove(event)
    163              event.set_id(None)
    164              self._save_if_not_disabled()
    165              self._notify(STATE_CHANGE_ANY)
    166          else:
    167              raise TimelineIOError("Event not in db.")
    168
    169 -    def get_categories(self):
    170          return list(self.categories)
    171
    172 -    def get_containers(self):
    173          containers = [event for event in self.events
    174                        if event.is_container()]
    175          return containers
    176
    177 -    def save_category(self, category):
    178          if (category.parent is not None and
    179              category.parent not in self.categories):
    180              raise TimelineIOError("Parent category not in db.")
    181          self._ensure_no_circular_parent(category)
    182          if not category in self.categories:
    183              if category.has_id():
    184                  raise TimelineIOError("Category with id %s not found in db." %
    185                                        category.id)
    186              self.categories.append(category)
    187              category.set_id(self.event_id_counter.get_next())
    188          self._save_if_not_disabled()
    189          self._notify(STATE_CHANGE_CATEGORY)
    190
    191 -    def delete_category(self, category_or_id):
    192          if isinstance(category_or_id, Category):
    193              category = category_or_id
    194          else:
    195              category = self._find_category_with_id(category_or_id)
    196          if category in self.categories:
    197              if category in self.hidden_categories:
    198                  self.hidden_categories.remove(category)
    199              self.categories.remove(category)
    200              category.set_id(None)
    201              # Loop to update parent attribute on children
    202              for cat in self.categories:
    203                  if cat.parent == category:
    204                      cat.parent = category.parent
    205              # Loop to update category for events
    206              for event in self.events:
    207                  if event.category == category:
    208                      event.category = category.parent
    209              self._save_if_not_disabled()
    210              self._notify(STATE_CHANGE_CATEGORY)
    211          else:
    212              raise TimelineIOError("Category not in db.")
    213
    214 -    def load_view_properties(self, view_properties):
    215          view_properties.displayed_period = self.displayed_period
    216          for cat in self.categories:
    217              visible = cat not in self.hidden_categories
    218              view_properties.set_category_visible(cat, visible)
    219
    220 -    def save_view_properties(self, view_properties):
    221          if view_properties.displayed_period is not None:
    222              if not view_properties.displayed_period.is_period():
    223                  raise TimelineIOError(_("Displayed period must be > 0."))
    224              self.displayed_period = view_properties.displayed_period
    225          self.hidden_categories = []
    226          for cat in self.categories:
    227              if not view_properties.category_visible(cat):
    228                  self.hidden_categories.append(cat)
    229          self._save_if_not_disabled()
    230
    231 -    def disable_save(self):
    232          self.save_disabled = True
    233
    234 -    def enable_save(self, call_save=True):
    235          if self.save_disabled == True:
    236              self.save_disabled = False
    237              if call_save == True:
    238                  self._save_if_not_disabled()
    239
    240 -    def place_event_after_event(self, event_to_place, target_event):
    241          if (event_to_place == target_event):
    242              return
    243          self.events.remove(event_to_place)
    244          new_index = self.events.index(target_event) + 1
    245          self.events.insert(new_index, event_to_place)
    246
    247 -    def place_event_before_event(self, event_to_place, target_event):
    248          if (event_to_place == target_event):
    249              return
    250          self.events.remove(event_to_place)
    251          new_index = self.events.index(target_event)
    252          self.events.insert(new_index, event_to_place)
    253
    254 -    def _ensure_no_circular_parent(self, cat):
    255          parent = cat.parent
    256          while parent is not None:
    257              if parent == cat:
    258                  raise TimelineIOError("Circular category parent.")
    259              else:
    260                  parent = parent.parent
    261
    262 -    def find_event_with_id(self, id):
    263          for e in self.events:
    264              if e.id == id:
    265                  return e
    266          return None
    267
    268 -    def _find_category_with_id(self, id):
    269          for c in self.categories:
    270              if c.id == id:
    271                  return c
    272          return None
    273
    274 -    def _save_if_not_disabled(self):
    275          if self.save_disabled == False:
    276              self._save()
    277
    278 -    def _get_displayed_period(self):
    279          """
    280          Inheritors can call this method to get the displayed period used in
    281          load_view_properties and save_view_properties.
    282          """
    283          return self.displayed_period
    284
    285 -    def _set_displayed_period(self, period):
    286          """
    287          Inheritors can call this method to set the displayed period used in
    288          load_view_properties and save_view_properties.
    289          """
    290          self.displayed_period = period
    291
    292 -    def _get_hidden_categories(self):
    293          """
    294          Inheritors can call this method to get the hidden categories used in
    295          load_view_properties and save_view_properties.
    296          """
    297          return self.hidden_categories
    298
    299 -    def _set_hidden_categories(self, hidden_categories):
    300          """
    301          Inheritors can call this method to set the hidden categories used in
    302          load_view_properties and save_view_properties.
    303          """
    304          self.hidden_categories = []
    305          for cat in hidden_categories:
    306              if cat not in self.categories:
    307                  raise ValueError("Category '%s' not in db." % cat.name)
    308              self.hidden_categories.append(cat)
    309
    310 -    def _save(self):
    311          """
    312          Inheritors can override this method to save this db to persistent
    313          storage.
    314
    315          Called whenever this db changes.
    316          """
    317          pass
    318