diff --git a/src/gen/db/dictionary.py b/src/gen/db/dictionary.py new file mode 100644 index 000000000..0f989db39 --- /dev/null +++ b/src/gen/db/dictionary.py @@ -0,0 +1,807 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2012 Douglas S. Blank +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# $Id: dbdictionary.py $ +# + +""" Implements a Db interface as a Dictionary """ + +#------------------------------------------------------------------------ +# +# Gramps Modules +# +#------------------------------------------------------------------------ +import cPickle +import base64 +import time +import gen +import re +from gen.db import DbReadBase, DbWriteBase, DbTxn +from gen.db import (PERSON_KEY, + FAMILY_KEY, + CITATION_KEY, + SOURCE_KEY, + EVENT_KEY, + MEDIA_KEY, + PLACE_KEY, + REPOSITORY_KEY, + NOTE_KEY) +import Utils + +class Cursor(object): + """ + Iterates through model returning (handle, raw_data)... + """ + def __init__(self, model, func): + self.model = model + self.func = func + def __enter__(self): + return self + def __iter__(self): + return self.__next__() + def __next__(self): + for item in self.model.all(): + yield (item.handle, self.func(item.handle)) + def __exit__(self, *args, **kwargs): + pass + def iter(self): + for item in self.model.all(): + yield (item.handle, self.func(item.handle)) + yield None + +class Bookmarks: + def get(self): + return [] # handles + def append(self, handle): + pass + +class DictionaryTxn(DbTxn): + def __init__(self, message, db): + DbTxn.__init__(self, message, db) + + def get(self, key, default=None, txn=None, **kwargs): + """ + Returns the data object associated with key + """ + if txn and key in txn: + return txn[key] + else: + return None + + def put(self, handle, new_data, txn): + """ + """ + txn[handle] = new_data + +class DictionaryDb(DbWriteBase, DbReadBase): + """ + A Gramps Database Backend. This replicates the grampsdb functions. + """ + + def __init__(self, *args, **kwargs): + DbReadBase.__init__(self) + DbWriteBase.__init__(self) + # skip GEDCOM cross-ref check for now: + self.set_feature("skip-check-xref", True) + self.readonly = False + self.db_is_open = True + self.name_formats = [] + self.bookmarks = Bookmarks() + self.family_bookmarks = Bookmarks() + self.event_bookmarks = Bookmarks() + self.place_bookmarks = Bookmarks() + self.citation_bookmarks = Bookmarks() + self.source_bookmarks = Bookmarks() + self.repo_bookmarks = Bookmarks() + self.media_bookmarks = Bookmarks() + self.note_bookmarks = Bookmarks() + self.set_person_id_prefix('I%04d') + self.set_object_id_prefix('O%04d') + self.set_family_id_prefix('F%04d') + self.set_citation_id_prefix('C%04d') + self.set_source_id_prefix('S%04d') + self.set_place_id_prefix('P%04d') + self.set_event_id_prefix('E%04d') + self.set_repository_id_prefix('R%04d') + self.set_note_id_prefix('N%04d') + # ---------------------------------- + self.id_trans = DictionaryTxn("ID Transaction", self) + self.fid_trans = DictionaryTxn("FID Transaction", self) + self.pid_trans = DictionaryTxn("PID Transaction", self) + self.cid_trans = DictionaryTxn("CID Transaction", self) + self.sid_trans = DictionaryTxn("SID Transaction", self) + self.oid_trans = DictionaryTxn("OID Transaction", self) + self.rid_trans = DictionaryTxn("RID Transaction", self) + self.nid_trans = DictionaryTxn("NID Transaction", self) + self.eid_trans = DictionaryTxn("EID Transaction", self) + self.cmap_index = 0 + self.smap_index = 0 + self.emap_index = 0 + self.pmap_index = 0 + self.fmap_index = 0 + self.lmap_index = 0 + self.omap_index = 0 + self.rmap_index = 0 + self.nmap_index = 0 + self.env = None + self.person_map = {} + self.family_map = {} + self.place_map = {} + self.citation_map = {} + self.source_map = {} + self.repository_map = {} + self.note_map = {} + self.media_map = {} + self.event_map = {} + self.metadata = {} + self.name_group = {} + self.undo_callback = None + self.redo_callback = None + self.undo_history_callback = None + self.modified = 0 + self.txn = DictionaryTxn("DbDictionary Transaction", self) + self.transaction = None + + def transaction_commit(self, txn): + pass + + def enable_signals(self): + pass + + def get_undodb(self): + return None + + def transaction_abort(self, txn): + pass + + @staticmethod + def _validated_id_prefix(val, default): + if isinstance(val, basestring) and val: + try: + str_ = val % 1 + except TypeError: # missing conversion specifier + prefix_var = val + "%d" + except ValueError: # incomplete format + prefix_var = default+"%04d" + else: + prefix_var = val # OK as given + else: + prefix_var = default+"%04d" # not a string or empty string + return prefix_var + + @staticmethod + def __id2user_format(id_pattern): + """ + Return a method that accepts a Gramps ID and adjusts it to the users + format. + """ + pattern_match = re.match(r"(.*)%[0 ](\d+)[diu]$", id_pattern) + if pattern_match: + str_prefix = pattern_match.group(1) + nr_width = pattern_match.group(2) + def closure_func(gramps_id): + if gramps_id and gramps_id.startswith(str_prefix): + id_number = gramps_id[len(str_prefix):] + if id_number.isdigit(): + id_value = int(id_number, 10) + if len(str(id_value)) > nr_width: + # The ID to be imported is too large to fit in the + # users format. For now just create a new ID, + # because that is also what happens with IDs that + # are identical to IDs already in the database. If + # the problem of colliding import and already + # present IDs is solved the code here also needs + # some solution. + gramps_id = id_pattern % 1 + else: + gramps_id = id_pattern % id_value + return gramps_id + else: + def closure_func(gramps_id): + return gramps_id + return closure_func + + def set_person_id_prefix(self, val): + """ + Set the naming template for GRAMPS Person ID values. + + The string is expected to be in the form of a simple text string, or + in a format that contains a C/Python style format string using %d, + such as I%d or I%04d. + """ + self.person_prefix = self._validated_id_prefix(val, "I") + self.id2user_format = self.__id2user_format(self.person_prefix) + + def set_citation_id_prefix(self, val): + """ + Set the naming template for GRAMPS Citation ID values. + + The string is expected to be in the form of a simple text string, or + in a format that contains a C/Python style format string using %d, + such as C%d or C%04d. + """ + self.citation_prefix = self._validated_id_prefix(val, "C") + self.cid2user_format = self.__id2user_format(self.citation_prefix) + + def set_source_id_prefix(self, val): + """ + Set the naming template for GRAMPS Source ID values. + + The string is expected to be in the form of a simple text string, or + in a format that contains a C/Python style format string using %d, + such as S%d or S%04d. + """ + self.source_prefix = self._validated_id_prefix(val, "S") + self.sid2user_format = self.__id2user_format(self.source_prefix) + + def set_object_id_prefix(self, val): + """ + Set the naming template for GRAMPS MediaObject ID values. + + The string is expected to be in the form of a simple text string, or + in a format that contains a C/Python style format string using %d, + such as O%d or O%04d. + """ + self.mediaobject_prefix = self._validated_id_prefix(val, "O") + self.oid2user_format = self.__id2user_format(self.mediaobject_prefix) + + def set_place_id_prefix(self, val): + """ + Set the naming template for GRAMPS Place ID values. + + The string is expected to be in the form of a simple text string, or + in a format that contains a C/Python style format string using %d, + such as P%d or P%04d. + """ + self.place_prefix = self._validated_id_prefix(val, "P") + self.pid2user_format = self.__id2user_format(self.place_prefix) + + def set_family_id_prefix(self, val): + """ + Set the naming template for GRAMPS Family ID values. The string is + expected to be in the form of a simple text string, or in a format + that contains a C/Python style format string using %d, such as F%d + or F%04d. + """ + self.family_prefix = self._validated_id_prefix(val, "F") + self.fid2user_format = self.__id2user_format(self.family_prefix) + + def set_event_id_prefix(self, val): + """ + Set the naming template for GRAMPS Event ID values. + + The string is expected to be in the form of a simple text string, or + in a format that contains a C/Python style format string using %d, + such as E%d or E%04d. + """ + self.event_prefix = self._validated_id_prefix(val, "E") + self.eid2user_format = self.__id2user_format(self.event_prefix) + + def set_repository_id_prefix(self, val): + """ + Set the naming template for GRAMPS Repository ID values. + + The string is expected to be in the form of a simple text string, or + in a format that contains a C/Python style format string using %d, + such as R%d or R%04d. + """ + self.repository_prefix = self._validated_id_prefix(val, "R") + self.rid2user_format = self.__id2user_format(self.repository_prefix) + + def set_note_id_prefix(self, val): + """ + Set the naming template for GRAMPS Note ID values. + + The string is expected to be in the form of a simple text string, or + in a format that contains a C/Python style format string using %d, + such as N%d or N%04d. + """ + self.note_prefix = self._validated_id_prefix(val, "N") + self.nid2user_format = self.__id2user_format(self.note_prefix) + + def __find_next_gramps_id(self, prefix, map_index, trans): + """ + Helper function for find_next__gramps_id methods + """ + index = prefix % map_index + while trans.get(str(index), txn=self.txn) is not None: + map_index += 1 + index = prefix % map_index + map_index += 1 + return (map_index, index) + + def find_next_person_gramps_id(self): + """ + Return the next available GRAMPS' ID for a Person object based off the + person ID prefix. + """ + self.pmap_index, gid = self.__find_next_gramps_id(self.person_prefix, + self.pmap_index, self.id_trans) + return gid + + def find_next_place_gramps_id(self): + """ + Return the next available GRAMPS' ID for a Place object based off the + place ID prefix. + """ + self.lmap_index, gid = self.__find_next_gramps_id(self.place_prefix, + self.lmap_index, self.pid_trans) + return gid + + def find_next_event_gramps_id(self): + """ + Return the next available GRAMPS' ID for a Event object based off the + event ID prefix. + """ + self.emap_index, gid = self.__find_next_gramps_id(self.event_prefix, + self.emap_index, self.eid_trans) + return gid + + def find_next_object_gramps_id(self): + """ + Return the next available GRAMPS' ID for a MediaObject object based + off the media object ID prefix. + """ + self.omap_index, gid = self.__find_next_gramps_id(self.mediaobject_prefix, + self.omap_index, self.oid_trans) + return gid + + def find_next_citation_gramps_id(self): + """ + Return the next available GRAMPS' ID for a Citation object based off the + citation ID prefix. + """ + self.cmap_index, gid = self.__find_next_gramps_id(self.citation_prefix, + self.cmap_index, self.cid_trans) + return gid + + def find_next_source_gramps_id(self): + """ + Return the next available GRAMPS' ID for a Source object based off the + source ID prefix. + """ + self.smap_index, gid = self.__find_next_gramps_id(self.source_prefix, + self.smap_index, self.sid_trans) + return gid + + def find_next_family_gramps_id(self): + """ + Return the next available GRAMPS' ID for a Family object based off the + family ID prefix. + """ + self.fmap_index, gid = self.__find_next_gramps_id(self.family_prefix, + self.fmap_index, self.fid_trans) + return gid + + def find_next_repository_gramps_id(self): + """ + Return the next available GRAMPS' ID for a Respository object based + off the repository ID prefix. + """ + self.rmap_index, gid = self.__find_next_gramps_id(self.repository_prefix, + self.rmap_index, self.rid_trans) + return gid + + def find_next_note_gramps_id(self): + """ + Return the next available GRAMPS' ID for a Note object based off the + note ID prefix. + """ + self.nmap_index, gid = self.__find_next_gramps_id(self.note_prefix, + self.nmap_index, self.nid_trans) + return gid + + def get_mediapath(self): + return None + + def get_name_group_keys(self): + return [] + + def get_name_group_mapping(self, key): + return None + + def get_researcher(self): + obj = gen.lib.Researcher() + return obj + + def get_person_handles(self): + return self.person_map.keys() + + def get_family_handles(self): + return self.family_map.keys() + + def get_event_handles(self): + return self.event_map.keys() + + def get_citation_handles(self): + return self.citation_map.keys() + + def get_source_handles(self): + return self.source_map.keys() + + def get_place_handles(self): + return self.place_map.keys() + + def get_repository_handles(self): + return self.repository_map.keys() + + def get_media_object_handles(self): + return self.media_map.keys() + + def get_note_handles(self): + return self.note_map.keys() + + def get_tag_handles(self, sort_handles=False): + return [] + + def get_event_from_handle(self, handle): + return self.event_map[handle] + + def get_family_from_handle(self, handle): + return self.family_map[handle] + + def get_repository_from_handle(self, handle): + return self.repository_map[handle] + + def get_person_from_handle(self, handle): + return self.person_map[handle] + + def get_place_from_handle(self, handle): + place = self.person_map[handle] + return place + + def get_citation_from_handle(self, handle): + citation = self.citation_map[handle] + return citation + + def get_source_from_handle(self, handle): + source = self.source_map[handle] + return source + + def get_note_from_handle(self, handle): + note = self.note_map[handle] + return note + + def get_object_from_handle(self, handle): + media = self.media_map[handle] + return media + + def get_default_person(self): + return None + + def iter_people(self): + return (person for person in self.person_map.values()) + + def iter_person_handles(self): + return (handle for handle in self.person_map.keys()) + + def iter_families(self): + return (family for family in self.family_map.values()) + + def iter_family_handles(self): + return (handle for handle in self.family_map.keys()) + + def get_tag_from_name(self, name): + for tag in self.tag_map.values(): + if tag.name == name: + return tag + return None + + def get_family_from_gramps_id(self, gramps_id): + for family in self.family_map.values(): + if family.gramps_id == gramps_id: + return family + return None + + def get_person_from_gramps_id(self, gramps_id): + for person in self.person_map.values(): + if person.gramps_id == gramps_id: + return person + return person + + def get_number_of_people(self): + return len(self.person_map) + + def get_number_of_events(self): + return len(self.event_map) + + def get_number_of_places(self): + return len(self.place_map) + + def get_number_of_tags(self): + return 0 # FIXME + + def get_number_of_families(self): + return len(self.family_map) + + def get_number_of_notes(self): + return len(self.note_map) + + def get_number_of_citations(self): + return len(self.citation_map) + + def get_number_of_sources(self): + return len(self.source_map) + + def get_number_of_media_objects(self): + return len(self.media_map) + + def get_number_of_repositories(self): + return len(self.repository_map) + + def get_place_cursor(self): + return Cursor(self.place_map, self.get_raw_place_data).iter() + + def get_person_cursor(self): + return Cursor(self.person_map, self.get_raw_person_data).iter() + + def get_family_cursor(self): + return Cursor(self.family_map, self.get_raw_family_data).iter() + + def get_events_cursor(self): + return Cursor(self.event_map, self.get_raw_event_data).iter() + + def get_citation_cursor(self): + return Cursor(self.citation_map, self.get_raw_citation_data).iter() + + def get_source_cursor(self): + return Cursor(self.source_map, self.get_raw_source_data).iter() + + def has_gramps_id(self, obj_key, gramps_id): + key2table = { + PERSON_KEY: self.person_map, + FAMILY_KEY: self.family_map, + SOURCE_KEY: self.source_map, + CITATION_KEY: self.citation_map, + EVENT_KEY: self.event_map, + MEDIA_KEY: self.media_map, + PLACE_KEY: self.place_map, + REPOSITORY_KEY: self.repository_map, + NOTE_KEY: self.note_map, + } + map = key2table[obj_key] + for item in map.values(): + if item.gramps_id == gramps_id: + return True + return False + + def has_person_handle(self, handle): + return handle in self.person_map + + def has_family_handle(self, handle): + return handle in self.family_map + + def has_citation_handle(self, handle): + return handle in self.citation_map + + def has_source_handle(self, handle): + return handle in self.source_map + + def has_repository_handle(self, handle): + return handle in self.repository_map + + def has_note_handle(self, handle): + return handle in self.note_map + + def has_place_handle(self, handle): + return handle in self.place_map + + def has_event_handle(self, handle): + return handle in self.event_map + + def has_tag_handle(self, handle): + return handle in self.tag_map + + def has_object_handle(self, handle): + return handle in self.media_map + + def has_name_group_key(self, key): + # FIXME: + return False + + def set_name_group_mapping(self, key, value): + # FIXME: + pass + + def set_default_person_handle(self, handle): + pass + + def set_mediapath(self, mediapath): + pass + + def get_raw_person_data(self, handle): + if handle in self.person_map: + return self.person_map[handle].serialize() + return None + + def get_raw_family_data(self, handle): + if handle in self.family_map: + return self.family_map[handle].serialize() + return None + + def get_raw_citation_data(self, handle): + if handle in self.citation_map: + return self.citation_map[handle].serialize() + return None + + def get_raw_source_data(self, handle): + if handle in self.source_map: + return self.source_map[handle].serialize() + return None + + def get_raw_repository_data(self, handle): + if handle in self.repository_map: + return self.repository_map[handle].serialize() + return None + + def get_raw_note_data(self, handle): + if handle in self.note_map: + return self.note_map[handle].serialize() + return None + + def get_raw_place_data(self, handle): + if handle in self.place_map: + return self.place_map[handle].serialize() + return None + + def get_raw_object_data(self, handle): + if handle in self.media_map: + return self.media_map[handle].serialize() + return None + + def add_person(self, person, trans, set_gid=True): + if not person.handle: + person.handle = Utils.create_id() + if not person.gramps_id or set_gid: + person.gramps_id = self.find_next_person_gramps_id() + self.commit_person(person, trans) + return person.handle + + def add_family(self, family, trans, set_gid=True): + if not family.handle: + family.handle = Utils.create_id() + if not family.gramps_id or set_gid: + family.gramps_id = self.find_next_family_gramps_id() + self.commit_family(family, trans) + return family.handle + + def add_citation(self, citation, trans, set_gid=True): + if not citation.handle: + citation.handle = Utils.create_id() + if not citation.gramps_id or set_gid: + citation.gramps_id = self.find_next_citation_gramps_id() + self.commit_citation(citation, trans) + return citation.handle + + def add_source(self, source, trans, set_gid=True): + if not source.handle: + source.handle = Utils.create_id() + if not source.gramps_id or set_gid: + source.gramps_id = self.find_next_source_gramps_id() + self.commit_source(source, trans) + return source.handle + + def add_repository(self, repository, trans, set_gid=True): + if not repository.handle: + repository.handle = Utils.create_id() + if not repository.gramps_id or set_gid: + repository.gramps_id = self.find_next_repository_gramps_id() + self.commit_repository(repository, trans) + return repository.handle + + def add_note(self, note, trans, set_gid=True): + if not note.handle: + note.handle = Utils.create_id() + if not note.gramps_id or set_gid: + note.gramps_id = self.find_next_note_gramps_id() + self.commit_note(note, trans) + return note.handle + + def add_place(self, place, trans, set_gid=True): + if not place.handle: + place.handle = Utils.create_id() + if not place.gramps_id or set_gid: + place.gramps_id = self.find_next_place_gramps_id() + self.commit_place(place, trans) + return place.handle + + def add_event(self, event, trans, set_gid=True): + if not event.handle: + event.handle = Utils.create_id() + if not event.gramps_id or set_gid: + event.gramps_id = self.find_next_event_gramps_id() + self.commit_event(event, trans) + return event.handle + + def add_tag(self, tag, trans): + if not tag.handle: + tag.handle = Utils.create_id() + self.commit_event(tag, trans) + return tag.handle + + def add_object(self, obj, transaction, set_gid=True): + """ + Add a MediaObject to the database, assigning internal IDs if they have + not already been defined. + + If not set_gid, then gramps_id is not set. + """ + if not obj.handle: + obj.handle = Utils.create_id() + if not obj.gramps_id or set_gid: + obj.gramps_id = self.find_next_object_gramps_id() + self.commit_media_object(obj, transaction) + return obj.handle + + def commit_person(self, person, trans, change_time=None): + self.person_map[person.handle] = person + + def commit_family(self, family, trans, change_time=None): + self.family_map[family.handle] = family + + def commit_citation(self, citation, trans, change_time=None): + self.citation_map[citation.handle] = citation + + def commit_source(self, source, trans, change_time=None): + self.source_map[source.handle] = source + + def commit_repository(self, repository, trans, change_time=None): + self.repository_map[repository.handle] = repository + + def commit_note(self, note, trans, change_time=None): + self.note_map[note.handle] = note + + def commit_place(self, place, trans, change_time=None): + self.place_map[place.handle] = place + + def commit_event(self, event, trans, change_time=None): + self.event_map[event.handle] = event + + def commit_tag(self, tag, trans, change_time=None): + self.tag_map[tag.handle] = tag + + def commit_media_object(self, obj, transaction, change_time=None): + self.media_map[obj.handle] = obj + + def get_gramps_ids(self, obj_key): + key2table = { + PERSON_KEY: self.person_map, + FAMILY_KEY: self.family_map, + CITATION_KEY: self.citation_map, + SOURCE_KEY: self.source_map, + EVENT_KEY: self.event_map, + MEDIA_KEY: self.media_map, + PLACE_KEY: self.place_map, + REPOSITORY_KEY: self.repository_map, + NOTE_KEY: self.note_map, + } + table = key2table[obj_key] + return [item.gramps_id for item in table.values()] + + def transaction_begin(self, transaction): + return + + def disable_signals(self): + pass + + def set_researcher(self, owner): + pass + + def request_rebuild(self): + pass +