883576ef12
* src/images/sources.svg: new icon * src/images/reports.svg: new icon * src/images/tools.svg: new icon * src/images/events.svg: new icon * src/images/place.svg: new icon * src/images/tools.svg: new icon * src/ViewManager.py: use new icons * src/gramps_main.py: register new icons 2006-09-22 Don Allingham <don@gramps-project.org> * src/GrampsDb/_GrampsGEDDB.py: support for disabling transactions * src/GrampsDb/_GrampsXMLDB.py: support for disabling transactions * src/GrampsDb/_GrampsBSDDB.py: support for disabling transactions * src/GrampsDb/_GrampsDbBase.py: support for disabling transactions * src/GrampsDb/_ReadGedcom.py: check for IO Eror * src/ViewManager.py: display message if a portability problem is detected * src/QuestionDialog.py: Add Warning dialog that can be disabled * src/DbLoader.py: Detect missing database problem * src/ArgHandler.py: support for disabling transactions * src/GrampsCfg.py: new config keys for transactions * src/Config/_GrampsConfigKeys.py: new config keys for transactions 2006-09-17 Don Allingham <don@gramps-project.org> * src/ViewManager.py: handle missing database on autoload (#447) * src/ArgHandler.py: handle missing database on autoload (#447) * src/DbLoader.py: handle missing database on autoload (#447) * src/Makefile.am: remove uninstalled packages from makefile * src/GrampsDb/_ReadXML.py: place vs. address changes * src/GrampsDb/_WriteXML.py: place vs. address changes * src/GrampsDb/_EditPlace.py: place vs. address changes * src/Editors/_EditPlace.py: place vs. address changes * src/Editors/_EditLocation.py: place vs. address changes * src/RelLib/_Address.py: place vs. address changes * src/RelLib/_LocationBase.py: place vs. address changes * src/RelLib/_Location.py: place vs. address changes * src/DisplayTabs/_LocationModel.py: place vs. address changes * src/DisplayTabs/_LocationEmbedList.py: place vs. address changes * src/glade/gramps.glade: place vs. address changes svn: r7325
2286 lines
84 KiB
Python
2286 lines
84 KiB
Python
#
|
|
# Gramps - a GTK+/GNOME based genealogy program
|
|
#
|
|
# Copyright (C) 2000-2006 Donald N. Allingham
|
|
#
|
|
# 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$
|
|
|
|
"""
|
|
Provides the Berkeley DB (BSDDB) database backend for GRAMPS
|
|
"""
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Standard python modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
import cPickle as pickle
|
|
import os
|
|
import re
|
|
import time
|
|
import locale
|
|
from gettext import gettext as _
|
|
from bsddb import dbshelve, db
|
|
import logging
|
|
log = logging.getLogger(".GrampsDb")
|
|
|
|
# hack to use native set for python2.4
|
|
# and module sets for earlier pythons
|
|
try:
|
|
set()
|
|
except NameError:
|
|
from sets import Set as set
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# Gramps modules
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
from RelLib import *
|
|
from _GrampsDbBase import *
|
|
from _DbUtils import db_copy
|
|
import const
|
|
import Errors
|
|
from BasicUtils import UpdateCallback
|
|
|
|
_MINVERSION = 5
|
|
_DBVERSION = 11
|
|
|
|
def find_surname(key,data):
|
|
return str(data[3][5])
|
|
|
|
def find_idmap(key,data):
|
|
return str(data[1])
|
|
|
|
# Secondary database key lookups for reference_map table
|
|
# reference_map data values are of the form:
|
|
# ((primary_object_class_name, primary_object_handle),
|
|
# (referenced_object_class_name, referenced_object_handle))
|
|
|
|
def find_primary_handle(key,data):
|
|
return str((data)[0][1])
|
|
|
|
def find_referenced_handle(key,data):
|
|
return str((data)[1][1])
|
|
|
|
class GrampsBSDDBCursor(GrampsCursor):
|
|
|
|
def __init__(self,source,txn=None):
|
|
self.cursor = source.db.cursor(txn)
|
|
|
|
def first(self):
|
|
d = self.cursor.first()
|
|
if d:
|
|
return (d[0],pickle.loads(d[1]))
|
|
return None
|
|
|
|
def next(self):
|
|
d = self.cursor.next()
|
|
if d:
|
|
return (d[0],pickle.loads(d[1]))
|
|
return None
|
|
|
|
def close(self):
|
|
self.cursor.close()
|
|
|
|
def delete(self):
|
|
self.cursor.delete()
|
|
|
|
class GrampsBSDDBAssocCursor(GrampsCursor):
|
|
|
|
def __init__(self,source,txn=None):
|
|
self.cursor = source.cursor(txn)
|
|
|
|
def first(self):
|
|
d = self.cursor.first()
|
|
if d:
|
|
return (d[0],pickle.loads(d[1]))
|
|
return None
|
|
|
|
def next(self):
|
|
d = self.cursor.next()
|
|
if d:
|
|
return (d[0],pickle.loads(d[1]))
|
|
return None
|
|
|
|
def close(self):
|
|
self.cursor.close()
|
|
|
|
def delete(self):
|
|
self.cursor.delete()
|
|
|
|
class GrampsBSDDBDupCursor(GrampsBSDDBAssocCursor):
|
|
"""Cursor that includes handling for duplicate keys"""
|
|
|
|
def set(self,key):
|
|
return self.cursor.set(str(key))
|
|
|
|
def next_dup(self):
|
|
return self.cursor.next_dup()
|
|
|
|
|
|
#-------------------------------------------------------------------------
|
|
#
|
|
# GrampsBSDDB
|
|
#
|
|
#-------------------------------------------------------------------------
|
|
class GrampsBSDDB(GrampsDbBase,UpdateCallback):
|
|
"""GRAMPS database object. This object is a base class for other
|
|
objects."""
|
|
|
|
def __init__(self, use_txn = True):
|
|
"""creates a new GrampsDB"""
|
|
|
|
GrampsDbBase.__init__(self)
|
|
self.txn = None
|
|
self.secondary_connected = False
|
|
self.UseTXN = use_txn
|
|
|
|
def open_flags(self):
|
|
if self.UseTXN:
|
|
return db.DB_CREATE|db.DB_AUTO_COMMIT
|
|
else:
|
|
return db.DB_CREATE
|
|
|
|
def open_table(self,file_name,table_name,dbtype=db.DB_HASH):
|
|
dbmap = dbshelve.DBShelf(self.env)
|
|
dbmap.db.set_pagesize(16384)
|
|
if self.readonly:
|
|
dbmap.open(file_name, table_name, dbtype, db.DB_RDONLY)
|
|
else:
|
|
dbmap.open(file_name, table_name, dbtype, self.open_flags(), 0666)
|
|
return dbmap
|
|
|
|
def _all_handles(self,table):
|
|
return table.keys(self.txn)
|
|
|
|
def get_person_cursor(self):
|
|
return GrampsBSDDBCursor(self.person_map,self.txn)
|
|
|
|
def get_family_cursor(self):
|
|
return GrampsBSDDBCursor(self.family_map,self.txn)
|
|
|
|
def get_event_cursor(self):
|
|
return GrampsBSDDBCursor(self.event_map,self.txn)
|
|
|
|
def get_place_cursor(self):
|
|
return GrampsBSDDBCursor(self.place_map,self.txn)
|
|
|
|
def get_source_cursor(self):
|
|
return GrampsBSDDBCursor(self.source_map,self.txn)
|
|
|
|
def get_media_cursor(self):
|
|
return GrampsBSDDBCursor(self.media_map,self.txn)
|
|
|
|
def get_repository_cursor(self):
|
|
return GrampsBSDDBCursor(self.repository_map,self.txn)
|
|
|
|
def has_person_handle(self,handle):
|
|
"""
|
|
returns True if the handle exists in the current Person database.
|
|
"""
|
|
return self.person_map.get(str(handle),txn=self.txn) != None
|
|
|
|
def has_family_handle(self,handle):
|
|
"""
|
|
returns True if the handle exists in the current Family database.
|
|
"""
|
|
return self.family_map.get(str(handle),txn=self.txn) != None
|
|
|
|
def has_object_handle(self,handle):
|
|
"""
|
|
returns True if the handle exists in the current MediaObjectdatabase.
|
|
"""
|
|
return self.media_map.get(str(handle),txn=self.txn) != None
|
|
|
|
def has_repository_handle(self,handle):
|
|
"""
|
|
returns True if the handle exists in the current Repository database.
|
|
"""
|
|
return self.repository_map.get(str(handle),txn=self.txn) != None
|
|
|
|
def has_event_handle(self,handle):
|
|
"""
|
|
returns True if the handle exists in the current Repository database.
|
|
"""
|
|
return self.event_map.get(str(handle),txn=self.txn) != None
|
|
|
|
def has_place_handle(self,handle):
|
|
"""
|
|
returns True if the handle exists in the current Repository database.
|
|
"""
|
|
return self.place_map.get(str(handle),txn=self.txn) != None
|
|
|
|
def has_source_handle(self,handle):
|
|
"""
|
|
returns True if the handle exists in the current Repository database.
|
|
"""
|
|
return self.source_map.get(str(handle),txn=self.txn) != None
|
|
|
|
def get_raw_person_data(self,handle):
|
|
return self.person_map.get(str(handle),txn=self.txn)
|
|
|
|
def get_raw_family_data(self,handle):
|
|
return self.family_map.get(str(handle),txn=self.txn)
|
|
|
|
def get_raw_object_data(self,handle):
|
|
return self.media_map.get(str(handle),txn=self.txn)
|
|
|
|
def get_raw_place_data(self,handle):
|
|
return self.place_map.get(str(handle),txn=self.txn)
|
|
|
|
def get_raw_event_data(self,handle):
|
|
return self.event_map.get(str(handle),txn=self.txn)
|
|
|
|
def get_raw_source_data(self,handle):
|
|
return self.source_map.get(str(handle),txn=self.txn)
|
|
|
|
def get_raw_repository_data(self,handle):
|
|
return self.repository_map.get(str(handle),txn=self.txn)
|
|
|
|
# cursors for lookups in the reference_map for back reference
|
|
# lookups. The reference_map has three indexes:
|
|
# the main index: a tuple of (primary_handle,referenced_handle)
|
|
# the primary_handle index: the primary_handle
|
|
# the referenced_handle index: the referenced_handle
|
|
# the main index is unique, the others allow duplicate entries.
|
|
|
|
def get_reference_map_cursor(self):
|
|
return GrampsBSDDBAssocCursor(self.reference_map,self.txn)
|
|
|
|
def get_reference_map_primary_cursor(self):
|
|
return GrampsBSDDBDupCursor(self.reference_map_primary_map,self.txn)
|
|
|
|
def get_reference_map_referenced_cursor(self):
|
|
return GrampsBSDDBDupCursor(self.reference_map_referenced_map,self.txn)
|
|
|
|
# These are overriding the GrampsDbBase's methods of saving metadata
|
|
# because we now have txn-capable metadata table
|
|
def set_default_person_handle(self, handle):
|
|
"""sets the default Person to the passed instance"""
|
|
if not self.readonly:
|
|
if self.UseTXN:
|
|
# Start transaction if needed
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
self.metadata.put('default',str(handle),txn=the_txn)
|
|
if self.UseTXN:
|
|
the_txn.commit()
|
|
else:
|
|
self.metadata.sync()
|
|
|
|
def get_default_person(self):
|
|
"""returns the default Person of the database"""
|
|
person = self.get_person_from_handle(self.get_default_handle())
|
|
if person:
|
|
return person
|
|
elif (self.metadata) and (not self.readonly):
|
|
if self.UseTXN:
|
|
# Start transaction if needed
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
self.metadata.put('default',None,txn=the_txn)
|
|
if self.UseTXN:
|
|
the_txn.commit()
|
|
else:
|
|
self.metadata.sync()
|
|
return None
|
|
|
|
def _set_column_order(self, col_list, name):
|
|
if self.metadata and not self.readonly:
|
|
if self.UseTXN:
|
|
# Start transaction if needed
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
self.metadata.put(name,col_list,txn=the_txn)
|
|
if self.UseTXN:
|
|
the_txn.commit()
|
|
else:
|
|
self.metadata.sync()
|
|
|
|
def version_supported(self):
|
|
dbversion = self.metadata.get('version',default=0)
|
|
return ((dbversion <= _DBVERSION) and (dbversion >= _MINVERSION))
|
|
|
|
def need_upgrade(self):
|
|
dbversion = self.metadata.get('version',default=0)
|
|
return not self.readonly and dbversion < _DBVERSION
|
|
|
|
def load(self,name,callback,mode="w"):
|
|
if self.db_is_open:
|
|
self.close()
|
|
|
|
self.readonly = mode == "r"
|
|
if self.readonly:
|
|
self.UseTXN = False
|
|
|
|
callback(12)
|
|
|
|
self.full_name = os.path.abspath(name)
|
|
self.brief_name = os.path.basename(name)
|
|
|
|
self.env = db.DBEnv()
|
|
self.env.set_cachesize(0,0x2000000) # 16MB
|
|
|
|
if self.UseTXN:
|
|
# These env settings are only needed for Txn environment
|
|
self.env.set_lk_max_locks(25000)
|
|
self.env.set_lk_max_objects(25000)
|
|
self.env.set_flags(db.DB_LOG_AUTOREMOVE,1) # clean up unused logs
|
|
|
|
# The DB_PRIVATE flag must go if we ever move to multi-user setup
|
|
env_flags = db.DB_CREATE|db.DB_RECOVER|db.DB_PRIVATE|\
|
|
db.DB_INIT_MPOOL|db.DB_INIT_LOCK|\
|
|
db.DB_INIT_LOG|db.DB_INIT_TXN|db.DB_THREAD
|
|
env_name = os.path.expanduser(const.bsddbenv_dir)
|
|
if not os.path.isdir(env_name):
|
|
os.mkdir(env_name)
|
|
else:
|
|
env_flags = db.DB_CREATE|db.DB_PRIVATE|\
|
|
db.DB_INIT_MPOOL|db.DB_INIT_LOG
|
|
env_name = os.path.expanduser('~')
|
|
|
|
self.env.open(env_name,env_flags)
|
|
if self.UseTXN:
|
|
self.env.txn_checkpoint()
|
|
|
|
callback(25)
|
|
self.metadata = self.open_table(self.full_name,"meta")
|
|
|
|
# If we cannot work with this DB version,
|
|
# it makes no sense to go further
|
|
if not self.version_supported:
|
|
self._close_early()
|
|
|
|
self.family_map = self.open_table(self.full_name, "family")
|
|
self.place_map = self.open_table(self.full_name, "places")
|
|
self.source_map = self.open_table(self.full_name, "sources")
|
|
self.media_map = self.open_table(self.full_name, "media")
|
|
self.event_map = self.open_table(self.full_name, "events")
|
|
self.person_map = self.open_table(self.full_name, "person")
|
|
self.repository_map = self.open_table(self.full_name, "repository")
|
|
self.reference_map = self.open_table(self.full_name, "reference_map",
|
|
dbtype=db.DB_BTREE)
|
|
callback(37)
|
|
|
|
self._load_metadata()
|
|
|
|
gstats = self.metadata.get('gender_stats',default=None)
|
|
|
|
if not self.readonly:
|
|
if self.UseTXN:
|
|
# Start transaction if needed
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
|
|
if gstats == None:
|
|
# New database. Set up the current version.
|
|
self.metadata.put('version',_DBVERSION,txn=the_txn)
|
|
elif not self.metadata.has_key('version'):
|
|
# Not new database, but the version is missing.
|
|
# Use 0, but it is likely to fail anyway.
|
|
self.metadata.put('version',0,txn=the_txn)
|
|
|
|
if self.UseTXN:
|
|
the_txn.commit()
|
|
else:
|
|
self.metadata.sync()
|
|
|
|
self.genderStats = GenderStats(gstats)
|
|
|
|
# Here we take care of any changes in the tables related to new code.
|
|
# If secondary indices change, then they should removed
|
|
# or rebuilt by upgrade as well. In any case, the
|
|
# self.secondary_connected flag should be set accordingly.
|
|
|
|
if self.need_upgrade():
|
|
self.gramps_upgrade(callback)
|
|
|
|
callback(50)
|
|
|
|
if not self.secondary_connected:
|
|
self.connect_secondary()
|
|
|
|
callback(75)
|
|
|
|
self.open_undodb()
|
|
self.db_is_open = True
|
|
|
|
callback(87)
|
|
|
|
# Re-set the undo history to a fresh session start
|
|
self.undoindex = -1
|
|
self.translist = [None] * len(self.translist)
|
|
self.abort_possible = True
|
|
self.undo_history_timestamp = time.time()
|
|
|
|
return 1
|
|
|
|
def load_from(self, other_database, filename, callback):
|
|
self.load(filename,callback)
|
|
db_copy(other_database,self,callback)
|
|
return 1
|
|
|
|
def _load_metadata(self):
|
|
# name display formats
|
|
self.name_formats = self.metadata.get('name_formats',default=[])
|
|
# upgrade formats if they were saved in the old way
|
|
for format_ix in range(len(self.name_formats)):
|
|
format = self.name_formats[format_ix]
|
|
if len(format) == 3:
|
|
format = format + (True,)
|
|
self.name_formats[format_ix] = format
|
|
# bookmarks
|
|
self.bookmarks = self.metadata.get('bookmarks',default=[])
|
|
self.family_bookmarks = self.metadata.get('family_bookmarks',
|
|
default=[])
|
|
self.event_bookmarks = self.metadata.get('event_bookmarks',
|
|
default=[])
|
|
self.source_bookmarks = self.metadata.get('source_bookmarks',
|
|
default=[])
|
|
self.repo_bookmarks = self.metadata.get('repo_bookmarks',
|
|
default=[])
|
|
self.media_bookmarks = self.metadata.get('media_bookmarks',
|
|
default=[])
|
|
self.place_bookmarks = self.metadata.get('place_bookmarks',
|
|
default=[])
|
|
# Custom type values
|
|
self.family_event_names = set(self.metadata.get('fevent_names',
|
|
default=[]))
|
|
self.individual_event_names = set(self.metadata.get('pevent_names',
|
|
default=[]))
|
|
self.family_attributes = set(self.metadata.get('fattr_names',
|
|
default=[]))
|
|
self.individual_attributes = set(self.metadata.get('pattr_names',
|
|
default=[]))
|
|
self.marker_names = set(self.metadata.get('marker_names',default=[]))
|
|
self.child_ref_types = set(self.metadata.get('child_refs',
|
|
default=[]))
|
|
self.family_rel_types = set(self.metadata.get('family_rels',
|
|
default=[]))
|
|
self.event_role_names = set(self.metadata.get('event_roles',
|
|
default=[]))
|
|
self.name_types = set(self.metadata.get('name_types',default=[]))
|
|
self.repository_types = set(self.metadata.get('repo_types',
|
|
default=[]))
|
|
self.source_media_types = set(self.metadata.get('sm_types',
|
|
default=[]))
|
|
self.url_types = set(self.metadata.get('url_types',default=[]))
|
|
self.media_attributes = set(self.metadata.get('mattr_names',
|
|
default=[]))
|
|
|
|
def connect_secondary(self):
|
|
"""
|
|
This method connects or creates secondary index tables.
|
|
It assumes that the tables either exist and are in the right
|
|
format or do not exist (in which case they get created).
|
|
|
|
It is the responsibility of upgrade code to either create
|
|
or remove invalid secondary index tables.
|
|
"""
|
|
|
|
# index tables used just for speeding up searches
|
|
if self.readonly:
|
|
table_flags = db.DB_RDONLY
|
|
else:
|
|
table_flags = self.open_flags()
|
|
|
|
self.surnames = db.DB(self.env)
|
|
self.surnames.set_flags(db.DB_DUP|db.DB_DUPSORT)
|
|
self.surnames.open(self.full_name, "surnames", db.DB_BTREE,
|
|
flags=table_flags)
|
|
|
|
self.name_group = db.DB(self.env)
|
|
self.name_group.set_flags(db.DB_DUP)
|
|
self.name_group.open(self.full_name, "name_group",
|
|
db.DB_HASH, flags=table_flags)
|
|
|
|
self.id_trans = db.DB(self.env)
|
|
self.id_trans.set_flags(db.DB_DUP)
|
|
self.id_trans.open(self.full_name, "idtrans",
|
|
db.DB_HASH, flags=table_flags)
|
|
|
|
self.fid_trans = db.DB(self.env)
|
|
self.fid_trans.set_flags(db.DB_DUP)
|
|
self.fid_trans.open(self.full_name, "fidtrans",
|
|
db.DB_HASH, flags=table_flags)
|
|
|
|
self.eid_trans = db.DB(self.env)
|
|
self.eid_trans.set_flags(db.DB_DUP)
|
|
self.eid_trans.open(self.full_name, "eidtrans",
|
|
db.DB_HASH, flags=table_flags)
|
|
|
|
self.pid_trans = db.DB(self.env)
|
|
self.pid_trans.set_flags(db.DB_DUP)
|
|
self.pid_trans.open(self.full_name, "pidtrans",
|
|
db.DB_HASH, flags=table_flags)
|
|
|
|
self.sid_trans = db.DB(self.env)
|
|
self.sid_trans.set_flags(db.DB_DUP)
|
|
self.sid_trans.open(self.full_name, "sidtrans",
|
|
db.DB_HASH, flags=table_flags)
|
|
|
|
self.oid_trans = db.DB(self.env)
|
|
self.oid_trans.set_flags(db.DB_DUP)
|
|
self.oid_trans.open(self.full_name, "oidtrans",
|
|
db.DB_HASH, flags=table_flags)
|
|
|
|
self.rid_trans = db.DB(self.env)
|
|
self.rid_trans.set_flags(db.DB_DUP)
|
|
self.rid_trans.open(self.full_name, "ridtrans",
|
|
db.DB_HASH, flags=table_flags)
|
|
|
|
self.reference_map_primary_map = db.DB(self.env)
|
|
self.reference_map_primary_map.set_flags(db.DB_DUP)
|
|
self.reference_map_primary_map.open(self.full_name,
|
|
"reference_map_primary_map",
|
|
db.DB_BTREE, flags=table_flags)
|
|
|
|
self.reference_map_referenced_map = db.DB(self.env)
|
|
self.reference_map_referenced_map.set_flags(db.DB_DUP|db.DB_DUPSORT)
|
|
self.reference_map_referenced_map.open(self.full_name,
|
|
"reference_map_referenced_map",
|
|
db.DB_BTREE, flags=table_flags)
|
|
|
|
if not self.readonly:
|
|
self.person_map.associate(self.surnames, find_surname, table_flags)
|
|
self.person_map.associate(self.id_trans, find_idmap, table_flags)
|
|
self.family_map.associate(self.fid_trans,find_idmap, table_flags)
|
|
self.event_map.associate(self.eid_trans, find_idmap, table_flags)
|
|
self.repository_map.associate(self.rid_trans, find_idmap,
|
|
table_flags)
|
|
self.place_map.associate(self.pid_trans, find_idmap, table_flags)
|
|
self.media_map.associate(self.oid_trans, find_idmap, table_flags)
|
|
self.source_map.associate(self.sid_trans, find_idmap, table_flags)
|
|
self.reference_map.associate(self.reference_map_primary_map,
|
|
find_primary_handle,
|
|
table_flags)
|
|
self.reference_map.associate(self.reference_map_referenced_map,
|
|
find_referenced_handle,
|
|
table_flags)
|
|
self.secondary_connected = True
|
|
|
|
|
|
def rebuild_secondary(self,callback=None):
|
|
if self.readonly:
|
|
return
|
|
|
|
table_flags = self.open_flags()
|
|
|
|
# remove existing secondary indices
|
|
self.id_trans.close()
|
|
junk = db.DB(self.env)
|
|
junk.remove(self.full_name,"idtrans")
|
|
|
|
self.surnames.close()
|
|
junk = db.DB(self.env)
|
|
junk.remove(self.full_name,"surnames")
|
|
|
|
# Repair secondary indices related to family_map
|
|
self.fid_trans.close()
|
|
junk = db.DB(self.env)
|
|
junk.remove(self.full_name,"fidtrans")
|
|
|
|
# Repair secondary indices related to place_map
|
|
self.pid_trans.close()
|
|
junk = db.DB(self.env)
|
|
junk.remove(self.full_name,"pidtrans")
|
|
|
|
# Repair secondary indices related to media_map
|
|
self.oid_trans.close()
|
|
junk = db.DB(self.env)
|
|
junk.remove(self.full_name,"oidtrans")
|
|
|
|
# Repair secondary indices related to source_map
|
|
self.sid_trans.close()
|
|
junk = db.DB(self.env)
|
|
junk.remove(self.full_name,"sidtrans")
|
|
|
|
# Repair secondary indices related to event_map
|
|
self.eid_trans.close()
|
|
junk = db.DB(self.env)
|
|
junk.remove(self.full_name,"eidtrans")
|
|
|
|
# Repair secondary indices related to repository_map
|
|
self.rid_trans.close()
|
|
junk = db.DB(self.env)
|
|
junk.remove(self.full_name,"ridtrans")
|
|
|
|
# Repair secondary indices related to reference_map
|
|
self.reference_map_primary_map.close()
|
|
junk = db.DB(self.env)
|
|
junk.remove(self.full_name,"reference_map_primary_map")
|
|
|
|
self.reference_map_referenced_map.close()
|
|
junk = db.DB(self.env)
|
|
junk.remove(self.full_name,"reference_map_referenced_map")
|
|
|
|
# Set flag saying that we have removed secondary indices
|
|
# and then call the creating routine
|
|
self.secondary_connected = False
|
|
self.connect_secondary()
|
|
|
|
def find_backlink_handles(self, handle, include_classes=None):
|
|
"""
|
|
Find all objects that hold a reference to the object handle.
|
|
Returns an interator over a list of (class_name,handle) tuples.
|
|
|
|
@param handle: handle of the object to search for.
|
|
@type handle: database handle
|
|
@param include_classes: list of class names to include in the results.
|
|
Default: None means include all classes.
|
|
@type include_classes: list of class names
|
|
|
|
Note that this is a generator function, it returns a iterator for
|
|
use in loops. If you want a list of the results use:
|
|
|
|
result_list = [i for i in find_backlink_handles(handle)]
|
|
"""
|
|
|
|
|
|
# Use the secondary index to locate all the reference_map entries
|
|
# that include a reference to the object we are looking for.
|
|
referenced_cur = self.get_reference_map_referenced_cursor()
|
|
|
|
try:
|
|
ret = referenced_cur.set(handle)
|
|
except:
|
|
ret = None
|
|
|
|
while (ret is not None):
|
|
(key,data) = ret
|
|
|
|
# data values are of the form:
|
|
# ((primary_object_class_name, primary_object_handle),
|
|
# (referenced_object_class_name, referenced_object_handle))
|
|
# so we need the first tuple to give us the type to compare
|
|
|
|
### FIXME: this is a dirty hack that works without no
|
|
### sensible explanation. For some reason, for a readonly
|
|
### database, secondary index returns a primary table key
|
|
### corresponding to the data, not the data.
|
|
if self.readonly:
|
|
data = self.reference_map.get(data)
|
|
else:
|
|
data = pickle.loads(data)
|
|
if include_classes == None or \
|
|
KEY_TO_CLASS_MAP[data[0][0]] in include_classes:
|
|
yield (KEY_TO_CLASS_MAP[data[0][0]],data[0][1])
|
|
|
|
ret = referenced_cur.next_dup()
|
|
|
|
referenced_cur.close()
|
|
|
|
return
|
|
|
|
def _delete_primary_from_reference_map(self,handle,transaction,txn=None):
|
|
"""
|
|
Remove all references to the primary object from the reference_map.
|
|
"""
|
|
|
|
primary_cur = self.get_reference_map_primary_cursor()
|
|
|
|
try:
|
|
ret = primary_cur.set(handle)
|
|
except:
|
|
ret = None
|
|
|
|
remove_list = set()
|
|
while (ret is not None):
|
|
(key,data) = ret
|
|
|
|
# data values are of the form:
|
|
# ((primary_object_class_name, primary_object_handle),
|
|
# (referenced_object_class_name, referenced_object_handle))
|
|
|
|
# so we need the second tuple give us a reference that we can
|
|
# combine with the primary_handle to get the main key.
|
|
|
|
main_key = (handle, pickle.loads(data)[1][1])
|
|
|
|
# The trick is not to remove while inside the cursor,
|
|
# but collect them all and remove after the cursor is closed
|
|
remove_list.add(main_key)
|
|
|
|
ret = primary_cur.next_dup()
|
|
|
|
primary_cur.close()
|
|
|
|
# Now that the cursor is closed, we can remove things
|
|
for main_key in remove_list:
|
|
self._remove_reference(main_key,transaction,txn)
|
|
|
|
def _update_reference_map(self, obj, transaction, txn=None):
|
|
"""
|
|
If txn is given, then changes are written right away using txn.
|
|
"""
|
|
|
|
# Add references to the reference_map for all primary object referenced
|
|
# from the primary object 'obj' or any of its secondary objects.
|
|
|
|
handle = obj.handle
|
|
update = self.reference_map_primary_map.has_key(str(handle))
|
|
|
|
if update:
|
|
# First thing to do is get hold of all rows in the reference_map
|
|
# table that hold a reference from this primary obj. This means
|
|
# finding all the rows that have this handle somewhere in the
|
|
# list of (class_name,handle) pairs.
|
|
# The primary_map sec index allows us to look this up quickly.
|
|
|
|
existing_references = set()
|
|
|
|
primary_cur = self.get_reference_map_primary_cursor()
|
|
|
|
try:
|
|
ret = primary_cur.set(handle)
|
|
except:
|
|
ret = None
|
|
|
|
while (ret is not None):
|
|
(key,data) = ret
|
|
|
|
# data values are of the form:
|
|
# ((primary_object_class_name, primary_object_handle),
|
|
# (referenced_object_class_name, referenced_object_handle))
|
|
# so we need the second tuple give us a reference that we can
|
|
# compare with what is returned from
|
|
# get_referenced_handles_recursively
|
|
|
|
# secondary DBs are not DBShelf's, so we need to do pickling
|
|
# and unpicking ourselves here
|
|
existing_reference = pickle.loads(data)[1]
|
|
existing_references.add(
|
|
(KEY_TO_CLASS_MAP[existing_reference[0]],
|
|
existing_reference[1]))
|
|
ret = primary_cur.next_dup()
|
|
|
|
primary_cur.close()
|
|
|
|
# Once we have the list of rows that already have a reference
|
|
# we need to compare it with the list of objects that are
|
|
# still references from the primary object.
|
|
|
|
current_references = set(obj.get_referenced_handles_recursively())
|
|
|
|
no_longer_required_references = existing_references.difference(
|
|
current_references)
|
|
|
|
new_references = current_references.difference(existing_references)
|
|
|
|
else:
|
|
# No existing refs are found:
|
|
# all we have is new, nothing to remove
|
|
no_longer_required_references = set()
|
|
new_references = set(obj.get_referenced_handles_recursively())
|
|
|
|
# handle addition of new references
|
|
for (ref_class_name,ref_handle) in new_references:
|
|
data = ((CLASS_TO_KEY_MAP[obj.__class__.__name__],handle),
|
|
(CLASS_TO_KEY_MAP[ref_class_name],ref_handle),)
|
|
self._add_reference((handle,ref_handle),data,transaction,txn)
|
|
|
|
# handle deletion of old references
|
|
for (ref_class_name,ref_handle) in no_longer_required_references:
|
|
try:
|
|
self._remove_reference((handle,ref_handle),transaction,txn)
|
|
except:
|
|
# ignore missing old reference
|
|
pass
|
|
|
|
def _remove_reference(self,key,transaction,txn=None):
|
|
"""
|
|
Removes the reference specified by the key,
|
|
preserving the change in the passed transaction.
|
|
"""
|
|
if not self.readonly:
|
|
if transaction.batch:
|
|
self.reference_map.delete(str(key),txn=txn)
|
|
if not self.UseTXN:
|
|
self.reference_map.sync()
|
|
else:
|
|
old_data = self.reference_map.get(str(key),txn=self.txn)
|
|
transaction.add(REFERENCE_KEY,str(key),old_data,None)
|
|
transaction.reference_del.append(str(key))
|
|
|
|
def _add_reference(self,key,data,transaction,txn=None):
|
|
"""
|
|
Adds the reference specified by the key and the data,
|
|
preserving the change in the passed transaction.
|
|
"""
|
|
|
|
if self.readonly or not key:
|
|
return
|
|
|
|
if transaction.batch:
|
|
self.reference_map.put(str(key),data,txn=txn)
|
|
if not self.UseTXN:
|
|
self.reference_map.sync()
|
|
else:
|
|
transaction.add(REFERENCE_KEY,str(key),None,data)
|
|
transaction.reference_add.append((str(key),data))
|
|
|
|
def reindex_reference_map(self):
|
|
"""
|
|
Reindex all primary records in the database.
|
|
This will be a slow process for large databases.
|
|
|
|
At present this method does not clear the reference_map before it
|
|
reindexes. This is fine when if reindex is run to index new content
|
|
or when upgrading from a non-reference_map version of the database.
|
|
But it might be a problem if reindex is used to repair a broken index
|
|
because any references to primary objects that are no longer in the
|
|
database will remain in the reference_map index.
|
|
|
|
So if you want to reindex for repair purposes you need to
|
|
clear the reference_map first.
|
|
"""
|
|
|
|
|
|
# Make a dictionary of the functions and classes that we need for
|
|
# each of the primary object tables.
|
|
primary_tables = {
|
|
'Person': {'cursor_func': self.get_person_cursor,
|
|
'class_func': Person},
|
|
'Family': {'cursor_func': self.get_family_cursor,
|
|
'class_func': Family},
|
|
'Event': {'cursor_func': self.get_event_cursor,
|
|
'class_func': Event},
|
|
'Place': {'cursor_func': self.get_place_cursor,
|
|
'class_func': Place},
|
|
'Source': {'cursor_func': self.get_source_cursor,
|
|
'class_func': Source},
|
|
'MediaObject': {'cursor_func': self.get_media_cursor,
|
|
'class_func': MediaObject},
|
|
'Repository': {'cursor_func': self.get_repository_cursor,
|
|
'class_func': Repository},
|
|
}
|
|
|
|
# Now we use the functions and classes defined above
|
|
# to loop through each of the primary object tables.
|
|
for primary_table_name in primary_tables.keys():
|
|
|
|
cursor = primary_tables[primary_table_name]['cursor_func']()
|
|
data = cursor.first()
|
|
|
|
# Grab the real object class here so that the lookup does
|
|
# not happen inside the cursor loop.
|
|
class_func = primary_tables[primary_table_name]['class_func']
|
|
|
|
while data:
|
|
found_handle,val = data
|
|
obj = class_func()
|
|
obj.unserialize(val)
|
|
|
|
self._update_reference_map(obj,transaction)
|
|
|
|
data = cursor.next()
|
|
|
|
cursor.close()
|
|
|
|
return
|
|
|
|
def _close_metadata(self):
|
|
if not self.readonly:
|
|
if self.UseTXN:
|
|
# Start transaction if needed
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
|
|
# name display formats
|
|
self.metadata.put('name_formats',self.name_formats,txn=the_txn)
|
|
# bookmarks
|
|
self.metadata.put('bookmarks',self.bookmarks,txn=the_txn)
|
|
self.metadata.put('family_bookmarks',self.family_bookmarks,
|
|
txn=the_txn)
|
|
self.metadata.put('event_bookmarks',self.event_bookmarks,
|
|
txn=the_txn)
|
|
self.metadata.put('source_bookmarks',self.source_bookmarks,
|
|
txn=the_txn)
|
|
self.metadata.put('place_bookmarks',self.place_bookmarks,
|
|
txn=the_txn)
|
|
self.metadata.put('repo_bookmarks',self.repo_bookmarks,txn=the_txn)
|
|
self.metadata.put('media_bookmarks',self.media_bookmarks,
|
|
txn=the_txn)
|
|
# gender stats
|
|
self.metadata.put('gender_stats',self.genderStats.save_stats(),
|
|
txn=the_txn)
|
|
# Custom type values
|
|
self.metadata.put('fevent_names',list(self.family_event_names),
|
|
txn=the_txn)
|
|
self.metadata.put('pevent_names',list(self.individual_event_names),
|
|
txn=the_txn)
|
|
self.metadata.put('fattr_names',list(self.family_attributes),
|
|
txn=the_txn)
|
|
self.metadata.put('pattr_names',list(self.individual_attributes),
|
|
txn=the_txn)
|
|
self.metadata.put('marker_names',list(self.marker_names),
|
|
txn=the_txn)
|
|
self.metadata.put('child_refs',list(self.child_ref_types),
|
|
txn=the_txn)
|
|
self.metadata.put('family_rels',list(self.family_rel_types),
|
|
txn=the_txn)
|
|
self.metadata.put('event_roles',list(self.event_role_names),
|
|
txn=the_txn)
|
|
self.metadata.put('name_types',list(self.name_types),
|
|
txn=the_txn)
|
|
self.metadata.put('repo_types',list(self.repository_types),
|
|
txn=the_txn)
|
|
self.metadata.put('sm_types',list(self.source_media_types),
|
|
txn=the_txn)
|
|
self.metadata.put('url_types',list(self.url_types),
|
|
txn=the_txn)
|
|
self.metadata.put('mattr_names',list(self.media_attributes),
|
|
txn=the_txn)
|
|
if self.UseTXN:
|
|
the_txn.commit()
|
|
else:
|
|
self.metadata.sync()
|
|
|
|
self.metadata.close()
|
|
|
|
def _close_early(self):
|
|
"""
|
|
Bail out if the incompatible version is discovered:
|
|
* close cleanly to not damage data/env
|
|
* raise exception
|
|
"""
|
|
self.metadata.close()
|
|
self.env.close()
|
|
self.metadata = None
|
|
self.env = None
|
|
self.db_is_open = False
|
|
raise Errors.FileVersionError(
|
|
"The database version is not supported by this "
|
|
"version of GRAMPS.\nPlease upgrade to the "
|
|
"corresponding version or use XML for porting"
|
|
"data between different database versions.")
|
|
|
|
def close(self):
|
|
if not self.db_is_open:
|
|
return
|
|
|
|
if self.UseTXN:
|
|
self.env.txn_checkpoint()
|
|
|
|
self._close_metadata()
|
|
self.name_group.close()
|
|
self.surnames.close()
|
|
self.id_trans.close()
|
|
self.fid_trans.close()
|
|
self.eid_trans.close()
|
|
self.rid_trans.close()
|
|
self.oid_trans.close()
|
|
self.sid_trans.close()
|
|
self.pid_trans.close()
|
|
self.reference_map_primary_map.close()
|
|
self.reference_map_referenced_map.close()
|
|
self.reference_map.close()
|
|
|
|
# primary databases must be closed after secondary indexes, or
|
|
# we run into problems with any active cursors.
|
|
self.person_map.close()
|
|
self.family_map.close()
|
|
self.repository_map.close()
|
|
self.place_map.close()
|
|
self.source_map.close()
|
|
self.media_map.close()
|
|
self.event_map.close()
|
|
|
|
# Attempt to clear log sequence numbers, to make database portable
|
|
# This will only work for python2.5 and higher
|
|
try:
|
|
self.env.lsn_reset(self.full_name)
|
|
except AttributeError:
|
|
pass
|
|
|
|
self.env.close()
|
|
|
|
self.close_undodb()
|
|
|
|
self.person_map = None
|
|
self.family_map = None
|
|
self.repository_map = None
|
|
self.place_map = None
|
|
self.source_map = None
|
|
self.media_map = None
|
|
self.event_map = None
|
|
self.surnames = None
|
|
self.env = None
|
|
self.metadata = None
|
|
self.db_is_open = False
|
|
|
|
def _do_remove_object(self,handle,transaction,data_map,key,del_list):
|
|
if self.readonly or not handle:
|
|
return
|
|
|
|
handle = str(handle)
|
|
if transaction.batch:
|
|
if self.UseTXN:
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
self._delete_primary_from_reference_map(handle,transaction,
|
|
txn=the_txn)
|
|
data_map.delete(handle,txn=the_txn)
|
|
if not self.UseTXN:
|
|
data_map.sync()
|
|
if the_txn:
|
|
the_txn.commit()
|
|
else:
|
|
self._delete_primary_from_reference_map(handle,transaction)
|
|
old_data = data_map.get(handle,txn=self.txn)
|
|
transaction.add(key,handle,old_data,None)
|
|
del_list.append(handle)
|
|
|
|
def _del_person(self,handle):
|
|
self.person_map.delete(str(handle),txn=self.txn)
|
|
if not self.UseTXN:
|
|
self.person_map.sync()
|
|
|
|
def _del_source(self,handle):
|
|
self.source_map.delete(str(handle),txn=self.txn)
|
|
if not self.UseTXN:
|
|
self.source_map.sync()
|
|
|
|
def _del_repository(self,handle):
|
|
self.repository_map.delete(str(handle),txn=self.txn)
|
|
if not self.UseTXN:
|
|
self.repository_map.sync()
|
|
|
|
def _del_place(self,handle):
|
|
self.place_map.delete(str(handle),txn=self.txn)
|
|
if not self.UseTXN:
|
|
self.place_map.sync()
|
|
|
|
def _del_media(self,handle):
|
|
self.media_map.delete(str(handle),txn=self.txn)
|
|
if not self.UseTXN:
|
|
self.media_map.sync()
|
|
|
|
def _del_family(self,handle):
|
|
self.family_map.delete(str(handle),txn=self.txn)
|
|
if not self.UseTXN:
|
|
self.family_map.sync()
|
|
|
|
def _del_event(self,handle):
|
|
self.event_map.delete(str(handle),txn=self.txn)
|
|
if not self.UseTXN:
|
|
self.event_map.sync()
|
|
|
|
def set_name_group_mapping(self,name,group):
|
|
if not self.readonly:
|
|
if self.UseTXN:
|
|
# Start transaction if needed
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
name = str(name)
|
|
data = self.name_group.get(name,txn=the_txn)
|
|
if not group and data:
|
|
self.name_group.delete(name,txn=the_txn)
|
|
else:
|
|
self.name_group.put(name,group,txn=the_txn)
|
|
if self.UseTXN:
|
|
the_txn.commit()
|
|
else:
|
|
self.name_group.sync()
|
|
self.emit('person-rebuild')
|
|
|
|
def get_surname_list(self):
|
|
vals = [ (locale.strxfrm(unicode(val)),unicode(val))
|
|
for val in set(self.surnames.keys()) ]
|
|
vals.sort()
|
|
return [item[1] for item in vals]
|
|
|
|
def _get_obj_from_gramps_id(self,val,tbl,class_init,prim_tbl):
|
|
if tbl.has_key(str(val)):
|
|
data = tbl.get(str(val),txn=self.txn)
|
|
obj = class_init()
|
|
### FIXME: this is a dirty hack that works without no
|
|
### sensible explanation. For some reason, for a readonly
|
|
### database, secondary index returns a primary table key
|
|
### corresponding to the data, not the data.
|
|
if self.readonly:
|
|
tuple_data = prim_tbl.get(data,txn=self.txn)
|
|
else:
|
|
tuple_data = pickle.loads(data)
|
|
obj.unserialize(tuple_data)
|
|
return obj
|
|
else:
|
|
return None
|
|
|
|
def get_person_from_gramps_id(self,val):
|
|
"""
|
|
Finds a Person in the database from the passed gramps' ID.
|
|
If no such Person exists, None is returned.
|
|
"""
|
|
return self._get_obj_from_gramps_id(val,self.id_trans,Person,
|
|
self.person_map)
|
|
|
|
def get_family_from_gramps_id(self,val):
|
|
"""
|
|
Finds a Family in the database from the passed gramps' ID.
|
|
If no such Family exists, None is return.
|
|
"""
|
|
return self._get_obj_from_gramps_id(val,self.fid_trans,Family,
|
|
self.family_map)
|
|
|
|
def get_event_from_gramps_id(self,val):
|
|
"""
|
|
Finds an Event in the database from the passed gramps' ID.
|
|
If no such Family exists, None is returned.
|
|
"""
|
|
return self._get_obj_from_gramps_id(val,self.eid_trans,Event,
|
|
self.event_map)
|
|
|
|
def get_place_from_gramps_id(self,val):
|
|
"""
|
|
Finds a Place in the database from the passed gramps' ID.
|
|
If no such Place exists, None is returned.
|
|
"""
|
|
return self._get_obj_from_gramps_id(val,self.pid_trans,Place,
|
|
self.place_map)
|
|
|
|
def get_source_from_gramps_id(self,val):
|
|
"""
|
|
Finds a Source in the database from the passed gramps' ID.
|
|
If no such Source exists, None is returned.
|
|
"""
|
|
return self._get_obj_from_gramps_id(val,self.sid_trans,Source,
|
|
self.source_map)
|
|
|
|
def get_object_from_gramps_id(self,val):
|
|
"""
|
|
Finds a MediaObject in the database from the passed gramps' ID.
|
|
If no such MediaObject exists, None is returned.
|
|
"""
|
|
return self._get_obj_from_gramps_id(val,self.oid_trans,MediaObject,
|
|
self.media_map)
|
|
|
|
def get_repository_from_gramps_id(self,val):
|
|
"""
|
|
Finds a Repository in the database from the passed gramps' ID.
|
|
If no such MediaObject exists, None is returned.
|
|
"""
|
|
return self._get_obj_from_gramps_id(val,self.rid_trans,Repository,
|
|
self.repository_map)
|
|
|
|
def _commit_base(self, obj, data_map, key, update_list, add_list,
|
|
transaction, change_time):
|
|
"""
|
|
Commits the specified object to the database, storing the changes
|
|
as part of the transaction.
|
|
"""
|
|
if self.readonly or not obj or not obj.handle:
|
|
return
|
|
|
|
if change_time:
|
|
obj.change = int(change_time)
|
|
else:
|
|
obj.change = int(time.time())
|
|
handle = str(obj.handle)
|
|
|
|
if transaction.batch:
|
|
if self.UseTXN:
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
self._update_reference_map(obj,transaction,txn=the_txn)
|
|
data_map.put(handle,obj.serialize(),txn=the_txn)
|
|
if not self.UseTXN:
|
|
data_map.sync()
|
|
if the_txn:
|
|
the_txn.commit()
|
|
old_data = None
|
|
else:
|
|
self._update_reference_map(obj,transaction)
|
|
old_data = data_map.get(handle,txn=self.txn)
|
|
new_data = obj.serialize()
|
|
transaction.add(key,handle,old_data,new_data)
|
|
if old_data:
|
|
update_list.append((handle,new_data))
|
|
else:
|
|
add_list.append((handle,new_data))
|
|
return old_data
|
|
|
|
def _do_commit(self,add_list,db_map):
|
|
retlist = []
|
|
for (handle,data) in add_list:
|
|
db_map.put(handle,data,self.txn)
|
|
if not self.UseTXN:
|
|
db_map.sync()
|
|
retlist.append(str(handle))
|
|
return retlist
|
|
|
|
def _get_from_handle(self, handle, class_type, data_map):
|
|
try:
|
|
data = data_map.get(str(handle),txn=self.txn)
|
|
except:
|
|
data = None
|
|
# under certain circumstances during a database reload,
|
|
# data_map can be none. If so, then don't report an error
|
|
if data_map:
|
|
log.error("Failed to get from handle",exc_info=True)
|
|
if data:
|
|
newobj = class_type()
|
|
newobj.unserialize(data)
|
|
return newobj
|
|
return None
|
|
|
|
def _find_from_handle(self,handle,transaction,class_type,dmap,add_func):
|
|
obj = class_type()
|
|
handle = str(handle)
|
|
if dmap.has_key(handle):
|
|
data = dmap.get(handle,txn=self.txn)
|
|
obj.unserialize(data)
|
|
else:
|
|
obj.set_handle(handle)
|
|
add_func(obj,transaction)
|
|
return obj
|
|
|
|
def transaction_begin(self,msg="",batch=False,no_magic=False):
|
|
"""
|
|
Creates a new Transaction tied to the current UNDO database. The
|
|
transaction has no effect until it is committed using the
|
|
transaction_commit function of the this database object.
|
|
"""
|
|
|
|
if batch:
|
|
# A batch transaction does not store the commits
|
|
# Aborting the session completely will become impossible.
|
|
self.abort_possible = False
|
|
# Undo is also impossible after batch transaction
|
|
self.undoindex = -1
|
|
self.translist = [None] * len(self.translist)
|
|
transaction = BdbTransaction(msg,self.undodb,batch,no_magic)
|
|
if transaction.batch:
|
|
if self.UseTXN:
|
|
self.env.txn_checkpoint()
|
|
self.env.set_flags(db.DB_TXN_NOSYNC,1) # async txn
|
|
|
|
if self.secondary_connected and not transaction.no_magic:
|
|
# Disconnect unneeded secondary indices
|
|
self.surnames.close()
|
|
junk = db.DB(self.env)
|
|
junk.remove(self.full_name,"surnames")
|
|
|
|
self.reference_map_referenced_map.close()
|
|
junk = db.DB(self.env)
|
|
junk.remove(self.full_name,"reference_map_referenced_map")
|
|
|
|
return transaction
|
|
|
|
def transaction_commit(self,transaction,msg):
|
|
|
|
# Start BSD DB transaction -- DBTxn
|
|
if self.UseTXN:
|
|
self.txn = self.env.txn_begin()
|
|
else:
|
|
self.txn = None
|
|
|
|
GrampsDbBase.transaction_commit(self,transaction,msg)
|
|
|
|
for (key,data) in transaction.reference_add:
|
|
self.reference_map.put(str(key),data,txn=self.txn)
|
|
|
|
for key in transaction.reference_del:
|
|
self.reference_map.delete(str(key),txn=self.txn)
|
|
|
|
if (len(transaction.reference_add)+len(transaction.reference_del)) > 0\
|
|
and not self.UseTXN:
|
|
self.reference_map.sync()
|
|
|
|
# Commit BSD DB transaction -- DBTxn
|
|
if self.UseTXN:
|
|
self.txn.commit()
|
|
if transaction.batch:
|
|
if self.UseTXN:
|
|
self.env.txn_checkpoint()
|
|
self.env.set_flags(db.DB_TXN_NOSYNC,0) # sync txn
|
|
|
|
if not transaction.no_magic:
|
|
# create new secondary indices to replace the ones removed
|
|
open_flags = self.open_flags()
|
|
dupe_flags = db.DB_DUP|db.DB_DUPSORT
|
|
|
|
self.surnames = db.DB(self.env)
|
|
self.surnames.set_flags(dupe_flags)
|
|
self.surnames.open(self.full_name,"surnames",
|
|
db.DB_BTREE,flags=open_flags)
|
|
self.person_map.associate(self.surnames,find_surname,
|
|
open_flags)
|
|
|
|
self.reference_map_referenced_map = db.DB(self.env)
|
|
self.reference_map_referenced_map.set_flags(dupe_flags)
|
|
self.reference_map_referenced_map.open(
|
|
self.full_name,"reference_map_referenced_map",
|
|
db.DB_BTREE,flags=open_flags)
|
|
self.reference_map.associate(self.reference_map_referenced_map,
|
|
find_referenced_handle,open_flags)
|
|
self.txn = None
|
|
|
|
def undo(self,update_history=True):
|
|
print "Undoing it"
|
|
if self.UseTXN:
|
|
self.txn = self.env.txn_begin()
|
|
status = GrampsDbBase.undo(self,update_history)
|
|
if self.UseTXN:
|
|
if status:
|
|
self.txn.commit()
|
|
else:
|
|
self.txn.abort()
|
|
self.txn = None
|
|
return status
|
|
|
|
def redo(self,update_history=True):
|
|
print "Redoing it"
|
|
if self.UseTXN:
|
|
self.txn = self.env.txn_begin()
|
|
status = GrampsDbBase.redo(self,update_history)
|
|
if self.UseTXN:
|
|
if status:
|
|
self.txn.commit()
|
|
else:
|
|
self.txn.abort()
|
|
self.txn = None
|
|
return status
|
|
|
|
def undo_reference(self,data,handle):
|
|
if data == None:
|
|
self.reference_map.delete(handle,txn=self.txn)
|
|
else:
|
|
self.reference_map.put(handle,data,txn=self.txn)
|
|
|
|
def undo_data(self,data,handle,db_map,signal_root):
|
|
if data == None:
|
|
self.emit(signal_root + '-delete',([handle],))
|
|
db_map.delete(handle,txn=self.txn)
|
|
else:
|
|
ex_data = db_map.get(handle,txn=self.txn)
|
|
if ex_data:
|
|
signal = signal_root + '-update'
|
|
else:
|
|
signal = signal_root + '-add'
|
|
db_map.put(handle,data,txn=self.txn)
|
|
self.emit(signal,([handle],))
|
|
|
|
def gramps_upgrade(self,callback=None):
|
|
UpdateCallback.__init__(self,callback)
|
|
|
|
child_rel_notrans = [
|
|
"None", "Birth", "Adopted", "Stepchild",
|
|
"Sponsored", "Foster", "Unknown", "Other", ]
|
|
|
|
version = self.metadata.get('version',default=_MINVERSION)
|
|
|
|
t = time.time()
|
|
if version < 6:
|
|
self.gramps_upgrade_6()
|
|
if version < 7:
|
|
self.gramps_upgrade_7()
|
|
if version < 8:
|
|
self.gramps_upgrade_8()
|
|
if version < 9:
|
|
self.gramps_upgrade_9()
|
|
elif version < 10:
|
|
self.gramps_upgrade_10()
|
|
elif version < 11:
|
|
self.gramps_upgrade_11()
|
|
print "Upgrade time:", int(time.time()-t), "seconds"
|
|
|
|
def gramps_upgrade_6(self):
|
|
print "Upgrading to DB version 6"
|
|
order = []
|
|
for val in self.get_media_column_order():
|
|
if val[1] != 6:
|
|
order.append(val)
|
|
self.set_media_column_order(order)
|
|
if self.UseTXN:
|
|
# Start transaction if needed
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
self.metadata.put('version',6,txn=the_txn)
|
|
if self.UseTXN:
|
|
the_txn.commit()
|
|
else:
|
|
self.metadata.sync()
|
|
|
|
def gramps_upgrade_7(self):
|
|
print "Upgrading to DB version 7"
|
|
|
|
self.genderStats = GenderStats()
|
|
cursor = self.get_person_cursor()
|
|
data = cursor.first()
|
|
while data:
|
|
handle,val = data
|
|
p = Person(val)
|
|
self.genderStats.count_person(p)
|
|
data = cursor.next()
|
|
cursor.close()
|
|
if self.UseTXN:
|
|
# Start transaction if needed
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
self.metadata.put('version',7,txn=the_txn)
|
|
if self.UseTXN:
|
|
the_txn.commit()
|
|
else:
|
|
self.metadata.sync()
|
|
|
|
def gramps_upgrade_8(self):
|
|
print "Upgrading to DB version 8"
|
|
cursor = self.get_person_cursor()
|
|
data = cursor.first()
|
|
while data:
|
|
handle,val = data
|
|
handle_list = val[8]
|
|
if type(handle_list) == list:
|
|
# Check to prevent crash on corrupted data (event_list=None)
|
|
for handle in handle_list:
|
|
event = self.get_event_from_handle(handle)
|
|
self.individual_event_names.add(event.name)
|
|
data = cursor.next()
|
|
cursor.close()
|
|
|
|
cursor = self.get_family_cursor()
|
|
data = cursor.first()
|
|
while data:
|
|
handle,val = data
|
|
handle_list = val[6]
|
|
if type(handle_list) == list:
|
|
# Check to prevent crash on corrupted data (event_list=None)
|
|
for handle in handle_list:
|
|
event = self.get_event_from_handle(handle)
|
|
self.family_event_names.add(event.name)
|
|
data = cursor.next()
|
|
cursor.close()
|
|
if self.UseTXN:
|
|
# Start transaction if needed
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
self.metadata.put('version',8,txn=the_txn)
|
|
if self.UseTXN:
|
|
the_txn.commit()
|
|
else:
|
|
self.metadata.sync()
|
|
|
|
def gramps_upgrade_9(self):
|
|
print "Upgrading to DB version 10 -- this may take a while"
|
|
# The very very first thing is to check for duplicates in the
|
|
# primary tables and remove them.
|
|
self.set_total(7)
|
|
status,length = low_level_9(self,self.update)
|
|
self.reset()
|
|
|
|
self.set_total(length)
|
|
|
|
# Remove column metadata, since columns have changed.
|
|
# This will reset all columns to defaults
|
|
for name in (PERSON_COL_KEY,CHILD_COL_KEY,PLACE_COL_KEY,SOURCE_COL_KEY,
|
|
MEDIA_COL_KEY,EVENT_COL_KEY,FAMILY_COL_KEY):
|
|
try:
|
|
if self.UseTXN:
|
|
# Start transaction if needed
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
self.metadata.delete(name,txn=the_txn)
|
|
if self.UseTXN:
|
|
the_txn.commit()
|
|
else:
|
|
self.metadata.sync()
|
|
except KeyError:
|
|
if self.UseTXN:
|
|
the_txn.abort()
|
|
|
|
# Then we remove the surname secondary index table
|
|
# because its format changed from HASH to DUPSORTed BTREE.
|
|
junk = db.DB(self.env)
|
|
junk.remove(self.full_name,"surnames")
|
|
|
|
# Create one secondary index for reference_map
|
|
# because every commit will require this to exist
|
|
table_flags = self.open_flags()
|
|
self.reference_map_primary_map = db.DB(self.env)
|
|
self.reference_map_primary_map.set_flags(db.DB_DUP)
|
|
self.reference_map_primary_map.open(self.full_name,
|
|
"reference_map_primary_map",
|
|
db.DB_BTREE, flags=table_flags)
|
|
self.reference_map.associate(self.reference_map_primary_map,
|
|
find_primary_handle,
|
|
table_flags)
|
|
|
|
### Now we're ready to proceed with the normal upgrade.
|
|
# First, make sure the stored default person handle is str, not unicode
|
|
try:
|
|
if self.UseTXN:
|
|
# Start transaction if needed
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
handle = self.metadata.get('default',txn=the_txn)
|
|
self.metadata.put('default',str(handle),txn=the_txn)
|
|
if self.UseTXN:
|
|
the_txn.commit()
|
|
else:
|
|
self.metadata.sync()
|
|
except KeyError:
|
|
# default person was not stored in database
|
|
if self.UseTXN:
|
|
the_txn.abort()
|
|
|
|
# The rest of the upgrade deals with real data, not metadata
|
|
# so starting (batch) transaction here.
|
|
trans = self.transaction_begin("",True)
|
|
|
|
# Numerous changes were made between dbversions 8 and 9.
|
|
# If nothing else, we switched from storing pickled gramps classes
|
|
# to storing builtin objects, via running serialize() recursively
|
|
# until the very bottom.
|
|
# Every stored object needs to be re-committed here.
|
|
|
|
# Change every Source to have reporef_list
|
|
for handle in self.source_map.keys():
|
|
info = self.source_map[handle]
|
|
source = Source()
|
|
source.handle = handle
|
|
# We already have a new Source object with the reporef_list
|
|
# just fill in the rest of the fields for this source
|
|
(junk_handle, source.gramps_id, source.title, source.author,
|
|
source.pubinfo, source.note, source.media_list,
|
|
source.abbrev, source.change, source.datamap) = info
|
|
self.commit_source(source,trans)
|
|
self.update()
|
|
|
|
# Family upgrade
|
|
for handle in self.family_map.keys():
|
|
info = self.family_map[handle]
|
|
family = Family()
|
|
family.handle = handle
|
|
# Restore data from dbversion 8 (gramps 2.0.9)
|
|
(junk_handle, family.gramps_id, family.father_handle,
|
|
family.mother_handle, child_list, the_type,
|
|
event_list, family.media_list, family.attribute_list,
|
|
lds_seal, complete, family.source_list,
|
|
family.note, family.change) = info
|
|
|
|
if complete:
|
|
family.marker.set(MarkerType.COMPLETE)
|
|
|
|
# Change every event handle to the EventRef
|
|
for event_handle in event_list:
|
|
event_ref = EventRef()
|
|
event_ref.ref = event_handle
|
|
event_ref.role.set(EventRoleType.FAMILY)
|
|
family.event_ref_list.append(event_ref)
|
|
|
|
# Change child_list into child_ref_list
|
|
for child_handle in child_list:
|
|
child_ref = ChildRef()
|
|
child_ref.ref = child_handle
|
|
family.child_ref_list.append(child_ref)
|
|
|
|
# Change relationship type from int to tuple
|
|
family.type.set(the_type)
|
|
|
|
# In all Attributes, convert type from string to a tuple
|
|
for attribute in family.attribute_list:
|
|
convert_attribute_9(attribute)
|
|
|
|
# Cover attributes contained in MediaRefs
|
|
for media_ref in family.media_list:
|
|
convert_mediaref_9(media_ref)
|
|
|
|
# Switch from fixed lds ords to a list
|
|
if lds_seal:
|
|
lds_seal.type = LdsOrd.SEAL_TO_SPOUSE
|
|
lds_seal.private = False
|
|
lds_seal.status = lds_seal_spouse_dict_9[lds_seal.status]
|
|
family.lds_ord_list = [lds_seal]
|
|
|
|
self.commit_family(family,trans)
|
|
self.update()
|
|
|
|
# Person upgrade
|
|
# Needs to be run after the family upgrade completed.
|
|
def_rel = ChildRefType._DEFAULT
|
|
for handle in self.person_map.keys():
|
|
info = self.person_map[handle]
|
|
person = Person()
|
|
person.handle = handle
|
|
# Restore data from dbversion 8 (gramps 2.0.9--2.0.11)
|
|
(junk_handle, person.gramps_id, person.gender,
|
|
person.primary_name, person.alternate_names, nickname,
|
|
death_handle, birth_handle, event_list,
|
|
person.family_list, parent_family_list,
|
|
person.media_list, person.address_list, person.attribute_list,
|
|
person.urls, lds_bapt, lds_endow, lds_seal,
|
|
complete, person.source_list, person.note,
|
|
person.change, person.private) = (info + (False,))[0:23]
|
|
|
|
# Convert complete flag into marker
|
|
if complete:
|
|
person.marker.set(MarkerType.COMPLETE)
|
|
|
|
# Change every event handle to the EventRef
|
|
if birth_handle:
|
|
event_ref = EventRef()
|
|
event_ref.ref = birth_handle
|
|
person.event_ref_list.append(event_ref)
|
|
person.birth_ref_index = len(person.event_ref_list) - 1
|
|
|
|
if death_handle:
|
|
event_ref = EventRef()
|
|
event_ref.ref = death_handle
|
|
person.event_ref_list.append(event_ref)
|
|
person.death_ref_index = len(person.event_ref_list) - 1
|
|
|
|
for event_handle in event_list:
|
|
event_ref = EventRef()
|
|
event_ref.ref = event_handle
|
|
person.event_ref_list.append(event_ref)
|
|
|
|
# In all Name instances, convert type from string to a tuple
|
|
for name in [person.primary_name] + person.alternate_names:
|
|
old_type = name.type
|
|
new_type = NameType()
|
|
# Mapping "Other Name" from gramps 2.0.x to Unknown
|
|
if old_type == 'Other Name':
|
|
new_type.set(NameType.UNKNOWN)
|
|
else:
|
|
new_type.set_from_xml_str(old_type)
|
|
name.type = new_type
|
|
name.call = ''
|
|
|
|
# Change parent_family_list into list of handles
|
|
# and transfer the relationship info into the family's
|
|
# child_ref (in family.child_ref_list) as tuples.
|
|
for (family_handle,mrel,frel) in parent_family_list:
|
|
person.parent_family_list.append(family_handle)
|
|
# Only change family is the relations are non-default
|
|
if (mrel,frel) != (def_rel,def_rel):
|
|
family = self.get_family_from_handle(family_handle)
|
|
child_handle_list = [ref.ref for ref in
|
|
family.child_ref_list]
|
|
index = child_handle_list.index(person.handle)
|
|
child_ref = family.child_ref_list[index]
|
|
child_ref.frel.set(frel)
|
|
child_ref.mrel.set(mrel)
|
|
self.commit_family(family,trans)
|
|
|
|
# In all Attributes, convert type from string to a tuple
|
|
for attribute in person.attribute_list:
|
|
convert_attribute_9(attribute)
|
|
|
|
# Nickname becomes an attribute
|
|
if nickname.strip():
|
|
attr = Attribute()
|
|
attr.set_type(AttributeType.NICKNAME)
|
|
attr.set_value(nickname)
|
|
person.attribute_list.append(attr)
|
|
|
|
# Cover attributes contained in MediaRefs
|
|
for media_ref in person.media_list:
|
|
convert_mediaref_9(media_ref)
|
|
|
|
# In all Urls, add type attribute
|
|
for url in person.urls:
|
|
convert_url_9(url)
|
|
|
|
# Switch from fixed lds ords to a list
|
|
if lds_bapt:
|
|
lds_bapt.type = LdsOrd.BAPTISM
|
|
lds_bapt.status = lds_bapt_dict_9[lds_bapt.status]
|
|
person.lds_ord_list.append(lds_bapt)
|
|
if lds_endow:
|
|
lds_endow.type = LdsOrd.ENDOWMENT
|
|
lds_endow.status = lds_bapt_dict_9[lds_endow.status]
|
|
person.lds_ord_list.append(lds_endow)
|
|
if lds_seal:
|
|
lds_seal.type = LdsOrd.SEAL_TO_PARENTS
|
|
lds_seal.status = lds_seal_parent_dict_9[lds_seal.status]
|
|
person.lds_ord_list.append(lds_seal)
|
|
# Old lds ords did not have private attribute
|
|
for item in person.lds_ord_list:
|
|
item.private = False
|
|
|
|
self.commit_person(person,trans)
|
|
self.update()
|
|
|
|
# Event upgrade
|
|
# Turns out that a lof ot events have duplicate gramps IDs
|
|
# We need to fix this. For some reason secondary index gets confused
|
|
# so we resolve duplicate IDs manually.
|
|
# First a quick pass via the cursor to get a list of event ids
|
|
eid_list = []
|
|
cursor = self.get_event_cursor()
|
|
data = cursor.first()
|
|
while data:
|
|
handle,val = data
|
|
eid_list.append(val[1])
|
|
data = cursor.next()
|
|
cursor.close()
|
|
|
|
# Find the largest ID and extract the integer:
|
|
# We can do this because in 2.0.x the event id is never exposed
|
|
eid_list.sort()
|
|
if len(eid_list) == 0:
|
|
max_id_number = 0
|
|
else:
|
|
last_id = eid_list[-1]
|
|
nre = re.compile("\D+(\d+)")
|
|
max_id_number = int(nre.match(last_id).groups()[0])
|
|
|
|
# get the list of all IDs that are non-unique
|
|
dup_ids = [eid for eid in eid_list if eid_list.count(eid) > 1 ]
|
|
|
|
for handle in self.event_map.keys():
|
|
info = self.event_map[handle]
|
|
event = Event()
|
|
event.handle = handle
|
|
(junk_handle, event.gramps_id, old_type, event.date,
|
|
event.description, event.place, cause, event.private,
|
|
event.source_list, event.note, witness_list,
|
|
event.media_list, event.change) = info
|
|
|
|
# Change ID if it is non-unique
|
|
if event.gramps_id in dup_ids:
|
|
max_id_number += 1
|
|
event.gramps_id = self.eprefix % max_id_number
|
|
|
|
# Convert old string-based type to GrampsType
|
|
event.type.set_from_xml_str(old_type)
|
|
|
|
# Cover attributes contained in MediaRefs
|
|
for media_ref in event.media_list:
|
|
convert_mediaref_9(media_ref)
|
|
|
|
# Upgrade witness -- no more Witness class
|
|
if type(witness_list) != list:
|
|
witness_list = []
|
|
for witness in witness_list:
|
|
if witness.type == 0: # witness name recorded
|
|
# Add name and comment to the event note
|
|
note_text = event.get_note() + "\n" + \
|
|
_("Witness name: %s") % witness.val
|
|
if witness.comment:
|
|
note_text += "\n" + _("Witness comment: %s") \
|
|
% witness.comment
|
|
event.set_note(note_text)
|
|
elif witness.type == 1: # witness ID recorded
|
|
person = self.get_person_from_handle(witness.val)
|
|
if person:
|
|
# Add an EventRef from that person
|
|
# to this event using ROLE_WITNESS role
|
|
event_ref = EventRef()
|
|
event_ref.ref = event.handle
|
|
event_ref.role.set(EventRoleType.WITNESS)
|
|
# Add privacy and comment
|
|
event_ref.private = witness.private
|
|
if witness.comment:
|
|
event_ref.set_note(witness.comment)
|
|
person.event_ref_list.append(event_ref)
|
|
self.commit_person(person,trans)
|
|
else:
|
|
# Broken witness: dangling witness handle
|
|
# with no corresponding person in the db
|
|
note_text = event.get_note() + "\n" + \
|
|
_("Broken witness reference detected "
|
|
"while upgrading database to version 9.")
|
|
event.set_note(note_text)
|
|
|
|
# This is an upgrade_10 step
|
|
if cause.strip():
|
|
attr = Attribute()
|
|
attr.set_type(AttributeType.CAUSE)
|
|
attr.set_value(cause)
|
|
event.add_attribute(attr)
|
|
|
|
self.commit_event(event,trans)
|
|
self.update()
|
|
|
|
# Place upgrade
|
|
for handle in self.place_map.keys():
|
|
info = self.place_map[handle]
|
|
place = Place()
|
|
place.handle = handle
|
|
(junk_handle, place.gramps_id, place.title, place.long, place.lat,
|
|
place.main_loc, place.alt_loc, place.urls, place.media_list,
|
|
place.source_list, place.note, place.change) = info
|
|
|
|
# Cover attributes contained in MediaRefs
|
|
for media_ref in place.media_list:
|
|
convert_mediaref_9(media_ref)
|
|
|
|
# In all Urls, add type attribute
|
|
for url in place.urls:
|
|
convert_url_9(url)
|
|
|
|
self.commit_place(place,trans)
|
|
self.update()
|
|
|
|
# Media upgrade
|
|
for handle in self.media_map.keys():
|
|
info = self.media_map[handle]
|
|
media_object = MediaObject()
|
|
media_object.handle = handle
|
|
(junk_handle, media_object.gramps_id, media_object.path,
|
|
media_object.mime, media_object.desc, media_object.attribute_list,
|
|
media_object.source_list, media_object.note, media_object.change,
|
|
media_object.date) = info
|
|
|
|
# In all Attributes, convert type from string to a tuple
|
|
for attribute in media_object.attribute_list:
|
|
convert_attribute_9(attribute)
|
|
|
|
self.commit_media_object(media_object,trans)
|
|
self.update()
|
|
|
|
self.transaction_commit(trans,"Upgrade to DB version 9")
|
|
# Close secodnary index
|
|
self.reference_map_primary_map.close()
|
|
|
|
if self.UseTXN:
|
|
# Separate transaction to save metadata
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
self.metadata.put('version',10,txn=the_txn)
|
|
if self.UseTXN:
|
|
the_txn.commit()
|
|
else:
|
|
self.metadata.sync()
|
|
|
|
print "Done upgrading to DB version 10"
|
|
|
|
def gramps_upgrade_10(self):
|
|
print "Upgrading to DB version 10 -- this may take a while"
|
|
|
|
# Remove event column metadata, since columns have changed.
|
|
# This will reset all columns to defaults in event view
|
|
for name in (PERSON_COL_KEY,EVENT_COL_KEY):
|
|
try:
|
|
if self.UseTXN:
|
|
# Start transaction if needed
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
self.metadata.delete(name,txn=the_txn)
|
|
if self.UseTXN:
|
|
the_txn.commit()
|
|
else:
|
|
self.metadata.sync()
|
|
except KeyError:
|
|
if self.UseTXN:
|
|
the_txn.abort()
|
|
|
|
# Create one secondary index for reference_map
|
|
# because every commit will require this to exist
|
|
table_flags = self.open_flags()
|
|
self.reference_map_primary_map = db.DB(self.env)
|
|
self.reference_map_primary_map.set_flags(db.DB_DUP)
|
|
self.reference_map_primary_map.open(self.full_name,
|
|
"reference_map_primary_map",
|
|
db.DB_BTREE, flags=table_flags)
|
|
self.reference_map.associate(self.reference_map_primary_map,
|
|
find_primary_handle,
|
|
table_flags)
|
|
|
|
# so starting (batch) transaction here.
|
|
trans = self.transaction_begin("",True)
|
|
|
|
# This upgrade adds attribute lists to Event and EventRef objects
|
|
length = self.get_number_of_events() + len(self.person_map) \
|
|
+ self.get_number_of_families()
|
|
self.set_total(length)
|
|
|
|
for handle in self.event_map.keys():
|
|
info = self.event_map[handle]
|
|
|
|
(junk_handle, gramps_id, the_type, date,description,
|
|
place, cause,source_list, note, media_list,
|
|
change, marker, private) = info
|
|
|
|
new_info = (handle, gramps_id, the_type, date,
|
|
description, place, source_list, note, media_list,
|
|
[], change, marker, private)
|
|
|
|
event = Event()
|
|
event.unserialize(new_info)
|
|
|
|
# Cause is removed, so we're converting it into an attribute
|
|
if cause.strip():
|
|
attr = Attribute()
|
|
attr.set_type(AttributeType.CAUSE)
|
|
attr.set_value(cause)
|
|
event.add_attribute(attr)
|
|
|
|
self.commit_event(event,trans)
|
|
self.update()
|
|
|
|
# Personal event references
|
|
for handle in self.person_map.keys():
|
|
info = self.person_map[handle]
|
|
|
|
(junk_handle,gramps_id,gender,
|
|
primary_name,alternate_names,death_ref_index,
|
|
birth_ref_index,event_ref_list,family_list,
|
|
parent_family_list,media_list,address_list,attribute_list,
|
|
urls,lds_ord_list,source_list,note,change,marker,
|
|
private,person_ref_list,) = info
|
|
|
|
new_info = (handle,gramps_id,gender,Name().serialize(),[],
|
|
death_ref_index,birth_ref_index,[],
|
|
family_list,parent_family_list,media_list,address_list,
|
|
attribute_list,urls,lds_ord_list,source_list,note,
|
|
change,marker,private,person_ref_list,)
|
|
|
|
person = Person()
|
|
person.unserialize(new_info)
|
|
|
|
# Names lost the "sname" attribute
|
|
person.primary_name.unserialize(convert_name_10(primary_name))
|
|
person.alternate_names = [Name().unserialize(convert_name_10(name))
|
|
for name in alternate_names]
|
|
|
|
# Events gained attribute_list
|
|
for (privacy,note,ref,role) in event_ref_list:
|
|
event_ref = EventRef()
|
|
new_event_ref_data = (privacy,note,[],ref,role)
|
|
event_ref.unserialize(new_event_ref_data)
|
|
person.add_event_ref(event_ref)
|
|
|
|
self.commit_person(person,trans)
|
|
self.update()
|
|
|
|
# Family event references
|
|
for handle in self.family_map.keys():
|
|
info = self.family_map[handle]
|
|
|
|
(junk_handle,gramps_id,father_handle,
|
|
mother_handle,child_ref_list,the_type,event_ref_list,
|
|
media_list,attribute_list,lds_seal_list,source_list,note,
|
|
change, marker, private) = info
|
|
|
|
new_info = (handle,gramps_id,father_handle,
|
|
mother_handle,child_ref_list,the_type,[],
|
|
media_list,attribute_list,lds_seal_list,
|
|
source_list,note,change, marker, private)
|
|
|
|
family = Family()
|
|
family.unserialize(new_info)
|
|
|
|
for (privacy,note,ref,role) in event_ref_list:
|
|
event_ref = EventRef()
|
|
new_event_ref_data = (privacy,note,[],ref,role)
|
|
event_ref.unserialize(new_event_ref_data)
|
|
family.add_event_ref(event_ref)
|
|
|
|
self.commit_family(family,trans)
|
|
self.update()
|
|
self.reset()
|
|
|
|
self.transaction_commit(trans,"Upgrade to DB version 10")
|
|
# Close secodnary index
|
|
self.reference_map_primary_map.close()
|
|
|
|
if self.UseTXN:
|
|
# Separate transaction to save metadata
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
self.metadata.put('version',10,txn=the_txn)
|
|
if self.UseTXN:
|
|
the_txn.commit()
|
|
else:
|
|
self.metadata.sync()
|
|
|
|
print "Done upgrading to DB version 10"
|
|
|
|
def gramps_upgrade_11(self):
|
|
print "Upgrading to DB version 11 -- this may take a while"
|
|
|
|
table_flags = self.open_flags()
|
|
self.reference_map_primary_map = db.DB(self.env)
|
|
self.reference_map_primary_map.set_flags(db.DB_DUP)
|
|
self.reference_map_primary_map.open(self.full_name,
|
|
"reference_map_primary_map",
|
|
db.DB_BTREE, flags=table_flags)
|
|
self.reference_map.associate(self.reference_map_primary_map,
|
|
find_primary_handle,
|
|
table_flags)
|
|
|
|
|
|
# This upgrade adds attribute lists to Event and EventRef objects
|
|
length = len(self.person_map) + len(self.place_map)
|
|
self.set_total(length)
|
|
|
|
# so starting (batch) transaction here.
|
|
trans = self.transaction_begin("",True)
|
|
|
|
# Personal event references
|
|
for handle in self.person_map.keys():
|
|
info = self.person_map[handle]
|
|
|
|
new_address_list = []
|
|
for addr in info[11]:
|
|
loc = ( addr[9], addr[4], u'', addr[5], addr[6],
|
|
addr[7], addr[8])
|
|
addr = (addr[0],addr[1],addr[2],addr[3], loc)
|
|
new_address_list.append(addr)
|
|
|
|
new_info = info[0:11] + (new_address_list,) + info[12:]
|
|
|
|
person = Person()
|
|
person.unserialize(new_info)
|
|
|
|
self.commit_person(person,trans)
|
|
self.update()
|
|
|
|
# Personal event references
|
|
for handle in self.place_map.keys():
|
|
info = self.place_map[handle]
|
|
|
|
(h, gramps_id, title, long, lat, main_loc, alt_loc, urls,
|
|
media_list, source_list, note, change, marker, private) = info
|
|
|
|
if main_loc:
|
|
m, p, c = main_loc
|
|
m = ((u'', m[0], c, m[1], m[2], m[3], m[4]))
|
|
|
|
main_loc = (m, p)
|
|
|
|
loc_list = []
|
|
for l in alt_loc:
|
|
|
|
m, p, c = l
|
|
m = ((u'', m[0], c, m[1], m[2], m[3], m[4]))
|
|
|
|
l = (m, p)
|
|
loc_list.append(l)
|
|
|
|
info = (h, gramps_id, title, long, lat, main_loc, loc_list, urls,
|
|
media_list, source_list, note, change, marker, private)
|
|
|
|
place = Place()
|
|
place.unserialize(info)
|
|
|
|
self.commit_place(place,trans)
|
|
self.update()
|
|
|
|
self.reset()
|
|
|
|
self.transaction_commit(trans,"Upgrade to DB version 10")
|
|
|
|
self.reference_map_primary_map.close()
|
|
|
|
if self.UseTXN:
|
|
# Separate transaction to save metadata
|
|
the_txn = self.env.txn_begin()
|
|
else:
|
|
the_txn = None
|
|
self.metadata.put('version', 11, txn=the_txn)
|
|
if self.UseTXN:
|
|
the_txn.commit()
|
|
else:
|
|
self.metadata.sync()
|
|
|
|
print "Done upgrading to DB version 11"
|
|
|
|
class BdbTransaction(Transaction):
|
|
def __init__(self,msg,db,batch=False,no_magic=False):
|
|
Transaction.__init__(self,msg,db,batch,no_magic)
|
|
self.reference_del = []
|
|
self.reference_add = []
|
|
|
|
def convert_attribute_9(attribute):
|
|
old_type = attribute.type
|
|
new_type = AttributeType()
|
|
new_type.set_from_xml_str(old_type)
|
|
attribute.type = new_type
|
|
|
|
def convert_mediaref_9(media_ref):
|
|
for attribute in media_ref.attribute_list:
|
|
convert_attribute_9(attribute)
|
|
|
|
def convert_url_9(url):
|
|
path = url.path.strip()
|
|
if (path.find('mailto:') == 0) or (url.path.find('@') != -1):
|
|
new_type = UrlType.EMAIL
|
|
elif path.find('http://') == 0:
|
|
new_type = UrlType.WEB_HOME
|
|
elif path.find('ftp://') == 0:
|
|
new_type = UrlType.WEB_FTP
|
|
else:
|
|
new_type = UrlType.CUSTOM
|
|
url.type = UrlType(new_type)
|
|
|
|
lds_bapt_dict_9 = {
|
|
0: LdsOrd.STATUS_NONE,
|
|
1: LdsOrd.STATUS_CHILD,
|
|
2: LdsOrd.STATUS_CLEARED,
|
|
3: LdsOrd.STATUS_COMPLETED,
|
|
4: LdsOrd.STATUS_INFANT,
|
|
5: LdsOrd.STATUS_PRE_1970,
|
|
6: LdsOrd.STATUS_QUALIFIED,
|
|
7: LdsOrd.STATUS_STILLBORN,
|
|
8: LdsOrd.STATUS_SUBMITTED,
|
|
9: LdsOrd.STATUS_UNCLEARED,
|
|
}
|
|
|
|
lds_seal_parent_dict_9 = {
|
|
0: LdsOrd.STATUS_NONE,
|
|
1: LdsOrd.STATUS_BIC,
|
|
2: LdsOrd.STATUS_CLEARED,
|
|
3: LdsOrd.STATUS_COMPLETED,
|
|
4: LdsOrd.STATUS_DNS,
|
|
5: LdsOrd.STATUS_PRE_1970,
|
|
6: LdsOrd.STATUS_QUALIFIED,
|
|
7: LdsOrd.STATUS_STILLBORN,
|
|
8: LdsOrd.STATUS_SUBMITTED,
|
|
9: LdsOrd.STATUS_UNCLEARED,
|
|
}
|
|
|
|
lds_seal_spouse_dict_9 = {
|
|
0: LdsOrd.STATUS_NONE,
|
|
1: LdsOrd.STATUS_CANCELED,
|
|
2: LdsOrd.STATUS_CLEARED,
|
|
3: LdsOrd.STATUS_COMPLETED,
|
|
4: LdsOrd.STATUS_DNS,
|
|
5: LdsOrd.STATUS_PRE_1970,
|
|
6: LdsOrd.STATUS_QUALIFIED,
|
|
7: LdsOrd.STATUS_DNS_CAN,
|
|
8: LdsOrd.STATUS_SUBMITTED,
|
|
9: LdsOrd.STATUS_UNCLEARED,
|
|
}
|
|
|
|
def low_level_9(the_db,update):
|
|
"""
|
|
This is a low-level repair routine.
|
|
|
|
It is fixing DB inconsistencies such as duplicates.
|
|
Returns a (status,name) tuple.
|
|
The boolean status indicates the success of the procedure.
|
|
The name indicates the problematic table (empty if status is True).
|
|
"""
|
|
the_length = 0
|
|
for the_map in [('Person',the_db.person_map),
|
|
('Family',the_db.family_map),
|
|
('Event',the_db.event_map),
|
|
('Place',the_db.place_map),
|
|
('Source',the_db.source_map),
|
|
('Media',the_db.media_map)]:
|
|
|
|
# print "Low-level repair: table: %s" % the_map[0]
|
|
status,length = _table_low_level_9(the_db.env,the_map[1])
|
|
if update:
|
|
update()
|
|
if status:
|
|
# print "Done."
|
|
the_length += length
|
|
else:
|
|
print "Low-level repair: Problem with table: %s" % the_map[0]
|
|
return (False,the_map[0])
|
|
return (True,the_length)
|
|
|
|
|
|
def _table_low_level_9(env,table):
|
|
"""
|
|
Low level repair for a given db table.
|
|
"""
|
|
|
|
handle_list = table.keys()
|
|
length = len(handle_list)
|
|
dup_handles = set(
|
|
[ handle for handle in handle_list if handle_list.count(handle) > 1 ]
|
|
)
|
|
|
|
if not dup_handles:
|
|
# print " No dupes found for this table"
|
|
return (True,length)
|
|
|
|
the_txn = env.txn_begin()
|
|
table_cursor = GrampsBSDDBDupCursor(table,txn=the_txn)
|
|
# Dirty hack to prevent records from unpickling by DBShelve
|
|
table_cursor._extract = lambda rec: rec
|
|
|
|
for handle in dup_handles:
|
|
print " Duplicates found for handle: %s" % handle
|
|
try:
|
|
ret = table_cursor.set(handle)
|
|
except:
|
|
print " Failed setting initial cursor."
|
|
table_cursor.close()
|
|
the_txn.abort()
|
|
return (False,None)
|
|
|
|
for count in range(handle_list.count(handle)-1):
|
|
try:
|
|
table_cursor.delete()
|
|
print " Succesfully deleted dupe #%d" % (count+1)
|
|
except:
|
|
print " Failed deleting dupe."
|
|
table_cursor.close()
|
|
the_txn.abort()
|
|
return (False,None)
|
|
|
|
try:
|
|
ret = table_cursor.next_dup()
|
|
except:
|
|
print " Failed moving the cursor."
|
|
table_cursor.close()
|
|
the_txn.abort()
|
|
return (False,None)
|
|
|
|
table_cursor.close()
|
|
the_txn.commit()
|
|
return (True,length)
|
|
|
|
def convert_name_10(name):
|
|
# Names lost the "sname" attribute
|
|
(privacy,source_list,note,date,first_name,surname,suffix,title,name_type,
|
|
prefix,patronymic,sname,group_as,sort_as,display_as,call) = name
|
|
return (privacy,source_list,note,date,first_name,surname,suffix,title,
|
|
name_type,prefix,patronymic,group_as,sort_as,display_as,call)
|
|
|
|
if __name__ == "__main__":
|
|
|
|
import sys
|
|
|
|
d = GrampsBSDDB()
|
|
d.load(sys.argv[1],lambda x: x)
|
|
|
|
c = d.get_person_cursor()
|
|
data = c.first()
|
|
while data:
|
|
person = Person(data[1])
|
|
print data[0], person.get_primary_name().get_name(),
|
|
data = c.next()
|
|
c.close()
|
|
|
|
print d.surnames.keys()
|