From 242abf9f697605ca0b3a8aee20e8105789b0306b Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Tue, 12 May 2015 16:30:46 -0400 Subject: [PATCH 001/105] Database backend as a plugin: this set of changes moves most or all of Bsddb from gramps.gen.db to gramps.plugins.database. The id of the plugin is 'bsddb' which can be loaded using the make_database(id, dbstate) API (for now). Next step is to add an identifying text in the directory to indicate which database backend to use. --- gramps/cli/arghandler.py | 5 +- gramps/cli/clidbman.py | 9 +- gramps/cli/grampscli.py | 6 +- gramps/gen/db/__init__.py | 27 ++- gramps/gen/db/backup.py | 209 +---------------- gramps/gen/dbstate.py | 6 +- gramps/gen/plug/_gramplet.py | 1 - gramps/gen/plug/_manager.py | 5 + gramps/gen/plug/_pluginreg.py | 31 ++- gramps/gen/utils/callman.py | 3 +- gramps/gui/dbloader.py | 4 +- gramps/gui/dbman.py | 11 +- gramps/gui/pluginmanager.py | 7 +- gramps/plugins/database/bsddb.gpr.py | 31 +++ gramps/plugins/database/bsddb.py | 3 + .../database/bsddb_support/__init__.py | 96 ++++++++ .../plugins/database/bsddb_support/backup.py | 213 ++++++++++++++++++ .../database/bsddb_support}/bsddbtxn.py | 0 .../database/bsddb_support}/cursor.py | 0 .../database/bsddb_support}/dictionary.py | 24 +- .../database/bsddb_support}/read.py | 45 ++-- .../database/bsddb_support}/undoredo.py | 8 +- .../database/bsddb_support}/upgrade.py | 29 +-- .../database/bsddb_support}/write.py | 45 ++-- 24 files changed, 506 insertions(+), 312 deletions(-) create mode 100644 gramps/plugins/database/bsddb.gpr.py create mode 100644 gramps/plugins/database/bsddb.py create mode 100644 gramps/plugins/database/bsddb_support/__init__.py create mode 100644 gramps/plugins/database/bsddb_support/backup.py rename gramps/{gen/db => plugins/database/bsddb_support}/bsddbtxn.py (100%) rename gramps/{gen/db => plugins/database/bsddb_support}/cursor.py (100%) rename gramps/{gen/db => plugins/database/bsddb_support}/dictionary.py (98%) rename gramps/{gen/db => plugins/database/bsddb_support}/read.py (98%) rename gramps/{gen/db => plugins/database/bsddb_support}/undoredo.py (98%) rename gramps/{gen/db => plugins/database/bsddb_support}/upgrade.py (98%) rename gramps/{gen/db => plugins/database/bsddb_support}/write.py (99%) diff --git a/gramps/cli/arghandler.py b/gramps/cli/arghandler.py index 0db169d30..c7a5f5a4a 100644 --- a/gramps/cli/arghandler.py +++ b/gramps/cli/arghandler.py @@ -44,7 +44,7 @@ import sys #------------------------------------------------------------------------- from gramps.gen.recentfiles import recent_files from gramps.gen.utils.file import rm_tempdir, get_empty_tempdir -from gramps.gen.db import DbBsddb +from gramps.gen.db import make_database from .clidbman import CLIDbManager, NAME_FILE, find_locker_name from gramps.gen.plug import BasePluginManager @@ -491,7 +491,8 @@ class ArgHandler(object): self.imp_db_path, title = self.dbman.create_new_db_cli() else: self.imp_db_path = get_empty_tempdir("import_dbdir") - newdb = DbBsddb() + + newdb = make_database("bsddb", self.dbstate) newdb.write_version(self.imp_db_path) try: diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py index 1a7e51d85..6ca96ec0d 100644 --- a/gramps/cli/clidbman.py +++ b/gramps/cli/clidbman.py @@ -54,7 +54,7 @@ _LOG = logging.getLogger(DBLOGNAME) #------------------------------------------------------------------------- from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -from gramps.gen.db import DbBsddb +from gramps.gen.db import make_database from gramps.gen.plug import BasePluginManager from gramps.gen.config import config from gramps.gen.constfunc import win, conv_to_unicode @@ -294,7 +294,8 @@ class CLIDbManager(object): if create_db: # write the version number into metadata - newdb = DbBsddb() + + newdb = make_database("bsddb", self.dbstate) newdb.write_version(new_path) (tval, last) = time_val(new_path) @@ -360,8 +361,8 @@ class CLIDbManager(object): # Create a new database self.__start_cursor(_("Importing data...")) - dbclass = DbBsddb - dbase = dbclass() + + dbase = make_database("bsddb", self.dbstate) dbase.load(new_path, user.callback) import_function = plugin.get_import_function() diff --git a/gramps/cli/grampscli.py b/gramps/cli/grampscli.py index a45564a19..00b66d352 100644 --- a/gramps/cli/grampscli.py +++ b/gramps/cli/grampscli.py @@ -49,7 +49,7 @@ from gramps.gen.config import config from gramps.gen.const import PLUGINS_DIR, USER_PLUGINS from gramps.gen.errors import DbError from gramps.gen.dbstate import DbState -from gramps.gen.db import DbBsddb +from gramps.gen.db import make_database from gramps.gen.db.exceptions import (DbUpgradeRequiredError, BsddbDowngradeError, DbVersionError, @@ -152,9 +152,9 @@ class CLIDbLoader(object): else: mode = 'w' - dbclass = DbBsddb + db = make_database("bsddb", self.dbstate) - self.dbstate.change_database(dbclass()) + self.dbstate.change_database(db) self.dbstate.db.disable_signals() self._begin_progress() diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py index 5b79ce334..b977887b7 100644 --- a/gramps/gen/db/__init__.py +++ b/gramps/gen/db/__init__.py @@ -86,11 +86,26 @@ More details can be found in the manual's from .base import * from .dbconst import * -from .cursor import * -from .read import * -from .bsddbtxn import * +#from .cursor import * +#from .read import * +#from .bsddbtxn import * from .txn import * -from .undoredo import * +#from .undoredo import * from .exceptions import * -from .write import * -from .backup import backup, restore +#from .write import * +#from .backup import backup, restore + +def make_database(id, dbstate): + from gramps.cli.grampscli import CLIManager + from gramps.gen.plug import BasePluginManager + from gramps.cli.user import User + + climanager = CLIManager(dbstate, setloader=False, user=User()) # do not load db_loader + climanager.do_reg_plugins(dbstate, None) + pmgr = BasePluginManager.get_instance() + pdata = pmgr.get_plugin(id) + + mod = pmgr.load_plugin(pdata) + database = getattr(mod, pdata.databaseclass) + return database() + diff --git a/gramps/gen/db/backup.py b/gramps/gen/db/backup.py index 9329caaff..52d4d5f05 100644 --- a/gramps/gen/db/backup.py +++ b/gramps/gen/db/backup.py @@ -1,213 +1,8 @@ -# -# Gramps - a GTK+/GNOME based genealogy program -# -# Copyright (C) 2007 Donald N. Allingham -# Copyright (C) 2011 Tim G L Lyons -# -# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# -# gen/db/backup.py - -""" -Description -=========== - -This module Provides backup and restore functions for a database. The -backup function saves the data into backup files, while the restore -function loads the data back into a database. - -You should only restore the data into an empty database. - -Implementation -============== - -Not all of the database tables need to be backed up, since many are -automatically generated from the others. The tables that are backed up -are the primary tables and the metadata table. - -The database consists of a table of "pickled" tuples. Each of the -primary tables is "walked", and the pickled tuple is extracted, and -written to the backup file. - -Restoring the data is just as simple. The backup file is parsed an -entry at a time, and inserted into the associated database table. The -derived tables are built automatically as the items are entered into -db. -""" - -#------------------------------------------------------------------------- -# -# load standard python libraries -# -#------------------------------------------------------------------------- -import os -import pickle - -#------------------------------------------------------------------------ -# -# Gramps libs -# -#------------------------------------------------------------------------ -from .exceptions import DbException -from .write import FAMILY_TBL, PLACES_TBL, SOURCES_TBL, MEDIA_TBL, \ - EVENTS_TBL, PERSON_TBL, REPO_TBL, NOTE_TBL, TAG_TBL, META, CITATIONS_TBL - -#------------------------------------------------------------------------ -# -# Set up logging -# -#------------------------------------------------------------------------ import logging LOG = logging.getLogger(".Backup") def backup(database): - """ - Exports the database to a set of backup files. These files consist - of the pickled database tables, one file for each table. - - The heavy lifting is done by the private :py:func:`__do__export` function. - The purpose of this function is to catch any exceptions that occur. - - :param database: database instance to backup - :type database: DbDir - """ - try: - __do_export(database) - except (OSError, IOError) as msg: - raise DbException(str(msg)) - -def __mk_backup_name(database, base): - """ - Return the backup name of the database table - - :param database: database instance - :type database: DbDir - :param base: base name of the table - :type base: str - """ - return os.path.join(database.get_save_path(), base + ".gbkp") - -def __mk_tmp_name(database, base): - """ - Return the temporary backup name of the database table - - :param database: database instance - :type database: DbDir - :param base: base name of the table - :type base: str - """ - return os.path.join(database.get_save_path(), base + ".gbkp.new") - -def __do_export(database): - """ - Loop through each table of the database, saving the pickled data - a file. - - :param database: database instance to backup - :type database: DbDir - """ - try: - for (base, tbl) in __build_tbl_map(database): - backup_name = __mk_tmp_name(database, base) - backup_table = open(backup_name, 'wb') - - cursor = tbl.cursor() - data = cursor.first() - while data: - pickle.dump(data, backup_table, 2) - data = cursor.next() - cursor.close() - backup_table.close() - except (IOError,OSError): - return - - for (base, tbl) in __build_tbl_map(database): - new_name = __mk_backup_name(database, base) - old_name = __mk_tmp_name(database, base) - if os.path.isfile(new_name): - os.unlink(new_name) - os.rename(old_name, new_name) + print("FIXME") def restore(database): - """ - Restores the database to a set of backup files. These files consist - of the pickled database tables, one file for each table. - - The heavy lifting is done by the private :py:func:`__do__restore` function. - The purpose of this function is to catch any exceptions that occur. - - :param database: database instance to restore - :type database: DbDir - """ - try: - __do_restore(database) - except (OSError, IOError) as msg: - raise DbException(str(msg)) - -def __do_restore(database): - """ - Loop through each table of the database, restoring the pickled data - to the appropriate database file. - - :param database: database instance to backup - :type database: DbDir - """ - for (base, tbl) in __build_tbl_map(database): - backup_name = __mk_backup_name(database, base) - backup_table = open(backup_name, 'rb') - __load_tbl_txn(database, backup_table, tbl) - - database.rebuild_secondary() - -def __load_tbl_txn(database, backup_table, tbl): - """ - Return the temporary backup name of the database table - - :param database: database instance - :type database: DbDir - :param backup_table: file containing the backup data - :type backup_table: file - :param tbl: Berkeley db database table - :type tbl: Berkeley db database table - """ - try: - while True: - data = pickle.load(backup_table) - txn = database.env.txn_begin() - tbl.put(data[0], data[1], txn=txn) - txn.commit() - except EOFError: - backup_table.close() - -def __build_tbl_map(database): - """ - Builds a table map of names to database tables. - - :param database: database instance to backup - :type database: DbDir - """ - return [ - ( PERSON_TBL, database.person_map.db), - ( FAMILY_TBL, database.family_map.db), - ( PLACES_TBL, database.place_map.db), - ( SOURCES_TBL, database.source_map.db), - ( CITATIONS_TBL, database.citation_map.db), - ( REPO_TBL, database.repository_map.db), - ( NOTE_TBL, database.note_map.db), - ( MEDIA_TBL, database.media_map.db), - ( EVENTS_TBL, database.event_map.db), - ( TAG_TBL, database.tag_map.db), - ( META, database.metadata.db), - ] + print("FIXME") diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py index 839d05cd2..12ee68ea9 100644 --- a/gramps/gen/dbstate.py +++ b/gramps/gen/dbstate.py @@ -23,8 +23,8 @@ Provide the database state class """ -from .db import DbBsddbRead from .db import DbReadBase +from .db import make_database from .proxy.proxybase import ProxyDbBase from .utils.callback import Callback from .config import config @@ -45,7 +45,7 @@ class DbState(Callback): just a place holder until a real DB is assigned. """ Callback.__init__(self) - self.db = DbBsddbRead() + self.db = make_database("bsddb", self) self.open = False self.stack = [] @@ -88,7 +88,7 @@ class DbState(Callback): """ self.emit('no-database', ()) self.db.close() - self.db = DbBsddbRead() + self.db = make_database("bsddb", self) self.db.db_is_open = False self.open = False self.emit('database-changed', (self.db, )) diff --git a/gramps/gen/plug/_gramplet.py b/gramps/gen/plug/_gramplet.py index c040bae7d..b812a7fb7 100644 --- a/gramps/gen/plug/_gramplet.py +++ b/gramps/gen/plug/_gramplet.py @@ -70,7 +70,6 @@ class Gramplet(object): self.connect(self.gui.textview, "motion-notify-event", self.gui.on_motion) self.connect_signal('Person', self._active_changed) - self._db_changed(self.dbstate.db) active_person = self.get_active('Person') if active_person: # already changed diff --git a/gramps/gen/plug/_manager.py b/gramps/gen/plug/_manager.py index e047f6166..e30a089ae 100644 --- a/gramps/gen/plug/_manager.py +++ b/gramps/gen/plug/_manager.py @@ -412,6 +412,11 @@ class BasePluginManager(object): """ return self.__pgr.sidebar_plugins() + def get_reg_databases(self): + """ Return list of registered database backends + """ + return self.__pgr.database_plugins() + def get_external_opt_dict(self): """ Return the dictionary of external options. """ return self.__external_opt_dict diff --git a/gramps/gen/plug/_pluginreg.py b/gramps/gen/plug/_pluginreg.py index 04167cafc..5619f18ed 100644 --- a/gramps/gen/plug/_pluginreg.py +++ b/gramps/gen/plug/_pluginreg.py @@ -70,8 +70,9 @@ VIEW = 8 RELCALC = 9 GRAMPLET = 10 SIDEBAR = 11 +DATABASE = 12 PTYPE = [REPORT , QUICKREPORT, TOOL, IMPORT, EXPORT, DOCGEN, GENERAL, - MAPSERVICE, VIEW, RELCALC, GRAMPLET, SIDEBAR] + MAPSERVICE, VIEW, RELCALC, GRAMPLET, SIDEBAR, DATABASE] PTYPE_STR = { REPORT: _('Report') , QUICKREPORT: _('Quickreport'), @@ -85,6 +86,7 @@ PTYPE_STR = { RELCALC: _('Relationships'), GRAMPLET: _('Gramplet'), SIDEBAR: _('Sidebar'), + DATABASE: _('Database'), } #possible report categories @@ -206,7 +208,7 @@ class PluginData(object): The python path where the plugin implementation can be found .. attribute:: ptype The plugin type. One of REPORT , QUICKREPORT, TOOL, IMPORT, - EXPORT, DOCGEN, GENERAL, MAPSERVICE, VIEW, GRAMPLET + EXPORT, DOCGEN, GENERAL, MAPSERVICE, VIEW, GRAMPLET, DATABASE .. attribute:: authors List of authors of the plugin, default=[] .. attribute:: authors_email @@ -349,6 +351,11 @@ class PluginData(object): the plugin is appended to the list of plugins. If START, then the plugin is prepended. Only set START if you want a plugin to be the first in the order of plugins + + Attributes for DATABASE plugins + + .. attribute:: databaseclass + The class in the module that is the database class """ def __init__(self): @@ -421,6 +428,8 @@ class PluginData(object): self._menu_label = '' #VIEW and SIDEBAR attr self._order = END + #DATABASE attr + self._databaseclass = None #GENERAL attr self._data = [] self._process = None @@ -931,6 +940,17 @@ class PluginData(object): order = property(_get_order, _set_order) + #DATABASE attributes + def _set_databaseclass(self, databaseclass): + if not self._ptype == DATABASE: + raise ValueError('databaseclass may only be set for DATABASE plugins') + self._databaseclass = databaseclass + + def _get_databaseclass(self): + return self._databaseclass + + databaseclass = property(_get_databaseclass, _set_databaseclass) + #GENERAL attr def _set_data(self, data): if not self._ptype in (GENERAL,): @@ -1032,6 +1052,7 @@ def make_environment(**kwargs): 'REPORT_MODE_CLI': REPORT_MODE_CLI, 'TOOL_MODE_GUI': TOOL_MODE_GUI, 'TOOL_MODE_CLI': TOOL_MODE_CLI, + 'DATABASE': DATABASE, 'GRAMPSVERSION': GRAMPSVERSION, 'START': START, 'END': END, @@ -1297,6 +1318,12 @@ class PluginRegister(object): """ return self.type_plugins(SIDEBAR) + def database_plugins(self): + """ + Return a list of :class:`PluginData` that are of type DATABASE + """ + return self.type_plugins(DATABASE) + def filter_load_on_reg(self): """ Return a list of :class:`PluginData` that have load_on_reg == True diff --git a/gramps/gen/utils/callman.py b/gramps/gen/utils/callman.py index ca6a94d86..939a0f6fa 100644 --- a/gramps/gen/utils/callman.py +++ b/gramps/gen/utils/callman.py @@ -303,7 +303,8 @@ class CallbackManager(object): Do a custom db connect signal outside of the primary object ones managed automatically. """ - self.custom_signal_keys.append(self.database.connect(name, callback)) + if self.database: + self.custom_signal_keys.append(self.database.connect(name, callback)) def __callbackcreator(self, signal, noarg=False): """ diff --git a/gramps/gui/dbloader.py b/gramps/gui/dbloader.py index 6d4b283d4..a0e180f90 100644 --- a/gramps/gui/dbloader.py +++ b/gramps/gui/dbloader.py @@ -55,7 +55,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext from gramps.cli.grampscli import CLIDbLoader from gramps.gen.config import config -from gramps.gen.db import DbBsddb +from gramps.gen.db import make_database from gramps.gen.db.exceptions import (DbUpgradeRequiredError, BsddbDowngradeError, DbVersionError, @@ -305,7 +305,7 @@ class DbLoader(CLIDbLoader): else: mode = 'w' - db = DbBsddb() + db = make_database("bsddb", self.dbstate) db.disable_signals() self.dbstate.no_database() diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py index f57912547..cfc2d4a68 100644 --- a/gramps/gui/dbman.py +++ b/gramps/gui/dbman.py @@ -73,7 +73,7 @@ _ = glocale.translation.gettext from gramps.gen.const import URL_WIKISTRING from .user import User from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2 -from gramps.gen.db import DbBsddb +from gramps.gen.db import make_database from .pluginmanager import GuiPluginManager from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val from .ddtargets import DdTargets @@ -535,8 +535,8 @@ class DbManager(CLIDbManager): new_path, newname = self._create_new_db("%s : %s" % (parent_name, name)) self.__start_cursor(_("Extracting archive...")) - dbclass = DbBsddb - dbase = dbclass() + + dbase = make_database("bsddb", self.dbstate) dbase.load(new_path, None) self.__start_cursor(_("Importing archive...")) @@ -723,11 +723,10 @@ class DbManager(CLIDbManager): fname = os.path.join(dirname, filename) os.unlink(fname) - newdb = DbBsddb() + newdb = make_database("bsddb", self.dbstate) newdb.write_version(dirname) - dbclass = DbBsddb - dbase = dbclass() + dbase = make_database("bsddb", self.dbstate) dbase.set_save_path(dirname) dbase.load(dirname, None) diff --git a/gramps/gui/pluginmanager.py b/gramps/gui/pluginmanager.py index 51833b94b..9466759f8 100644 --- a/gramps/gui/pluginmanager.py +++ b/gramps/gui/pluginmanager.py @@ -314,7 +314,12 @@ class GuiPluginManager(Callback): return [plg for plg in self.basemgr.get_reg_docgens() if plg.id not in self.__hidden_plugins] + def get_reg_databases(self): + """ Return list of non hidden registered database backends + """ + return [plg for plg in self.basemgr.get_reg_databases() + if plg.id not in self.__hidden_plugins] + def get_reg_general(self, category=None): return [plg for plg in self.basemgr.get_reg_general(category) if plg.id not in self.__hidden_plugins] - diff --git a/gramps/plugins/database/bsddb.gpr.py b/gramps/plugins/database/bsddb.gpr.py new file mode 100644 index 000000000..20f2e9fda --- /dev/null +++ b/gramps/plugins/database/bsddb.gpr.py @@ -0,0 +1,31 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com> +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +plg = newplugin() +plg.id = 'bsddb' +plg.name = _("BSDDB Database Backend") +plg.name_accell = _("_BSDDB Database Backend") +plg.description = _("Berkeley Software Distribution Database Backend") +plg.version = '1.0' +plg.gramps_target_version = "4.2" +plg.status = STABLE +plg.fname = 'bsddb.py' +plg.ptype = DATABASE +plg.databaseclass = 'DbBsddb' diff --git a/gramps/plugins/database/bsddb.py b/gramps/plugins/database/bsddb.py new file mode 100644 index 000000000..9cbca25ce --- /dev/null +++ b/gramps/plugins/database/bsddb.py @@ -0,0 +1,3 @@ + +from bsddb_support import DbBsddb + diff --git a/gramps/plugins/database/bsddb_support/__init__.py b/gramps/plugins/database/bsddb_support/__init__.py new file mode 100644 index 000000000..524ecb660 --- /dev/null +++ b/gramps/plugins/database/bsddb_support/__init__.py @@ -0,0 +1,96 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Gramps Database API. + +Database Architecture +===================== + +Access to the database is made through Python classes. Exactly +what functionality you have is dependent on the properties of the +database. For example, if you are accessing a read-only view, then +you will only have access to a subset of the methods available. + +At the root of any database interface is either :py:class:`.DbReadBase` and/or +:py:class:`.DbWriteBase`. These define the methods to read and write to a +database, respectively. + +The full database hierarchy is: + +- :py:class:`.DbBsddb` - read and write implementation to BSDDB databases + + * :py:class:`.DbWriteBase` - virtual and implementation-independent methods + for reading data + + * :py:class:`.DbBsddbRead` - read-only (accessors, getters) implementation + to BSDDB databases + + + :py:class:`.DbReadBase` - virtual and implementation-independent + methods for reading data + + + :py:class:`.Callback` - callback and signal functions + + * :py:class:`.UpdateCallback` - callback functionality + +- :py:class:`.DbDjango` - read and write implementation to Django-based + databases + + * :py:class:`.DbWriteBase` - virtual and implementation-independent methods + for reading data + + * :py:class:`.DbReadBase` - virtual and implementation-independent methods + for reading data + +DbBsddb +======= + +The :py:class:`.DbBsddb` interface defines a hierarchical database +(non-relational) written in +`PyBSDDB <http://www.jcea.es/programacion/pybsddb.htm>`_. There is no +such thing as a database schema, and the meaning of the data is +defined in the Python classes above. The data is stored as pickled +tuples and unserialized into the primary data types (below). + +DbDjango +======== + +The DbDjango interface defines the Gramps data in terms of +*models* and *relations* from the +`Django project <http://www.djangoproject.com/>`_. The database +backend can be any implementation that supports Django, including +such popular SQL implementations as sqlite, MySQL, Postgresql, and +Oracle. The data is retrieved from the SQL fields, serialized and +then unserialized into the primary data types (below). + +More details can be found in the manual's +`Using database API <http://www.gramps-project.org/wiki/index.php?title=Using_database_API>`_. +""" + +from gramps.gen.db.base import * +from gramps.gen.db.dbconst import * +from .cursor import * +from .read import * +from .bsddbtxn import * +from gramps.gen.db.txn import * +from .undoredo import * +from gramps.gen.db.exceptions import * +from .write import * +from .backup import backup, restore diff --git a/gramps/plugins/database/bsddb_support/backup.py b/gramps/plugins/database/bsddb_support/backup.py new file mode 100644 index 000000000..29dd46c54 --- /dev/null +++ b/gramps/plugins/database/bsddb_support/backup.py @@ -0,0 +1,213 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2007 Donald N. Allingham +# Copyright (C) 2011 Tim G L Lyons +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# gen/db/backup.py + +""" +Description +=========== + +This module Provides backup and restore functions for a database. The +backup function saves the data into backup files, while the restore +function loads the data back into a database. + +You should only restore the data into an empty database. + +Implementation +============== + +Not all of the database tables need to be backed up, since many are +automatically generated from the others. The tables that are backed up +are the primary tables and the metadata table. + +The database consists of a table of "pickled" tuples. Each of the +primary tables is "walked", and the pickled tuple is extracted, and +written to the backup file. + +Restoring the data is just as simple. The backup file is parsed an +entry at a time, and inserted into the associated database table. The +derived tables are built automatically as the items are entered into +db. +""" + +#------------------------------------------------------------------------- +# +# load standard python libraries +# +#------------------------------------------------------------------------- +import os +import pickle + +#------------------------------------------------------------------------ +# +# Gramps libs +# +#------------------------------------------------------------------------ +from gramps.gen.db.exceptions import DbException +from .write import FAMILY_TBL, PLACES_TBL, SOURCES_TBL, MEDIA_TBL, \ + EVENTS_TBL, PERSON_TBL, REPO_TBL, NOTE_TBL, TAG_TBL, META, CITATIONS_TBL + +#------------------------------------------------------------------------ +# +# Set up logging +# +#------------------------------------------------------------------------ +import logging +LOG = logging.getLogger(".Backup") + +def backup(database): + """ + Exports the database to a set of backup files. These files consist + of the pickled database tables, one file for each table. + + The heavy lifting is done by the private :py:func:`__do__export` function. + The purpose of this function is to catch any exceptions that occur. + + :param database: database instance to backup + :type database: DbDir + """ + try: + __do_export(database) + except (OSError, IOError) as msg: + raise DbException(str(msg)) + +def __mk_backup_name(database, base): + """ + Return the backup name of the database table + + :param database: database instance + :type database: DbDir + :param base: base name of the table + :type base: str + """ + return os.path.join(database.get_save_path(), base + ".gbkp") + +def __mk_tmp_name(database, base): + """ + Return the temporary backup name of the database table + + :param database: database instance + :type database: DbDir + :param base: base name of the table + :type base: str + """ + return os.path.join(database.get_save_path(), base + ".gbkp.new") + +def __do_export(database): + """ + Loop through each table of the database, saving the pickled data + a file. + + :param database: database instance to backup + :type database: DbDir + """ + try: + for (base, tbl) in __build_tbl_map(database): + backup_name = __mk_tmp_name(database, base) + backup_table = open(backup_name, 'wb') + + cursor = tbl.cursor() + data = cursor.first() + while data: + pickle.dump(data, backup_table, 2) + data = cursor.next() + cursor.close() + backup_table.close() + except (IOError,OSError): + return + + for (base, tbl) in __build_tbl_map(database): + new_name = __mk_backup_name(database, base) + old_name = __mk_tmp_name(database, base) + if os.path.isfile(new_name): + os.unlink(new_name) + os.rename(old_name, new_name) + +def restore(database): + """ + Restores the database to a set of backup files. These files consist + of the pickled database tables, one file for each table. + + The heavy lifting is done by the private :py:func:`__do__restore` function. + The purpose of this function is to catch any exceptions that occur. + + :param database: database instance to restore + :type database: DbDir + """ + try: + __do_restore(database) + except (OSError, IOError) as msg: + raise DbException(str(msg)) + +def __do_restore(database): + """ + Loop through each table of the database, restoring the pickled data + to the appropriate database file. + + :param database: database instance to backup + :type database: DbDir + """ + for (base, tbl) in __build_tbl_map(database): + backup_name = __mk_backup_name(database, base) + backup_table = open(backup_name, 'rb') + __load_tbl_txn(database, backup_table, tbl) + + database.rebuild_secondary() + +def __load_tbl_txn(database, backup_table, tbl): + """ + Return the temporary backup name of the database table + + :param database: database instance + :type database: DbDir + :param backup_table: file containing the backup data + :type backup_table: file + :param tbl: Berkeley db database table + :type tbl: Berkeley db database table + """ + try: + while True: + data = pickle.load(backup_table) + txn = database.env.txn_begin() + tbl.put(data[0], data[1], txn=txn) + txn.commit() + except EOFError: + backup_table.close() + +def __build_tbl_map(database): + """ + Builds a table map of names to database tables. + + :param database: database instance to backup + :type database: DbDir + """ + return [ + ( PERSON_TBL, database.person_map.db), + ( FAMILY_TBL, database.family_map.db), + ( PLACES_TBL, database.place_map.db), + ( SOURCES_TBL, database.source_map.db), + ( CITATIONS_TBL, database.citation_map.db), + ( REPO_TBL, database.repository_map.db), + ( NOTE_TBL, database.note_map.db), + ( MEDIA_TBL, database.media_map.db), + ( EVENTS_TBL, database.event_map.db), + ( TAG_TBL, database.tag_map.db), + ( META, database.metadata.db), + ] diff --git a/gramps/gen/db/bsddbtxn.py b/gramps/plugins/database/bsddb_support/bsddbtxn.py similarity index 100% rename from gramps/gen/db/bsddbtxn.py rename to gramps/plugins/database/bsddb_support/bsddbtxn.py diff --git a/gramps/gen/db/cursor.py b/gramps/plugins/database/bsddb_support/cursor.py similarity index 100% rename from gramps/gen/db/cursor.py rename to gramps/plugins/database/bsddb_support/cursor.py diff --git a/gramps/gen/db/dictionary.py b/gramps/plugins/database/bsddb_support/dictionary.py similarity index 98% rename from gramps/gen/db/dictionary.py rename to gramps/plugins/database/bsddb_support/dictionary.py index d3974774a..2d785ef6e 100644 --- a/gramps/gen/db/dictionary.py +++ b/gramps/plugins/database/bsddb_support/dictionary.py @@ -40,18 +40,18 @@ from . import (PERSON_KEY, NOTE_KEY, TAG_KEY) -from ..utils.id import create_id -from ..lib.researcher import Researcher -from ..lib.mediaobj import MediaObject -from ..lib.person import Person -from ..lib.family import Family -from ..lib.src import Source -from ..lib.citation import Citation -from ..lib.event import Event -from ..lib.place import Place -from ..lib.repo import Repository -from ..lib.note import Note -from ..lib.tag import Tag +from gramps.gen.utils.id import create_id +from gramps.gen.lib.researcher import Researcher +from gramps.gen.lib.mediaobj import MediaObject +from gramps.gen.lib.person import Person +from gramps.gen.lib.family import Family +from gramps.gen.lib.src import Source +from gramps.gen.lib.citation import Citation +from gramps.gen.lib.event import Event +from gramps.gen.lib.place import Place +from gramps.gen.lib.repo import Repository +from gramps.gen.lib.note import Note +from gramps.gen.lib.tag import Tag class Cursor(object): """ diff --git a/gramps/gen/db/read.py b/gramps/plugins/database/bsddb_support/read.py similarity index 98% rename from gramps/gen/db/read.py rename to gramps/plugins/database/bsddb_support/read.py index 87dc112d6..d51810581 100644 --- a/gramps/gen/db/read.py +++ b/gramps/plugins/database/bsddb_support/read.py @@ -53,30 +53,32 @@ import logging # GRAMPS libraries # #------------------------------------------------------------------------- -from ..lib.mediaobj import MediaObject -from ..lib.person import Person -from ..lib.family import Family -from ..lib.src import Source -from ..lib.citation import Citation -from ..lib.event import Event -from ..lib.place import Place -from ..lib.repo import Repository -from ..lib.note import Note -from ..lib.tag import Tag -from ..lib.genderstats import GenderStats -from ..lib.researcher import Researcher -from ..lib.nameorigintype import NameOriginType +from gramps.gen.lib.mediaobj import MediaObject +from gramps.gen.lib.person import Person +from gramps.gen.lib.family import Family +from gramps.gen.lib.src import Source +from gramps.gen.lib.citation import Citation +from gramps.gen.lib.event import Event +from gramps.gen.lib.place import Place +from gramps.gen.lib.repo import Repository +from gramps.gen.lib.note import Note +from gramps.gen.lib.tag import Tag +from gramps.gen.lib.genderstats import GenderStats +from gramps.gen.lib.researcher import Researcher +from gramps.gen.lib.nameorigintype import NameOriginType -from .dbconst import * -from ..utils.callback import Callback -from ..utils.cast import conv_dbstr_to_unicode -from . import (BsddbBaseCursor, DbReadBase) -from ..utils.id import create_id -from ..errors import DbError -from ..constfunc import handle2internal, get_env_var -from ..const import GRAMPS_LOCALE as glocale +from gramps.gen.utils.callback import Callback +from gramps.gen.utils.cast import conv_dbstr_to_unicode +from . import BsddbBaseCursor +from gramps.gen.db.base import DbReadBase +from gramps.gen.utils.id import create_id +from gramps.gen.errors import DbError +from gramps.gen.constfunc import handle2internal, get_env_var +from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext +from gramps.gen.db.dbconst import * + LOG = logging.getLogger(DBLOGNAME) LOG = logging.getLogger(".citation") #------------------------------------------------------------------------- @@ -84,7 +86,6 @@ LOG = logging.getLogger(".citation") # constants # #------------------------------------------------------------------------- -from .dbconst import * _SIGBASE = ('person', 'family', 'source', 'citation', 'event', 'media', 'place', 'repository', diff --git a/gramps/gen/db/undoredo.py b/gramps/plugins/database/bsddb_support/undoredo.py similarity index 98% rename from gramps/gen/db/undoredo.py rename to gramps/plugins/database/bsddb_support/undoredo.py index 7fed4d876..da6b3368e 100644 --- a/gramps/gen/db/undoredo.py +++ b/gramps/plugins/database/bsddb_support/undoredo.py @@ -43,7 +43,7 @@ except: DBPageNotFoundError = 0 DBInvalidArgError = 0 -from ..const import GRAMPS_LOCALE as glocale +from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext #------------------------------------------------------------------------- @@ -51,10 +51,10 @@ _ = glocale.translation.gettext # Gramps modules # #------------------------------------------------------------------------- -from ..constfunc import conv_to_unicode, handle2internal, win -from .dbconst import * +from gramps.gen.constfunc import conv_to_unicode, handle2internal, win +from gramps.gen.db.dbconst import * from . import BSDDBTxn -from ..errors import DbError +from gramps.gen.errors import DbError #------------------------------------------------------------------------- # diff --git a/gramps/gen/db/upgrade.py b/gramps/plugins/database/bsddb_support/upgrade.py similarity index 98% rename from gramps/gen/db/upgrade.py rename to gramps/plugins/database/bsddb_support/upgrade.py index 29c5eb8ab..100014e65 100644 --- a/gramps/gen/db/upgrade.py +++ b/gramps/plugins/database/bsddb_support/upgrade.py @@ -39,22 +39,23 @@ from bsddb3 import db # Gramps modules # #------------------------------------------------------------------------- -from ..const import GRAMPS_LOCALE as glocale +from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -from ..constfunc import handle2internal -from ..lib.markertype import MarkerType -from ..lib.nameorigintype import NameOriginType -from ..lib.place import Place -from ..lib.placeref import PlaceRef -from ..lib.placetype import PlaceType -from ..lib.eventtype import EventType -from ..lib.tag import Tag -from ..utils.file import create_checksum -from ..utils.id import create_id +from gramps.gen.constfunc import handle2internal +from gramps.gen.lib.markertype import MarkerType +from gramps.gen.lib.nameorigintype import NameOriginType +from gramps.gen.lib.place import Place +from gramps.gen.lib.placeref import PlaceRef +from gramps.gen.lib.placetype import PlaceType +from gramps.gen.lib.eventtype import EventType +from gramps.gen.lib.tag import Tag +from gramps.gen.utils.file import create_checksum +from gramps.gen.utils.id import create_id from . import BSDDBTxn from .write import _mkname, SURNAMES -from .dbconst import (PERSON_KEY, FAMILY_KEY, EVENT_KEY, - MEDIA_KEY, PLACE_KEY, REPOSITORY_KEY, SOURCE_KEY) +from gramps.gen.db.dbconst import (PERSON_KEY, FAMILY_KEY, EVENT_KEY, + MEDIA_KEY, PLACE_KEY, REPOSITORY_KEY, + SOURCE_KEY) from gramps.gui.dialog import (InfoDialog) LOG = logging.getLogger(".upgrade") @@ -324,7 +325,7 @@ def upgrade_datamap_17(datamap): """ new_srcattr_list = [] private = False - from ..lib.srcattrtype import SrcAttributeType + from gramps.gen.lib.srcattrtype import SrcAttributeType for (key, value) in datamap.items(): the_type = SrcAttributeType(key).serialize() new_srcattr_list.append((private, the_type, value)) diff --git a/gramps/gen/db/write.py b/gramps/plugins/database/bsddb_support/write.py similarity index 99% rename from gramps/gen/db/write.py rename to gramps/plugins/database/bsddb_support/write.py index bcf4ece07..55dcf771d 100644 --- a/gramps/gen/db/write.py +++ b/gramps/plugins/database/bsddb_support/write.py @@ -56,33 +56,34 @@ except: # Gramps modules # #------------------------------------------------------------------------- -from ..lib.person import Person -from ..lib.family import Family -from ..lib.src import Source -from ..lib.citation import Citation -from ..lib.event import Event -from ..lib.place import Place -from ..lib.repo import Repository -from ..lib.mediaobj import MediaObject -from ..lib.note import Note -from ..lib.tag import Tag -from ..lib.genderstats import GenderStats -from ..lib.researcher import Researcher +from gramps.gen.lib.person import Person +from gramps.gen.lib.family import Family +from gramps.gen.lib.src import Source +from gramps.gen.lib.citation import Citation +from gramps.gen.lib.event import Event +from gramps.gen.lib.place import Place +from gramps.gen.lib.repo import Repository +from gramps.gen.lib.mediaobj import MediaObject +from gramps.gen.lib.note import Note +from gramps.gen.lib.tag import Tag +from gramps.gen.lib.genderstats import GenderStats +from gramps.gen.lib.researcher import Researcher from . import (DbBsddbRead, DbWriteBase, BSDDBTxn, DbTxn, BsddbBaseCursor, BsddbDowngradeError, DbVersionError, DbEnvironmentError, DbUpgradeRequiredError, find_surname, - find_byte_surname, find_surname_name, DbUndoBSDDB as DbUndo, - exceptions) -from .dbconst import * -from ..utils.callback import Callback -from ..utils.cast import conv_dbstr_to_unicode -from ..utils.id import create_id -from ..updatecallback import UpdateCallback -from ..errors import DbError -from ..constfunc import (win, conv_to_unicode, handle2internal, + find_byte_surname, find_surname_name, DbUndoBSDDB as DbUndo) + +from gramps.gen.db import exceptions +from gramps.gen.db.dbconst import * +from gramps.gen.utils.callback import Callback +from gramps.gen.utils.cast import conv_dbstr_to_unicode +from gramps.gen.utils.id import create_id +from gramps.gen.updatecallback import UpdateCallback +from gramps.gen.errors import DbError +from gramps.gen.constfunc import (win, conv_to_unicode, handle2internal, get_env_var) -from ..const import HOME_DIR, GRAMPS_LOCALE as glocale +from gramps.gen.const import HOME_DIR, GRAMPS_LOCALE as glocale _ = glocale.translation.gettext _LOG = logging.getLogger(DBLOGNAME) From 2dd365f8bcc6ed738ea8e07feb738b488923f429 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Tue, 12 May 2015 19:09:17 -0400 Subject: [PATCH 002/105] Moved make_database to DbState --- gramps/cli/arghandler.py | 3 +-- gramps/cli/clidbman.py | 5 ++--- gramps/cli/grampscli.py | 3 +-- gramps/gen/db/__init__.py | 15 --------------- gramps/gen/dbstate.py | 26 +++++++++++++++++++++++--- gramps/gui/dbloader.py | 3 +-- gramps/gui/dbman.py | 7 +++---- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/gramps/cli/arghandler.py b/gramps/cli/arghandler.py index c7a5f5a4a..47e9b8da6 100644 --- a/gramps/cli/arghandler.py +++ b/gramps/cli/arghandler.py @@ -44,7 +44,6 @@ import sys #------------------------------------------------------------------------- from gramps.gen.recentfiles import recent_files from gramps.gen.utils.file import rm_tempdir, get_empty_tempdir -from gramps.gen.db import make_database from .clidbman import CLIDbManager, NAME_FILE, find_locker_name from gramps.gen.plug import BasePluginManager @@ -492,7 +491,7 @@ class ArgHandler(object): else: self.imp_db_path = get_empty_tempdir("import_dbdir") - newdb = make_database("bsddb", self.dbstate) + newdb = self.dbstate.make_database("bsddb") newdb.write_version(self.imp_db_path) try: diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py index 6ca96ec0d..c83034e7a 100644 --- a/gramps/cli/clidbman.py +++ b/gramps/cli/clidbman.py @@ -54,7 +54,6 @@ _LOG = logging.getLogger(DBLOGNAME) #------------------------------------------------------------------------- from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -from gramps.gen.db import make_database from gramps.gen.plug import BasePluginManager from gramps.gen.config import config from gramps.gen.constfunc import win, conv_to_unicode @@ -295,7 +294,7 @@ class CLIDbManager(object): if create_db: # write the version number into metadata - newdb = make_database("bsddb", self.dbstate) + newdb = self.dbstate.make_database("bsddb") newdb.write_version(new_path) (tval, last) = time_val(new_path) @@ -362,7 +361,7 @@ class CLIDbManager(object): # Create a new database self.__start_cursor(_("Importing data...")) - dbase = make_database("bsddb", self.dbstate) + dbase = self.dbstate.make_database("bsddb") dbase.load(new_path, user.callback) import_function = plugin.get_import_function() diff --git a/gramps/cli/grampscli.py b/gramps/cli/grampscli.py index 00b66d352..d1d3cfc07 100644 --- a/gramps/cli/grampscli.py +++ b/gramps/cli/grampscli.py @@ -49,7 +49,6 @@ from gramps.gen.config import config from gramps.gen.const import PLUGINS_DIR, USER_PLUGINS from gramps.gen.errors import DbError from gramps.gen.dbstate import DbState -from gramps.gen.db import make_database from gramps.gen.db.exceptions import (DbUpgradeRequiredError, BsddbDowngradeError, DbVersionError, @@ -152,7 +151,7 @@ class CLIDbLoader(object): else: mode = 'w' - db = make_database("bsddb", self.dbstate) + db = self.dbstate.make_database("bsddb") self.dbstate.change_database(db) self.dbstate.db.disable_signals() diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py index b977887b7..4ddaf5386 100644 --- a/gramps/gen/db/__init__.py +++ b/gramps/gen/db/__init__.py @@ -94,18 +94,3 @@ from .txn import * from .exceptions import * #from .write import * #from .backup import backup, restore - -def make_database(id, dbstate): - from gramps.cli.grampscli import CLIManager - from gramps.gen.plug import BasePluginManager - from gramps.cli.user import User - - climanager = CLIManager(dbstate, setloader=False, user=User()) # do not load db_loader - climanager.do_reg_plugins(dbstate, None) - pmgr = BasePluginManager.get_instance() - pdata = pmgr.get_plugin(id) - - mod = pmgr.load_plugin(pdata) - database = getattr(mod, pdata.databaseclass) - return database() - diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py index 12ee68ea9..04a8e683d 100644 --- a/gramps/gen/dbstate.py +++ b/gramps/gen/dbstate.py @@ -24,7 +24,6 @@ Provide the database state class """ from .db import DbReadBase -from .db import make_database from .proxy.proxybase import ProxyDbBase from .utils.callback import Callback from .config import config @@ -45,7 +44,7 @@ class DbState(Callback): just a place holder until a real DB is assigned. """ Callback.__init__(self) - self.db = make_database("bsddb", self) + self.db = self.make_database("bsddb") self.open = False self.stack = [] @@ -88,7 +87,7 @@ class DbState(Callback): """ self.emit('no-database', ()) self.db.close() - self.db = make_database("bsddb", self) + self.db = self.make_database("bsddb") self.db.db_is_open = False self.open = False self.emit('database-changed', (self.db, )) @@ -122,3 +121,24 @@ class DbState(Callback): """ self.db = self.stack.pop() self.emit('database-changed', (self.db, )) + + def make_database(self, id): + """ + Make a database, given a plugin id. + """ + from .plug import BasePluginManager + from .const import PLUGINS_DIR, USER_PLUGINS + + pmgr = BasePluginManager.get_instance() + pdata = pmgr.get_plugin(id) + + if not pdata: + # This might happen if using gramps from outside, and + # we haven't loaded plugins yet + pmgr.reg_plugins(PLUGINS_DIR, self, None) + pmgr.reg_plugins(USER_PLUGINS, self, None, load_on_reg=True) + pdata = pmgr.get_plugin(id) + + mod = pmgr.load_plugin(pdata) + database = getattr(mod, pdata.databaseclass) + return database() diff --git a/gramps/gui/dbloader.py b/gramps/gui/dbloader.py index a0e180f90..a0105d9a3 100644 --- a/gramps/gui/dbloader.py +++ b/gramps/gui/dbloader.py @@ -55,7 +55,6 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext from gramps.cli.grampscli import CLIDbLoader from gramps.gen.config import config -from gramps.gen.db import make_database from gramps.gen.db.exceptions import (DbUpgradeRequiredError, BsddbDowngradeError, DbVersionError, @@ -305,7 +304,7 @@ class DbLoader(CLIDbLoader): else: mode = 'w' - db = make_database("bsddb", self.dbstate) + db = self.dbstate.make_database("bsddb") db.disable_signals() self.dbstate.no_database() diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py index cfc2d4a68..a9ece2272 100644 --- a/gramps/gui/dbman.py +++ b/gramps/gui/dbman.py @@ -73,7 +73,6 @@ _ = glocale.translation.gettext from gramps.gen.const import URL_WIKISTRING from .user import User from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2 -from gramps.gen.db import make_database from .pluginmanager import GuiPluginManager from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val from .ddtargets import DdTargets @@ -536,7 +535,7 @@ class DbManager(CLIDbManager): self.__start_cursor(_("Extracting archive...")) - dbase = make_database("bsddb", self.dbstate) + dbase = self.dbstate.make_database("bsddb") dbase.load(new_path, None) self.__start_cursor(_("Importing archive...")) @@ -723,10 +722,10 @@ class DbManager(CLIDbManager): fname = os.path.join(dirname, filename) os.unlink(fname) - newdb = make_database("bsddb", self.dbstate) + newdb = self.dbstate.make_database("bsddb") newdb.write_version(dirname) - dbase = make_database("bsddb", self.dbstate) + dbase = self.dbstate.make_database("bsddb") dbase.set_save_path(dirname) dbase.load(dirname, None) From af0b308b1e92644aea7730dde53c62af4236a1b3 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Tue, 12 May 2015 22:03:10 -0400 Subject: [PATCH 003/105] Only BSDDB plugin needs bsddb3; back/restore moved to db --- gramps/cli/clidbman.py | 53 +----- gramps/gen/db/__init__.py | 6 - gramps/gen/db/backup.py | 8 - gramps/gen/db/dbconst.py | 15 +- gramps/gen/plug/_gramplet.py | 6 - gramps/grampsapp.py | 8 - gramps/gui/aboutdialog.py | 9 +- gramps/gui/dbman.py | 3 +- gramps/gui/editors/editfamily.py | 10 +- gramps/gui/logger/_errorreportassistant.py | 9 +- gramps/gui/viewmanager.py | 3 +- .../database/bsddb_support/__init__.py | 1 - .../plugins/database/bsddb_support/backup.py | 139 ---------------- .../plugins/database/bsddb_support/summary.py | 62 +++++++ .../database/bsddb_support}/test/__init__.py | 0 .../bsddb_support}/test/cursor_test.py | 0 .../database/bsddb_support}/test/db_test.py | 0 .../bsddb_support}/test/grampsdbtestbase.py | 0 .../bsddb_support}/test/reference_map_test.py | 0 .../plugins/database/bsddb_support/write.py | 156 ++++++++++++++++-- .../{bsddb_support => }/dictionary.py | 0 gramps/plugins/gramplet/leak.py | 8 +- 22 files changed, 239 insertions(+), 257 deletions(-) delete mode 100644 gramps/gen/db/backup.py create mode 100644 gramps/plugins/database/bsddb_support/summary.py rename gramps/{gen/db => plugins/database/bsddb_support}/test/__init__.py (100%) rename gramps/{gen/db => plugins/database/bsddb_support}/test/cursor_test.py (100%) rename gramps/{gen/db => plugins/database/bsddb_support}/test/db_test.py (100%) rename gramps/{gen/db => plugins/database/bsddb_support}/test/grampsdbtestbase.py (100%) rename gramps/{gen/db => plugins/database/bsddb_support}/test/reference_map_test.py (100%) rename gramps/plugins/database/{bsddb_support => }/dictionary.py (100%) diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py index c83034e7a..7c77876f1 100644 --- a/gramps/cli/clidbman.py +++ b/gramps/cli/clidbman.py @@ -137,57 +137,8 @@ class CLIDbManager(object): current DB. Returns ("Unknown", "Unknown", "Unknown") if invalid DB or other error. """ - from bsddb3 import dbshelve, db - - from gramps.gen.db import META, PERSON_TBL - from gramps.gen.db.dbconst import BDBVERSFN - - bdbversion_file = os.path.join(dirpath, BDBVERSFN) - if os.path.isfile(bdbversion_file): - vers_file = open(bdbversion_file) - bsddb_version = vers_file.readline().strip() - else: - return "Unknown", "Unknown", "Unknown" - - current_bsddb_version = str(db.version()) - if bsddb_version != current_bsddb_version: - return "Unknown", bsddb_version, "Unknown" - - env = db.DBEnv() - flags = db.DB_CREATE | db.DB_PRIVATE |\ - db.DB_INIT_MPOOL |\ - db.DB_INIT_LOG | db.DB_INIT_TXN - try: - env.open(dirpath, flags) - except Exception as msg: - LOG.warning("Error opening db environment for '%s': %s" % - (name, str(msg))) - try: - env.close() - except Exception as msg: - LOG.warning("Error closing db environment for '%s': %s" % - (name, str(msg))) - return "Unknown", bsddb_version, "Unknown" - dbmap1 = dbshelve.DBShelf(env) - fname = os.path.join(dirpath, META + ".db") - try: - dbmap1.open(fname, META, db.DB_HASH, db.DB_RDONLY) - except: - env.close() - return "Unknown", bsddb_version, "Unknown" - schema_version = dbmap1.get(b'version', default=None) - dbmap1.close() - dbmap2 = dbshelve.DBShelf(env) - fname = os.path.join(dirpath, PERSON_TBL + ".db") - try: - dbmap2.open(fname, PERSON_TBL, db.DB_HASH, db.DB_RDONLY) - except: - env.close() - return "Unknown", bsddb_version, schema_version - count = len(dbmap2) - dbmap2.close() - env.close() - return (count, bsddb_version, schema_version) + ## Maybe return the txt file contents, for now + return ("Unknown", "Unknown", "Unknown") def family_tree_summary(self): """ diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py index 4ddaf5386..066571e38 100644 --- a/gramps/gen/db/__init__.py +++ b/gramps/gen/db/__init__.py @@ -86,11 +86,5 @@ More details can be found in the manual's from .base import * from .dbconst import * -#from .cursor import * -#from .read import * -#from .bsddbtxn import * from .txn import * -#from .undoredo import * from .exceptions import * -#from .write import * -#from .backup import backup, restore diff --git a/gramps/gen/db/backup.py b/gramps/gen/db/backup.py deleted file mode 100644 index 52d4d5f05..000000000 --- a/gramps/gen/db/backup.py +++ /dev/null @@ -1,8 +0,0 @@ -import logging -LOG = logging.getLogger(".Backup") - -def backup(database): - print("FIXME") - -def restore(database): - print("FIXME") diff --git a/gramps/gen/db/dbconst.py b/gramps/gen/db/dbconst.py index ba3514afb..7b91ab7e6 100644 --- a/gramps/gen/db/dbconst.py +++ b/gramps/gen/db/dbconst.py @@ -31,8 +31,7 @@ Declare constants used by database modules __all__ = ( ('DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO', 'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN', - 'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'DBFLAGS_O', 'DBFLAGS_R', - 'DBFLAGS_D', 'SCHVERSFN', 'PCKVERSFN' + 'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN' ) + ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY', @@ -60,18 +59,6 @@ DBLOCKS = 100000 # Maximum number of locks supported DBOBJECTS = 100000 # Maximum number of simultaneously locked objects DBUNDO = 1000 # Maximum size of undo buffer -try: - from bsddb3.db import DB_CREATE, DB_AUTO_COMMIT, DB_DUP, DB_DUPSORT, DB_RDONLY - DBFLAGS_O = DB_CREATE | DB_AUTO_COMMIT # Default flags for database open - DBFLAGS_R = DB_RDONLY # Flags to open a database read-only - DBFLAGS_D = DB_DUP | DB_DUPSORT # Default flags for duplicate keys -except: - print("WARNING: no bsddb support") - # FIXME: make this more abstract to deal with other backends, or do not import - DBFLAGS_O = DB_CREATE = DB_AUTO_COMMIT = 0 - DBFLAGS_R = DB_RDONLY = 0 - DBFLAGS_D = DB_DUP = DB_DUPSORT = 0 - PERSON_KEY = 0 FAMILY_KEY = 1 SOURCE_KEY = 2 diff --git a/gramps/gen/plug/_gramplet.py b/gramps/gen/plug/_gramplet.py index b812a7fb7..0159a8d03 100644 --- a/gramps/gen/plug/_gramplet.py +++ b/gramps/gen/plug/_gramplet.py @@ -320,8 +320,6 @@ class Gramplet(object): self._idle_id = 0 LOG.debug("gramplet updater: %s : One time, done!" % self.gui.title) return False - # FIXME: find out why Data Entry has this error, or just ignore it - import bsddb3 as bsddb try: retval = next(self._generator) if not retval: @@ -332,10 +330,6 @@ class Gramplet(object): LOG.debug("gramplet updater: %s: return %s" % (self.gui.title, retval)) return retval - except bsddb.db.DBCursorClosedError: - # not sure why---caused by Data Entry Gramplet - LOG.warn("bsddb.db.DBCursorClosedError in: %s" % self.gui.title) - return False except StopIteration: self._idle_id = 0 self._generator.close() diff --git a/gramps/grampsapp.py b/gramps/grampsapp.py index b9e4a6015..e2d7215e7 100644 --- a/gramps/grampsapp.py +++ b/gramps/grampsapp.py @@ -136,14 +136,6 @@ if not sys.version_info >= MIN_PYTHON_VERSION : 'v3': MIN_PYTHON_VERSION[2]}) sys.exit(1) -try: - import bsddb3 -except ImportError: - logging.warning(_("\nYou don't have the python3 bsddb3 package installed." - " This package is needed to start Gramps.\n\n" - "Gramps will terminate now.")) - sys.exit(1) - #------------------------------------------------------------------------- # # Gramps libraries diff --git a/gramps/gui/aboutdialog.py b/gramps/gui/aboutdialog.py index 264983cc1..7ab181cc2 100644 --- a/gramps/gui/aboutdialog.py +++ b/gramps/gui/aboutdialog.py @@ -28,7 +28,12 @@ import os import sys import io -import bsddb3 as bsddb + +try: + import bsddb3 as bsddb ## ok, in try/except + BSDDB_STR = ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version())) +except: + BSDDB_STR = 'not found' ##import logging ##_LOG = logging.getLogger(".GrampsAboutDialog") @@ -125,7 +130,7 @@ class GrampsAboutDialog(Gtk.AboutDialog): "Distribution: %s") % (ellipses(str(VERSION)), ellipses(str(sys.version).replace('\n','')), - ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version())), + BSDDB_STR, ellipses(get_env_var('LANG','')), ellipses(operatingsystem), ellipses(distribution))) diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py index a9ece2272..60f28bb6f 100644 --- a/gramps/gui/dbman.py +++ b/gramps/gui/dbman.py @@ -78,7 +78,6 @@ from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val from .ddtargets import DdTargets from gramps.gen.recentfiles import rename_filename, remove_filename from .glade import Glade -from gramps.gen.db.backup import restore from gramps.gen.db.exceptions import DbException @@ -732,7 +731,7 @@ class DbManager(CLIDbManager): self.__start_cursor(_("Rebuilding database from backup files")) try: - restore(dbase) + dbase.restore() except DbException as msg: DbManager.ERROR(_("Error restoring backup data"), msg) diff --git a/gramps/gui/editors/editfamily.py b/gramps/gui/editors/editfamily.py index 61071debc..2a3ee69cf 100644 --- a/gramps/gui/editors/editfamily.py +++ b/gramps/gui/editors/editfamily.py @@ -26,7 +26,6 @@ # python modules # #------------------------------------------------------------------------- -from bsddb3 import db as bsddb_db import pickle #------------------------------------------------------------------------- @@ -1027,10 +1026,11 @@ class EditFamily(EditPrimary): ) def save(self, *obj): - try: - self.__do_save() - except bsddb_db.DBRunRecoveryError as msg: - RunDatabaseRepair(msg[1]) + ## FIXME: how to catch a specific error? + #try: + self.__do_save() + #except bsddb_db.DBRunRecoveryError as msg: + # RunDatabaseRepair(msg[1]) def __do_save(self): self.ok_button.set_sensitive(False) diff --git a/gramps/gui/logger/_errorreportassistant.py b/gramps/gui/logger/_errorreportassistant.py index d032e306f..d1972dbd6 100644 --- a/gramps/gui/logger/_errorreportassistant.py +++ b/gramps/gui/logger/_errorreportassistant.py @@ -30,7 +30,12 @@ from gi.repository import GdkPixbuf from gi.repository import GObject import cairo import sys, os -import bsddb3 as bsddb + +try: + import bsddb3 as bsddb # ok, in try/except + BSDDB_STR = str(bsddb.__version__) + " " + str(bsddb.db.version()) +except: + BSDDB_STR = 'not found' #------------------------------------------------------------------------- # @@ -166,7 +171,7 @@ class ErrorReportAssistant(Gtk.Assistant): "gobject version: %s\n"\ "cairo version : %s"\ % (str(sys.version).replace('\n',''), - str(bsddb.__version__) + " " + str(bsddb.db.version()), + BSDDB_STR, str(VERSION), get_env_var('LANG',''), operatingsystem, diff --git a/gramps/gui/viewmanager.py b/gramps/gui/viewmanager.py index 75b4271fc..a296f0122 100644 --- a/gramps/gui/viewmanager.py +++ b/gramps/gui/viewmanager.py @@ -87,7 +87,6 @@ from gramps.gen.utils.file import media_path_full from .dbloader import DbLoader from .display import display_help, display_url from .configure import GrampsPreferences -from gramps.gen.db.backup import backup from gramps.gen.db.exceptions import DbException from .aboutdialog import GrampsAboutDialog from .navigator import Navigator @@ -760,7 +759,7 @@ class ViewManager(CLIManager): self.uistate.progress.show() self.uistate.push_message(self.dbstate, _("Autobackup...")) try: - backup(self.dbstate.db) + self.dbstate.db.backup() except DbException as msg: ErrorDialog(_("Error saving backup data"), msg) self.uistate.set_busy_cursor(False) diff --git a/gramps/plugins/database/bsddb_support/__init__.py b/gramps/plugins/database/bsddb_support/__init__.py index 524ecb660..1dd4bce56 100644 --- a/gramps/plugins/database/bsddb_support/__init__.py +++ b/gramps/plugins/database/bsddb_support/__init__.py @@ -93,4 +93,3 @@ from gramps.gen.db.txn import * from .undoredo import * from gramps.gen.db.exceptions import * from .write import * -from .backup import backup, restore diff --git a/gramps/plugins/database/bsddb_support/backup.py b/gramps/plugins/database/bsddb_support/backup.py index 29dd46c54..94350da56 100644 --- a/gramps/plugins/database/bsddb_support/backup.py +++ b/gramps/plugins/database/bsddb_support/backup.py @@ -72,142 +72,3 @@ from .write import FAMILY_TBL, PLACES_TBL, SOURCES_TBL, MEDIA_TBL, \ import logging LOG = logging.getLogger(".Backup") -def backup(database): - """ - Exports the database to a set of backup files. These files consist - of the pickled database tables, one file for each table. - - The heavy lifting is done by the private :py:func:`__do__export` function. - The purpose of this function is to catch any exceptions that occur. - - :param database: database instance to backup - :type database: DbDir - """ - try: - __do_export(database) - except (OSError, IOError) as msg: - raise DbException(str(msg)) - -def __mk_backup_name(database, base): - """ - Return the backup name of the database table - - :param database: database instance - :type database: DbDir - :param base: base name of the table - :type base: str - """ - return os.path.join(database.get_save_path(), base + ".gbkp") - -def __mk_tmp_name(database, base): - """ - Return the temporary backup name of the database table - - :param database: database instance - :type database: DbDir - :param base: base name of the table - :type base: str - """ - return os.path.join(database.get_save_path(), base + ".gbkp.new") - -def __do_export(database): - """ - Loop through each table of the database, saving the pickled data - a file. - - :param database: database instance to backup - :type database: DbDir - """ - try: - for (base, tbl) in __build_tbl_map(database): - backup_name = __mk_tmp_name(database, base) - backup_table = open(backup_name, 'wb') - - cursor = tbl.cursor() - data = cursor.first() - while data: - pickle.dump(data, backup_table, 2) - data = cursor.next() - cursor.close() - backup_table.close() - except (IOError,OSError): - return - - for (base, tbl) in __build_tbl_map(database): - new_name = __mk_backup_name(database, base) - old_name = __mk_tmp_name(database, base) - if os.path.isfile(new_name): - os.unlink(new_name) - os.rename(old_name, new_name) - -def restore(database): - """ - Restores the database to a set of backup files. These files consist - of the pickled database tables, one file for each table. - - The heavy lifting is done by the private :py:func:`__do__restore` function. - The purpose of this function is to catch any exceptions that occur. - - :param database: database instance to restore - :type database: DbDir - """ - try: - __do_restore(database) - except (OSError, IOError) as msg: - raise DbException(str(msg)) - -def __do_restore(database): - """ - Loop through each table of the database, restoring the pickled data - to the appropriate database file. - - :param database: database instance to backup - :type database: DbDir - """ - for (base, tbl) in __build_tbl_map(database): - backup_name = __mk_backup_name(database, base) - backup_table = open(backup_name, 'rb') - __load_tbl_txn(database, backup_table, tbl) - - database.rebuild_secondary() - -def __load_tbl_txn(database, backup_table, tbl): - """ - Return the temporary backup name of the database table - - :param database: database instance - :type database: DbDir - :param backup_table: file containing the backup data - :type backup_table: file - :param tbl: Berkeley db database table - :type tbl: Berkeley db database table - """ - try: - while True: - data = pickle.load(backup_table) - txn = database.env.txn_begin() - tbl.put(data[0], data[1], txn=txn) - txn.commit() - except EOFError: - backup_table.close() - -def __build_tbl_map(database): - """ - Builds a table map of names to database tables. - - :param database: database instance to backup - :type database: DbDir - """ - return [ - ( PERSON_TBL, database.person_map.db), - ( FAMILY_TBL, database.family_map.db), - ( PLACES_TBL, database.place_map.db), - ( SOURCES_TBL, database.source_map.db), - ( CITATIONS_TBL, database.citation_map.db), - ( REPO_TBL, database.repository_map.db), - ( NOTE_TBL, database.note_map.db), - ( MEDIA_TBL, database.media_map.db), - ( EVENTS_TBL, database.event_map.db), - ( TAG_TBL, database.tag_map.db), - ( META, database.metadata.db), - ] diff --git a/gramps/plugins/database/bsddb_support/summary.py b/gramps/plugins/database/bsddb_support/summary.py new file mode 100644 index 000000000..ccc94d2dc --- /dev/null +++ b/gramps/plugins/database/bsddb_support/summary.py @@ -0,0 +1,62 @@ +## Removed from clidbman.py +## specific to bsddb + +from bsddb3 import dbshelve, db +import os + +from gramps.gen.db import META, PERSON_TBL +from gramps.gen.db.dbconst import BDBVERSFN + +def get_dbdir_summary(dirpath, name): + """ + Returns (people_count, bsddb_version, schema_version) of + current DB. + Returns ("Unknown", "Unknown", "Unknown") if invalid DB or other error. + """ + + bdbversion_file = os.path.join(dirpath, BDBVERSFN) + if os.path.isfile(bdbversion_file): + vers_file = open(bdbversion_file) + bsddb_version = vers_file.readline().strip() + else: + return "Unknown", "Unknown", "Unknown" + + current_bsddb_version = str(db.version()) + if bsddb_version != current_bsddb_version: + return "Unknown", bsddb_version, "Unknown" + + env = db.DBEnv() + flags = db.DB_CREATE | db.DB_PRIVATE |\ + db.DB_INIT_MPOOL |\ + db.DB_INIT_LOG | db.DB_INIT_TXN + try: + env.open(dirpath, flags) + except Exception as msg: + LOG.warning("Error opening db environment for '%s': %s" % + (name, str(msg))) + try: + env.close() + except Exception as msg: + LOG.warning("Error closing db environment for '%s': %s" % + (name, str(msg))) + return "Unknown", bsddb_version, "Unknown" + dbmap1 = dbshelve.DBShelf(env) + fname = os.path.join(dirpath, META + ".db") + try: + dbmap1.open(fname, META, db.DB_HASH, db.DB_RDONLY) + except: + env.close() + return "Unknown", bsddb_version, "Unknown" + schema_version = dbmap1.get(b'version', default=None) + dbmap1.close() + dbmap2 = dbshelve.DBShelf(env) + fname = os.path.join(dirpath, PERSON_TBL + ".db") + try: + dbmap2.open(fname, PERSON_TBL, db.DB_HASH, db.DB_RDONLY) + except: + env.close() + return "Unknown", bsddb_version, schema_version + count = len(dbmap2) + dbmap2.close() + env.close() + return (count, bsddb_version, schema_version) diff --git a/gramps/gen/db/test/__init__.py b/gramps/plugins/database/bsddb_support/test/__init__.py similarity index 100% rename from gramps/gen/db/test/__init__.py rename to gramps/plugins/database/bsddb_support/test/__init__.py diff --git a/gramps/gen/db/test/cursor_test.py b/gramps/plugins/database/bsddb_support/test/cursor_test.py similarity index 100% rename from gramps/gen/db/test/cursor_test.py rename to gramps/plugins/database/bsddb_support/test/cursor_test.py diff --git a/gramps/gen/db/test/db_test.py b/gramps/plugins/database/bsddb_support/test/db_test.py similarity index 100% rename from gramps/gen/db/test/db_test.py rename to gramps/plugins/database/bsddb_support/test/db_test.py diff --git a/gramps/gen/db/test/grampsdbtestbase.py b/gramps/plugins/database/bsddb_support/test/grampsdbtestbase.py similarity index 100% rename from gramps/gen/db/test/grampsdbtestbase.py rename to gramps/plugins/database/bsddb_support/test/grampsdbtestbase.py diff --git a/gramps/gen/db/test/reference_map_test.py b/gramps/plugins/database/bsddb_support/test/reference_map_test.py similarity index 100% rename from gramps/gen/db/test/reference_map_test.py rename to gramps/plugins/database/bsddb_support/test/reference_map_test.py diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py index 55dcf771d..42a5ad138 100644 --- a/gramps/plugins/database/bsddb_support/write.py +++ b/gramps/plugins/database/bsddb_support/write.py @@ -40,16 +40,12 @@ from functools import wraps import logging from sys import maxsize, getfilesystemencoding, version_info -try: - from bsddb3 import dbshelve, db -except: - # FIXME: make this more abstract to deal with other backends - class db: - DB_HASH = 0 - DBRunRecoveryError = 0 - DBAccessError = 0 - DBPageNotFoundError = 0 - DBInvalidArgError = 0 +from bsddb3 import dbshelve, db +from bsddb3.db import DB_CREATE, DB_AUTO_COMMIT, DB_DUP, DB_DUPSORT, DB_RDONLY + +DBFLAGS_O = DB_CREATE | DB_AUTO_COMMIT # Default flags for database open +DBFLAGS_R = DB_RDONLY # Flags to open a database read-only +DBFLAGS_D = DB_DUP | DB_DUPSORT # Default flags for duplicate keys #------------------------------------------------------------------------- # @@ -2448,6 +2444,146 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): """ return DbTxn + def backup(self): + """ + Exports the database to a set of backup files. These files consist + of the pickled database tables, one file for each table. + + The heavy lifting is done by the private :py:func:`__do__export` function. + The purpose of this function is to catch any exceptions that occur. + + :param database: database instance to backup + :type database: DbDir + """ + try: + do_export(self) + except (OSError, IOError) as msg: + raise DbException(str(msg)) + + def restore(self): + """ + Restores the database to a set of backup files. These files consist + of the pickled database tables, one file for each table. + + The heavy lifting is done by the private :py:func:`__do__restore` function. + The purpose of this function is to catch any exceptions that occur. + + :param database: database instance to restore + :type database: DbDir + """ + try: + do_restore(self) + except (OSError, IOError) as msg: + raise DbException(str(msg)) + +def mk_backup_name(database, base): + """ + Return the backup name of the database table + + :param database: database instance + :type database: DbDir + :param base: base name of the table + :type base: str + """ + return os.path.join(database.get_save_path(), base + ".gbkp") + +def mk_tmp_name(database, base): + """ + Return the temporary backup name of the database table + + :param database: database instance + :type database: DbDir + :param base: base name of the table + :type base: str + """ + return os.path.join(database.get_save_path(), base + ".gbkp.new") + +def do_export(database): + """ + Loop through each table of the database, saving the pickled data + a file. + + :param database: database instance to backup + :type database: DbDir + """ + try: + for (base, tbl) in build_tbl_map(database): + backup_name = mk_tmp_name(database, base) + backup_table = open(backup_name, 'wb') + + cursor = tbl.cursor() + data = cursor.first() + while data: + pickle.dump(data, backup_table, 2) + data = cursor.next() + cursor.close() + backup_table.close() + except (IOError,OSError): + return + + for (base, tbl) in build_tbl_map(database): + new_name = mk_backup_name(database, base) + old_name = mk_tmp_name(database, base) + if os.path.isfile(new_name): + os.unlink(new_name) + os.rename(old_name, new_name) + +def do_restore(database): + """ + Loop through each table of the database, restoring the pickled data + to the appropriate database file. + + :param database: database instance to backup + :type database: DbDir + """ + for (base, tbl) in build_tbl_map(database): + backup_name = mk_backup_name(database, base) + backup_table = open(backup_name, 'rb') + load_tbl_txn(database, backup_table, tbl) + + database.rebuild_secondary() + +def load_tbl_txn(database, backup_table, tbl): + """ + Return the temporary backup name of the database table + + :param database: database instance + :type database: DbDir + :param backup_table: file containing the backup data + :type backup_table: file + :param tbl: Berkeley db database table + :type tbl: Berkeley db database table + """ + try: + while True: + data = pickle.load(backup_table) + txn = database.env.txn_begin() + tbl.put(data[0], data[1], txn=txn) + txn.commit() + except EOFError: + backup_table.close() + +def build_tbl_map(database): + """ + Builds a table map of names to database tables. + + :param database: database instance to backup + :type database: DbDir + """ + return [ + ( PERSON_TBL, database.person_map.db), + ( FAMILY_TBL, database.family_map.db), + ( PLACES_TBL, database.place_map.db), + ( SOURCES_TBL, database.source_map.db), + ( CITATIONS_TBL, database.citation_map.db), + ( REPO_TBL, database.repository_map.db), + ( NOTE_TBL, database.note_map.db), + ( MEDIA_TBL, database.media_map.db), + ( EVENTS_TBL, database.event_map.db), + ( TAG_TBL, database.tag_map.db), + ( META, database.metadata.db), + ] + def _mkname(path, name): return os.path.join(path, name + DBEXT) diff --git a/gramps/plugins/database/bsddb_support/dictionary.py b/gramps/plugins/database/dictionary.py similarity index 100% rename from gramps/plugins/database/bsddb_support/dictionary.py rename to gramps/plugins/database/dictionary.py diff --git a/gramps/plugins/gramplet/leak.py b/gramps/plugins/gramplet/leak.py index 58ae93bd5..82d479822 100644 --- a/gramps/plugins/gramplet/leak.py +++ b/gramps/plugins/gramplet/leak.py @@ -31,7 +31,6 @@ Show uncollected objects in a window. #------------------------------------------------------------------------ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -from bsddb3.db import DBError #------------------------------------------------------------------------ # @@ -156,6 +155,13 @@ class Leak(Gramplet): parent=self.uistate.window) def display(self): + try: + from bsddb3.db import DBError + except: + class DBError(Exception): + """ + Dummy. + """ gc.collect(2) self.model.clear() count = 0 From 81ebdd97ccffc43acbb9f70936093c58789af26c Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Tue, 12 May 2015 23:08:54 -0400 Subject: [PATCH 004/105] Database backend writes its plugin id in database.txt --- gramps/gen/db/dbconst.py | 4 +++- gramps/plugins/database/bsddb_support/write.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/gramps/gen/db/dbconst.py b/gramps/gen/db/dbconst.py index 7b91ab7e6..e53535248 100644 --- a/gramps/gen/db/dbconst.py +++ b/gramps/gen/db/dbconst.py @@ -31,7 +31,8 @@ Declare constants used by database modules __all__ = ( ('DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO', 'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN', - 'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN' + 'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN', + 'DBBACKEND' ) + ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY', @@ -47,6 +48,7 @@ DBUNDOFN = "undo.db" # File name of 'undo' database DBLOCKFN = "lock" # File name of lock file DBRECOVFN = "need_recover" # File name of recovery file BDBVERSFN = "bdbversion.txt"# File name of Berkeley DB version file +DBBACKEND = "database.txt" # File name of Database backend file SCHVERSFN = "schemaversion.txt"# File name of schema version file PCKVERSFN = "pickleupgrade.txt" # Indicator that pickle has been upgrade t Python3 DBLOGNAME = ".Db" # Name of logger diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py index 42a5ad138..920bd2938 100644 --- a/gramps/plugins/database/bsddb_support/write.py +++ b/gramps/plugins/database/bsddb_support/write.py @@ -2428,6 +2428,11 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): version = str(_DBVERSION) version_file.write(version) + versionpath = os.path.join(name, str(DBBACKEND)) + _LOG.debug("Write database backend file to 'bsddb'") + with open(versionpath, "w") as version_file: + version_file.write("bsddb") + self.metadata.close() self.env.close() From 16a5665e3e871dc7e96ebc5bf7278c3dd0c3be96 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Wed, 13 May 2015 08:09:30 -0400 Subject: [PATCH 005/105] Added Django and Dictionary plugins, to be developed --- gramps/plugins/database/dbdjango.py | 2036 ++++++++++++++++ gramps/plugins/database/dictionarydb.gpr.py | 31 + .../{dictionary.py => dictionarydb.py} | 22 +- .../database/django_support/libdjango.py | 2063 +++++++++++++++++ gramps/plugins/database/djangodb.gpr.py | 31 + gramps/plugins/database/djangodb.py | 2036 ++++++++++++++++ 6 files changed, 6208 insertions(+), 11 deletions(-) create mode 100644 gramps/plugins/database/dbdjango.py create mode 100644 gramps/plugins/database/dictionarydb.gpr.py rename gramps/plugins/database/{dictionary.py => dictionarydb.py} (98%) create mode 100644 gramps/plugins/database/django_support/libdjango.py create mode 100644 gramps/plugins/database/djangodb.gpr.py create mode 100644 gramps/plugins/database/djangodb.py diff --git a/gramps/plugins/database/dbdjango.py b/gramps/plugins/database/dbdjango.py new file mode 100644 index 000000000..adb28dc54 --- /dev/null +++ b/gramps/plugins/database/dbdjango.py @@ -0,0 +1,2036 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2009 Douglas S. Blank <doug.blank@gmail.com> +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" Implements a Db interface """ + +#------------------------------------------------------------------------ +# +# Python Modules +# +#------------------------------------------------------------------------ +import sys +import time +import re +import base64 +import pickle +import os + +#------------------------------------------------------------------------ +# +# Gramps Modules +# +#------------------------------------------------------------------------ +import gramps +from gramps.gen.lib import (Person, Family, Event, Place, Repository, + Citation, Source, Note, MediaObject, Tag, + Researcher) +from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn +from gramps.gen.utils.callback import Callback +from gramps.gen.updatecallback import UpdateCallback +from gramps.gen.db import (PERSON_KEY, + FAMILY_KEY, + CITATION_KEY, + SOURCE_KEY, + EVENT_KEY, + MEDIA_KEY, + PLACE_KEY, + REPOSITORY_KEY, + NOTE_KEY) +from gramps.gen.utils.id import create_id +from django.db import transaction + +class Environment(object): + """ + Implements the Environment API. + """ + def __init__(self, db): + self.db = db + + def txn_begin(self): + return DjangoTxn("DbDjango Transaction", self.db) + +class Table(object): + """ + Implements Table interface. + """ + def __init__(self, funcs): + self.funcs = funcs + + def cursor(self): + """ + Returns a Cursor for this Table. + """ + return self.funcs["cursor_func"]() + + def put(key, data, txn=None): + self[key] = data + +class Map(dict): + """ + Implements the map API for person_map, etc. + + Takes a Table() as argument. + """ + def __init__(self, tbl, *args, **kwargs): + super().__init__(*args, **kwargs) + self.db = tbl + +class MetaCursor(object): + def __init__(self): + pass + def __enter__(self): + return self + def __iter__(self): + return self.__next__() + def __next__(self): + yield None + def __exit__(self, *args, **kwargs): + pass + def iter(self): + yield None + def first(self): + self._iter = self.__iter__() + return self.next() + def next(self): + try: + return next(self._iter) + except: + return None + def close(self): + pass + +class Cursor(object): + 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 (bytes(item.handle, "utf-8"), self.func(item.handle)) + def __exit__(self, *args, **kwargs): + pass + def iter(self): + for item in self.model.all(): + yield (bytes(item.handle, "utf-8"), self.func(item.handle)) + yield None + def first(self): + self._iter = self.__iter__() + return self.next() + def next(self): + try: + return next(self._iter) + except: + return None + def close(self): + pass + +class Bookmarks(object): + def __init__(self): + self.handles = [] + def get(self): + return self.handles + def append(self, handle): + self.handles.append(handle) + +class DjangoTxn(DbTxn): + def __init__(self, message, db, table=None): + DbTxn.__init__(self, message, db) + self.table = table + + def get(self, key, default=None, txn=None, **kwargs): + """ + Returns the data object associated with key + """ + try: + return self.table.objects(handle=key) + except: + if txn and key in txn: + return txn[key] + else: + return None + + def put(self, handle, new_data, txn): + """ + """ + txn[handle] = new_data + + def commit(self): + pass + +class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): + """ + A Gramps Database Backend. This replicates the grampsdb functions. + """ + # Set up dictionary for callback signal handler + # --------------------------------------------- + # 1. Signals for primary objects + __signals__ = dict((obj+'-'+op, signal) + for obj in + ['person', 'family', 'event', 'place', + 'source', 'citation', 'media', 'note', 'repository', 'tag'] + for op, signal in zip( + ['add', 'update', 'delete', 'rebuild'], + [(list,), (list,), (list,), None] + ) + ) + + # 2. Signals for long operations + __signals__.update(('long-op-'+op, signal) for op, signal in zip( + ['start', 'heartbeat', 'end'], + [(object,), None, None] + )) + + # 3. Special signal for change in home person + __signals__['home-person-changed'] = None + + # 4. Signal for change in person group name, parameters are + __signals__['person-groupname-rebuild'] = (str, str) + + def __init__(self, directory=None): + DbReadBase.__init__(self) + DbWriteBase.__init__(self) + Callback.__init__(self) + self._tables = { + 'Person': + { + "handle_func": self.get_person_from_handle, + "gramps_id_func": self.get_person_from_gramps_id, + "class_func": gramps.gen.lib.Person, + "cursor_func": self.get_person_cursor, + "handles_func": self.get_person_handles, + "iter_func": self.iter_people, + }, + 'Family': + { + "handle_func": self.get_family_from_handle, + "gramps_id_func": self.get_family_from_gramps_id, + "class_func": gramps.gen.lib.Family, + "cursor_func": self.get_family_cursor, + "handles_func": self.get_family_handles, + "iter_func": self.iter_families, + }, + 'Source': + { + "handle_func": self.get_source_from_handle, + "gramps_id_func": self.get_source_from_gramps_id, + "class_func": gramps.gen.lib.Source, + "cursor_func": self.get_source_cursor, + "handles_func": self.get_source_handles, + "iter_func": self.iter_sources, + }, + 'Citation': + { + "handle_func": self.get_citation_from_handle, + "gramps_id_func": self.get_citation_from_gramps_id, + "class_func": gramps.gen.lib.Citation, + "cursor_func": self.get_citation_cursor, + "handles_func": self.get_citation_handles, + "iter_func": self.iter_citations, + }, + 'Event': + { + "handle_func": self.get_event_from_handle, + "gramps_id_func": self.get_event_from_gramps_id, + "class_func": gramps.gen.lib.Event, + "cursor_func": self.get_event_cursor, + "handles_func": self.get_event_handles, + "iter_func": self.iter_events, + }, + 'Media': + { + "handle_func": self.get_object_from_handle, + "gramps_id_func": self.get_object_from_gramps_id, + "class_func": gramps.gen.lib.MediaObject, + "cursor_func": self.get_media_cursor, + "handles_func": self.get_media_object_handles, + "iter_func": self.iter_media_objects, + }, + 'Place': + { + "handle_func": self.get_place_from_handle, + "gramps_id_func": self.get_place_from_gramps_id, + "class_func": gramps.gen.lib.Place, + "cursor_func": self.get_place_cursor, + "handles_func": self.get_place_handles, + "iter_func": self.iter_places, + }, + 'Repository': + { + "handle_func": self.get_repository_from_handle, + "gramps_id_func": self.get_repository_from_gramps_id, + "class_func": gramps.gen.lib.Repository, + "cursor_func": self.get_repository_cursor, + "handles_func": self.get_repository_handles, + "iter_func": self.iter_repositories, + }, + 'Note': + { + "handle_func": self.get_note_from_handle, + "gramps_id_func": self.get_note_from_gramps_id, + "class_func": gramps.gen.lib.Note, + "cursor_func": self.get_note_cursor, + "handles_func": self.get_note_handles, + "iter_func": self.iter_notes, + }, + 'Tag': + { + "handle_func": self.get_tag_from_handle, + "gramps_id_func": None, + "class_func": gramps.gen.lib.Tag, + "cursor_func": self.get_tag_cursor, + "handles_func": self.get_tag_handles, + "iter_func": self.iter_tags, + }, + } + # 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.undo_callback = None + self.redo_callback = None + self.undo_history_callback = None + self.modified = 0 + self.txn = DjangoTxn("DbDjango Transaction", self) + self.transaction = None + # Import cache for gedcom import, uses transactions, and + # two step adding of objects. + self.import_cache = {} + self.use_import_cache = False + self.use_db_cache = True + self._directory = directory + if directory: + self.load(directory) + + def load(self, directory, pulse_progress=None, mode=None): + self._directory = directory + from django.conf import settings + default_settings = {} + settings_file = os.path.join(directory, "default_settings.py") + with open(settings_file) as f: + code = compile(f.read(), settings_file, 'exec') + exec(code, globals(), default_settings) + + class Module(object): + def __init__(self, dictionary): + self.dictionary = dictionary + def __getattr__(self, item): + return self.dictionary[item] + + try: + settings.configure(Module(default_settings)) + except RuntimeError: + # already configured; ignore + pass + + import django + django.setup() + + from django_support.libdjango import DjangoInterface + self.dji = DjangoInterface() + 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 = DjangoTxn("ID Transaction", self, self.dji.Person) + self.fid_trans = DjangoTxn("FID Transaction", self, self.dji.Family) + self.pid_trans = DjangoTxn("PID Transaction", self, self.dji.Place) + self.cid_trans = DjangoTxn("CID Transaction", self, self.dji.Citation) + self.sid_trans = DjangoTxn("SID Transaction", self, self.dji.Source) + self.oid_trans = DjangoTxn("OID Transaction", self, self.dji.Media) + self.rid_trans = DjangoTxn("RID Transaction", self, self.dji.Repository) + self.nid_trans = DjangoTxn("NID Transaction", self, self.dji.Note) + self.eid_trans = DjangoTxn("EID Transaction", self, self.dji.Event) + 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 = Environment(self) + self.person_map = Map(Table(self._tables["Person"])) + self.family_map = Map(Table(self._tables["Family"])) + self.place_map = Map(Table(self._tables["Place"])) + self.citation_map = Map(Table(self._tables["Citation"])) + self.source_map = Map(Table(self._tables["Source"])) + self.repository_map = Map(Table(self._tables["Repository"])) + self.note_map = Map(Table(self._tables["Note"])) + self.media_map = Map(Table(self._tables["Media"])) + self.event_map = Map(Table(self._tables["Event"])) + self.tag_map = Map(Table(self._tables["Tag"])) + self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) + self.name_group = {} + self.event_names = set() + self.individual_attributes = set() + self.family_attributes = set() + self.source_attributes = set() + self.child_ref_types = set() + self.family_rel_types = set() + self.event_role_names = set() + self.name_types = set() + self.origin_types = set() + self.repository_types = set() + self.note_types = set() + self.source_media_types = set() + self.url_types = set() + self.media_attributes = set() + self.place_types = set() + + def prepare_import(self): + """ + DbDjango does not commit data on gedcom import, but saves them + for later commit. + """ + self.use_import_cache = True + self.import_cache = {} + + @transaction.atomic + def commit_import(self): + """ + Commits the items that were queued up during the last gedcom + import for two step adding. + """ + # First we add the primary objects: + for key in list(self.import_cache.keys()): + obj = self.import_cache[key] + if isinstance(obj, Person): + self.dji.add_person(obj.serialize()) + elif isinstance(obj, Family): + self.dji.add_family(obj.serialize()) + elif isinstance(obj, Event): + self.dji.add_event(obj.serialize()) + elif isinstance(obj, Place): + self.dji.add_place(obj.serialize()) + elif isinstance(obj, Repository): + self.dji.add_repository(obj.serialize()) + elif isinstance(obj, Citation): + self.dji.add_citation(obj.serialize()) + elif isinstance(obj, Source): + self.dji.add_source(obj.serialize()) + elif isinstance(obj, Note): + self.dji.add_note(obj.serialize()) + elif isinstance(obj, MediaObject): + self.dji.add_media(obj.serialize()) + elif isinstance(obj, Tag): + self.dji.add_tag(obj.serialize()) + # Next we add the links: + for key in list(self.import_cache.keys()): + obj = self.import_cache[key] + if isinstance(obj, Person): + self.dji.add_person_detail(obj.serialize()) + elif isinstance(obj, Family): + self.dji.add_family_detail(obj.serialize()) + elif isinstance(obj, Event): + self.dji.add_event_detail(obj.serialize()) + elif isinstance(obj, Place): + self.dji.add_place_detail(obj.serialize()) + elif isinstance(obj, Repository): + self.dji.add_repository_detail(obj.serialize()) + elif isinstance(obj, Citation): + self.dji.add_citation_detail(obj.serialize()) + elif isinstance(obj, Source): + self.dji.add_source_detail(obj.serialize()) + elif isinstance(obj, Note): + self.dji.add_note_detail(obj.serialize()) + elif isinstance(obj, MediaObject): + self.dji.add_media_detail(obj.serialize()) + elif isinstance(obj, Tag): + self.dji.add_tag_detail(obj.serialize()) + self.use_import_cache = False + self.import_cache = {} + self.dji.update_publics() + + def transaction_commit(self, txn): + pass + + def request_rebuild(self): + # caches are ok, but let's compute public's + self.dji.update_publics() + self.emit('person-rebuild') + self.emit('family-rebuild') + self.emit('place-rebuild') + self.emit('source-rebuild') + self.emit('citation-rebuild') + self.emit('media-rebuild') + self.emit('event-rebuild') + self.emit('repository-rebuild') + self.emit('note-rebuild') + self.emit('tag-rebuild') + + def get_undodb(self): + return None + + def transaction_abort(self, txn): + pass + + @staticmethod + def _validated_id_prefix(val, default): + if isinstance(val, str) 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_<object>_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 = Researcher() + return obj + + def get_tag_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Tag.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Tag.all()] + + def get_person_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Person.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Person.all()] + + def get_family_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Family.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Family.all()] + + def get_event_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Event.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Event.all()] + + def get_citation_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Citation.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Citation.all()] + + def get_source_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Source.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Source.all()] + + def get_place_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Place.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Place.all()] + + def get_repository_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Repository.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Repository.all()] + + def get_media_object_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Media.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Media.all()] + + def get_note_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Note.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Note.all()] + + def get_media_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + media = self.dji.Media.get(handle=handle) + except: + return None + return self.make_media(media) + + def get_event_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + event = self.dji.Event.get(handle=handle) + except: + return None + return self.make_event(event) + + def get_family_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + family = self.dji.Family.get(handle=handle) + except: + return None + return self.make_family(family) + + def get_repository_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + repository = self.dji.Repository.get(handle=handle) + except: + return None + return self.make_repository(repository) + + def get_person_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + person = self.dji.Person.get(handle=handle) + except: + return None + return self.make_person(person) + + def get_tag_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + tag = self.dji.Tag.get(handle=handle) + except: + return None + return self.make_tag(tag) + + def make_repository(self, repository): + if self.use_db_cache and repository.cache: + data = repository.from_cache() + else: + data = self.dji.get_repository(repository) + return Repository.create(data) + + def make_citation(self, citation): + if self.use_db_cache and citation.cache: + data = citation.from_cache() + else: + data = self.dji.get_citation(citation) + return Citation.create(data) + + def make_source(self, source): + if self.use_db_cache and source.cache: + data = source.from_cache() + else: + data = self.dji.get_source(source) + return Source.create(data) + + def make_family(self, family): + if self.use_db_cache and family.cache: + data = family.from_cache() + else: + data = self.dji.get_family(family) + return Family.create(data) + + def make_person(self, person): + if self.use_db_cache and person.cache: + data = person.from_cache() + else: + data = self.dji.get_person(person) + return Person.create(data) + + def make_event(self, event): + if self.use_db_cache and event.cache: + data = event.from_cache() + else: + data = self.dji.get_event(event) + return Event.create(data) + + def make_note(self, note): + if self.use_db_cache and note.cache: + data = note.from_cache() + else: + data = self.dji.get_note(note) + return Note.create(data) + + def make_tag(self, tag): + data = self.dji.get_tag(tag) + return Tag.create(data) + + def make_place(self, place): + if self.use_db_cache and place.cache: + data = place.from_cache() + else: + data = self.dji.get_place(place) + return Place.create(data) + + def make_media(self, media): + if self.use_db_cache and media.cache: + data = media.from_cache() + else: + data = self.dji.get_media(media) + return MediaObject.create(data) + + def get_place_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + place = self.dji.Place.get(handle=handle) + except: + return None + return self.make_place(place) + + def get_citation_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + citation = self.dji.Citation.get(handle=handle) + except: + return None + return self.make_citation(citation) + + def get_source_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + source = self.dji.Source.get(handle=handle) + except: + return None + return self.make_source(source) + + def get_note_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + note = self.dji.Note.get(handle=handle) + except: + return None + return self.make_note(note) + + def get_object_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + media = self.dji.Media.get(handle=handle) + except: + return None + return self.make_media(media) + + def get_default_person(self): + people = self.dji.Person.all() + if people.count() > 0: + return self.make_person(people[0]) + return None + + def iter_people(self): + return (self.get_person_from_handle(person.handle) + for person in self.dji.Person.all()) + + def iter_person_handles(self): + return (person.handle for person in self.dji.Person.all()) + + def iter_families(self): + return (self.get_family_from_handle(family.handle) + for family in self.dji.Family.all()) + + def iter_family_handles(self): + return (family.handle for family in self.dji.Family.all()) + + def iter_notes(self): + return (self.get_note_from_handle(note.handle) + for note in self.dji.Note.all()) + + def iter_note_handles(self): + return (note.handle for note in self.dji.Note.all()) + + def iter_events(self): + return (self.get_event_from_handle(event.handle) + for event in self.dji.Event.all()) + + def iter_event_handles(self): + return (event.handle for event in self.dji.Event.all()) + + def iter_places(self): + return (self.get_place_from_handle(place.handle) + for place in self.dji.Place.all()) + + def iter_place_handles(self): + return (place.handle for place in self.dji.Place.all()) + + def iter_repositories(self): + return (self.get_repository_from_handle(repository.handle) + for repository in self.dji.Repository.all()) + + def iter_repository_handles(self): + return (repository.handle for repository in self.dji.Repository.all()) + + def iter_sources(self): + return (self.get_source_from_handle(source.handle) + for source in self.dji.Source.all()) + + def iter_source_handles(self): + return (source.handle for source in self.dji.Source.all()) + + def iter_citations(self): + return (self.get_citation_from_handle(citation.handle) + for citation in self.dji.Citation.all()) + + def iter_citation_handles(self): + return (citation.handle for citation in self.dji.Citation.all()) + + def iter_tags(self): + return (self.get_tag_from_handle(tag.handle) + for tag in self.dji.Tag.all()) + + def iter_tag_handles(self): + return (tag.handle for tag in self.dji.Tag.all()) + + def iter_media_objects(self): + return (self.get_media_from_handle(media.handle) + for media in self.dji.Media.all()) + + def get_tag_from_name(self, name): + try: + tag = self.dji.Tag.filter(name=name) + return self.make_tag(tag[0]) + except: + return None + + def get_person_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Person.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_person(match_list[0]) + else: + return None + + def get_family_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + try: + family = self.dji.Family.get(gramps_id=gramps_id) + except: + return None + return self.make_family(family) + + def get_source_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Source.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_source(match_list[0]) + else: + return None + + def get_citation_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Citation.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_citation(match_list[0]) + else: + return None + + def get_event_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Event.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_event(match_list[0]) + else: + return None + + def get_object_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Media.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_media(match_list[0]) + else: + return None + + def get_place_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Place.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_place(match_list[0]) + else: + return None + + def get_repository_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Repsoitory.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_repository(match_list[0]) + else: + return None + + def get_note_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Note.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_note(match_list[0]) + else: + return None + + def get_number_of_people(self): + return self.dji.Person.count() + + def get_number_of_events(self): + return self.dji.Event.count() + + def get_number_of_places(self): + return self.dji.Place.count() + + def get_number_of_tags(self): + return self.dji.Tag.count() + + def get_number_of_families(self): + return self.dji.Family.count() + + def get_number_of_notes(self): + return self.dji.Note.count() + + def get_number_of_citations(self): + return self.dji.Citation.count() + + def get_number_of_sources(self): + return self.dji.Source.count() + + def get_number_of_media_objects(self): + return self.dji.Media.count() + + def get_number_of_repositories(self): + return self.dji.Repository.count() + + def get_place_cursor(self): + return Cursor(self.dji.Place, self.get_raw_place_data) + + def get_person_cursor(self): + return Cursor(self.dji.Person, self.get_raw_person_data) + + def get_family_cursor(self): + return Cursor(self.dji.Family, self.get_raw_family_data) + + def get_event_cursor(self): + return Cursor(self.dji.Event, self.get_raw_event_data) + + def get_citation_cursor(self): + return Cursor(self.dji.Citation, self.get_raw_citation_data) + + def get_source_cursor(self): + return Cursor(self.dji.Source, self.get_raw_source_data) + + def get_note_cursor(self): + return Cursor(self.dji.Note, self.get_raw_note_data) + + def get_tag_cursor(self): + return Cursor(self.dji.Tag, self.get_raw_tag_data) + + def get_repository_cursor(self): + return Cursor(self.dji.Repository, self.get_raw_repository_data) + + def get_media_cursor(self): + return Cursor(self.dji.Media, self.get_raw_object_data) + + def has_gramps_id(self, obj_key, gramps_id): + key2table = { + PERSON_KEY: self.dji.Person, + FAMILY_KEY: self.dji.Family, + SOURCE_KEY: self.dji.Source, + CITATION_KEY: self.dji.Citation, + EVENT_KEY: self.dji.Event, + MEDIA_KEY: self.dji.Media, + PLACE_KEY: self.dji.Place, + REPOSITORY_KEY: self.dji.Repository, + NOTE_KEY: self.dji.Note, + } + table = key2table[obj_key] + return table.filter(gramps_id=gramps_id).count() > 0 + + def has_person_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Person.filter(handle=handle).count() == 1 + + def has_family_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Family.filter(handle=handle).count() == 1 + + def has_citation_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Citation.filter(handle=handle).count() == 1 + + def has_source_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Source.filter(handle=handle).count() == 1 + + def has_repository_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Repository.filter(handle=handle).count() == 1 + + def has_note_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Note.filter(handle=handle).count() == 1 + + def has_place_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Place.filter(handle=handle).count() == 1 + + def has_event_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Event.filter(handle=handle).count() == 1 + + def has_tag_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Tag.filter(handle=handle).count() == 1 + + def has_object_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Media.filter(handle=handle).count() == 1 + + 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): + try: + return self.dji.get_person(self.dji.Person.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_family_data(self, handle): + try: + return self.dji.get_family(self.dji.Family.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_citation_data(self, handle): + try: + return self.dji.get_citation(self.dji.Citation.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_source_data(self, handle): + try: + return self.dji.get_source(self.dji.Source.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_repository_data(self, handle): + try: + return self.dji.get_repository(self.dji.Repository.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_note_data(self, handle): + try: + return self.dji.get_note(self.dji.Note.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_place_data(self, handle): + try: + return self.dji.get_place(self.dji.Place.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_object_data(self, handle): + try: + return self.dji.get_media(self.dji.Media.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_tag_data(self, handle): + try: + return self.dji.get_tag(self.dji.Tag.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_event_data(self, handle): + try: + return self.dji.get_event(self.dji.Event.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def add_person(self, person, trans, set_gid=True): + if not person.handle: + person.handle = create_id() + if not person.gramps_id and set_gid: + person.gramps_id = self.find_next_person_gramps_id() + self.commit_person(person, trans) + self.emit("person-add", ([person.handle],)) + return person.handle + + def add_family(self, family, trans, set_gid=True): + if not family.handle: + family.handle = create_id() + if not family.gramps_id and set_gid: + family.gramps_id = self.find_next_family_gramps_id() + self.commit_family(family, trans) + self.emit("family-add", ([family.handle],)) + return family.handle + + def add_citation(self, citation, trans, set_gid=True): + if not citation.handle: + citation.handle = create_id() + if not citation.gramps_id and set_gid: + citation.gramps_id = self.find_next_citation_gramps_id() + self.commit_citation(citation, trans) + self.emit("citation-add", ([citation.handle],)) + return citation.handle + + def add_source(self, source, trans, set_gid=True): + if not source.handle: + source.handle = create_id() + if not source.gramps_id and set_gid: + source.gramps_id = self.find_next_source_gramps_id() + self.commit_source(source, trans) + self.emit("source-add", ([source.handle],)) + return source.handle + + def add_repository(self, repository, trans, set_gid=True): + if not repository.handle: + repository.handle = create_id() + if not repository.gramps_id and set_gid: + repository.gramps_id = self.find_next_repository_gramps_id() + self.commit_repository(repository, trans) + self.emit("repository-add", ([repository.handle],)) + return repository.handle + + def add_note(self, note, trans, set_gid=True): + if not note.handle: + note.handle = create_id() + if not note.gramps_id and set_gid: + note.gramps_id = self.find_next_note_gramps_id() + self.commit_note(note, trans) + self.emit("note-add", ([note.handle],)) + return note.handle + + def add_place(self, place, trans, set_gid=True): + if not place.handle: + place.handle = create_id() + if not place.gramps_id and 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 = create_id() + if not event.gramps_id and 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 = 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 = create_id() + if not obj.gramps_id and 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): + if self.use_import_cache: + self.import_cache[person.handle] = person + else: + raw = person.serialize() + items = self.dji.Person.filter(handle=person.handle) + if items.count() > 0: + # Hack, for the moment: delete and re-add + items[0].delete() + self.dji.add_person(person.serialize()) + self.dji.add_person_detail(person.serialize()) + if items.count() > 0: + self.emit("person-update", ([person.handle],)) + else: + self.emit("person-add", ([person.handle],)) + + def commit_family(self, family, trans, change_time=None): + if self.use_import_cache: + self.import_cache[family.handle] = family + else: + raw = family.serialize() + items = self.dji.Family.filter(handle=family.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_family(family.serialize()) + self.dji.add_family_detail(family.serialize()) + if items.count() > 0: + self.emit("family-update", ([family.handle],)) + else: + self.emit("family-add", ([family.handle],)) + + def commit_citation(self, citation, trans, change_time=None): + if self.use_import_cache: + self.import_cache[citation.handle] = citation + else: + raw = citation.serialize() + items = self.dji.Citation.filter(handle=citation.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_citation(citation.serialize()) + self.dji.add_citation_detail(citation.serialize()) + if items.count() > 0: + self.emit("citation-update", ([citation.handle],)) + else: + self.emit("citation-add", ([citation.handle],)) + + def commit_source(self, source, trans, change_time=None): + if self.use_import_cache: + self.import_cache[source.handle] = source + else: + raw = source.serialize() + items = self.dji.Source.filter(handle=source.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_source(source.serialize()) + self.dji.add_source_detail(source.serialize()) + if items.count() > 0: + self.emit("source-update", ([source.handle],)) + else: + self.emit("source-add", ([source.handle],)) + + def commit_repository(self, repository, trans, change_time=None): + if self.use_import_cache: + self.import_cache[repository.handle] = repository + else: + raw = repository.serialize() + items = self.dji.Repository.filter(handle=repository.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_repository(repository.serialize()) + self.dji.add_repository_detail(repository.serialize()) + if items.count() > 0: + self.emit("repository-update", ([repository.handle],)) + else: + self.emit("repository-add", ([repository.handle],)) + + def commit_note(self, note, trans, change_time=None): + if self.use_import_cache: + self.import_cache[note.handle] = note + else: + raw = note.serialize() + items = self.dji.Note.filter(handle=note.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_note(note.serialize()) + self.dji.add_note_detail(note.serialize()) + if items.count() > 0: + self.emit("note-update", ([note.handle],)) + else: + self.emit("note-add", ([note.handle],)) + + def commit_place(self, place, trans, change_time=None): + if self.use_import_cache: + self.import_cache[place.handle] = place + else: + raw = place.serialize() + items = self.dji.Place.filter(handle=place.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_place(place.serialize()) + self.dji.add_place_detail(place.serialize()) + if items.count() > 0: + self.emit("place-update", ([place.handle],)) + else: + self.emit("place-add", ([place.handle],)) + + def commit_event(self, event, trans, change_time=None): + if self.use_import_cache: + self.import_cache[event.handle] = event + else: + raw = event.serialize() + items = self.dji.Event.filter(handle=event.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_event(event.serialize()) + self.dji.add_event_detail(event.serialize()) + if items.count() > 0: + self.emit("event-update", ([event.handle],)) + else: + self.emit("event-add", ([event.handle],)) + + def commit_tag(self, tag, trans, change_time=None): + if self.use_import_cache: + self.import_cache[tag.handle] = tag + else: + raw = tag.serialize() + items = self.dji.Tag.filter(handle=tag.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_tag(tag.serialize()) + self.dji.add_tag_detail(tag.serialize()) + if items.count() > 0: + self.emit("tag-update", ([tag.handle],)) + else: + self.emit("tag-add", ([tag.handle],)) + + def commit_media_object(self, media, transaction, change_time=None): + """ + Commit the specified MediaObject to the database, storing the changes + as part of the transaction. + """ + if self.use_import_cache: + self.import_cache[obj.handle] = media + else: + raw = media.serialize() + items = self.dji.Media.filter(handle=media.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_media(media.serialize()) + self.dji.add_media_detail(media.serialize()) + if items.count() > 0: + self.emit("media-update", ([media.handle],)) + else: + self.emit("media-add", ([media.handle],)) + + def get_gramps_ids(self, obj_key): + key2table = { + PERSON_KEY: self.id_trans, + FAMILY_KEY: self.fid_trans, + CITATION_KEY: self.cid_trans, + SOURCE_KEY: self.sid_trans, + EVENT_KEY: self.eid_trans, + MEDIA_KEY: self.oid_trans, + PLACE_KEY: self.pid_trans, + REPOSITORY_KEY: self.rid_trans, + NOTE_KEY: self.nid_trans, + } + + table = key2table[obj_key] + return list(table.keys()) + + def transaction_begin(self, transaction): + return + + def set_researcher(self, owner): + pass + + def copy_from_db(self, db): + """ + A (possibily) implementation-specific method to get data from + db into this database. + """ + # First we add the primary objects: + for key in db._tables.keys(): + cursor = db._tables[key]["cursor_func"] + for (handle, data) in cursor(): + if key == "Person": + self.dji.add_person(data) + elif key == "Family": + self.dji.add_family(data) + elif key == "Event": + self.dji.add_event(data) + elif key == "Place": + self.dji.add_place(data) + elif key == "Repository": + self.dji.add_repository(data) + elif key == "Citation": + self.dji.add_citation(data) + elif key == "Source": + self.dji.add_source(data) + elif key == "Note": + self.dji.add_note(data) + elif key == "Media": + self.dji.add_media(data) + elif key == "Tag": + self.dji.add_tag(data) + for key in db._tables.keys(): + cursor = db._tables[key]["cursor_func"] + for (handle, data) in cursor(): + if key == "Person": + self.dji.add_person_detail(data) + elif key == "Family": + self.dji.add_family_detail(data) + elif key == "Event": + self.dji.add_event_detail(data) + elif key == "Place": + self.dji.add_place_detail(data) + elif key == "Repository": + self.dji.add_repository_detail(data) + elif key == "Citation": + self.dji.add_citation_detail(data) + elif key == "Source": + self.dji.add_source_detail(data) + elif key == "Note": + self.dji.add_note_detail(data) + elif key == "Media": + self.dji.add_media_detail(data) + elif key == "Tag": + self.dji.add_tag_detail(data) + # Next we add the links: + self.dji.update_publics() + + def get_from_name_and_handle(self, table_name, handle): + """ + Returns a gen.lib object (or None) given table_name and + handle. + + Examples: + + >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") + >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") + """ + if table_name in self._tables: + return self._tables[table_name]["handle_func"](handle) + return None + + def is_empty(self): + """ + Is the database empty? + """ + return (self.get_number_of_people() == 0 and + self.get_number_of_events() == 0 and + self.get_number_of_places() == 0 and + self.get_number_of_tags() == 0 and + self.get_number_of_families() == 0 and + self.get_number_of_notes() == 0 and + self.get_number_of_citations() == 0 and + self.get_number_of_sources() == 0 and + self.get_number_of_media_objects() == 0 and + self.get_number_of_repositories() == 0) + + __callback_map = {} + + def set_prefixes(self, person, media, family, source, citation, + place, event, repository, note): + self.set_person_id_prefix(person) + self.set_object_id_prefix(media) + self.set_family_id_prefix(family) + self.set_source_id_prefix(source) + self.set_citation_id_prefix(citation) + self.set_place_id_prefix(place) + self.set_event_id_prefix(event) + self.set_repository_id_prefix(repository) + self.set_note_id_prefix(note) + + def has_changed(self): + return False + + def find_backlink_handles(self, handle, include_classes=None): + ## FIXME: figure out how to get objects that refer + ## to this handle + return [] + + def get_note_bookmarks(self): + return self.note_bookmarks + + def get_media_bookmarks(self): + return self.media_bookmarks + + def get_repo_bookmarks(self): + return self.repo_bookmarks + + def get_citation_bookmarks(self): + return self.citation_bookmarks + + def get_source_bookmarks(self): + return self.source_bookmarks + + def get_place_bookmarks(self): + return self.place_bookmarks + + def get_event_bookmarks(self): + return self.event_bookmarks + + def get_bookmarks(self): + return self.bookmarks + + def get_family_bookmarks(self): + return self.family_bookmarks + + def get_save_path(self): + return "/tmp/" + + ## Get types: + def get_event_attribute_types(self): + """ + Return a list of all Attribute types assocated with Event instances + in the database. + """ + return list(self.event_attributes) + + def get_event_types(self): + """ + Return a list of all event types in the database. + """ + return list(self.event_names) + + def get_person_event_types(self): + """ + Deprecated: Use get_event_types + """ + return list(self.event_names) + + def get_person_attribute_types(self): + """ + Return a list of all Attribute types assocated with Person instances + in the database. + """ + return list(self.individual_attributes) + + def get_family_attribute_types(self): + """ + Return a list of all Attribute types assocated with Family instances + in the database. + """ + return list(self.family_attributes) + + def get_family_event_types(self): + """ + Deprecated: Use get_event_types + """ + return list(self.event_names) + + def get_media_attribute_types(self): + """ + Return a list of all Attribute types assocated with Media and MediaRef + instances in the database. + """ + return list(self.media_attributes) + + def get_family_relation_types(self): + """ + Return a list of all relationship types assocated with Family + instances in the database. + """ + return list(self.family_rel_types) + + def get_child_reference_types(self): + """ + Return a list of all child reference types assocated with Family + instances in the database. + """ + return list(self.child_ref_types) + + def get_event_roles(self): + """ + Return a list of all custom event role names assocated with Event + instances in the database. + """ + return list(self.event_role_names) + + def get_name_types(self): + """ + Return a list of all custom names types assocated with Person + instances in the database. + """ + return list(self.name_types) + + def get_origin_types(self): + """ + Return a list of all custom origin types assocated with Person/Surname + instances in the database. + """ + return list(self.origin_types) + + def get_repository_types(self): + """ + Return a list of all custom repository types assocated with Repository + instances in the database. + """ + return list(self.repository_types) + + def get_note_types(self): + """ + Return a list of all custom note types assocated with Note instances + in the database. + """ + return list(self.note_types) + + def get_source_attribute_types(self): + """ + Return a list of all Attribute types assocated with Source/Citation + instances in the database. + """ + return list(self.source_attributes) + + def get_source_media_types(self): + """ + Return a list of all custom source media types assocated with Source + instances in the database. + """ + return list(self.source_media_types) + + def get_url_types(self): + """ + Return a list of all custom names types assocated with Url instances + in the database. + """ + return list(self.url_types) + + def get_place_types(self): + """ + Return a list of all custom place types assocated with Place instances + in the database. + """ + return list(self.place_types) + + def get_default_handle(self): + people = self.dji.Person.all() + if people.count() > 0: + return people[0].handle + return None + + def close(self): + pass + + def get_surname_list(self): + return [] + + def is_open(self): + return True + + def get_table_names(self): + """Return a list of valid table names.""" + return list(self._tables.keys()) + + def find_initial_person(self): + return self.get_default_person() + + # Removals: + def remove_person(self, handle, txn): + self.dji.Person.filter(handle=handle)[0].delete() + self.emit("person-delete", ([handle],)) + + def remove_source(self, handle, transaction): + self.dji.Source.filter(handle=handle)[0].delete() + self.emit("source-delete", ([handle],)) + + def remove_citation(self, handle, transaction): + self.dji.Citation.filter(handle=handle)[0].delete() + self.emit("citation-delete", ([handle],)) + + def remove_event(self, handle, transaction): + self.dji.Event.filter(handle=handle)[0].delete() + self.emit("event-delete", ([handle],)) + + def remove_object(self, handle, transaction): + self.dji.Media.filter(handle=handle)[0].delete() + self.emit("media-delete", ([handle],)) + + def remove_place(self, handle, transaction): + self.dji.Place.filter(handle=handle)[0].delete() + self.emit("place-delete", ([handle],)) + + def remove_family(self, handle, transaction): + self.dji.Family.filter(handle=handle)[0].delete() + self.emit("family-delete", ([handle],)) + + def remove_repository(self, handle, transaction): + self.dji.Repository.filter(handle=handle)[0].delete() + self.emit("repository-delete", ([handle],)) + + def remove_note(self, handle, transaction): + self.dji.Note.filter(handle=handle)[0].delete() + self.emit("note-delete", ([handle],)) + + def remove_tag(self, handle, transaction): + self.dji.Tag.filter(handle=handle)[0].delete() + self.emit("tag-delete", ([handle],)) + + def remove_from_surname_list(self, person): + ## FIXME + pass + + def get_dbname(self): + return "Django Database" + + ## missing + + def find_place_child_handles(self, handle): + pass + + def get_cursor(self, table, txn=None, update=False, commit=False): + pass + + def get_from_name_and_handle(self, table_name, handle): + """ + Returns a gen.lib object (or None) given table_name and + handle. + + Examples: + + >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") + >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") + """ + if table_name in self._tables: + return self._tables[table_name]["handle_func"](handle) + return None + + def get_number_of_records(self, table): + pass + + def get_place_parent_cursor(self): + pass + + def get_place_tree_cursor(self): + pass + + def get_table_metadata(self, table_name): + """Return the metadata for a valid table name.""" + if table_name in self._tables: + return self._tables[table_name] + return None + + def get_transaction_class(self): + pass + + def undo(self, update_history=True): + # FIXME: + return self.undodb.undo(update_history) + + def redo(self, update_history=True): + # FIXME: + return self.undodb.redo(update_history) + + def backup(self): + pass + + def restore(self): + pass diff --git a/gramps/plugins/database/dictionarydb.gpr.py b/gramps/plugins/database/dictionarydb.gpr.py new file mode 100644 index 000000000..3b0e62eca --- /dev/null +++ b/gramps/plugins/database/dictionarydb.gpr.py @@ -0,0 +1,31 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com> +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +plg = newplugin() +plg.id = 'dictionarydb' +plg.name = _("Dictionary Database Backend") +plg.name_accell = _("Di_ctionary Database Backend") +plg.description = _("Dictionary (in-memory) Database Backend") +plg.version = '1.0' +plg.gramps_target_version = "4.2" +plg.status = STABLE +plg.fname = 'dictionarydb.py' +plg.ptype = DATABASE +plg.databaseclass = 'DictionaryDb' diff --git a/gramps/plugins/database/dictionary.py b/gramps/plugins/database/dictionarydb.py similarity index 98% rename from gramps/plugins/database/dictionary.py rename to gramps/plugins/database/dictionarydb.py index 2d785ef6e..a3dbd9586 100644 --- a/gramps/plugins/database/dictionary.py +++ b/gramps/plugins/database/dictionarydb.py @@ -28,17 +28,17 @@ import pickle import base64 import time import re -from . import DbReadBase, DbWriteBase, DbTxn -from . import (PERSON_KEY, - FAMILY_KEY, - CITATION_KEY, - SOURCE_KEY, - EVENT_KEY, - MEDIA_KEY, - PLACE_KEY, - REPOSITORY_KEY, - NOTE_KEY, - TAG_KEY) +from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn +from gramps.gen.db import (PERSON_KEY, + FAMILY_KEY, + CITATION_KEY, + SOURCE_KEY, + EVENT_KEY, + MEDIA_KEY, + PLACE_KEY, + REPOSITORY_KEY, + NOTE_KEY, + TAG_KEY) from gramps.gen.utils.id import create_id from gramps.gen.lib.researcher import Researcher diff --git a/gramps/plugins/database/django_support/libdjango.py b/gramps/plugins/database/django_support/libdjango.py new file mode 100644 index 000000000..fa0a596c8 --- /dev/null +++ b/gramps/plugins/database/django_support/libdjango.py @@ -0,0 +1,2063 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2009 Douglas S. Blank <doug.blank@gmail.com> +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" Interface to Django models """ + +#------------------------------------------------------------------------ +# +# Python Modules +# +#------------------------------------------------------------------------ +import time +import sys +import pickle +import base64 +import collections + +#------------------------------------------------------------------------ +# +# Django Modules +# +#------------------------------------------------------------------------ +from django.contrib.contenttypes.models import ContentType +from django.db import transaction + +#------------------------------------------------------------------------ +# +# Gramps Modules +# +#------------------------------------------------------------------------ +import gramps.webapp.grampsdb.models as models +from gramps.gen.lib import Name +from gramps.gen.utils.id import create_id +from gramps.gen.constfunc import conv_to_unicode + +# To get a django person from a django database: +# djperson = dji.Person.get(handle='djhgsdh324hjg234hj24') +# +# To turn the djperson into a Gramps Person: +# tuple = dji.get_person(djperson) +# gperson = lib.gen.Person(tuple) +# OR +# gperson = dbdjango.DbDjango().get_person_from_handle(handle) + +def check_diff(item, raw): + encoded = str(base64.encodebytes(pickle.dumps(raw)), "utf-8") + if item.cache != encoded: + print("Different:", item.__class__.__name__, item.gramps_id) + print("raw :", raw) + print("cache:", item.from_cache()) + # FIXING, TOO: + item.save_cache() + +#------------------------------------------------------------------------- +# +# Import functions +# +#------------------------------------------------------------------------- +def lookup_role_index(role0, event_ref_list): + """ + Find the handle in a unserialized event_ref_list and return code. + """ + if role0 is None: + return -1 + else: + count = 0 + for event_ref in event_ref_list: + (private, note_list, attribute_list, ref, erole) = event_ref + try: + event = models.Event.objects.get(handle=ref) + except: + return -1 + if event.event_type[0] == role0: + return count + count += 1 + return -1 + +def totime(dtime): + if dtime: + return int(time.mktime(dtime.timetuple())) + else: + return 0 + +#------------------------------------------------------------------------- +# +# Export functions +# +#------------------------------------------------------------------------- +def todate(t): + return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(t)) + +def lookup(index, event_ref_list): + """ + Get the unserialized event_ref in an list of them and return it. + """ + if index < 0: + return None + else: + count = 0 + for event_ref in event_ref_list: + (private, note_list, attribute_list, ref, role) = event_ref + if index == count: + return ref + count += 1 + return None + +def get_datamap(grampsclass): + return [x[0] for x in grampsclass._DATAMAP if x[0] != grampsclass.CUSTOM] + +#------------------------------------------------------------------------- +# +# Django Interface +# +#------------------------------------------------------------------------- +class DjangoInterface(object): + """ + DjangoInterface for interoperating between Gramps and Django. + + This interface comes in a number of parts: + get_ITEMS() + add_ITEMS() + + get_ITEM(ITEM) + + Given an ITEM from a Django table, construct a Gramps Raw Data tuple. + + add_ITEM(data) + + Given a Gramps Raw Data tuple, add the data to the Django tables. + + + """ + def __init__(self): + self.debug = 0 + + def __getattr__(self, name): + """ + Django Objects database interface. + + >>> self.Person.all() + >>> self.Person.get(id=1) + >>> self.Person.get(handle='gh71234dhf3746347734') + """ + if hasattr(models, name): + return getattr(models, name).objects + else: + raise AttributeError("no such model: '%s'" % name) + + def get_next_id(self, obj, prefix): + """ + Get next gramps_id + + >>> dji.get_next_id(Person, "P") + 'P0002' + >>> dji.get_next_id(Media, "M") + 'M2349' + """ + ids = [o["gramps_id"] for o in obj.objects.values("gramps_id")] + count = 1 + while "%s%04d" % (prefix, count) in ids: + count += 1 + return "%s%04d" % (prefix, count) + + def get_model(self, name): + if hasattr(models, name): + return getattr(models, name) + elif hasattr(models, name.title()): + return getattr(models, name.title()) + else: + raise AttributeError("no such model: '%s'" % name) + + # ----------------------------------------------- + # Get methods to retrieve list data from the tables + # ----------------------------------------------- + + def clear_tables(self, *args): + return models.clear_tables(*args) + + def get_tag_list(self, obj): + return obj.get_tag_list() + + def get_attribute_list(self, obj): + obj_type = ContentType.objects.get_for_model(obj) + attribute_list = models.Attribute.objects.filter(object_id=obj.id, + object_type=obj_type) + return list(map(self.pack_attribute, attribute_list)) + + def get_primary_name(self, person): + names = person.name_set.filter(preferred=True).order_by("order") + if len(names) > 0: + return Name.create(self.pack_name(names[0])) + else: + return Name() + + def get_alternate_names(self, person): + names = person.name_set.filter(preferred=False).order_by("order") + return [Name.create(self.pack_name(n)) for n in names] + + def get_names(self, person, preferred): + names = person.name_set.filter(preferred=preferred).order_by("order") + if preferred: + if len(names) > 0: + return self.pack_name(names[0]) + else: + return Name().serialize() + else: + return list(map(self.pack_name, names)) + + def get_source_attribute_list(self, source): + return [(map.private, map.key, map.value) for map in source.sourceattribute_set.all().order_by("order")] + + def get_citation_attribute_list(self, citation): + return [(map.private, map.key, map.value) for map in citation.citationattribute_set.all().order_by("order")] + + def get_media_list(self, obj): + obj_type = ContentType.objects.get_for_model(obj) + mediarefs = models.MediaRef.objects.filter(object_id=obj.id, + object_type=obj_type) + return list(map(self.pack_media_ref, mediarefs)) + + def get_note_list(self, obj): + obj_type = ContentType.objects.get_for_model(obj) + noterefs = models.NoteRef.objects.filter(object_id=obj.id, + object_type=obj_type) + return [noteref.ref_object.handle for noteref in noterefs] + + def get_repository_ref_list(self, obj): + obj_type = ContentType.objects.get_for_model(obj) + reporefs = models.RepositoryRef.objects.filter(object_id=obj.id, + object_type=obj_type) + return list(map(self.pack_repository_ref, reporefs)) + + def get_place_ref_list(self, obj): + obj_type = ContentType.objects.get_for_model(obj) + refs = models.PlaceRef.objects.filter(object_id=obj.id, + object_type=obj_type) + return list(map(self.pack_place_ref, refs)) + + def get_url_list(self, obj): + return list(map(self.pack_url, obj.url_set.all().order_by("order"))) + + def get_address_list(self, obj, with_parish): # person or repository + addresses = obj.address_set.all().order_by("order") + return [self.pack_address(address, with_parish) + for address in addresses] + + def get_child_ref_list(self, family): + obj_type = ContentType.objects.get_for_model(family) + childrefs = models.ChildRef.objects.filter(object_id=family.id, + object_type=obj_type).order_by("order") + return list(map(self.pack_child_ref, childrefs)) + + def get_citation_list(self, obj): + obj_type = ContentType.objects.get_for_model(obj) + citationrefs = models.CitationRef.objects.filter(object_id=obj.id, + object_type=obj_type).order_by("order") + return [citationref.citation.handle for citationref in citationrefs] + + def get_event_refs(self, obj, order="order"): + obj_type = ContentType.objects.get_for_model(obj) + eventrefs = models.EventRef.objects.filter(object_id=obj.id, + object_type=obj_type).order_by(order) + return eventrefs + + def get_event_ref_list(self, obj): + obj_type = ContentType.objects.get_for_model(obj) + eventrefs = models.EventRef.objects.filter(object_id=obj.id, + object_type=obj_type).order_by("order") + return list(map(self.pack_event_ref, eventrefs)) + + def get_family_list(self, person): # person has families + return [fam.family.handle for fam in + models.MyFamilies.objects.filter(person=person).order_by("order")] + + def get_parent_family_list(self, person): # person's parents has families + return [fam.family.handle for fam in + models.MyParentFamilies.objects.filter(person=person).order_by("order")] + + def get_person_ref_list(self, person): + obj_type = ContentType.objects.get_for_model(person) + return list(map(self.pack_person_ref, + models.PersonRef.objects.filter(object_id=person.id, + object_type=obj_type))) + + def get_lds_list(self, obj): # person or family + return list(map(self.pack_lds, obj.lds_set.all().order_by("order"))) + + def get_place_handle(self, obj): # obj is event + if obj.place: + return obj.place.handle + return '' + + ## Packers: + + def get_event(self, event): + handle = event.handle + gid = event.gramps_id + the_type = tuple(event.event_type) + description = event.description + change = totime(event.last_changed) + private = event.private + note_list = self.get_note_list(event) + citation_list = self.get_citation_list(event) + media_list = self.get_media_list(event) + attribute_list = self.get_attribute_list(event) + date = self.get_date(event) + place_handle = self.get_place_handle(event) + tag_list = self.get_tag_list(event) + return (str(handle), gid, the_type, date, description, place_handle, + citation_list, note_list, media_list, attribute_list, + change, tag_list, private) + + def get_note_markup(self, note): + retval = [] + markups = models.Markup.objects.filter(note=note).order_by("order") + for markup in markups: + if markup.string and markup.string.isdigit(): + value = int(markup.string) + else: + value = markup.string + start_stop_list = markup.start_stop_list + ss_list = eval(start_stop_list) + retval += [(tuple(markup.styled_text_tag_type), value, ss_list)] + return retval + + def get_tag(self, tag): + changed = totime(tag.last_changed) + return (str(tag.handle), + tag.name, + tag.color, + tag.priority, + changed) + + def get_note(self, note): + styled_text = [note.text, self.get_note_markup(note)] + changed = totime(note.last_changed) + tag_list = self.get_tag_list(note) + return (str(note.handle), + note.gramps_id, + styled_text, + note.preformatted, + tuple(note.note_type), + changed, + tag_list, + note.private) + + def get_family(self, family): + child_ref_list = self.get_child_ref_list(family) + event_ref_list = self.get_event_ref_list(family) + media_list = self.get_media_list(family) + attribute_list = self.get_attribute_list(family) + lds_seal_list = self.get_lds_list(family) + citation_list = self.get_citation_list(family) + note_list = self.get_note_list(family) + tag_list = self.get_tag_list(family) + if family.father: + father_handle = family.father.handle + else: + father_handle = '' + if family.mother: + mother_handle = family.mother.handle + else: + mother_handle = '' + return (str(family.handle), family.gramps_id, + father_handle, mother_handle, + child_ref_list, tuple(family.family_rel_type), + event_ref_list, media_list, + attribute_list, lds_seal_list, + citation_list, note_list, + totime(family.last_changed), + tag_list, + family.private) + + def get_repository(self, repository): + note_list = self.get_note_list(repository) + address_list = self.get_address_list(repository, with_parish=False) + url_list = self.get_url_list(repository) + tag_list = self.get_tag_list(repository) + return (str(repository.handle), + repository.gramps_id, + tuple(repository.repository_type), + repository.name, + note_list, + address_list, + url_list, + totime(repository.last_changed), + tag_list, + repository.private) + + def get_citation(self, citation): + note_list = self.get_note_list(citation) + media_list = self.get_media_list(citation) + attribute_list = self.get_citation_attribute_list(citation) + tag_list = self.get_tag_list(citation) + date = self.get_date(citation) + # I guess citations can have no source + if citation.source: + handle = citation.source.handle + else: + handle = None + return (str(citation.handle), + citation.gramps_id, + date, + citation.page, + citation.confidence, + handle, + note_list, + media_list, + attribute_list, + totime(citation.last_changed), + tag_list, + citation.private) + + def get_source(self, source): + note_list = self.get_note_list(source) + media_list = self.get_media_list(source) + attribute_list = self.get_source_attribute_list(source) + reporef_list = self.get_repository_ref_list(source) + tag_list = self.get_tag_list(source) + return (str(source.handle), + source.gramps_id, + source.title, + source.author, + source.pubinfo, + note_list, + media_list, + source.abbrev, + totime(source.last_changed), + attribute_list, + reporef_list, + tag_list, + source.private) + + def get_media(self, media): + attribute_list = self.get_attribute_list(media) + citation_list = self.get_citation_list(media) + note_list = self.get_note_list(media) + tag_list = self.get_tag_list(media) + date = self.get_date(media) + return (str(media.handle), + media.gramps_id, + conv_to_unicode(media.path, None), + str(media.mime), + str(media.desc), + media.checksum, + attribute_list, + citation_list, + note_list, + totime(media.last_changed), + date, + tag_list, + media.private) + + def get_person(self, person): + primary_name = self.get_names(person, True) # one + alternate_names = self.get_names(person, False) # list + event_ref_list = self.get_event_ref_list(person) + family_list = self.get_family_list(person) + parent_family_list = self.get_parent_family_list(person) + media_list = self.get_media_list(person) + address_list = self.get_address_list(person, with_parish=False) + attribute_list = self.get_attribute_list(person) + url_list = self.get_url_list(person) + lds_ord_list = self.get_lds_list(person) + pcitation_list = self.get_citation_list(person) + pnote_list = self.get_note_list(person) + person_ref_list = self.get_person_ref_list(person) + # This looks up the events for the first EventType given: + death_ref_index = person.death_ref_index + birth_ref_index = person.birth_ref_index + tag_list = self.get_tag_list(person) + + return (str(person.handle), + person.gramps_id, + tuple(person.gender_type)[0], + primary_name, + alternate_names, + death_ref_index, + birth_ref_index, + event_ref_list, + family_list, + parent_family_list, + media_list, + address_list, + attribute_list, + url_list, + lds_ord_list, + pcitation_list, + pnote_list, + totime(person.last_changed), + tag_list, + person.private, + person_ref_list) + + def get_date(self, obj): + if ((obj.calendar == obj.modifier == obj.quality == obj.sortval == obj.newyear == 0) and + obj.text == "" and (not obj.slash1) and (not obj.slash2) and + (obj.day1 == obj.month1 == obj.year1 == 0) and + (obj.day2 == obj.month2 == obj.year2 == 0)): + return None + elif ((not obj.slash1) and (not obj.slash2) and + (obj.day2 == obj.month2 == obj.year2 == 0)): + dateval = (obj.day1, obj.month1, obj.year1, obj.slash1) + else: + dateval = (obj.day1, obj.month1, obj.year1, obj.slash1, + obj.day2, obj.month2, obj.year2, obj.slash2) + return (obj.calendar, obj.modifier, obj.quality, dateval, + obj.text, obj.sortval, obj.newyear) + + def get_place(self, place): + locations = place.location_set.all().order_by("order") + alt_location_list = [self.pack_location(location, True) for location in locations] + url_list = self.get_url_list(place) + media_list = self.get_media_list(place) + citation_list = self.get_citation_list(place) + note_list = self.get_note_list(place) + tag_list = self.get_tag_list(place) + place_ref_list = self.get_place_ref_list(place) + return (str(place.handle), + place.gramps_id, + place.title, + place.long, + place.lat, + place_ref_list, + place.name, + [], ## FIXME: get_alt_names + tuple(place.place_type), + place.code, + alt_location_list, + url_list, + media_list, + citation_list, + note_list, + totime(place.last_changed), + tag_list, + place.private) + + # --------------------------------- + # Packers + # --------------------------------- + + ## The packers build GRAMPS raw unserialized data. + + ## Reference packers + + def pack_child_ref(self, child_ref): + citation_list = self.get_citation_list(child_ref) + note_list = self.get_note_list(child_ref) + return (child_ref.private, citation_list, note_list, child_ref.ref_object.handle, + tuple(child_ref.father_rel_type), tuple(child_ref.mother_rel_type)) + + def pack_person_ref(self, personref): + citation_list = self.get_citation_list(personref) + note_list = self.get_note_list(personref) + return (personref.private, + citation_list, + note_list, + personref.ref_object.handle, + personref.description) + + def pack_media_ref(self, media_ref): + citation_list = self.get_citation_list(media_ref) + note_list = self.get_note_list(media_ref) + attribute_list = self.get_attribute_list(media_ref) + if ((media_ref.x1 == media_ref.y1 == media_ref.x2 == media_ref.y2 == -1) or + (media_ref.x1 == media_ref.y1 == media_ref.x2 == media_ref.y2 == 0)): + role = None + else: + role = (media_ref.x1, media_ref.y1, media_ref.x2, media_ref.y2) + return (media_ref.private, citation_list, note_list, attribute_list, + media_ref.ref_object.handle, role) + + def pack_repository_ref(self, repo_ref): + note_list = self.get_note_list(repo_ref) + return (note_list, + repo_ref.ref_object.handle, + repo_ref.call_number, + tuple(repo_ref.source_media_type), + repo_ref.private) + + def pack_place_ref(self, place_ref): + date = self.get_date(place_ref) + return (place_ref.ref_object.handle, date) + + def pack_media_ref(self, media_ref): + note_list = self.get_note_list(media_ref) + attribute_list = self.get_attribute_list(media_ref) + citation_list = self.get_citation_list(media_ref) + return (media_ref.private, citation_list, note_list, attribute_list, + media_ref.ref_object.handle, (media_ref.x1, + media_ref.y1, + media_ref.x2, + media_ref.y2)) + + def pack_event_ref(self, event_ref): + note_list = self.get_note_list(event_ref) + attribute_list = self.get_attribute_list(event_ref) + return (event_ref.private, note_list, attribute_list, + event_ref.ref_object.handle, tuple(event_ref.role_type)) + + def pack_citation(self, citation): + handle = citation.handle + gid = citation.gramps_id + date = self.get_date(citation) + page = citation.page + confidence = citation.confidence + source_handle = citation.source.handle + note_list = self.get_note_list(citation) + media_list = self.get_media_list(citation) + attribute_list = self.get_citation_attribute_list(citation) + changed = totime(citation.last_changed) + private = citation.private + tag_list = self.get_tag_list(citation) + return (handle, gid, date, page, confidence, source_handle, + note_list, media_list, attribute_list, changed, tag_list, + private) + + def pack_address(self, address, with_parish): + citation_list = self.get_citation_list(address) + date = self.get_date(address) + note_list = self.get_note_list(address) + locations = address.location_set.all().order_by("order") + if len(locations) > 0: + location = self.pack_location(locations[0], with_parish) + else: + if with_parish: + location = (("", "", "", "", "", "", ""), "") + else: + location = ("", "", "", "", "", "", "") + return (address.private, citation_list, note_list, date, location) + + def pack_lds(self, lds): + citation_list = self.get_citation_list(lds) + note_list = self.get_note_list(lds) + date = self.get_date(lds) + if lds.famc: + famc = lds.famc.handle + else: + famc = None + place_handle = self.get_place_handle(lds) + return (citation_list, note_list, date, lds.lds_type[0], place_handle, + famc, lds.temple, lds.status[0], lds.private) + + def pack_source(self, source): + note_list = self.get_note_list(source) + media_list = self.get_media_list(source) + reporef_list = self.get_repository_ref_list(source) + attribute_list = self.get_source_attribute_list(source) + tag_list = self.get_tag_list(source) + return (source.handle, source.gramps_id, source.title, + source.author, source.pubinfo, + note_list, + media_list, + source.abbrev, + totime(last_changed), attribute_list, + reporef_list, + tag_list, + source.private) + + def pack_name(self, name): + citation_list = self.get_citation_list(name) + note_list = self.get_note_list(name) + date = self.get_date(name) + return (name.private, citation_list, note_list, date, + name.first_name, name.make_surname_list(), name.suffix, + name.title, tuple(name.name_type), + name.group_as, name.sort_as.val, + name.display_as.val, name.call, name.nick, + name.famnick) + + def pack_location(self, loc, with_parish): + if with_parish: + return ((loc.street, loc.locality, loc.city, loc.county, loc.state, loc.country, + loc.postal, loc.phone), loc.parish) + else: + return (loc.street, loc.locality, loc.city, loc.county, loc.state, loc.country, + loc.postal, loc.phone) + + def pack_url(self, url): + return (url.private, url.path, url.desc, tuple(url.url_type)) + + def pack_attribute(self, attribute): + citation_list = self.get_citation_list(attribute) + note_list = self.get_note_list(attribute) + return (attribute.private, + citation_list, + note_list, + tuple(attribute.attribute_type), + attribute.value) + + + ## Export lists: + + def add_child_ref_list(self, obj, ref_list): + ## Currently, only Family references children + for child_data in ref_list: + self.add_child_ref(obj, child_data) + + def add_citation_list(self, obj, citation_list): + for citation_handle in citation_list: + self.add_citation_ref(obj, citation_handle) + + def add_event_ref_list(self, obj, event_ref_list): + for event_ref in event_ref_list: + self.add_event_ref(obj, event_ref) + + def add_surname_list(self, name, surname_list): + order = 1 + for data in surname_list: + (surname_text, prefix, primary, origin_type, + connector) = data + surname = models.Surname() + surname.surname = surname_text + surname.prefix = prefix + surname.primary = primary + surname.name_origin_type = models.get_type(models.NameOriginType, + origin_type) + surname.connector = connector + surname.name = name + surname.order = order + surname.save() + order += 1 + + def add_note_list(self, obj, note_list): + for handle in note_list: + # Just the handle + try: + note = models.Note.objects.get(handle=handle) + self.add_note_ref(obj, note) + except: + print(("ERROR: Note does not exist: '%s'" % + str(handle)), file=sys.stderr) + + def add_alternate_name_list(self, person, alternate_names): + for name in alternate_names: + if name: + self.add_name(person, name, False) + + def add_parent_family_list(self, person, parent_family_list): + for parent_family_data in parent_family_list: + self.add_parent_family(person, parent_family_data) + + def add_media_ref_list(self, person, media_list): + for media_data in media_list: + self.add_media_ref(person, media_data) + + def add_attribute_list(self, obj, attribute_list): + for attribute_data in attribute_list: + self.add_attribute(obj, attribute_data) + + def add_tag_list(self, obj, tag_list): + for tag_handle in tag_list: + try: + tag = models.Tag.objects.get(handle=tag_handle) + except: + print(("ERROR: Tag does not exist: '%s'" % + str(tag_handle)), file=sys.stderr) + obj.tags.add(tag) + + def add_url_list(self, field, obj, url_list): + if not url_list: return None + count = 1 + for url_data in url_list: + self.add_url(field, obj, url_data, count) + count += 1 + + def add_person_ref_list(self, obj, person_ref_list): + for person_ref_data in person_ref_list: + self.add_person_ref(obj, person_ref_data) + + def add_address_list(self, field, obj, address_list): + count = 1 + for address_data in address_list: + self.add_address(field, obj, address_data, count) + count += 1 + + def add_lds_list(self, field, obj, lds_ord_list): + count = 1 + for ldsord in lds_ord_list: + lds = self.add_lds(field, obj, ldsord, count) + #obj.lds_list.add(lds) + #obj.save() + count += 1 + + def add_repository_ref_list(self, obj, reporef_list): + for data in reporef_list: + self.add_repository_ref(obj, data) + + def add_place_ref_list(self, obj, placeref_list): + for data in placeref_list: + self.add_place_ref(obj, data) + + def add_family_ref_list(self, person, family_list): + for family_handle in family_list: + self.add_family_ref(person, family_handle) + + def add_alt_name_list(self, place, alt_name_list): + print("FIXME: add alt_name_list!", alt_name_list) + + ## Export reference objects: + + def add_person_ref_default(self, obj, person, private=False, desc=None): + count = person.references.count() + person_ref = models.PersonRef(referenced_by=obj, + ref_object=person, + private=private, + order=count + 1, + description=desc) + person_ref.save() + + def add_person_ref(self, obj, person_ref_data): + (private, + citation_list, + note_list, + handle, + desc) = person_ref_data + try: + person = models.Person.objects.get(handle=handle) + except: + print(("ERROR: Person does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + + count = person.references.count() + person_ref = models.PersonRef(referenced_by=obj, + ref_object=person, + private=private, + order=count + 1, + description=desc) + person_ref.save() + self.add_note_list(person_ref, note_list) + self.add_citation_list(person_ref, citation_list) + + def add_note_ref(self, obj, note): + count = note.references.count() + note_ref = models.NoteRef(referenced_by=obj, + ref_object=note, + private=False, + order=count + 1) + note_ref.save() + + def add_media_ref_default(self, obj, media, private=False, role=None): + count = media.references.count() + if not role: + role = (0,0,0,0) + media_ref = models.MediaRef(referenced_by=obj, + ref_object=media, + x1=role[0], + y1=role[1], + x2=role[2], + y2=role[3], + private=private, + order=count + 1) + media_ref.save() + + def add_media_ref(self, obj, media_ref_data): + (private, citation_list, note_list, attribute_list, + ref, role) = media_ref_data + try: + media = models.Media.objects.get(handle=ref) + except: + print(("ERROR: Media does not exist: '%s'" % + str(ref)), file=sys.stderr) + return + count = media.references.count() + if not role: + role = (0,0,0,0) + media_ref = models.MediaRef(referenced_by=obj, + ref_object=media, + x1=role[0], + y1=role[1], + x2=role[2], + y2=role[3], + private=private, + order=count + 1) + media_ref.save() + self.add_note_list(media_ref, note_list) + self.add_attribute_list(media_ref, attribute_list) + self.add_citation_list(media_ref, citation_list) + + def add_citation_ref_default(self, obj, citation, private=False): + object_type = ContentType.objects.get_for_model(obj) + count = models.CitationRef.objects.filter(object_id=obj.id,object_type=object_type).count() + citation_ref = models.CitationRef(private=private, + referenced_by=obj, + citation=citation, + order=count + 1) + citation_ref.save() + + def add_citation_ref(self, obj, handle): + try: + citation = models.Citation.objects.get(handle=handle) + except: + print(("ERROR: Citation does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + + object_type = ContentType.objects.get_for_model(obj) + count = models.CitationRef.objects.filter(object_id=obj.id,object_type=object_type).count() + citation_ref = models.CitationRef(private=False, + referenced_by=obj, + citation=citation, + order=count + 1) + citation_ref.save() + + def add_citation(self, citation_data): + (handle, gid, date, page, confidence, source_handle, note_list, + media_list, attribute_list, changed, tag_list, private) = citation_data + citation = models.Citation( + handle=handle, + gramps_id=gid, + private=private, + last_changed=todate(changed), + confidence=confidence, + page=page) + citation.save(save_cache=False) + + def add_citation_detail(self, citation_data): + (handle, gid, date, page, confidence, source_handle, note_list, + media_list, attribute_list, change, tag_list, private) = citation_data + try: + citation = models.Citation.objects.get(handle=handle) + except: + print(("ERROR: Citation does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + try: + source = models.Source.objects.get(handle=source_handle) + except: + print(("ERROR: Source does not exist: '%s'" % + str(source_handle)), file=sys.stderr) + return + citation.source = source + self.add_date(citation, date) + citation.save(save_cache=False) + self.add_note_list(citation, note_list) + self.add_media_ref_list(citation, media_list) + self.add_citation_attribute_list(citation, attribute_list) + self.add_tag_list(citation, tag_list) + citation.save_cache() + + def add_child_ref_default(self, obj, child, frel=1, mrel=1, private=False): + object_type = ContentType.objects.get_for_model(obj) # obj is family + count = models.ChildRef.objects.filter(object_id=obj.id,object_type=object_type).count() + child_ref = models.ChildRef(private=private, + referenced_by=obj, + ref_object=child, + order=count + 1, + father_rel_type=models.get_type(models.ChildRefType, frel), # birth + mother_rel_type=models.get_type(models.ChildRefType, mrel)) + child_ref.save() + + def add_child_ref(self, obj, data): + (private, citation_list, note_list, ref, frel, mrel) = data + try: + child = models.Person.objects.get(handle=ref) + except: + print(("ERROR: Person does not exist: '%s'" % + str(ref)), file=sys.stderr) + return + object_type = ContentType.objects.get_for_model(obj) + count = models.ChildRef.objects.filter(object_id=obj.id,object_type=object_type).count() + child_ref = models.ChildRef(private=private, + referenced_by=obj, + ref_object=child, + order=count + 1, + father_rel_type=models.get_type(models.ChildRefType, frel), + mother_rel_type=models.get_type(models.ChildRefType, mrel)) + child_ref.save() + self.add_citation_list(child_ref, citation_list) + self.add_note_list(child_ref, note_list) + + def add_event_ref_default(self, obj, event, private=False, role=models.EventRoleType._DEFAULT): + object_type = ContentType.objects.get_for_model(obj) + count = models.EventRef.objects.filter(object_id=obj.id,object_type=object_type).count() + event_ref = models.EventRef(private=private, + referenced_by=obj, + ref_object=event, + order=count + 1, + role_type = models.get_type(models.EventRoleType, role)) + event_ref.save() + + def add_event_ref(self, obj, event_data): + (private, note_list, attribute_list, ref, role) = event_data + try: + event = models.Event.objects.get(handle=ref) + except: + print(("ERROR: Event does not exist: '%s'" % + str(ref)), file=sys.stderr) + return + object_type = ContentType.objects.get_for_model(obj) + count = models.EventRef.objects.filter(object_id=obj.id,object_type=object_type).count() + event_ref = models.EventRef(private=private, + referenced_by=obj, + ref_object=event, + order=count + 1, + role_type = models.get_type(models.EventRoleType, role)) + event_ref.save() + self.add_note_list(event_ref, note_list) + self.add_attribute_list(event_ref, attribute_list) + + def add_repository_ref_default(self, obj, repository, private=False, call_number="", + source_media_type=models.SourceMediaType._DEFAULT): + object_type = ContentType.objects.get_for_model(obj) + count = models.RepositoryRef.objects.filter(object_id=obj.id,object_type=object_type).count() + repos_ref = models.RepositoryRef(private=private, + referenced_by=obj, + call_number=call_number, + source_media_type=models.get_type(models.SourceMediaType, + source_media_type), + ref_object=repository, + order=count + 1) + repos_ref.save() + + def add_repository_ref(self, obj, reporef_data): + (note_list, + ref, + call_number, + source_media_type, + private) = reporef_data + try: + repository = models.Repository.objects.get(handle=ref) + except: + print(("ERROR: Repository does not exist: '%s'" % + str(ref)), file=sys.stderr) + return + object_type = ContentType.objects.get_for_model(obj) + count = models.RepositoryRef.objects.filter(object_id=obj.id,object_type=object_type).count() + repos_ref = models.RepositoryRef(private=private, + referenced_by=obj, + call_number=call_number, + source_media_type=models.get_type(models.SourceMediaType, + source_media_type), + ref_object=repository, + order=count + 1) + repos_ref.save() + self.add_note_list(repos_ref, note_list) + + def add_family_ref(self, obj, handle): + try: + family = models.Family.objects.get(handle=handle) + except: + print(("ERROR: Family does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + #obj.families.add(family) + pfo = models.MyFamilies(person=obj, family=family, + order=len(models.MyFamilies.objects.filter(person=obj)) + 1) + pfo.save() + obj.save() + + ## Export individual objects: + + def add_source_attribute_list(self, source, attribute_list): + ## FIXME: dict to list + count = 1 + #for key in datamap_dict: + # value = datamap_dict[key] + # datamap = models.SourceDatamap(key=key, value=value, order=count) + # datamap.source = source + # datamap.save() + # count += 1 + + def add_citation_attribute_list(self, citation, attribute_list): + ## FIXME: dict to list + count = 1 + #for key in datamap_dict: + # value = datamap_dict[key] + # datamap = models.CitationDatamap(key=key, value=value, order=count) + # datamap.citation = citation + # datamap.save() + # count += 1 + + def add_lds(self, field, obj, data, order): + (lcitation_list, lnote_list, date, type, place_handle, + famc_handle, temple, status, private) = data + if place_handle: + try: + place = models.Place.objects.get(handle=place_handle) + except: + print(("ERROR: Place does not exist: '%s'" % + str(place_handle)), file=sys.stderr) + place = None + else: + place = None + if famc_handle: + try: + famc = models.Family.objects.get(handle=famc_handle) + except: + print(("ERROR: Family does not exist: '%s'" % + str(famc_handle)), file=sys.stderr) + famc = None + else: + famc = None + lds = models.Lds(lds_type = models.get_type(models.LdsType, type), + temple=temple, + place=place, + famc=famc, + order=order, + status = models.get_type(models.LdsStatus, status), + private=private) + self.add_date(lds, date) + lds.save() + self.add_note_list(lds, lnote_list) + self.add_citation_list(lds, lcitation_list) + if field == "person": + lds.person = obj + elif field == "family": + lds.family = obj + else: + raise AttributeError("invalid field '%s' to attach lds" % + str(field)) + lds.save() + return lds + + def add_address(self, field, obj, address_data, order): + (private, acitation_list, anote_list, date, location) = address_data + address = models.Address(private=private, order=order) + self.add_date(address, date) + address.save() + self.add_location("address", address, location, 1) + self.add_note_list(address, anote_list) + self.add_citation_list(address, acitation_list) + if field == "person": + address.person = obj + elif field == "repository": + address.repository = obj + else: + raise AttributeError("invalid field '%s' to attach address" % + str(field)) + address.save() + #obj.save() + #obj.addresses.add(address) + #obj.save() + + def add_attribute(self, obj, attribute_data): + (private, citation_list, note_list, the_type, value) = attribute_data + attribute_type = models.get_type(models.AttributeType, the_type) + attribute = models.Attribute(private=private, + attribute_of=obj, + attribute_type=attribute_type, + value=value) + attribute.save() + self.add_citation_list(attribute, citation_list) + self.add_note_list(attribute, note_list) + #obj.attributes.add(attribute) + #obj.save() + + def add_url(self, field, obj, url_data, order): + (private, path, desc, type) = url_data + url = models.Url(private=private, + path=path, + desc=desc, + order=order, + url_type=models.get_type(models.UrlType, type)) + if field == "person": + url.person = obj + elif field == "repository": + url.repository = obj + elif field == "place": + url.place = obj + else: + raise AttributeError("invalid field '%s' to attach to url" % + str(field)) + url.save() + #obj.url_list.add(url) + #obj.save() + + def add_place_ref_default(self, obj, place, date=None): + count = place.references.count() + object_type = ContentType.objects.get_for_model(obj) + count = models.PlaceRef.objects.filter(object_id=obj.id, + object_type=object_type).count() + place_ref = models.PlaceRef(referenced_by=obj, + ref_object=place, + order=count + 1) + self.add_date(obj, date) + place_ref.save() + + def add_place_ref(self, obj, data): + place_handle, date = data + if place_handle: + try: + place = models.Place.objects.get(handle=place_handle) + except: + print(("ERROR: Place does not exist: '%s'" % str(place_handle)), file=sys.stderr) + #from gramps.gen.utils.debug import format_exception + #print("".join(format_exception()), file=sys.stderr) + return + object_type = ContentType.objects.get_for_model(obj) + count = models.PlaceRef.objects.filter(object_id=obj.id,object_type=object_type).count() + place_ref = models.PlaceRef(referenced_by=obj, ref_object=place, order=count + 1) + place_ref.save() + self.add_date(place_ref, date) + + def add_parent_family(self, person, parent_family_handle): + try: + family = models.Family.objects.get(handle=parent_family_handle) + except: + print(("ERROR: Family does not exist: '%s'" % + str(parent_family_handle)), file=sys.stderr) + return + #person.parent_families.add(family) + pfo = models.MyParentFamilies( + person=person, + family=family, + order=len(models.MyParentFamilies.objects.filter(person=person)) + 1) + pfo.save() + person.save() + + def add_date(self, obj, date): + if date is None: + (calendar, modifier, quality, text, sortval, newyear) = \ + (0, 0, 0, "", 0, 0) + day1, month1, year1, slash1 = 0, 0, 0, 0 + day2, month2, year2, slash2 = 0, 0, 0, 0 + else: + (calendar, modifier, quality, dateval, text, sortval, newyear) = date + if len(dateval) == 4: + day1, month1, year1, slash1 = dateval + day2, month2, year2, slash2 = 0, 0, 0, 0 + elif len(dateval) == 8: + day1, month1, year1, slash1, day2, month2, year2, slash2 = dateval + else: + raise AttributeError("ERROR: dateval format '%s'" % str(dateval)) + obj.calendar = calendar + obj.modifier = modifier + obj.quality = quality + obj.text = text + obj.sortval = sortval + obj.newyear = newyear + obj.day1 = day1 + obj.month1 = month1 + obj.year1 = year1 + obj.slash1 = slash1 + obj.day2 = day2 + obj.month2 = month2 + obj.year2 = year2 + obj.slash2 = slash2 + + def add_name(self, person, data, preferred): + if data: + (private, citation_list, note_list, date, + first_name, surname_list, suffix, title, + name_type, group_as, sort_as, + display_as, call, nick, famnick) = data + + count = person.name_set.count() + name = models.Name() + name.order = count + 1 + name.preferred = preferred + name.private = private + name.first_name = first_name + name.suffix = suffix + name.title = title + name.name_type = models.get_type(models.NameType, name_type) + name.group_as = group_as + name.sort_as = models.get_type(models.NameFormatType, sort_as) + name.display_as = models.get_type(models.NameFormatType, display_as) + name.call = call + name.nick = nick + name.famnick = famnick + # we know person exists + # needs to have an ID for key + name.person = person + self.add_date(name, date) + name.save() + self.add_surname_list(name, surname_list) + self.add_note_list(name, note_list) + self.add_citation_list(name, citation_list) + #person.save() + + ## Export primary objects: + + def add_person(self, data): + # Unpack from the BSDDB: + (handle, # 0 + gid, # 1 + gender, # 2 + primary_name, # 3 + alternate_names, # 4 + death_ref_index, # 5 + birth_ref_index, # 6 + event_ref_list, # 7 + family_list, # 8 + parent_family_list, # 9 + media_list, # 10 + address_list, # 11 + attribute_list, # 12 + url_list, # 13 + lds_ord_list, # 14 + pcitation_list, # 15 + pnote_list, # 16 + change, # 17 + tag_list, # 18 + private, # 19 + person_ref_list, # 20 + ) = data + + person = models.Person(handle=handle, + gramps_id=gid, + last_changed=todate(change), + private=private, + gender_type=models.get_type(models.GenderType, gender)) + person.save(save_cache=False) + + def add_person_detail(self, data): + # Unpack from the BSDDB: + (handle, # 0 + gid, # 1 + gender, # 2 + primary_name, # 3 + alternate_names, # 4 + death_ref_index, # 5 + birth_ref_index, # 6 + event_ref_list, # 7 + family_list, # 8 + parent_family_list, # 9 + media_list, # 10 + address_list, # 11 + attribute_list, # 12 + url_list, # 13 + lds_ord_list, # 14 + pcitation_list, # 15 + pnote_list, # 16 + change, # 17 + tag_list, # 18 + private, # 19 + person_ref_list, # 20 + ) = data + + try: + person = models.Person.objects.get(handle=handle) + except: + print(("ERROR: Person does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + if primary_name: + self.add_name(person, primary_name, True) + self.add_alternate_name_list(person, alternate_names) + self.add_event_ref_list(person, event_ref_list) + self.add_family_ref_list(person, family_list) + self.add_parent_family_list(person, parent_family_list) + self.add_media_ref_list(person, media_list) + self.add_note_list(person, pnote_list) + self.add_attribute_list(person, attribute_list) + self.add_url_list("person", person, url_list) + self.add_person_ref_list(person, person_ref_list) + self.add_citation_list(person, pcitation_list) + self.add_address_list("person", person, address_list) + self.add_lds_list("person", person, lds_ord_list) + self.add_tag_list(person, tag_list) + # set person.birth and birth.death to correct events: + + obj_type = ContentType.objects.get_for_model(person) + events = models.EventRef.objects.filter( + object_id=person.id, + object_type=obj_type, + ref_object__event_type__val=models.EventType.BIRTH).order_by("order") + + all_events = self.get_event_ref_list(person) + if events: + person.birth = events[0].ref_object + person.birth_ref_index = lookup_role_index(models.EventType.BIRTH, all_events) + + events = models.EventRef.objects.filter( + object_id=person.id, + object_type=obj_type, + ref_object__event_type__val=models.EventType.DEATH).order_by("order") + if events: + person.death = events[0].ref_object + person.death_ref_index = lookup_role_index(models.EventType.DEATH, all_events) + person.save() + return person + + def save_note_markup(self, note, markup_list): + # delete any prexisting markup: + models.Markup.objects.filter(note=note).delete() + count = 1 + for markup in markup_list: + markup_code, value, start_stop_list = markup + m = models.Markup( + note=note, + order=count, + styled_text_tag_type=models.get_type(models.StyledTextTagType, + markup_code, + get_or_create=False), + string=value, + start_stop_list=str(start_stop_list)) + m.save() + + def add_note(self, data): + # Unpack from the BSDDB: + (handle, gid, styled_text, format, note_type, + change, tag_list, private) = data + text, markup_list = styled_text + n = models.Note(handle=handle, + gramps_id=gid, + last_changed=todate(change), + private=private, + preformatted=format, + text=text, + note_type=models.get_type(models.NoteType, note_type)) + n.save(save_cache=False) + self.save_note_markup(n, markup_list) + + def add_note_detail(self, data): + # Unpack from the BSDDB: + (handle, gid, styled_text, format, note_type, + change, tag_list, private) = data + note = models.Note.objects.get(handle=handle) + note.save(save_cache=False) + self.add_tag_list(note, tag_list) + note.save_cache() + + def add_family(self, data): + # Unpack from the BSDDB: + (handle, gid, father_handle, mother_handle, + child_ref_list, the_type, event_ref_list, media_list, + attribute_list, lds_seal_list, citation_list, note_list, + change, tag_list, private) = data + + family = models.Family(handle=handle, gramps_id=gid, + family_rel_type = models.get_type(models.FamilyRelType, the_type), + last_changed=todate(change), + private=private) + family.save(save_cache=False) + + def add_family_detail(self, data): + # Unpack from the BSDDB: + (handle, gid, father_handle, mother_handle, + child_ref_list, the_type, event_ref_list, media_list, + attribute_list, lds_seal_list, citation_list, note_list, + change, tag_list, private) = data + + try: + family = models.Family.objects.get(handle=handle) + except: + print(("ERROR: Family does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + # father_handle and/or mother_handle can be None + if father_handle: + try: + family.father = models.Person.objects.get(handle=father_handle) + except: + print(("ERROR: Father does not exist: '%s'" % + str(father_handle)), file=sys.stderr) + family.father = None + if mother_handle: + try: + family.mother = models.Person.objects.get(handle=mother_handle) + except: + print(("ERROR: Mother does not exist: '%s'" % + str(mother_handle)), file=sys.stderr) + family.mother = None + family.save(save_cache=False) + self.add_child_ref_list(family, child_ref_list) + self.add_note_list(family, note_list) + self.add_attribute_list(family, attribute_list) + self.add_citation_list(family, citation_list) + self.add_media_ref_list(family, media_list) + self.add_event_ref_list(family, event_ref_list) + self.add_lds_list("family", family, lds_seal_list) + self.add_tag_list(family, tag_list) + family.save_cache() + + def add_source(self, data): + (handle, gid, title, + author, pubinfo, + note_list, + media_list, + abbrev, + change, attribute_list, + reporef_list, + tag_list, + private) = data + source = models.Source(handle=handle, gramps_id=gid, title=title, + author=author, pubinfo=pubinfo, abbrev=abbrev, + last_changed=todate(change), private=private) + source.save(save_cache=False) + + def add_source_detail(self, data): + (handle, gid, title, + author, pubinfo, + note_list, + media_list, + abbrev, + change, attribute_list, + reporef_list, + tag_list, + private) = data + try: + source = models.Source.objects.get(handle=handle) + except: + print(("ERROR: Source does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + source.save(save_cache=False) + self.add_note_list(source, note_list) + self.add_media_ref_list(source, media_list) + self.add_source_attribute_list(source, attribute_list) + self.add_repository_ref_list(source, reporef_list) + self.add_tag_list(source, tag_list) + source.save_cache() + + def add_repository(self, data): + (handle, gid, the_type, name, note_list, + address_list, url_list, change, tag_list, private) = data + + repository = models.Repository(handle=handle, + gramps_id=gid, + last_changed=todate(change), + private=private, + repository_type=models.get_type(models.RepositoryType, the_type), + name=name) + repository.save(save_cache=False) + + def add_repository_detail(self, data): + (handle, gid, the_type, name, note_list, + address_list, url_list, change, tag_list, private) = data + try: + repository = models.Repository.objects.get(handle=handle) + except: + print(("ERROR: Repository does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + repository.save(save_cache=False) + self.add_note_list(repository, note_list) + self.add_url_list("repository", repository, url_list) + self.add_address_list("repository", repository, address_list) + self.add_tag_list(repository, tag_list) + repository.save_cache() + + def add_location(self, field, obj, location_data, order): + # location now has 8 items + # street, locality, city, county, state, + # country, postal, phone, parish + + if location_data == None: return + if len(location_data) == 8: + (street, locality, city, county, state, country, postal, phone) = location_data + parish = None + elif len(location_data) == 2: + ((street, locality, city, county, state, country, postal, phone), parish) = location_data + else: + print(("ERROR: unknown location: '%s'" % + str(location_data)), file=sys.stderr) + (street, locality, city, county, state, country, postal, phone, parish) = \ + ("", "", "", "", "", "", "", "", "") + location = models.Location(street = street, + locality = locality, + city = city, + county = county, + state = state, + country = country, + postal = postal, + phone = phone, + parish = parish, + order = order) + if field == "address": + location.address = obj + elif field == "place": + location.place = obj + else: + raise AttributeError("invalid field '%s' to attach to location" % + str(field)) + location.save() + #obj.locations.add(location) + #obj.save() + + def add_place(self, data): + ## ('cef246c95c132bcf6a0255d4d17', 'P0036', 'Santa Clara Co., CA, USA', '', '', [('cef243fb5634559442323368f63', None)], 'Santa Clara Co.', [], (3, ''), '', [], [], [], [], [], 1422124781, [], False) + (handle, gid, title, long, lat, + place_ref_list, + name, + alt_name_list, + place_type, + code, + alt_location_list, + url_list, + media_list, + citation_list, + note_list, + change, + tag_list, + private) = data + place = models.Place( + handle=handle, + gramps_id=gid, + title=title, + long=long, + lat=lat, + name=name, + place_type=models.get_type(models.PlaceType, place_type), + code=code, + last_changed=todate(change), + private=private) + try: + place.save(save_cache=False) + except: + print("FIXME: error in saving place") + + def add_place_detail(self, data): + (handle, gid, title, long, lat, + place_ref_list, + name, + alt_name_list, + place_type, + code, + alt_location_list, + url_list, + media_list, + citation_list, + note_list, + change, + tag_list, + private) = data + try: + place = models.Place.objects.get(handle=handle) + except: + print(("ERROR: Place does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + place.save(save_cache=False) + self.add_url_list("place", place, url_list) + self.add_media_ref_list(place, media_list) + self.add_citation_list(place, citation_list) + self.add_note_list(place, note_list) + self.add_tag_list(place, tag_list) + self.add_place_ref_list(place, place_ref_list) + self.add_alt_name_list(place, alt_name_list) + count = 1 + for loc_data in alt_location_list: + self.add_location("place", place, loc_data, count) + count + 1 + place.save_cache() + + def add_tag(self, data): + (handle, + name, + color, + priority, + change) = data + tag = models.Tag(handle=handle, + gramps_id=create_id(), + name=name, + color=color, + priority=priority, + last_changed=todate(change)) + tag.save(save_cache=False) + + def add_tag_detail(self, data): + (handle, + name, + color, + priority, + change) = data + tag = models.Tag.objects.get(handle=handle) + tag.save() + + def add_media(self, data): + (handle, gid, path, mime, desc, + checksum, + attribute_list, + citation_list, + note_list, + change, + date, + tag_list, + private) = data + media = models.Media(handle=handle, gramps_id=gid, + path=path, mime=mime, checksum=checksum, + desc=desc, last_changed=todate(change), + private=private) + self.add_date(media, date) + media.save(save_cache=False) + + def add_media_detail(self, data): + (handle, gid, path, mime, desc, + checksum, + attribute_list, + citation_list, + note_list, + change, + date, + tag_list, + private) = data + try: + media = models.Media.objects.get(handle=handle) + except: + print(("ERROR: Media does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + media.save(save_cache=False) + self.add_note_list(media, note_list) + self.add_citation_list(media, citation_list) + self.add_attribute_list(media, attribute_list) + self.add_tag_list(media, tag_list) + media.save_cache() + + def add_event(self, data): + (handle, gid, the_type, date, description, place_handle, + citation_list, note_list, media_list, attribute_list, + change, tag_list, private) = data + event = models.Event(handle=handle, + gramps_id=gid, + event_type=models.get_type(models.EventType, the_type), + private=private, + description=description, + last_changed=todate(change)) + self.add_date(event, date) + event.save(save_cache=False) + + def add_event_detail(self, data): + (handle, gid, the_type, date, description, place_handle, + citation_list, note_list, media_list, attribute_list, + change, tag_list, private) = data + try: + event = models.Event.objects.get(handle=handle) + except: + print(("ERROR: Event does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + try: + place = models.Place.objects.get(handle=place_handle) + except: + place = None + print(("ERROR: Place does not exist: '%s'" % + str(place_handle)), file=sys.stderr) + event.place = place + event.save(save_cache=False) + self.add_note_list(event, note_list) + self.add_attribute_list(event, attribute_list) + self.add_media_ref_list(event, media_list) + self.add_citation_list(event, citation_list) + self.add_tag_list(event, tag_list) + event.save_cache() + + def get_raw(self, item): + """ + Build and return the raw, serialized data of an object. + """ + if isinstance(item, models.Person): + raw = self.get_person(item) + elif isinstance(item, models.Family): + raw = self.get_family(item) + elif isinstance(item, models.Place): + raw = self.get_place(item) + elif isinstance(item, models.Media): + raw = self.get_media(item) + elif isinstance(item, models.Source): + raw = self.get_source(item) + elif isinstance(item, models.Citation): + raw = self.get_citation(item) + elif isinstance(item, models.Repository): + raw = self.get_repository(item) + elif isinstance(item, models.Note): + raw = self.get_note(item) + elif isinstance(item, models.Event): + raw = self.get_event(item) + else: + raise Exception("Don't know how to get raw '%s'" % type(item)) + return raw + + def check_caches(self, callback=None): + """ + Call this to check the caches for all primary models. + """ + if not isinstance(callback, collections.Callable): + callback = lambda percent: None # dummy + + callback(0) + count = 0.0 + total = (self.Note.all().count() + + self.Person.all().count() + + self.Event.all().count() + + self.Family.all().count() + + self.Repository.all().count() + + self.Place.all().count() + + self.Media.all().count() + + self.Source.all().count() + + self.Citation.all().count() + + self.Tag.all().count()) + + for item in self.Note.all(): + raw = self.get_note(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Person.all(): + raw = self.get_person(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Family.all(): + raw = self.get_family(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Source.all(): + raw = self.get_source(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Event.all(): + raw = self.get_event(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Repository.all(): + raw = self.get_repository(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Place.all(): + raw = self.get_place(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Media.all(): + raw = self.get_media(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Citation.all(): + raw = self.get_citation(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Tag.all(): + raw = self.get_tag(item) + check_diff(item, raw) + count += 1 + callback(100) + + def check_families(self): + """ + Check family structures. + """ + for family in self.Family.all(): + if family.mother: + if not family in family.mother.families.all(): + print("Mother not in family", mother, family) + if family.father: + if not family in family.father.families.all(): + print("Father not in family", mother, family) + for child in family.get_children(): + if family not in child.parent_families.all(): + print("Child not in family", child, family) + for person in self.Person.all(): + for family in person.families.all(): + if person not in [family.mother, family.father]: + print("Spouse not in family", person, family) + for family in person.parent_families.all(): + if person not in family.get_children(): + print("Child not in family", person, family) + + def is_public(self, obj, objref): + """ + Returns whether or not an item is "public", and the reason + why/why not. + + @param obj - an instance of any Primary object + @param objref - one of the PrimaryRef.objects + @return - a tuple containing a boolean (public?) and reason. + + There are three reasons why an item might not be public: + 1) The item itself is private. + 2) The item is referenced by a living Person. + 3) The item is referenced by some other private item. + """ + # If it is private, then no: + if obj.private: + return (False, "It is marked private.") + elif hasattr(obj, "probably_alive") and obj.probably_alive: + return (False, "It is marked probaby alive.") + elif hasattr(obj, "mother") and obj.mother: + public, reason = self.is_public(obj.mother, self.PersonRef) + if not public: + return public, reason + elif hasattr(obj, "father") and obj.father: + public, reason = self.is_public(obj.father, self.PersonRef) + if not public: + return public, reason + # FIXME: what about Associations... anything else? Check PrivateProxy + if objref: + if hasattr(objref.model, "ref_object"): + obj_ref_list = objref.filter(ref_object=obj) + elif hasattr(objref.model, "citation"): + obj_ref_list = objref.filter(citation=obj) + else: + raise Exception("objref '%s' needs a ref for '%s'" % (objref.model, obj)) + for reference in obj_ref_list: + ref_from_class = reference.object_type.model_class() + item = None + try: + item = ref_from_class.objects.get(id=reference.object_id) + except: + print("Warning: Corrupt reference: %s" % str(reference)) + continue + # If it is linked to by someone alive? public = False + if hasattr(item, "probably_alive") and item.probably_alive: + return (False, "It is referenced by someone who is probaby alive.") + # If it is linked to by something private? public = False + elif item.private: + return (False, "It is referenced by an item which is marked private.") + return (True, "It is visible to the public.") + + def update_public(self, obj, save=True): + """ + >>> dji.update_public(event) + + Given an Event or other instance, update the event's public + status, or any event referenced to by the instance. + + For example, if a person is found to be alive, then the + referenced events should be marked not public (public = False). + + """ + from gramps.webapp.utils import probably_alive + if obj.__class__.__name__ == "Event": + objref = self.EventRef + elif obj.__class__.__name__ == "Person": + objref = self.PersonRef + elif obj.__class__.__name__ == "Note": + objref = self.NoteRef + elif obj.__class__.__name__ == "Repository": + objref = self.RepositoryRef + elif obj.__class__.__name__ == "Citation": + objref = self.CitationRef + elif obj.__class__.__name__ == "Media": + objref = self.MediaRef + elif obj.__class__.__name__ == "Place": # no need for dependency + objref = None + elif obj.__class__.__name__ == "Source": # no need for dependency + objref = None + elif obj.__class__.__name__ == "Family": + objref = self.ChildRef # correct? + else: + raise Exception("Can't compute public of type '%s'" % str(obj)) + public, reason = self.is_public(obj, objref) # correct? + # Ok, update, if needed: + if obj.public != public: + obj.public = public + if save: + print("Updating public:", obj.__class__.__name__, obj.gramps_id) + obj.save() + #log = self.Log() + #log.referenced_by = obj + #log.object_id = obj.id + #log.object_type = obj_type + #log.log_type = "update public status" + #log.reason = reason + #log.order = 0 + #log.save() + + def update_publics(self, callback=None): + """ + Call this to update probably_alive for all primary models. + """ + if not isinstance(callback, collections.Callable): + callback = lambda percent: None # dummy + + callback(0) + count = 0.0 + total = (self.Note.all().count() + + self.Person.all().count() + + self.Event.all().count() + + self.Family.all().count() + + self.Repository.all().count() + + self.Place.all().count() + + self.Media.all().count() + + self.Source.all().count() + + self.Citation.all().count()) + + for item in self.Note.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Person.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Family.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Source.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Event.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Repository.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Place.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Media.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Citation.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + def update_probably_alive(self, callback=None): + """ + Call this to update primary_alive for people. + """ + from gramps.webapp.utils import probably_alive + if not isinstance(callback, collections.Callable): + callback = lambda percent: None # dummy + callback(0) + count = 0.0 + total = self.Person.all().count() + for item in self.Person.all(): + pa = probably_alive(item.handle) + if pa != item.probably_alive: + print("Updating probably_alive") + item.probably_alive = pa + item.save() + count += 1 + callback(100 * (count/total if total else 0)) diff --git a/gramps/plugins/database/djangodb.gpr.py b/gramps/plugins/database/djangodb.gpr.py new file mode 100644 index 000000000..57b30f2f5 --- /dev/null +++ b/gramps/plugins/database/djangodb.gpr.py @@ -0,0 +1,31 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com> +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +plg = newplugin() +plg.id = 'djangodb' +plg.name = _("Django Database Backend") +plg.name_accell = _("_Django Database Backend") +plg.description = _("Django Object Relational Model Database Backend") +plg.version = '1.0' +plg.gramps_target_version = "4.2" +plg.status = STABLE +plg.fname = 'djangodb.py' +plg.ptype = DATABASE +plg.databaseclass = 'DbDjango' diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py new file mode 100644 index 000000000..adb28dc54 --- /dev/null +++ b/gramps/plugins/database/djangodb.py @@ -0,0 +1,2036 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2009 Douglas S. Blank <doug.blank@gmail.com> +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" Implements a Db interface """ + +#------------------------------------------------------------------------ +# +# Python Modules +# +#------------------------------------------------------------------------ +import sys +import time +import re +import base64 +import pickle +import os + +#------------------------------------------------------------------------ +# +# Gramps Modules +# +#------------------------------------------------------------------------ +import gramps +from gramps.gen.lib import (Person, Family, Event, Place, Repository, + Citation, Source, Note, MediaObject, Tag, + Researcher) +from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn +from gramps.gen.utils.callback import Callback +from gramps.gen.updatecallback import UpdateCallback +from gramps.gen.db import (PERSON_KEY, + FAMILY_KEY, + CITATION_KEY, + SOURCE_KEY, + EVENT_KEY, + MEDIA_KEY, + PLACE_KEY, + REPOSITORY_KEY, + NOTE_KEY) +from gramps.gen.utils.id import create_id +from django.db import transaction + +class Environment(object): + """ + Implements the Environment API. + """ + def __init__(self, db): + self.db = db + + def txn_begin(self): + return DjangoTxn("DbDjango Transaction", self.db) + +class Table(object): + """ + Implements Table interface. + """ + def __init__(self, funcs): + self.funcs = funcs + + def cursor(self): + """ + Returns a Cursor for this Table. + """ + return self.funcs["cursor_func"]() + + def put(key, data, txn=None): + self[key] = data + +class Map(dict): + """ + Implements the map API for person_map, etc. + + Takes a Table() as argument. + """ + def __init__(self, tbl, *args, **kwargs): + super().__init__(*args, **kwargs) + self.db = tbl + +class MetaCursor(object): + def __init__(self): + pass + def __enter__(self): + return self + def __iter__(self): + return self.__next__() + def __next__(self): + yield None + def __exit__(self, *args, **kwargs): + pass + def iter(self): + yield None + def first(self): + self._iter = self.__iter__() + return self.next() + def next(self): + try: + return next(self._iter) + except: + return None + def close(self): + pass + +class Cursor(object): + 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 (bytes(item.handle, "utf-8"), self.func(item.handle)) + def __exit__(self, *args, **kwargs): + pass + def iter(self): + for item in self.model.all(): + yield (bytes(item.handle, "utf-8"), self.func(item.handle)) + yield None + def first(self): + self._iter = self.__iter__() + return self.next() + def next(self): + try: + return next(self._iter) + except: + return None + def close(self): + pass + +class Bookmarks(object): + def __init__(self): + self.handles = [] + def get(self): + return self.handles + def append(self, handle): + self.handles.append(handle) + +class DjangoTxn(DbTxn): + def __init__(self, message, db, table=None): + DbTxn.__init__(self, message, db) + self.table = table + + def get(self, key, default=None, txn=None, **kwargs): + """ + Returns the data object associated with key + """ + try: + return self.table.objects(handle=key) + except: + if txn and key in txn: + return txn[key] + else: + return None + + def put(self, handle, new_data, txn): + """ + """ + txn[handle] = new_data + + def commit(self): + pass + +class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): + """ + A Gramps Database Backend. This replicates the grampsdb functions. + """ + # Set up dictionary for callback signal handler + # --------------------------------------------- + # 1. Signals for primary objects + __signals__ = dict((obj+'-'+op, signal) + for obj in + ['person', 'family', 'event', 'place', + 'source', 'citation', 'media', 'note', 'repository', 'tag'] + for op, signal in zip( + ['add', 'update', 'delete', 'rebuild'], + [(list,), (list,), (list,), None] + ) + ) + + # 2. Signals for long operations + __signals__.update(('long-op-'+op, signal) for op, signal in zip( + ['start', 'heartbeat', 'end'], + [(object,), None, None] + )) + + # 3. Special signal for change in home person + __signals__['home-person-changed'] = None + + # 4. Signal for change in person group name, parameters are + __signals__['person-groupname-rebuild'] = (str, str) + + def __init__(self, directory=None): + DbReadBase.__init__(self) + DbWriteBase.__init__(self) + Callback.__init__(self) + self._tables = { + 'Person': + { + "handle_func": self.get_person_from_handle, + "gramps_id_func": self.get_person_from_gramps_id, + "class_func": gramps.gen.lib.Person, + "cursor_func": self.get_person_cursor, + "handles_func": self.get_person_handles, + "iter_func": self.iter_people, + }, + 'Family': + { + "handle_func": self.get_family_from_handle, + "gramps_id_func": self.get_family_from_gramps_id, + "class_func": gramps.gen.lib.Family, + "cursor_func": self.get_family_cursor, + "handles_func": self.get_family_handles, + "iter_func": self.iter_families, + }, + 'Source': + { + "handle_func": self.get_source_from_handle, + "gramps_id_func": self.get_source_from_gramps_id, + "class_func": gramps.gen.lib.Source, + "cursor_func": self.get_source_cursor, + "handles_func": self.get_source_handles, + "iter_func": self.iter_sources, + }, + 'Citation': + { + "handle_func": self.get_citation_from_handle, + "gramps_id_func": self.get_citation_from_gramps_id, + "class_func": gramps.gen.lib.Citation, + "cursor_func": self.get_citation_cursor, + "handles_func": self.get_citation_handles, + "iter_func": self.iter_citations, + }, + 'Event': + { + "handle_func": self.get_event_from_handle, + "gramps_id_func": self.get_event_from_gramps_id, + "class_func": gramps.gen.lib.Event, + "cursor_func": self.get_event_cursor, + "handles_func": self.get_event_handles, + "iter_func": self.iter_events, + }, + 'Media': + { + "handle_func": self.get_object_from_handle, + "gramps_id_func": self.get_object_from_gramps_id, + "class_func": gramps.gen.lib.MediaObject, + "cursor_func": self.get_media_cursor, + "handles_func": self.get_media_object_handles, + "iter_func": self.iter_media_objects, + }, + 'Place': + { + "handle_func": self.get_place_from_handle, + "gramps_id_func": self.get_place_from_gramps_id, + "class_func": gramps.gen.lib.Place, + "cursor_func": self.get_place_cursor, + "handles_func": self.get_place_handles, + "iter_func": self.iter_places, + }, + 'Repository': + { + "handle_func": self.get_repository_from_handle, + "gramps_id_func": self.get_repository_from_gramps_id, + "class_func": gramps.gen.lib.Repository, + "cursor_func": self.get_repository_cursor, + "handles_func": self.get_repository_handles, + "iter_func": self.iter_repositories, + }, + 'Note': + { + "handle_func": self.get_note_from_handle, + "gramps_id_func": self.get_note_from_gramps_id, + "class_func": gramps.gen.lib.Note, + "cursor_func": self.get_note_cursor, + "handles_func": self.get_note_handles, + "iter_func": self.iter_notes, + }, + 'Tag': + { + "handle_func": self.get_tag_from_handle, + "gramps_id_func": None, + "class_func": gramps.gen.lib.Tag, + "cursor_func": self.get_tag_cursor, + "handles_func": self.get_tag_handles, + "iter_func": self.iter_tags, + }, + } + # 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.undo_callback = None + self.redo_callback = None + self.undo_history_callback = None + self.modified = 0 + self.txn = DjangoTxn("DbDjango Transaction", self) + self.transaction = None + # Import cache for gedcom import, uses transactions, and + # two step adding of objects. + self.import_cache = {} + self.use_import_cache = False + self.use_db_cache = True + self._directory = directory + if directory: + self.load(directory) + + def load(self, directory, pulse_progress=None, mode=None): + self._directory = directory + from django.conf import settings + default_settings = {} + settings_file = os.path.join(directory, "default_settings.py") + with open(settings_file) as f: + code = compile(f.read(), settings_file, 'exec') + exec(code, globals(), default_settings) + + class Module(object): + def __init__(self, dictionary): + self.dictionary = dictionary + def __getattr__(self, item): + return self.dictionary[item] + + try: + settings.configure(Module(default_settings)) + except RuntimeError: + # already configured; ignore + pass + + import django + django.setup() + + from django_support.libdjango import DjangoInterface + self.dji = DjangoInterface() + 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 = DjangoTxn("ID Transaction", self, self.dji.Person) + self.fid_trans = DjangoTxn("FID Transaction", self, self.dji.Family) + self.pid_trans = DjangoTxn("PID Transaction", self, self.dji.Place) + self.cid_trans = DjangoTxn("CID Transaction", self, self.dji.Citation) + self.sid_trans = DjangoTxn("SID Transaction", self, self.dji.Source) + self.oid_trans = DjangoTxn("OID Transaction", self, self.dji.Media) + self.rid_trans = DjangoTxn("RID Transaction", self, self.dji.Repository) + self.nid_trans = DjangoTxn("NID Transaction", self, self.dji.Note) + self.eid_trans = DjangoTxn("EID Transaction", self, self.dji.Event) + 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 = Environment(self) + self.person_map = Map(Table(self._tables["Person"])) + self.family_map = Map(Table(self._tables["Family"])) + self.place_map = Map(Table(self._tables["Place"])) + self.citation_map = Map(Table(self._tables["Citation"])) + self.source_map = Map(Table(self._tables["Source"])) + self.repository_map = Map(Table(self._tables["Repository"])) + self.note_map = Map(Table(self._tables["Note"])) + self.media_map = Map(Table(self._tables["Media"])) + self.event_map = Map(Table(self._tables["Event"])) + self.tag_map = Map(Table(self._tables["Tag"])) + self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) + self.name_group = {} + self.event_names = set() + self.individual_attributes = set() + self.family_attributes = set() + self.source_attributes = set() + self.child_ref_types = set() + self.family_rel_types = set() + self.event_role_names = set() + self.name_types = set() + self.origin_types = set() + self.repository_types = set() + self.note_types = set() + self.source_media_types = set() + self.url_types = set() + self.media_attributes = set() + self.place_types = set() + + def prepare_import(self): + """ + DbDjango does not commit data on gedcom import, but saves them + for later commit. + """ + self.use_import_cache = True + self.import_cache = {} + + @transaction.atomic + def commit_import(self): + """ + Commits the items that were queued up during the last gedcom + import for two step adding. + """ + # First we add the primary objects: + for key in list(self.import_cache.keys()): + obj = self.import_cache[key] + if isinstance(obj, Person): + self.dji.add_person(obj.serialize()) + elif isinstance(obj, Family): + self.dji.add_family(obj.serialize()) + elif isinstance(obj, Event): + self.dji.add_event(obj.serialize()) + elif isinstance(obj, Place): + self.dji.add_place(obj.serialize()) + elif isinstance(obj, Repository): + self.dji.add_repository(obj.serialize()) + elif isinstance(obj, Citation): + self.dji.add_citation(obj.serialize()) + elif isinstance(obj, Source): + self.dji.add_source(obj.serialize()) + elif isinstance(obj, Note): + self.dji.add_note(obj.serialize()) + elif isinstance(obj, MediaObject): + self.dji.add_media(obj.serialize()) + elif isinstance(obj, Tag): + self.dji.add_tag(obj.serialize()) + # Next we add the links: + for key in list(self.import_cache.keys()): + obj = self.import_cache[key] + if isinstance(obj, Person): + self.dji.add_person_detail(obj.serialize()) + elif isinstance(obj, Family): + self.dji.add_family_detail(obj.serialize()) + elif isinstance(obj, Event): + self.dji.add_event_detail(obj.serialize()) + elif isinstance(obj, Place): + self.dji.add_place_detail(obj.serialize()) + elif isinstance(obj, Repository): + self.dji.add_repository_detail(obj.serialize()) + elif isinstance(obj, Citation): + self.dji.add_citation_detail(obj.serialize()) + elif isinstance(obj, Source): + self.dji.add_source_detail(obj.serialize()) + elif isinstance(obj, Note): + self.dji.add_note_detail(obj.serialize()) + elif isinstance(obj, MediaObject): + self.dji.add_media_detail(obj.serialize()) + elif isinstance(obj, Tag): + self.dji.add_tag_detail(obj.serialize()) + self.use_import_cache = False + self.import_cache = {} + self.dji.update_publics() + + def transaction_commit(self, txn): + pass + + def request_rebuild(self): + # caches are ok, but let's compute public's + self.dji.update_publics() + self.emit('person-rebuild') + self.emit('family-rebuild') + self.emit('place-rebuild') + self.emit('source-rebuild') + self.emit('citation-rebuild') + self.emit('media-rebuild') + self.emit('event-rebuild') + self.emit('repository-rebuild') + self.emit('note-rebuild') + self.emit('tag-rebuild') + + def get_undodb(self): + return None + + def transaction_abort(self, txn): + pass + + @staticmethod + def _validated_id_prefix(val, default): + if isinstance(val, str) 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_<object>_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 = Researcher() + return obj + + def get_tag_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Tag.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Tag.all()] + + def get_person_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Person.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Person.all()] + + def get_family_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Family.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Family.all()] + + def get_event_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Event.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Event.all()] + + def get_citation_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Citation.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Citation.all()] + + def get_source_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Source.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Source.all()] + + def get_place_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Place.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Place.all()] + + def get_repository_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Repository.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Repository.all()] + + def get_media_object_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Media.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Media.all()] + + def get_note_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Note.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Note.all()] + + def get_media_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + media = self.dji.Media.get(handle=handle) + except: + return None + return self.make_media(media) + + def get_event_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + event = self.dji.Event.get(handle=handle) + except: + return None + return self.make_event(event) + + def get_family_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + family = self.dji.Family.get(handle=handle) + except: + return None + return self.make_family(family) + + def get_repository_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + repository = self.dji.Repository.get(handle=handle) + except: + return None + return self.make_repository(repository) + + def get_person_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + person = self.dji.Person.get(handle=handle) + except: + return None + return self.make_person(person) + + def get_tag_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + tag = self.dji.Tag.get(handle=handle) + except: + return None + return self.make_tag(tag) + + def make_repository(self, repository): + if self.use_db_cache and repository.cache: + data = repository.from_cache() + else: + data = self.dji.get_repository(repository) + return Repository.create(data) + + def make_citation(self, citation): + if self.use_db_cache and citation.cache: + data = citation.from_cache() + else: + data = self.dji.get_citation(citation) + return Citation.create(data) + + def make_source(self, source): + if self.use_db_cache and source.cache: + data = source.from_cache() + else: + data = self.dji.get_source(source) + return Source.create(data) + + def make_family(self, family): + if self.use_db_cache and family.cache: + data = family.from_cache() + else: + data = self.dji.get_family(family) + return Family.create(data) + + def make_person(self, person): + if self.use_db_cache and person.cache: + data = person.from_cache() + else: + data = self.dji.get_person(person) + return Person.create(data) + + def make_event(self, event): + if self.use_db_cache and event.cache: + data = event.from_cache() + else: + data = self.dji.get_event(event) + return Event.create(data) + + def make_note(self, note): + if self.use_db_cache and note.cache: + data = note.from_cache() + else: + data = self.dji.get_note(note) + return Note.create(data) + + def make_tag(self, tag): + data = self.dji.get_tag(tag) + return Tag.create(data) + + def make_place(self, place): + if self.use_db_cache and place.cache: + data = place.from_cache() + else: + data = self.dji.get_place(place) + return Place.create(data) + + def make_media(self, media): + if self.use_db_cache and media.cache: + data = media.from_cache() + else: + data = self.dji.get_media(media) + return MediaObject.create(data) + + def get_place_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + place = self.dji.Place.get(handle=handle) + except: + return None + return self.make_place(place) + + def get_citation_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + citation = self.dji.Citation.get(handle=handle) + except: + return None + return self.make_citation(citation) + + def get_source_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + source = self.dji.Source.get(handle=handle) + except: + return None + return self.make_source(source) + + def get_note_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + note = self.dji.Note.get(handle=handle) + except: + return None + return self.make_note(note) + + def get_object_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + media = self.dji.Media.get(handle=handle) + except: + return None + return self.make_media(media) + + def get_default_person(self): + people = self.dji.Person.all() + if people.count() > 0: + return self.make_person(people[0]) + return None + + def iter_people(self): + return (self.get_person_from_handle(person.handle) + for person in self.dji.Person.all()) + + def iter_person_handles(self): + return (person.handle for person in self.dji.Person.all()) + + def iter_families(self): + return (self.get_family_from_handle(family.handle) + for family in self.dji.Family.all()) + + def iter_family_handles(self): + return (family.handle for family in self.dji.Family.all()) + + def iter_notes(self): + return (self.get_note_from_handle(note.handle) + for note in self.dji.Note.all()) + + def iter_note_handles(self): + return (note.handle for note in self.dji.Note.all()) + + def iter_events(self): + return (self.get_event_from_handle(event.handle) + for event in self.dji.Event.all()) + + def iter_event_handles(self): + return (event.handle for event in self.dji.Event.all()) + + def iter_places(self): + return (self.get_place_from_handle(place.handle) + for place in self.dji.Place.all()) + + def iter_place_handles(self): + return (place.handle for place in self.dji.Place.all()) + + def iter_repositories(self): + return (self.get_repository_from_handle(repository.handle) + for repository in self.dji.Repository.all()) + + def iter_repository_handles(self): + return (repository.handle for repository in self.dji.Repository.all()) + + def iter_sources(self): + return (self.get_source_from_handle(source.handle) + for source in self.dji.Source.all()) + + def iter_source_handles(self): + return (source.handle for source in self.dji.Source.all()) + + def iter_citations(self): + return (self.get_citation_from_handle(citation.handle) + for citation in self.dji.Citation.all()) + + def iter_citation_handles(self): + return (citation.handle for citation in self.dji.Citation.all()) + + def iter_tags(self): + return (self.get_tag_from_handle(tag.handle) + for tag in self.dji.Tag.all()) + + def iter_tag_handles(self): + return (tag.handle for tag in self.dji.Tag.all()) + + def iter_media_objects(self): + return (self.get_media_from_handle(media.handle) + for media in self.dji.Media.all()) + + def get_tag_from_name(self, name): + try: + tag = self.dji.Tag.filter(name=name) + return self.make_tag(tag[0]) + except: + return None + + def get_person_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Person.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_person(match_list[0]) + else: + return None + + def get_family_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + try: + family = self.dji.Family.get(gramps_id=gramps_id) + except: + return None + return self.make_family(family) + + def get_source_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Source.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_source(match_list[0]) + else: + return None + + def get_citation_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Citation.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_citation(match_list[0]) + else: + return None + + def get_event_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Event.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_event(match_list[0]) + else: + return None + + def get_object_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Media.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_media(match_list[0]) + else: + return None + + def get_place_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Place.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_place(match_list[0]) + else: + return None + + def get_repository_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Repsoitory.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_repository(match_list[0]) + else: + return None + + def get_note_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Note.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_note(match_list[0]) + else: + return None + + def get_number_of_people(self): + return self.dji.Person.count() + + def get_number_of_events(self): + return self.dji.Event.count() + + def get_number_of_places(self): + return self.dji.Place.count() + + def get_number_of_tags(self): + return self.dji.Tag.count() + + def get_number_of_families(self): + return self.dji.Family.count() + + def get_number_of_notes(self): + return self.dji.Note.count() + + def get_number_of_citations(self): + return self.dji.Citation.count() + + def get_number_of_sources(self): + return self.dji.Source.count() + + def get_number_of_media_objects(self): + return self.dji.Media.count() + + def get_number_of_repositories(self): + return self.dji.Repository.count() + + def get_place_cursor(self): + return Cursor(self.dji.Place, self.get_raw_place_data) + + def get_person_cursor(self): + return Cursor(self.dji.Person, self.get_raw_person_data) + + def get_family_cursor(self): + return Cursor(self.dji.Family, self.get_raw_family_data) + + def get_event_cursor(self): + return Cursor(self.dji.Event, self.get_raw_event_data) + + def get_citation_cursor(self): + return Cursor(self.dji.Citation, self.get_raw_citation_data) + + def get_source_cursor(self): + return Cursor(self.dji.Source, self.get_raw_source_data) + + def get_note_cursor(self): + return Cursor(self.dji.Note, self.get_raw_note_data) + + def get_tag_cursor(self): + return Cursor(self.dji.Tag, self.get_raw_tag_data) + + def get_repository_cursor(self): + return Cursor(self.dji.Repository, self.get_raw_repository_data) + + def get_media_cursor(self): + return Cursor(self.dji.Media, self.get_raw_object_data) + + def has_gramps_id(self, obj_key, gramps_id): + key2table = { + PERSON_KEY: self.dji.Person, + FAMILY_KEY: self.dji.Family, + SOURCE_KEY: self.dji.Source, + CITATION_KEY: self.dji.Citation, + EVENT_KEY: self.dji.Event, + MEDIA_KEY: self.dji.Media, + PLACE_KEY: self.dji.Place, + REPOSITORY_KEY: self.dji.Repository, + NOTE_KEY: self.dji.Note, + } + table = key2table[obj_key] + return table.filter(gramps_id=gramps_id).count() > 0 + + def has_person_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Person.filter(handle=handle).count() == 1 + + def has_family_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Family.filter(handle=handle).count() == 1 + + def has_citation_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Citation.filter(handle=handle).count() == 1 + + def has_source_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Source.filter(handle=handle).count() == 1 + + def has_repository_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Repository.filter(handle=handle).count() == 1 + + def has_note_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Note.filter(handle=handle).count() == 1 + + def has_place_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Place.filter(handle=handle).count() == 1 + + def has_event_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Event.filter(handle=handle).count() == 1 + + def has_tag_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Tag.filter(handle=handle).count() == 1 + + def has_object_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Media.filter(handle=handle).count() == 1 + + 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): + try: + return self.dji.get_person(self.dji.Person.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_family_data(self, handle): + try: + return self.dji.get_family(self.dji.Family.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_citation_data(self, handle): + try: + return self.dji.get_citation(self.dji.Citation.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_source_data(self, handle): + try: + return self.dji.get_source(self.dji.Source.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_repository_data(self, handle): + try: + return self.dji.get_repository(self.dji.Repository.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_note_data(self, handle): + try: + return self.dji.get_note(self.dji.Note.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_place_data(self, handle): + try: + return self.dji.get_place(self.dji.Place.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_object_data(self, handle): + try: + return self.dji.get_media(self.dji.Media.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_tag_data(self, handle): + try: + return self.dji.get_tag(self.dji.Tag.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_event_data(self, handle): + try: + return self.dji.get_event(self.dji.Event.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def add_person(self, person, trans, set_gid=True): + if not person.handle: + person.handle = create_id() + if not person.gramps_id and set_gid: + person.gramps_id = self.find_next_person_gramps_id() + self.commit_person(person, trans) + self.emit("person-add", ([person.handle],)) + return person.handle + + def add_family(self, family, trans, set_gid=True): + if not family.handle: + family.handle = create_id() + if not family.gramps_id and set_gid: + family.gramps_id = self.find_next_family_gramps_id() + self.commit_family(family, trans) + self.emit("family-add", ([family.handle],)) + return family.handle + + def add_citation(self, citation, trans, set_gid=True): + if not citation.handle: + citation.handle = create_id() + if not citation.gramps_id and set_gid: + citation.gramps_id = self.find_next_citation_gramps_id() + self.commit_citation(citation, trans) + self.emit("citation-add", ([citation.handle],)) + return citation.handle + + def add_source(self, source, trans, set_gid=True): + if not source.handle: + source.handle = create_id() + if not source.gramps_id and set_gid: + source.gramps_id = self.find_next_source_gramps_id() + self.commit_source(source, trans) + self.emit("source-add", ([source.handle],)) + return source.handle + + def add_repository(self, repository, trans, set_gid=True): + if not repository.handle: + repository.handle = create_id() + if not repository.gramps_id and set_gid: + repository.gramps_id = self.find_next_repository_gramps_id() + self.commit_repository(repository, trans) + self.emit("repository-add", ([repository.handle],)) + return repository.handle + + def add_note(self, note, trans, set_gid=True): + if not note.handle: + note.handle = create_id() + if not note.gramps_id and set_gid: + note.gramps_id = self.find_next_note_gramps_id() + self.commit_note(note, trans) + self.emit("note-add", ([note.handle],)) + return note.handle + + def add_place(self, place, trans, set_gid=True): + if not place.handle: + place.handle = create_id() + if not place.gramps_id and 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 = create_id() + if not event.gramps_id and 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 = 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 = create_id() + if not obj.gramps_id and 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): + if self.use_import_cache: + self.import_cache[person.handle] = person + else: + raw = person.serialize() + items = self.dji.Person.filter(handle=person.handle) + if items.count() > 0: + # Hack, for the moment: delete and re-add + items[0].delete() + self.dji.add_person(person.serialize()) + self.dji.add_person_detail(person.serialize()) + if items.count() > 0: + self.emit("person-update", ([person.handle],)) + else: + self.emit("person-add", ([person.handle],)) + + def commit_family(self, family, trans, change_time=None): + if self.use_import_cache: + self.import_cache[family.handle] = family + else: + raw = family.serialize() + items = self.dji.Family.filter(handle=family.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_family(family.serialize()) + self.dji.add_family_detail(family.serialize()) + if items.count() > 0: + self.emit("family-update", ([family.handle],)) + else: + self.emit("family-add", ([family.handle],)) + + def commit_citation(self, citation, trans, change_time=None): + if self.use_import_cache: + self.import_cache[citation.handle] = citation + else: + raw = citation.serialize() + items = self.dji.Citation.filter(handle=citation.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_citation(citation.serialize()) + self.dji.add_citation_detail(citation.serialize()) + if items.count() > 0: + self.emit("citation-update", ([citation.handle],)) + else: + self.emit("citation-add", ([citation.handle],)) + + def commit_source(self, source, trans, change_time=None): + if self.use_import_cache: + self.import_cache[source.handle] = source + else: + raw = source.serialize() + items = self.dji.Source.filter(handle=source.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_source(source.serialize()) + self.dji.add_source_detail(source.serialize()) + if items.count() > 0: + self.emit("source-update", ([source.handle],)) + else: + self.emit("source-add", ([source.handle],)) + + def commit_repository(self, repository, trans, change_time=None): + if self.use_import_cache: + self.import_cache[repository.handle] = repository + else: + raw = repository.serialize() + items = self.dji.Repository.filter(handle=repository.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_repository(repository.serialize()) + self.dji.add_repository_detail(repository.serialize()) + if items.count() > 0: + self.emit("repository-update", ([repository.handle],)) + else: + self.emit("repository-add", ([repository.handle],)) + + def commit_note(self, note, trans, change_time=None): + if self.use_import_cache: + self.import_cache[note.handle] = note + else: + raw = note.serialize() + items = self.dji.Note.filter(handle=note.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_note(note.serialize()) + self.dji.add_note_detail(note.serialize()) + if items.count() > 0: + self.emit("note-update", ([note.handle],)) + else: + self.emit("note-add", ([note.handle],)) + + def commit_place(self, place, trans, change_time=None): + if self.use_import_cache: + self.import_cache[place.handle] = place + else: + raw = place.serialize() + items = self.dji.Place.filter(handle=place.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_place(place.serialize()) + self.dji.add_place_detail(place.serialize()) + if items.count() > 0: + self.emit("place-update", ([place.handle],)) + else: + self.emit("place-add", ([place.handle],)) + + def commit_event(self, event, trans, change_time=None): + if self.use_import_cache: + self.import_cache[event.handle] = event + else: + raw = event.serialize() + items = self.dji.Event.filter(handle=event.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_event(event.serialize()) + self.dji.add_event_detail(event.serialize()) + if items.count() > 0: + self.emit("event-update", ([event.handle],)) + else: + self.emit("event-add", ([event.handle],)) + + def commit_tag(self, tag, trans, change_time=None): + if self.use_import_cache: + self.import_cache[tag.handle] = tag + else: + raw = tag.serialize() + items = self.dji.Tag.filter(handle=tag.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_tag(tag.serialize()) + self.dji.add_tag_detail(tag.serialize()) + if items.count() > 0: + self.emit("tag-update", ([tag.handle],)) + else: + self.emit("tag-add", ([tag.handle],)) + + def commit_media_object(self, media, transaction, change_time=None): + """ + Commit the specified MediaObject to the database, storing the changes + as part of the transaction. + """ + if self.use_import_cache: + self.import_cache[obj.handle] = media + else: + raw = media.serialize() + items = self.dji.Media.filter(handle=media.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_media(media.serialize()) + self.dji.add_media_detail(media.serialize()) + if items.count() > 0: + self.emit("media-update", ([media.handle],)) + else: + self.emit("media-add", ([media.handle],)) + + def get_gramps_ids(self, obj_key): + key2table = { + PERSON_KEY: self.id_trans, + FAMILY_KEY: self.fid_trans, + CITATION_KEY: self.cid_trans, + SOURCE_KEY: self.sid_trans, + EVENT_KEY: self.eid_trans, + MEDIA_KEY: self.oid_trans, + PLACE_KEY: self.pid_trans, + REPOSITORY_KEY: self.rid_trans, + NOTE_KEY: self.nid_trans, + } + + table = key2table[obj_key] + return list(table.keys()) + + def transaction_begin(self, transaction): + return + + def set_researcher(self, owner): + pass + + def copy_from_db(self, db): + """ + A (possibily) implementation-specific method to get data from + db into this database. + """ + # First we add the primary objects: + for key in db._tables.keys(): + cursor = db._tables[key]["cursor_func"] + for (handle, data) in cursor(): + if key == "Person": + self.dji.add_person(data) + elif key == "Family": + self.dji.add_family(data) + elif key == "Event": + self.dji.add_event(data) + elif key == "Place": + self.dji.add_place(data) + elif key == "Repository": + self.dji.add_repository(data) + elif key == "Citation": + self.dji.add_citation(data) + elif key == "Source": + self.dji.add_source(data) + elif key == "Note": + self.dji.add_note(data) + elif key == "Media": + self.dji.add_media(data) + elif key == "Tag": + self.dji.add_tag(data) + for key in db._tables.keys(): + cursor = db._tables[key]["cursor_func"] + for (handle, data) in cursor(): + if key == "Person": + self.dji.add_person_detail(data) + elif key == "Family": + self.dji.add_family_detail(data) + elif key == "Event": + self.dji.add_event_detail(data) + elif key == "Place": + self.dji.add_place_detail(data) + elif key == "Repository": + self.dji.add_repository_detail(data) + elif key == "Citation": + self.dji.add_citation_detail(data) + elif key == "Source": + self.dji.add_source_detail(data) + elif key == "Note": + self.dji.add_note_detail(data) + elif key == "Media": + self.dji.add_media_detail(data) + elif key == "Tag": + self.dji.add_tag_detail(data) + # Next we add the links: + self.dji.update_publics() + + def get_from_name_and_handle(self, table_name, handle): + """ + Returns a gen.lib object (or None) given table_name and + handle. + + Examples: + + >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") + >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") + """ + if table_name in self._tables: + return self._tables[table_name]["handle_func"](handle) + return None + + def is_empty(self): + """ + Is the database empty? + """ + return (self.get_number_of_people() == 0 and + self.get_number_of_events() == 0 and + self.get_number_of_places() == 0 and + self.get_number_of_tags() == 0 and + self.get_number_of_families() == 0 and + self.get_number_of_notes() == 0 and + self.get_number_of_citations() == 0 and + self.get_number_of_sources() == 0 and + self.get_number_of_media_objects() == 0 and + self.get_number_of_repositories() == 0) + + __callback_map = {} + + def set_prefixes(self, person, media, family, source, citation, + place, event, repository, note): + self.set_person_id_prefix(person) + self.set_object_id_prefix(media) + self.set_family_id_prefix(family) + self.set_source_id_prefix(source) + self.set_citation_id_prefix(citation) + self.set_place_id_prefix(place) + self.set_event_id_prefix(event) + self.set_repository_id_prefix(repository) + self.set_note_id_prefix(note) + + def has_changed(self): + return False + + def find_backlink_handles(self, handle, include_classes=None): + ## FIXME: figure out how to get objects that refer + ## to this handle + return [] + + def get_note_bookmarks(self): + return self.note_bookmarks + + def get_media_bookmarks(self): + return self.media_bookmarks + + def get_repo_bookmarks(self): + return self.repo_bookmarks + + def get_citation_bookmarks(self): + return self.citation_bookmarks + + def get_source_bookmarks(self): + return self.source_bookmarks + + def get_place_bookmarks(self): + return self.place_bookmarks + + def get_event_bookmarks(self): + return self.event_bookmarks + + def get_bookmarks(self): + return self.bookmarks + + def get_family_bookmarks(self): + return self.family_bookmarks + + def get_save_path(self): + return "/tmp/" + + ## Get types: + def get_event_attribute_types(self): + """ + Return a list of all Attribute types assocated with Event instances + in the database. + """ + return list(self.event_attributes) + + def get_event_types(self): + """ + Return a list of all event types in the database. + """ + return list(self.event_names) + + def get_person_event_types(self): + """ + Deprecated: Use get_event_types + """ + return list(self.event_names) + + def get_person_attribute_types(self): + """ + Return a list of all Attribute types assocated with Person instances + in the database. + """ + return list(self.individual_attributes) + + def get_family_attribute_types(self): + """ + Return a list of all Attribute types assocated with Family instances + in the database. + """ + return list(self.family_attributes) + + def get_family_event_types(self): + """ + Deprecated: Use get_event_types + """ + return list(self.event_names) + + def get_media_attribute_types(self): + """ + Return a list of all Attribute types assocated with Media and MediaRef + instances in the database. + """ + return list(self.media_attributes) + + def get_family_relation_types(self): + """ + Return a list of all relationship types assocated with Family + instances in the database. + """ + return list(self.family_rel_types) + + def get_child_reference_types(self): + """ + Return a list of all child reference types assocated with Family + instances in the database. + """ + return list(self.child_ref_types) + + def get_event_roles(self): + """ + Return a list of all custom event role names assocated with Event + instances in the database. + """ + return list(self.event_role_names) + + def get_name_types(self): + """ + Return a list of all custom names types assocated with Person + instances in the database. + """ + return list(self.name_types) + + def get_origin_types(self): + """ + Return a list of all custom origin types assocated with Person/Surname + instances in the database. + """ + return list(self.origin_types) + + def get_repository_types(self): + """ + Return a list of all custom repository types assocated with Repository + instances in the database. + """ + return list(self.repository_types) + + def get_note_types(self): + """ + Return a list of all custom note types assocated with Note instances + in the database. + """ + return list(self.note_types) + + def get_source_attribute_types(self): + """ + Return a list of all Attribute types assocated with Source/Citation + instances in the database. + """ + return list(self.source_attributes) + + def get_source_media_types(self): + """ + Return a list of all custom source media types assocated with Source + instances in the database. + """ + return list(self.source_media_types) + + def get_url_types(self): + """ + Return a list of all custom names types assocated with Url instances + in the database. + """ + return list(self.url_types) + + def get_place_types(self): + """ + Return a list of all custom place types assocated with Place instances + in the database. + """ + return list(self.place_types) + + def get_default_handle(self): + people = self.dji.Person.all() + if people.count() > 0: + return people[0].handle + return None + + def close(self): + pass + + def get_surname_list(self): + return [] + + def is_open(self): + return True + + def get_table_names(self): + """Return a list of valid table names.""" + return list(self._tables.keys()) + + def find_initial_person(self): + return self.get_default_person() + + # Removals: + def remove_person(self, handle, txn): + self.dji.Person.filter(handle=handle)[0].delete() + self.emit("person-delete", ([handle],)) + + def remove_source(self, handle, transaction): + self.dji.Source.filter(handle=handle)[0].delete() + self.emit("source-delete", ([handle],)) + + def remove_citation(self, handle, transaction): + self.dji.Citation.filter(handle=handle)[0].delete() + self.emit("citation-delete", ([handle],)) + + def remove_event(self, handle, transaction): + self.dji.Event.filter(handle=handle)[0].delete() + self.emit("event-delete", ([handle],)) + + def remove_object(self, handle, transaction): + self.dji.Media.filter(handle=handle)[0].delete() + self.emit("media-delete", ([handle],)) + + def remove_place(self, handle, transaction): + self.dji.Place.filter(handle=handle)[0].delete() + self.emit("place-delete", ([handle],)) + + def remove_family(self, handle, transaction): + self.dji.Family.filter(handle=handle)[0].delete() + self.emit("family-delete", ([handle],)) + + def remove_repository(self, handle, transaction): + self.dji.Repository.filter(handle=handle)[0].delete() + self.emit("repository-delete", ([handle],)) + + def remove_note(self, handle, transaction): + self.dji.Note.filter(handle=handle)[0].delete() + self.emit("note-delete", ([handle],)) + + def remove_tag(self, handle, transaction): + self.dji.Tag.filter(handle=handle)[0].delete() + self.emit("tag-delete", ([handle],)) + + def remove_from_surname_list(self, person): + ## FIXME + pass + + def get_dbname(self): + return "Django Database" + + ## missing + + def find_place_child_handles(self, handle): + pass + + def get_cursor(self, table, txn=None, update=False, commit=False): + pass + + def get_from_name_and_handle(self, table_name, handle): + """ + Returns a gen.lib object (or None) given table_name and + handle. + + Examples: + + >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") + >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") + """ + if table_name in self._tables: + return self._tables[table_name]["handle_func"](handle) + return None + + def get_number_of_records(self, table): + pass + + def get_place_parent_cursor(self): + pass + + def get_place_tree_cursor(self): + pass + + def get_table_metadata(self, table_name): + """Return the metadata for a valid table name.""" + if table_name in self._tables: + return self._tables[table_name] + return None + + def get_transaction_class(self): + pass + + def undo(self, update_history=True): + # FIXME: + return self.undodb.undo(update_history) + + def redo(self, update_history=True): + # FIXME: + return self.undodb.redo(update_history) + + def backup(self): + pass + + def restore(self): + pass From 6bff90419e99d814886b7a9f95e1450c93534596 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Wed, 13 May 2015 12:36:17 -0400 Subject: [PATCH 006/105] Loads tree based on id in database.txt --- gramps/cli/grampscli.py | 10 +++++++++- gramps/gui/dbloader.py | 10 +++++++++- gramps/plugins/database/djangodb.py | 19 ++++++++++++++++--- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/gramps/cli/grampscli.py b/gramps/cli/grampscli.py index d1d3cfc07..d7b9afea6 100644 --- a/gramps/cli/grampscli.py +++ b/gramps/cli/grampscli.py @@ -47,6 +47,7 @@ LOG = logging.getLogger(".grampscli") from gramps.gen.display.name import displayer as name_displayer from gramps.gen.config import config from gramps.gen.const import PLUGINS_DIR, USER_PLUGINS +from gramps.gen.db.dbconst import DBBACKEND from gramps.gen.errors import DbError from gramps.gen.dbstate import DbState from gramps.gen.db.exceptions import (DbUpgradeRequiredError, @@ -151,7 +152,14 @@ class CLIDbLoader(object): else: mode = 'w' - db = self.dbstate.make_database("bsddb") + dbid_path = os.path.join(filename, DBBACKEND) + if os.path.isfile(dbid_path): + with open(dbid_path) as fp: + dbid = fp.read().strip() + else: + dbid = "bsddb" + + db = self.dbstate.make_database(dbid) self.dbstate.change_database(db) self.dbstate.db.disable_signals() diff --git a/gramps/gui/dbloader.py b/gramps/gui/dbloader.py index a0105d9a3..863aea669 100644 --- a/gramps/gui/dbloader.py +++ b/gramps/gui/dbloader.py @@ -52,6 +52,7 @@ from gi.repository import GObject # #------------------------------------------------------------------------- from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.db.dbconst import DBBACKEND _ = glocale.translation.gettext from gramps.cli.grampscli import CLIDbLoader from gramps.gen.config import config @@ -304,7 +305,14 @@ class DbLoader(CLIDbLoader): else: mode = 'w' - db = self.dbstate.make_database("bsddb") + dbid_path = os.path.join(filename, DBBACKEND) + if os.path.isfile(dbid_path): + with open(dbid_path) as fp: + dbid = fp.read().strip() + else: + dbid = "bsddb" + + db = self.dbstate.make_database(dbid) db.disable_signals() self.dbstate.no_database() diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index adb28dc54..e0d8c043a 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -55,6 +55,11 @@ from gramps.gen.db import (PERSON_KEY, from gramps.gen.utils.id import create_id from django.db import transaction +## add this directory to sys path, so we can find django_support later: +import sys +import os +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + class Environment(object): """ Implements the Environment API. @@ -322,10 +327,15 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if directory: self.load(directory) - def load(self, directory, pulse_progress=None, mode=None): + def load(self, directory, pulse_progress=None, mode=None, + force_schema_upgrade=False, + force_bsddb_upgrade=False, + force_bsddb_downgrade=False, + force_python_upgrade=False): self._directory = directory from django.conf import settings - default_settings = {} + default_settings = {"__file__": + os.path.join(directory, "default_settings.py")} settings_file = os.path.join(directory, "default_settings.py") with open(settings_file) as f: code = compile(f.read(), settings_file, 'exec') @@ -1785,7 +1795,10 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): return self.family_bookmarks def get_save_path(self): - return "/tmp/" + return self._directory + + def set_save_path(self, directory): + self._directory = directory ## Get types: def get_event_attribute_types(self): From fbbd9d9c6e2d912cea54563868181468d23cefdd Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Wed, 13 May 2015 19:26:14 -0400 Subject: [PATCH 007/105] DictionaryDb: adding missing functions, bringing up to date --- gramps/plugins/database/dictionarydb.py | 454 ++++++++++++++++++++---- gramps/plugins/database/djangodb.py | 6 +- gramps/webapp/utils.py | 6 +- 3 files changed, 395 insertions(+), 71 deletions(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index a3dbd9586..621425e2d 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -29,6 +29,8 @@ import base64 import time import re from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn +from gramps.gen.utils.callback import Callback +from gramps.gen.updatecallback import UpdateCallback from gramps.gen.db import (PERSON_KEY, FAMILY_KEY, CITATION_KEY, @@ -53,33 +55,101 @@ from gramps.gen.lib.repo import Repository from gramps.gen.lib.note import Note from gramps.gen.lib.tag import Tag +class Environment(object): + """ + Implements the Environment API. + """ + def __init__(self, db): + self.db = db + + def txn_begin(self): + return DictionaryTxn("DictionaryDb Transaction", self.db) + +class Table(object): + """ + Implements Table interface. + """ + def __init__(self, funcs): + self.funcs = funcs + + def cursor(self): + """ + Returns a Cursor for this Table. + """ + return self.funcs["cursor_func"]() + + def put(key, data, txn=None): + self[key] = data + +class Map(dict): + """ + Implements the map API for person_map, etc. + + Takes a Table() as argument. + """ + def __init__(self, tbl, *args, **kwargs): + super().__init__(*args, **kwargs) + self.db = tbl + +class MetaCursor(object): + def __init__(self): + pass + def __enter__(self): + return self + def __iter__(self): + return self.__next__() + def __next__(self): + yield None + def __exit__(self, *args, **kwargs): + pass + def iter(self): + yield None + def first(self): + self._iter = self.__iter__() + return self.next() + def next(self): + try: + return next(self._iter) + except: + return None + def close(self): + pass + class Cursor(object): - """ - Iterates through model returning (handle, raw_data)... - """ - def __init__(self, model, func): - self.model = model + def __init__(self, map, func): + self.map = map self.func = func def __enter__(self): return self def __iter__(self): - return self + return self.__next__() def __next__(self): - for handle in self.model.keys(): - return (handle, self.func(handle)) - def next(self): - for handle in self.model.keys(): - return (handle, self.func(handle)) + for item in self.map.keys(): + yield (bytes(item, "utf-8"), self.func(item)) def __exit__(self, *args, **kwargs): pass + def iter(self): + for item in self.map.keys(): + yield (bytes(item, "utf-8"), self.func(item)) + yield None + def first(self): + self._iter = self.__iter__() + return self.next() + def next(self): + try: + return next(self._iter) + except: + return None def close(self): pass -class Bookmarks: +class Bookmarks(object): + def __init__(self): + self.handles = [] def get(self): - return [] # handles + return self.handles def append(self, handle): - pass + self.handles.append(handle) class DictionaryTxn(DbTxn): def __init__(self, message, db, batch=False): @@ -99,13 +169,38 @@ class DictionaryTxn(DbTxn): """ txn[handle] = new_data -class DictionaryDb(DbWriteBase, DbReadBase): +class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): """ A Gramps Database Backend. This replicates the grampsdb functions. """ + __signals__ = dict((obj+'-'+op, signal) + for obj in + ['person', 'family', 'event', 'place', + 'source', 'citation', 'media', 'note', 'repository', 'tag'] + for op, signal in zip( + ['add', 'update', 'delete', 'rebuild'], + [(list,), (list,), (list,), None] + ) + ) + + # 2. Signals for long operations + __signals__.update(('long-op-'+op, signal) for op, signal in zip( + ['start', 'heartbeat', 'end'], + [(object,), None, None] + )) + + # 3. Special signal for change in home person + __signals__['home-person-changed'] = None + + # 4. Signal for change in person group name, parameters are + __signals__['person-groupname-rebuild'] = (str, str) + + __callback_map = {} + def __init__(self, *args, **kwargs): DbReadBase.__init__(self) DbWriteBase.__init__(self) + Callback.__init__(self) self._tables['Person'].update( { "handle_func": self.get_person_from_handle, @@ -115,6 +210,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_person_handles, "add_func": self.add_person, "commit_func": self.commit_person, + "iter_func": self.iter_people, }) self._tables['Family'].update( { @@ -125,6 +221,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_family_handles, "add_func": self.add_family, "commit_func": self.commit_family, + "iter_func": self.iter_families, }) self._tables['Source'].update( { @@ -135,6 +232,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_source_handles, "add_func": self.add_source, "commit_func": self.commit_source, + "iter_func": self.iter_sources, }) self._tables['Citation'].update( { @@ -145,6 +243,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_citation_handles, "add_func": self.add_citation, "commit_func": self.commit_citation, + "iter_func": self.iter_citations, }) self._tables['Event'].update( { @@ -155,6 +254,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_event_handles, "add_func": self.add_event, "commit_func": self.commit_event, + "iter_func": self.iter_events, }) self._tables['Media'].update( { @@ -165,6 +265,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_media_object_handles, "add_func": self.add_object, "commit_func": self.commit_media_object, + "iter_func": self.iter_media_objects, }) self._tables['Place'].update( { @@ -175,6 +276,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_place_handles, "add_func": self.add_place, "commit_func": self.commit_place, + "iter_func": self.iter_places, }) self._tables['Repository'].update( { @@ -185,6 +287,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_repository_handles, "add_func": self.add_repository, "commit_func": self.commit_repository, + "iter_func": self.iter_repositories, }) self._tables['Note'].update( { @@ -195,6 +298,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_note_handles, "add_func": self.add_note, "commit_func": self.commit_note, + "iter_func": self.iter_notes, }) self._tables['Tag'].update( { @@ -205,6 +309,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_tag_handles, "add_func": self.add_tag, "commit_func": self.commit_tag, + "iter_func": self.iter_tags, }) # skip GEDCOM cross-ref check for now: self.set_feature("skip-check-xref", True) @@ -218,7 +323,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): self.place_bookmarks = Bookmarks() self.citation_bookmarks = Bookmarks() self.source_bookmarks = Bookmarks() - self.repo_bookmarks = Bookmarks() + self.repository_bookmarks = Bookmarks() self.media_bookmarks = Bookmarks() self.note_bookmarks = Bookmarks() self.set_person_id_prefix('I%04d') @@ -249,18 +354,18 @@ class DictionaryDb(DbWriteBase, DbReadBase): 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.tag_map = {} - self.metadata = {} + self.env = Environment(self) + self.person_map = Map(Table(self._tables["Person"])) + self.family_map = Map(Table(self._tables["Family"])) + self.place_map = Map(Table(self._tables["Place"])) + self.citation_map = Map(Table(self._tables["Citation"])) + self.source_map = Map(Table(self._tables["Source"])) + self.repository_map = Map(Table(self._tables["Repository"])) + self.note_map = Map(Table(self._tables["Note"])) + self.media_map = Map(Table(self._tables["Media"])) + self.event_map = Map(Table(self._tables["Event"])) + self.tag_map = Map(Table(self._tables["Tag"])) + self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) self.name_group = {} self.undo_callback = None self.redo_callback = None @@ -286,9 +391,6 @@ class DictionaryDb(DbWriteBase, DbReadBase): def transaction_commit(self, txn): pass - def enable_signals(self): - pass - def get_undodb(self): return None @@ -546,91 +648,107 @@ class DictionaryDb(DbWriteBase, DbReadBase): return obj def get_person_handles(self, sort_handles=False): - if sort_handles: - raise Exception("Implement!") - else: - return self.person_map.keys() + ## Fixme: implement sort + return self.person_map.keys() def get_family_handles(self): return self.family_map.keys() - def get_event_handles(self): + def get_event_handles(self, sort_handles=False): return self.event_map.keys() def get_citation_handles(self, sort_handles=False): - if sort_handles: - raise Exception("Implement!") - else: - return self.citation_map.keys() + ## Fixme: implement sort + return self.citation_map.keys() def get_source_handles(self, sort_handles=False): - if sort_handles: - raise Exception("Implement!") - else: - return self.source_map.keys() + ## Fixme: implement sort + return self.source_map.keys() def get_place_handles(self, sort_handles=False): - if sort_handles: - raise Exception("Implement!") - else: - return self.place_map.keys() + ## Fixme: implement sort + return self.place_map.keys() def get_repository_handles(self): return self.repository_map.keys() def get_media_object_handles(self, sort_handles=False): - if sort_handles: - raise Exception("Implement!") - else: - return self.media_map.keys() + ## Fixme: implement sort + return self.media_map.keys() def get_note_handles(self): return self.note_map.keys() def get_tag_handles(self, sort_handles=False): - if sort_handles: - raise Exception("Implement!") - else: - return self.tag_map.keys() + # FIXME: sort + return self.tag_map.keys() def get_event_from_handle(self, handle): - return self.event_map[handle] + event = None + if handle in self.event_map: + event = self.event_map[handle] + return event def get_family_from_handle(self, handle): - return self.family_map[handle] + family = None + if handle in self.family_map: + family = self.family_map[handle] + return family def get_repository_from_handle(self, handle): - return self.repository_map[handle] + repository = None + if handle in self.repository_map: + repository = self.repository_map[handle] + return repository def get_person_from_handle(self, handle): - return self.person_map[handle] + person = None + if handle in self.person_map: + person = self.person_map[handle] + return person def get_place_from_handle(self, handle): - place = self.place_map[handle] + place = None + if handle in self.place_map: + place = self.place_map[handle] return place def get_citation_from_handle(self, handle): - citation = self.citation_map[handle] + citation = None + if handle in self.citation_map: + citation = self.citation_map[handle] return citation def get_source_from_handle(self, handle): - source = self.source_map[handle] + source = None + if handle in self.source_map: + source = self.source_map[handle] return source def get_note_from_handle(self, handle): - note = self.note_map[handle] + note = None + if handle in self.note_map: + note = self.note_map[handle] return note def get_object_from_handle(self, handle): - media = self.media_map[handle] + media = None + if handle in self.media_map: + media = self.media_map[handle] return media def get_tag_from_handle(self, handle): - tag = self.tag_map[handle] + tag = None + if handle in self.tag_map: + tag = self.tag_map[handle] return tag def get_default_person(self): - return None + handle = self.get_default_handle() + if handle: + return self.get_person_from_handle(handle) + else: + return None def iter_people(self): return (person for person in self.person_map.values()) @@ -1203,3 +1321,205 @@ class DictionaryDb(DbWriteBase, DbReadBase): transaction.add(REFERENCE_KEY, TXNDEL, key, old_data, None) #transaction.reference_del.append(str(key)) self.reference_map.delete(key, txn=txn) + + ## Missing: + + def backup(self): + pass + + def close(self): + pass + + def find_backlink_handles(self, handle, include_classes=None): + return [] + + def find_initial_person(self): + items = self.person_map.keys() + if len(items) > 0: + return self.get_person_from_handle(list(items)[0]) + return None + + def find_place_child_handles(self, handle): + return [] + + def get_bookmarks(self): + return self.bookmarks + + def get_child_reference_types(self): + return [] + + def get_citation_bookmarks(self): + return self.citation_bookmarks + + def get_cursor(self, table, txn=None, update=False, commit=False): + pass + + def get_dbname(self): + return "DictionaryDb" + + def get_default_handle(self): + items = self.person_map.keys() + if len(items) > 0: + return list(items)[0] + return None + + def get_event_attribute_types(self): + return [] + + def get_event_bookmarks(self): + return self.event_bookmarks + + def get_event_roles(self): + return [] + + def get_event_types(self): + return [] + + def get_family_attribute_types(self): + return [] + + def get_family_bookmarks(self): + return self.family_bookmarks + + def get_family_event_types(self): + return [] + + def get_family_relation_types(self): + return [] + + def get_media_attribute_types(self): + return [] + + def get_media_bookmarks(self): + return self.media_bookmarks + + def get_name_types(self): + return [] + + def get_note_bookmarks(self): + return self.note_bookmarks + + def get_note_types(self): + return [] + + def get_number_of_records(self, table): + return 0 + + def get_origin_types(self): + return [] + + def get_person_attribute_types(self): + return [] + + def get_person_event_types(self): + return [] + + def get_place_bookmarks(self): + return self.place_bookmarks + + def get_place_tree_cursor(self): + return [] + + def get_place_types(self): + return [] + + def get_repo_bookmarks(self): + return self.repository_bookmarks + + def get_repository_types(self): + return [] + + def get_save_path(self): + return self._directory + + def get_source_attribute_types(self): + return [] + + def get_source_bookmarks(self): + return self.source_bookmarks + + def get_source_media_types(self): + return [] + + def get_surname_list(self): + return [] + + def get_url_types(self): + return [] + + def has_changed(self): + return True + + def is_open(self): + return True + + def iter_citation_handles(self): + return (key for key in self.citation_map.keys()) + + def iter_citations(self): + return (key for key in self.citation_map.values()) + + def iter_event_handles(self): + return (key for key in self.event_map.keys()) + + def iter_events(self): + return (key for key in self.event_map.values()) + + def iter_media_objects(self): + return (key for key in self.media_map.values()) + + def iter_note_handles(self): + return (key for key in self.note_map.keys()) + + def iter_notes(self): + return (key for key in self.note_map.values()) + + def iter_place_handles(self): + return (key for key in self.place_map.keys()) + + def iter_places(self): + return (key for key in self.place_map.values()) + + def iter_repositories(self): + return (key for key in self.repositories_map.values()) + + def iter_repository_handles(self): + return (key for key in self.repositories_map.keys()) + + def iter_source_handles(self): + return (key for key in self.source_map.keys()) + + def iter_sources(self): + return (key for key in self.source_map.values()) + + def iter_tag_handles(self): + return (key for key in self.tag_map.keys()) + + def iter_tags(self): + return (key for key in self.tag_map.values()) + + def load(self, directory, pulse_progress, mode, + force_schema_upgrade, + force_bsddb_upgrade, + force_bsddb_downgrade, + force_python_upgrade): + self._directory = directory + + def prepare_import(self): + pass + + def redo(self, update_history=True): + pass + + def restore(self): + pass + + def set_prefixes(self, person, media, family, source, citation, + place, event, repository, note): + pass + + def set_save_path(self, directory): + self._directory = directory + + def undo(self, update_history=True): + pass diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index e0d8c043a..ee6d4162d 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -68,7 +68,7 @@ class Environment(object): self.db = db def txn_begin(self): - return DjangoTxn("DbDjango Transaction", self.db) + return DjangoTxn("DjangoDb Transaction", self.db) class Table(object): """ @@ -210,6 +210,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): # 4. Signal for change in person group name, parameters are __signals__['person-groupname-rebuild'] = (str, str) + __callback_map = {} + def __init__(self, directory=None): DbReadBase.__init__(self) DbWriteBase.__init__(self) @@ -1745,8 +1747,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.get_number_of_media_objects() == 0 and self.get_number_of_repositories() == 0) - __callback_map = {} - def set_prefixes(self, person, media, family, source, citation, place, event, repository, note): self.set_person_id_prefix(person) diff --git a/gramps/webapp/utils.py b/gramps/webapp/utils.py index 07eddd341..10e0aaf38 100644 --- a/gramps/webapp/utils.py +++ b/gramps/webapp/utils.py @@ -143,8 +143,12 @@ def get_person_from_handle(db, handle): return None def probably_alive(handle): + ## FIXME: need to call after save? person = db.get_person_from_handle(handle) - return alive(person, db) + if person: + return alive(person, db) + else: + return True def format_number(number, with_grouping=True): if number != "": From 5115cd13e4518185d604403f4853b4addef9e419 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Wed, 13 May 2015 21:29:07 -0400 Subject: [PATCH 008/105] DictionaryDb: now reads/writes on open/close --- gramps/gen/db/dbconst.py | 58 +++++-- .../plugins/database/bsddb_support/write.py | 33 ---- gramps/plugins/database/dictionarydb.py | 142 +++++++++++++++--- gramps/plugins/database/djangodb.py | 6 - gramps/plugins/importer/importxml.py | 2 +- 5 files changed, 170 insertions(+), 71 deletions(-) diff --git a/gramps/gen/db/dbconst.py b/gramps/gen/db/dbconst.py index e53535248..21a17db80 100644 --- a/gramps/gen/db/dbconst.py +++ b/gramps/gen/db/dbconst.py @@ -28,20 +28,16 @@ Declare constants used by database modules # constants # #------------------------------------------------------------------------- -__all__ = ( - ('DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO', - 'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN', - 'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN', - 'DBBACKEND' - ) + - - ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY', - 'EVENT_KEY', 'MEDIA_KEY', 'PLACE_KEY', 'REPOSITORY_KEY', - 'NOTE_KEY', 'REFERENCE_KEY', 'TAG_KEY' - ) + - - ('TXNADD', 'TXNUPD', 'TXNDEL') - ) +__all__ = ( 'DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO', + 'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN', + 'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN', + 'DBBACKEND', + 'PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY', + 'EVENT_KEY', 'MEDIA_KEY', 'PLACE_KEY', 'REPOSITORY_KEY', + 'NOTE_KEY', 'REFERENCE_KEY', 'TAG_KEY', + 'TXNADD', 'TXNUPD', 'TXNDEL', + "CLASS_TO_KEY_MAP", "KEY_TO_CLASS_MAP", "KEY_TO_NAME_MAP" + ) DBEXT = ".db" # File extension to be used for database files DBUNDOFN = "undo.db" # File name of 'undo' database @@ -74,3 +70,37 @@ TAG_KEY = 9 CITATION_KEY = 10 TXNADD, TXNUPD, TXNDEL = 0, 1, 2 + +CLASS_TO_KEY_MAP = {"Person": PERSON_KEY, + "Family": FAMILY_KEY, + "Source": SOURCE_KEY, + "Citation": CITATION_KEY, + "Event": EVENT_KEY, + "MediaObject": MEDIA_KEY, + "Place": PLACE_KEY, + "Repository": REPOSITORY_KEY, + "Note" : NOTE_KEY, + "Tag": TAG_KEY} + +KEY_TO_CLASS_MAP = {PERSON_KEY: "Person", + FAMILY_KEY: "Family", + SOURCE_KEY: "Source", + CITATION_KEY: "Citation", + EVENT_KEY: "Event", + MEDIA_KEY: "MediaObject", + PLACE_KEY: "Place", + REPOSITORY_KEY: "Repository", + NOTE_KEY: "Note", + TAG_KEY: "Tag"} + +KEY_TO_NAME_MAP = {PERSON_KEY: 'person', + FAMILY_KEY: 'family', + EVENT_KEY: 'event', + SOURCE_KEY: 'source', + CITATION_KEY: 'citation', + PLACE_KEY: 'place', + MEDIA_KEY: 'media', + REPOSITORY_KEY: 'repository', + #REFERENCE_KEY: 'reference', + NOTE_KEY: 'note', + TAG_KEY: 'tag'} diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py index 920bd2938..836fcc364 100644 --- a/gramps/plugins/database/bsddb_support/write.py +++ b/gramps/plugins/database/bsddb_support/write.py @@ -130,39 +130,6 @@ DBERRS = (db.DBRunRecoveryError, db.DBAccessError, # these maps or modifying the values of the keys will break # existing databases. -CLASS_TO_KEY_MAP = {Person.__name__: PERSON_KEY, - Family.__name__: FAMILY_KEY, - Source.__name__: SOURCE_KEY, - Citation.__name__: CITATION_KEY, - Event.__name__: EVENT_KEY, - MediaObject.__name__: MEDIA_KEY, - Place.__name__: PLACE_KEY, - Repository.__name__:REPOSITORY_KEY, - Note.__name__: NOTE_KEY, - Tag.__name__: TAG_KEY} - -KEY_TO_CLASS_MAP = {PERSON_KEY: Person.__name__, - FAMILY_KEY: Family.__name__, - SOURCE_KEY: Source.__name__, - CITATION_KEY: Citation.__name__, - EVENT_KEY: Event.__name__, - MEDIA_KEY: MediaObject.__name__, - PLACE_KEY: Place.__name__, - REPOSITORY_KEY: Repository.__name__, - NOTE_KEY: Note.__name__, - TAG_KEY: Tag.__name__} - -KEY_TO_NAME_MAP = {PERSON_KEY: 'person', - FAMILY_KEY: 'family', - EVENT_KEY: 'event', - SOURCE_KEY: 'source', - CITATION_KEY: 'citation', - PLACE_KEY: 'place', - MEDIA_KEY: 'media', - REPOSITORY_KEY: 'repository', - #REFERENCE_KEY: 'reference', - NOTE_KEY: 'note', - TAG_KEY: 'tag'} #------------------------------------------------------------------------- # # Helper functions diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 621425e2d..09e2451b6 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -28,6 +28,7 @@ import pickle import base64 import time import re +import os from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback @@ -40,7 +41,8 @@ from gramps.gen.db import (PERSON_KEY, PLACE_KEY, REPOSITORY_KEY, NOTE_KEY, - TAG_KEY) + TAG_KEY, + KEY_TO_NAME_MAP) from gramps.gen.utils.id import create_id from gramps.gen.lib.researcher import Researcher @@ -197,7 +199,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): __callback_map = {} - def __init__(self, *args, **kwargs): + def __init__(self, directory=None): DbReadBase.__init__(self) DbWriteBase.__init__(self) Callback.__init__(self) @@ -323,7 +325,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.place_bookmarks = Bookmarks() self.citation_bookmarks = Bookmarks() self.source_bookmarks = Bookmarks() - self.repository_bookmarks = Bookmarks() + self.repo_bookmarks = Bookmarks() self.media_bookmarks = Bookmarks() self.note_bookmarks = Bookmarks() self.set_person_id_prefix('I%04d') @@ -373,6 +375,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.modified = 0 self.txn = DictionaryTxn("DbDictionary Transaction", self) self.transaction = None + self._directory = directory + if directory: + self.load(directory) def version_supported(self): """Return True when the file has a supported version.""" @@ -768,24 +773,66 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): 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 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_citation_from_gramps_id(self, gramps_id): + for citation in self.citation_map.values(): + if citation.gramps_id == gramps_id: + return citation + return None + + def get_source_from_gramps_id(self, gramps_id): + for source in self.source_map.values(): + if source.gramps_id == gramps_id: + return source + return None + + def get_event_from_gramps_id(self, gramps_id): + for event in self.event_map.values(): + if event.gramps_id == gramps_id: + return event + return None + + def get_media_from_gramps_id(self, gramps_id): + for media in self.media_map.values(): + if media.gramps_id == gramps_id: + return media + return None + def get_place_from_gramps_id(self, gramps_id): for place in self.place_map.values(): if place.gramps_id == gramps_id: return place return None + def get_repository_from_gramps_id(self, gramps_id): + for repository in self.repository_map.values(): + if repository.gramps_id == gramps_id: + return repository + return None + + def get_note_from_gramps_id(self, gramps_id): + for note in self.note_map.values(): + if note.gramps_id == gramps_id: + return note + return None + + def get_tag_from_gramps_id(self, gramps_id): + for tag in self.tag_map.values(): + if tag.gramps_id == gramps_id: + return tag + return None + def get_number_of_people(self): return len(self.person_map) @@ -1038,33 +1085,73 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return obj.handle def commit_person(self, person, trans, change_time=None): + if person.handle in self.person_map: + self.emit("person-update", ([person.handle],)) + else: + self.emit("person-add", ([person.handle],)) self.person_map[person.handle] = person def commit_family(self, family, trans, change_time=None): + if family.handle in self.family_map: + self.emit("family-update", ([family.handle],)) + else: + self.emit("family-add", ([family.handle],)) self.family_map[family.handle] = family def commit_citation(self, citation, trans, change_time=None): + if citation.handle in self.citation_map: + self.emit("citation-update", ([citation.handle],)) + else: + self.emit("citation-add", ([citation.handle],)) self.citation_map[citation.handle] = citation def commit_source(self, source, trans, change_time=None): + if source.handle in self.source_map: + self.emit("source-update", ([source.handle],)) + else: + self.emit("source-add", ([source.handle],)) self.source_map[source.handle] = source def commit_repository(self, repository, trans, change_time=None): + if repository.handle in self.repository_map: + self.emit("repository-update", ([repository.handle],)) + else: + self.emit("repository-add", ([repository.handle],)) self.repository_map[repository.handle] = repository def commit_note(self, note, trans, change_time=None): + if note.handle in self.note_map: + self.emit("note-update", ([note.handle],)) + else: + self.emit("note-add", ([note.handle],)) self.note_map[note.handle] = note def commit_place(self, place, trans, change_time=None): + if place.handle in self.place_map: + self.emit("place-update", ([place.handle],)) + else: + self.emit("place-add", ([place.handle],)) self.place_map[place.handle] = place def commit_event(self, event, trans, change_time=None): + if event.handle in self.event_map: + self.emit("event-update", ([event.handle],)) + else: + self.emit("event-add", ([event.handle],)) self.event_map[event.handle] = event def commit_tag(self, tag, trans, change_time=None): + if tag.handle in self.tag_map: + self.emit("tag-update", ([tag.handle],)) + else: + self.emit("tag-add", ([tag.handle],)) self.tag_map[tag.handle] = tag def commit_media_object(self, obj, transaction, change_time=None): + if commit.handle in self.commit_map: + self.emit("commit-update", ([commit.handle],)) + else: + self.emit("commit-add", ([commit.handle],)) self.media_map[obj.handle] = obj def get_gramps_ids(self, obj_key): @@ -1092,7 +1179,16 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): pass def request_rebuild(self): - pass + self.emit('person-rebuild') + self.emit('family-rebuild') + self.emit('place-rebuild') + self.emit('source-rebuild') + self.emit('citation-rebuild') + self.emit('media-rebuild') + self.emit('event-rebuild') + self.emit('repository-rebuild') + self.emit('note-rebuild') + self.emit('tag-rebuild') def copy_from_db(self, db): """ @@ -1163,6 +1259,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.delete_primary_from_reference_map(handle, transaction, txn=self.txn) self.person_map.delete(handle, txn=self.txn) + self.emit("person-delete", ([handle],)) transaction.add(PERSON_KEY, TXNDEL, handle, person.serialize(), None) def remove_source(self, handle, transaction): @@ -1262,6 +1359,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): txn=self.txn) old_data = data_map.get(handle, txn=self.txn) data_map.delete(handle, txn=self.txn) + self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) transaction.add(key, TXNDEL, handle, old_data, None) def delete_primary_from_reference_map(self, handle, transaction, txn=None): @@ -1328,7 +1426,12 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): pass def close(self): - pass + if self._directory: + from gramps.plugins.export.exportxml import XmlWriter + from gramps.cli.user import User + writer = XmlWriter(self, User(), strip_photos=0, compress=1) + filename = os.path.join(self._directory, "data.gramps") + writer.write(filename) def find_backlink_handles(self, handle, include_classes=None): return [] @@ -1424,7 +1527,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return [] def get_repo_bookmarks(self): - return self.repository_bookmarks + return self.repo_bookmarks def get_repository_types(self): return [] @@ -1498,12 +1601,17 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): def iter_tags(self): return (key for key in self.tag_map.values()) - def load(self, directory, pulse_progress, mode, - force_schema_upgrade, - force_bsddb_upgrade, - force_bsddb_downgrade, - force_python_upgrade): + def load(self, directory, pulse_progress=None, mode=None, + force_schema_upgrade=False, + force_bsddb_upgrade=False, + force_bsddb_downgrade=False, + force_python_upgrade=False): + from gramps.plugins.importer.importxml import importData + from gramps.cli.user import User self._directory = directory + filename = os.path.join(directory, "data.gramps") + if os.path.isfile(filename): + importData(self, filename, User()) def prepare_import(self): pass diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index ee6d4162d..56f40f5ac 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -1402,7 +1402,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not person.gramps_id and set_gid: person.gramps_id = self.find_next_person_gramps_id() self.commit_person(person, trans) - self.emit("person-add", ([person.handle],)) return person.handle def add_family(self, family, trans, set_gid=True): @@ -1411,7 +1410,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not family.gramps_id and set_gid: family.gramps_id = self.find_next_family_gramps_id() self.commit_family(family, trans) - self.emit("family-add", ([family.handle],)) return family.handle def add_citation(self, citation, trans, set_gid=True): @@ -1420,7 +1418,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not citation.gramps_id and set_gid: citation.gramps_id = self.find_next_citation_gramps_id() self.commit_citation(citation, trans) - self.emit("citation-add", ([citation.handle],)) return citation.handle def add_source(self, source, trans, set_gid=True): @@ -1429,7 +1426,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not source.gramps_id and set_gid: source.gramps_id = self.find_next_source_gramps_id() self.commit_source(source, trans) - self.emit("source-add", ([source.handle],)) return source.handle def add_repository(self, repository, trans, set_gid=True): @@ -1438,7 +1434,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not repository.gramps_id and set_gid: repository.gramps_id = self.find_next_repository_gramps_id() self.commit_repository(repository, trans) - self.emit("repository-add", ([repository.handle],)) return repository.handle def add_note(self, note, trans, set_gid=True): @@ -1447,7 +1442,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not note.gramps_id and set_gid: note.gramps_id = self.find_next_note_gramps_id() self.commit_note(note, trans) - self.emit("note-add", ([note.handle],)) return note.handle def add_place(self, place, trans, set_gid=True): diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py index 4f1a51574..8c978527c 100644 --- a/gramps/plugins/importer/importxml.py +++ b/gramps/plugins/importer/importxml.py @@ -57,7 +57,7 @@ from gramps.gen.lib import (Address, Attribute, AttributeType, ChildRef, StyledText, StyledTextTag, StyledTextTagType, Surname, Tag, Url, PlaceRef) from gramps.gen.db import DbTxn -from gramps.gen.db.write import CLASS_TO_KEY_MAP +#from gramps.gen.db.write import CLASS_TO_KEY_MAP from gramps.gen.errors import GrampsImportError from gramps.gen.utils.id import create_id from gramps.gen.utils.db import family_name From e7dc1a7bc44d831a123e3afcdd173a61055e5cf2 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Wed, 13 May 2015 22:01:47 -0400 Subject: [PATCH 009/105] Moved key maps to dbconst --- gramps/plugins/database/dictionarydb.py | 15 +++++++-------- gramps/plugins/importer/importxml.py | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 09e2451b6..eab579416 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -29,7 +29,7 @@ import base64 import time import re import os -from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn +from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback from gramps.gen.db import (PERSON_KEY, @@ -41,8 +41,7 @@ from gramps.gen.db import (PERSON_KEY, PLACE_KEY, REPOSITORY_KEY, NOTE_KEY, - TAG_KEY, - KEY_TO_NAME_MAP) + TAG_KEY) from gramps.gen.utils.id import create_id from gramps.gen.lib.researcher import Researcher @@ -1147,12 +1146,12 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit("tag-add", ([tag.handle],)) self.tag_map[tag.handle] = tag - def commit_media_object(self, obj, transaction, change_time=None): - if commit.handle in self.commit_map: - self.emit("commit-update", ([commit.handle],)) + def commit_media_object(self, media, transaction, change_time=None): + if media.handle in self.media_map: + self.emit("media-update", ([media.handle],)) else: - self.emit("commit-add", ([commit.handle],)) - self.media_map[obj.handle] = obj + self.emit("media-add", ([media.handle],)) + self.media_map[media.handle] = media def get_gramps_ids(self, obj_key): key2table = { diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py index 8c978527c..51525dbd6 100644 --- a/gramps/plugins/importer/importxml.py +++ b/gramps/plugins/importer/importxml.py @@ -68,7 +68,7 @@ from gramps.gen.display.name import displayer as name_displayer from gramps.gen.db.dbconst import (PERSON_KEY, FAMILY_KEY, SOURCE_KEY, EVENT_KEY, MEDIA_KEY, PLACE_KEY, REPOSITORY_KEY, NOTE_KEY, TAG_KEY, - CITATION_KEY) + CITATION_KEY, CLASS_TO_KEY_MAP) from gramps.gen.updatecallback import UpdateCallback from gramps.version import VERSION from gramps.gen.config import config From 73886e9232129340b6c7625db4e0201c0b3b194a Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Wed, 13 May 2015 22:55:23 -0400 Subject: [PATCH 010/105] DictionaryDb: implement delete --- gramps/plugins/database/dictionarydb.py | 36 +++++++------------------ 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index eab579416..647f6b0ee 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -999,6 +999,11 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return self.tag_map[handle].serialize() return None + def get_raw_event_data(self, handle): + if handle in self.event_map: + return self.event_map[handle].serialize() + return None + def add_person(self, person, trans, set_gid=True): if not person.handle: person.handle = create_id() @@ -1244,22 +1249,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): if self.readonly or not handle: return - person = self.get_person_from_handle(handle) - #self.genderStats.uncount_person (person) - #self.remove_from_surname_list(person) - if isinstance(handle, str): - handle = handle.encode('utf-8') - if transaction.batch: - with BSDDBTxn(self.env, self.person_map) as txn: - self.delete_primary_from_reference_map(handle, transaction, - txn=txn.txn) - txn.delete(handle) - else: - self.delete_primary_from_reference_map(handle, transaction, - txn=self.txn) - self.person_map.delete(handle, txn=self.txn) + if handle in self.person_map: + del self.person_map[handle] self.emit("person-delete", ([handle],)) - transaction.add(PERSON_KEY, TXNDEL, handle, person.serialize(), None) def remove_source(self, handle, transaction): """ @@ -1345,21 +1337,11 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): def __do_remove(self, handle, transaction, data_map, key): if self.readonly or not handle: return - if isinstance(handle, str): handle = handle.encode('utf-8') - if transaction.batch: - with BSDDBTxn(self.env, data_map) as txn: - self.delete_primary_from_reference_map(handle, transaction, - txn=txn.txn) - txn.delete(handle) - else: - self.delete_primary_from_reference_map(handle, transaction, - txn=self.txn) - old_data = data_map.get(handle, txn=self.txn) - data_map.delete(handle, txn=self.txn) + if handle in data_map: + del data_map[handle] self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) - transaction.add(key, TXNDEL, handle, old_data, None) def delete_primary_from_reference_map(self, handle, transaction, txn=None): """ From ca88f37bb6200bb340a3481d5fd83423e4d71fa7 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 06:31:59 -0400 Subject: [PATCH 011/105] Database backends: bsddb, django, and dictionary --- gramps/cli/clidbman.py | 11 +- gramps/gui/dbman.py | 44 +++-- gramps/plugins/database/dictionarydb.py | 20 ++- .../defaults/default_settings.py | 150 ++++++++++++++++++ .../django_support/defaults/sqlite.db | Bin 0 -> 293888 bytes gramps/plugins/database/djangodb.py | 23 ++- 6 files changed, 230 insertions(+), 18 deletions(-) create mode 100644 gramps/plugins/database/django_support/defaults/default_settings.py create mode 100644 gramps/plugins/database/django_support/defaults/sqlite.db diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py index 7c77876f1..eb7a59ffa 100644 --- a/gramps/cli/clidbman.py +++ b/gramps/cli/clidbman.py @@ -225,7 +225,7 @@ class CLIDbManager(object): """ print(_('Import finished...')) - def create_new_db_cli(self, title=None, create_db=True): + def create_new_db_cli(self, title=None, create_db=True, dbid=None): """ Create a new database. """ @@ -244,8 +244,9 @@ class CLIDbManager(object): if create_db: # write the version number into metadata - - newdb = self.dbstate.make_database("bsddb") + if dbid is None: + dbid = "bsddb" + newdb = self.dbstate.make_database(dbid) newdb.write_version(new_path) (tval, last) = time_val(new_path) @@ -254,11 +255,11 @@ class CLIDbManager(object): last, tval, False, "")) return new_path, title - def _create_new_db(self, title=None): + def _create_new_db(self, title=None, dbid=None): """ Create a new database, do extra stuff needed """ - return self.create_new_db_cli(title) + return self.create_new_db_cli(title, dbid=dbid) def import_new_db(self, filename, user): """ diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py index 60f28bb6f..d32e86ebb 100644 --- a/gramps/gui/dbman.py +++ b/gramps/gui/dbman.py @@ -72,7 +72,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext from gramps.gen.const import URL_WIKISTRING from .user import User -from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2 +from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2, ICON from .pluginmanager import GuiPluginManager from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val from .ddtargets import DdTargets @@ -80,7 +80,6 @@ from gramps.gen.recentfiles import rename_filename, remove_filename from .glade import Glade from gramps.gen.db.exceptions import DbException - _RETURN = Gdk.keyval_from_name("Return") _KP_ENTER = Gdk.keyval_from_name("KP_Enter") @@ -104,6 +103,25 @@ STOCK_COL = 6 RCS_BUTTON = { True : _('_Extract'), False : _('_Archive') } +class DatabaseDialog(Gtk.MessageDialog): + def __init__(self, parent=None): + Gtk.MessageDialog.__init__(self, + parent, + flags=Gtk.DialogFlags.MODAL, + type=Gtk.MessageType.QUESTION, + ) + self.set_icon(ICON) + self.set_title('') + + self.set_markup('<span size="larger" weight="bold">%s</span>' % + _('Database Backend')) + self.format_secondary_text( + _("Please select a database backend type")) + + self.add_button("BSDDB Database (standard)", 1) + self.add_button("Dictionary (in-memory)", 2) + self.add_button("Django Database", 3) + class DbManager(CLIDbManager): """ Database Manager. Opens a database manager window that allows users to @@ -765,19 +783,27 @@ class DbManager(CLIDbManager): message. """ self.new.set_sensitive(False) - try: - self._create_new_db() - except (OSError, IOError) as msg: - DbManager.ERROR(_("Could not create Family Tree"), - str(msg)) + # popup window and ask for dbid types, if more than one + ## FIXME: autoload from plugins + dbid = "bsddb" + d = DatabaseDialog(self.top) + database = d.run() + d.destroy() + if database >= 0: + dbid = {1:"bsddb",2:"dictionarydb",3:"djangodb"}[database] + try: + self._create_new_db(dbid=dbid) + except (OSError, IOError) as msg: + DbManager.ERROR(_("Could not create Family Tree"), + str(msg)) self.new.set_sensitive(True) - def _create_new_db(self, title=None, create_db=True): + def _create_new_db(self, title=None, create_db=True, dbid=None): """ Create a new database, append to model """ new_path, title = self.create_new_db_cli(conv_to_unicode(title, 'utf8'), - create_db) + create_db, dbid) path_name = os.path.join(new_path, NAME_FILE) (tval, last) = time_val(new_path) node = self.model.append(None, [title, new_path, path_name, diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 647f6b0ee..936d8f534 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -21,7 +21,7 @@ #------------------------------------------------------------------------ # -# Gramps Modules +# Python Modules # #------------------------------------------------------------------------ import pickle @@ -29,7 +29,15 @@ import base64 import time import re import os +import logging + +#------------------------------------------------------------------------ +# +# Gramps Modules +# +#------------------------------------------------------------------------ from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP +from gramps.gen.db.dbconst import * from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback from gramps.gen.db import (PERSON_KEY, @@ -56,6 +64,8 @@ from gramps.gen.lib.repo import Repository from gramps.gen.lib.note import Note from gramps.gen.lib.tag import Tag +_LOG = logging.getLogger(DBLOGNAME) + class Environment(object): """ Implements the Environment API. @@ -1612,3 +1622,11 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): def undo(self, update_history=True): pass + + def write_version(self, directory): + """Write files for a newly created DB.""" + versionpath = os.path.join(directory, str(DBBACKEND)) + _LOG.debug("Write database backend file to 'dictionarydb'") + with open(versionpath, "w") as version_file: + version_file.write("dictionarydb") + diff --git a/gramps/plugins/database/django_support/defaults/default_settings.py b/gramps/plugins/database/django_support/defaults/default_settings.py new file mode 100644 index 000000000..fae3ffd06 --- /dev/null +++ b/gramps/plugins/database/django_support/defaults/default_settings.py @@ -0,0 +1,150 @@ +import os +from gramps.gen.const import DATA_DIR + +WEB_DIR = os.path.dirname(os.path.realpath(__file__)) + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +INTERNAL_IPS = ('127.0.0.1',) + +ADMINS = ( + ('admin', 'your_email@domain.com'), +) + +MANAGERS = ADMINS +DATABASE_ROUTERS = [] +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(WEB_DIR, 'sqlite.db'), + } +} +DATABASE_ENGINE = 'sqlite3' +DATABASE_NAME = os.path.join(WEB_DIR, 'sqlite.db') +DATABASE_USER = '' +DATABASE_PASSWORD = '' +DATABASE_HOST = '' +DATABASE_PORT = '' +TIME_ZONE = 'America/New_York' +LANGUAGE_CODE = 'en-us' +SITE_ID = 1 +USE_I18N = True +MEDIA_ROOT = '' +MEDIA_URL = '' +ADMIN_MEDIA_PREFIX = '/gramps-media/' +SECRET_KEY = 'zd@%vslj5sqhx94_8)0hsx*rk9tj3^ly$x+^*tq4bggr&uh$ac' + +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', # 1.4 + 'django.template.loaders.app_directories.Loader', # 1.4 + #'django.template.loaders.filesystem.load_template_source', + #'django.template.loaders.app_directories.load_template_source', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', +# 'debug_toolbar.middleware.DebugToolbarMiddleware', +) + +ROOT_URLCONF = 'gramps.webapp.urls' +STATIC_URL = '/static/' # 1.4 + +TEMPLATE_DIRS = ( + # Use absolute paths, not relative paths. + os.path.join(DATA_DIR, "templates"), +) + +TEMPLATE_CONTEXT_PROCESSORS = ( + "django.contrib.auth.context_processors.auth", # 1.4 + "django.contrib.messages.context_processors.messages", # 1.4 +# "django.core.context_processors.auth", +# "django.core.context_processors.debug", + "django.core.context_processors.i18n", + "django.core.context_processors.media", + "gramps.webapp.grampsdb.views.context_processor", + "gramps.webapp.context.messages", +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.staticfiles', + 'django.contrib.messages', # 1.4 + 'django.contrib.sites', + 'django.contrib.admin', + 'gramps.webapp.grampsdb', +# 'django_extensions', +# 'debug_toolbar', +) + +DEBUG_TOOLBAR_PANELS = ( + 'debug_toolbar.panels.version.VersionDebugPanel', + 'debug_toolbar.panels.timer.TimerDebugPanel', + 'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel', + 'debug_toolbar.panels.headers.HeaderDebugPanel', + 'debug_toolbar.panels.request_vars.RequestVarsDebugPanel', + 'debug_toolbar.panels.template.TemplateDebugPanel', + 'debug_toolbar.panels.sql.SQLDebugPanel', + 'debug_toolbar.panels.signals.SignalDebugPanel', + 'debug_toolbar.panels.logger.LoggingPanel', + ) + +def custom_show_toolbar(request): + return True # Always show toolbar, for example purposes only. + +DEBUG_TOOLBAR_CONFIG = { + 'INTERCEPT_REDIRECTS': False, +# 'SHOW_TOOLBAR_CALLBACK': custom_show_toolbar, +# 'EXTRA_SIGNALS': ['myproject.signals.MySignal'], + 'HIDE_DJANGO_SQL': False, + } + +AUTH_PROFILE_MODULE = "grampsdb.Profile" + +# Had to add these to use settings.configure(): +DATABASE_OPTIONS = '' +URL_VALIDATOR_USER_AGENT = '' +DEFAULT_INDEX_TABLESPACE = '' +DEFAULT_TABLESPACE = '' +CACHE_BACKEND = 'locmem://' +TRANSACTIONS_MANAGED = False +LOCALE_PATHS = tuple() + +# Changes for Django 1.3: +USE_L10N = True +FORMAT_MODULE_PATH = "" +## End Changes for Django 1.3 + +# Changes for Django 1.4: +USE_TZ = False +## End Changes for Django 1.4 + +# Changes for Django 1.5: +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + } + } +DEFAULT_CHARSET = "utf-8" +## End Changes for Django 1.5 + +## Changes for Django 1.5.4: +LOGGING_CONFIG = None +AUTH_USER_MODEL = 'auth.User' +## End Changes for Django 1.5.4 + +LOGIN_URL = "/login/" +LOGOUT_URL = "/logout" +LOGIN_REDIRECT_URL = "/" + +## Changes for Django 1.6: +LOGGING = None + +## Changes for Django 1.7.1: +ABSOLUTE_URL_OVERRIDES = {} diff --git a/gramps/plugins/database/django_support/defaults/sqlite.db b/gramps/plugins/database/django_support/defaults/sqlite.db new file mode 100644 index 0000000000000000000000000000000000000000..a9ac911f065f752dfb25be75e2916ff5df508451 GIT binary patch literal 293888 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®`N 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 N%rWbcAX@iQ{{@Gr__6>1 literal 0 HcmV?d00001 diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 56f40f5ac..e380c9601 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -30,6 +30,9 @@ import re import base64 import pickle import os +import logging +import shutil +from django.db import transaction #------------------------------------------------------------------------ # @@ -53,13 +56,13 @@ from gramps.gen.db import (PERSON_KEY, REPOSITORY_KEY, NOTE_KEY) from gramps.gen.utils.id import create_id -from django.db import transaction +from gramps.gen.db.dbconst import * ## add this directory to sys path, so we can find django_support later: -import sys -import os sys.path.append(os.path.dirname(os.path.abspath(__file__))) +_LOG = logging.getLogger(DBLOGNAME) + class Environment(object): """ Implements the Environment API. @@ -2041,3 +2044,17 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def restore(self): pass + + def write_version(self, directory): + """Write files for a newly created DB.""" + versionpath = os.path.join(directory, str(DBBACKEND)) + _LOG.debug("Write database backend file to 'djangodb'") + with open(versionpath, "w") as version_file: + version_file.write("djangodb") + # Write default_settings, sqlite.db + defaults = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "django_support", "defaults") + _LOG.debug("Copy defaults from: " + defaults) + for filename in os.listdir(defaults): + fullpath = os.path.abspath(os.path.join(defaults, filename)) + shutil.copy2(fullpath, directory) From 28c609d4c7a19273fb86019c10c09428af0ea800 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 07:01:47 -0400 Subject: [PATCH 012/105] Hack to reset modules on subsequent uses of Django databases --- gramps/gen/dbstate.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py index 04a8e683d..069934a13 100644 --- a/gramps/gen/dbstate.py +++ b/gramps/gen/dbstate.py @@ -22,6 +22,7 @@ """ Provide the database state class """ +import sys from .db import DbReadBase from .proxy.proxybase import ProxyDbBase @@ -139,6 +140,34 @@ class DbState(Callback): pmgr.reg_plugins(USER_PLUGINS, self, None, load_on_reg=True) pdata = pmgr.get_plugin(id) + ### FIXME: Currently Django needs to reset modules + if pdata.id == "djangodb": + if self.modules_is_set(): + self.reset_modules() + else: + self.save_modules() + mod = pmgr.load_plugin(pdata) database = getattr(mod, pdata.databaseclass) return database() + + ## FIXME: + ## Work-around for databases that need sys refresh (django): + def modules_is_set(self): + if hasattr(self, "_modules"): + return self._modules != None + else: + self._modules = None + return False + + def reset_modules(self): + # First, clear out old modules: + for key in list(sys.modules.keys()): + del(sys.modules[key]) + # Next, restore previous: + for key in self._modules: + sys.modules[key] = self._modules[key] + + def save_modules(self): + self._modules = sys.modules.copy() + From 47a3a7ad0f1325e0ac6104e9c3f0dd65a0178ef9 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 09:07:10 -0400 Subject: [PATCH 013/105] Reworked backend Cursors; don't emit changes when changing in batch mode --- gramps/plugins/database/__init__.py | 0 gramps/plugins/database/dbdjango.py | 2036 ----------------------- gramps/plugins/database/dictionarydb.py | 106 +- gramps/plugins/database/djangodb.py | 103 +- 4 files changed, 121 insertions(+), 2124 deletions(-) create mode 100644 gramps/plugins/database/__init__.py delete mode 100644 gramps/plugins/database/dbdjango.py diff --git a/gramps/plugins/database/__init__.py b/gramps/plugins/database/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/gramps/plugins/database/dbdjango.py b/gramps/plugins/database/dbdjango.py deleted file mode 100644 index adb28dc54..000000000 --- a/gramps/plugins/database/dbdjango.py +++ /dev/null @@ -1,2036 +0,0 @@ -# Gramps - a GTK+/GNOME based genealogy program -# -# Copyright (C) 2009 Douglas S. Blank <doug.blank@gmail.com> -# -# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# - -""" Implements a Db interface """ - -#------------------------------------------------------------------------ -# -# Python Modules -# -#------------------------------------------------------------------------ -import sys -import time -import re -import base64 -import pickle -import os - -#------------------------------------------------------------------------ -# -# Gramps Modules -# -#------------------------------------------------------------------------ -import gramps -from gramps.gen.lib import (Person, Family, Event, Place, Repository, - Citation, Source, Note, MediaObject, Tag, - Researcher) -from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn -from gramps.gen.utils.callback import Callback -from gramps.gen.updatecallback import UpdateCallback -from gramps.gen.db import (PERSON_KEY, - FAMILY_KEY, - CITATION_KEY, - SOURCE_KEY, - EVENT_KEY, - MEDIA_KEY, - PLACE_KEY, - REPOSITORY_KEY, - NOTE_KEY) -from gramps.gen.utils.id import create_id -from django.db import transaction - -class Environment(object): - """ - Implements the Environment API. - """ - def __init__(self, db): - self.db = db - - def txn_begin(self): - return DjangoTxn("DbDjango Transaction", self.db) - -class Table(object): - """ - Implements Table interface. - """ - def __init__(self, funcs): - self.funcs = funcs - - def cursor(self): - """ - Returns a Cursor for this Table. - """ - return self.funcs["cursor_func"]() - - def put(key, data, txn=None): - self[key] = data - -class Map(dict): - """ - Implements the map API for person_map, etc. - - Takes a Table() as argument. - """ - def __init__(self, tbl, *args, **kwargs): - super().__init__(*args, **kwargs) - self.db = tbl - -class MetaCursor(object): - def __init__(self): - pass - def __enter__(self): - return self - def __iter__(self): - return self.__next__() - def __next__(self): - yield None - def __exit__(self, *args, **kwargs): - pass - def iter(self): - yield None - def first(self): - self._iter = self.__iter__() - return self.next() - def next(self): - try: - return next(self._iter) - except: - return None - def close(self): - pass - -class Cursor(object): - 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 (bytes(item.handle, "utf-8"), self.func(item.handle)) - def __exit__(self, *args, **kwargs): - pass - def iter(self): - for item in self.model.all(): - yield (bytes(item.handle, "utf-8"), self.func(item.handle)) - yield None - def first(self): - self._iter = self.__iter__() - return self.next() - def next(self): - try: - return next(self._iter) - except: - return None - def close(self): - pass - -class Bookmarks(object): - def __init__(self): - self.handles = [] - def get(self): - return self.handles - def append(self, handle): - self.handles.append(handle) - -class DjangoTxn(DbTxn): - def __init__(self, message, db, table=None): - DbTxn.__init__(self, message, db) - self.table = table - - def get(self, key, default=None, txn=None, **kwargs): - """ - Returns the data object associated with key - """ - try: - return self.table.objects(handle=key) - except: - if txn and key in txn: - return txn[key] - else: - return None - - def put(self, handle, new_data, txn): - """ - """ - txn[handle] = new_data - - def commit(self): - pass - -class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): - """ - A Gramps Database Backend. This replicates the grampsdb functions. - """ - # Set up dictionary for callback signal handler - # --------------------------------------------- - # 1. Signals for primary objects - __signals__ = dict((obj+'-'+op, signal) - for obj in - ['person', 'family', 'event', 'place', - 'source', 'citation', 'media', 'note', 'repository', 'tag'] - for op, signal in zip( - ['add', 'update', 'delete', 'rebuild'], - [(list,), (list,), (list,), None] - ) - ) - - # 2. Signals for long operations - __signals__.update(('long-op-'+op, signal) for op, signal in zip( - ['start', 'heartbeat', 'end'], - [(object,), None, None] - )) - - # 3. Special signal for change in home person - __signals__['home-person-changed'] = None - - # 4. Signal for change in person group name, parameters are - __signals__['person-groupname-rebuild'] = (str, str) - - def __init__(self, directory=None): - DbReadBase.__init__(self) - DbWriteBase.__init__(self) - Callback.__init__(self) - self._tables = { - 'Person': - { - "handle_func": self.get_person_from_handle, - "gramps_id_func": self.get_person_from_gramps_id, - "class_func": gramps.gen.lib.Person, - "cursor_func": self.get_person_cursor, - "handles_func": self.get_person_handles, - "iter_func": self.iter_people, - }, - 'Family': - { - "handle_func": self.get_family_from_handle, - "gramps_id_func": self.get_family_from_gramps_id, - "class_func": gramps.gen.lib.Family, - "cursor_func": self.get_family_cursor, - "handles_func": self.get_family_handles, - "iter_func": self.iter_families, - }, - 'Source': - { - "handle_func": self.get_source_from_handle, - "gramps_id_func": self.get_source_from_gramps_id, - "class_func": gramps.gen.lib.Source, - "cursor_func": self.get_source_cursor, - "handles_func": self.get_source_handles, - "iter_func": self.iter_sources, - }, - 'Citation': - { - "handle_func": self.get_citation_from_handle, - "gramps_id_func": self.get_citation_from_gramps_id, - "class_func": gramps.gen.lib.Citation, - "cursor_func": self.get_citation_cursor, - "handles_func": self.get_citation_handles, - "iter_func": self.iter_citations, - }, - 'Event': - { - "handle_func": self.get_event_from_handle, - "gramps_id_func": self.get_event_from_gramps_id, - "class_func": gramps.gen.lib.Event, - "cursor_func": self.get_event_cursor, - "handles_func": self.get_event_handles, - "iter_func": self.iter_events, - }, - 'Media': - { - "handle_func": self.get_object_from_handle, - "gramps_id_func": self.get_object_from_gramps_id, - "class_func": gramps.gen.lib.MediaObject, - "cursor_func": self.get_media_cursor, - "handles_func": self.get_media_object_handles, - "iter_func": self.iter_media_objects, - }, - 'Place': - { - "handle_func": self.get_place_from_handle, - "gramps_id_func": self.get_place_from_gramps_id, - "class_func": gramps.gen.lib.Place, - "cursor_func": self.get_place_cursor, - "handles_func": self.get_place_handles, - "iter_func": self.iter_places, - }, - 'Repository': - { - "handle_func": self.get_repository_from_handle, - "gramps_id_func": self.get_repository_from_gramps_id, - "class_func": gramps.gen.lib.Repository, - "cursor_func": self.get_repository_cursor, - "handles_func": self.get_repository_handles, - "iter_func": self.iter_repositories, - }, - 'Note': - { - "handle_func": self.get_note_from_handle, - "gramps_id_func": self.get_note_from_gramps_id, - "class_func": gramps.gen.lib.Note, - "cursor_func": self.get_note_cursor, - "handles_func": self.get_note_handles, - "iter_func": self.iter_notes, - }, - 'Tag': - { - "handle_func": self.get_tag_from_handle, - "gramps_id_func": None, - "class_func": gramps.gen.lib.Tag, - "cursor_func": self.get_tag_cursor, - "handles_func": self.get_tag_handles, - "iter_func": self.iter_tags, - }, - } - # 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.undo_callback = None - self.redo_callback = None - self.undo_history_callback = None - self.modified = 0 - self.txn = DjangoTxn("DbDjango Transaction", self) - self.transaction = None - # Import cache for gedcom import, uses transactions, and - # two step adding of objects. - self.import_cache = {} - self.use_import_cache = False - self.use_db_cache = True - self._directory = directory - if directory: - self.load(directory) - - def load(self, directory, pulse_progress=None, mode=None): - self._directory = directory - from django.conf import settings - default_settings = {} - settings_file = os.path.join(directory, "default_settings.py") - with open(settings_file) as f: - code = compile(f.read(), settings_file, 'exec') - exec(code, globals(), default_settings) - - class Module(object): - def __init__(self, dictionary): - self.dictionary = dictionary - def __getattr__(self, item): - return self.dictionary[item] - - try: - settings.configure(Module(default_settings)) - except RuntimeError: - # already configured; ignore - pass - - import django - django.setup() - - from django_support.libdjango import DjangoInterface - self.dji = DjangoInterface() - 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 = DjangoTxn("ID Transaction", self, self.dji.Person) - self.fid_trans = DjangoTxn("FID Transaction", self, self.dji.Family) - self.pid_trans = DjangoTxn("PID Transaction", self, self.dji.Place) - self.cid_trans = DjangoTxn("CID Transaction", self, self.dji.Citation) - self.sid_trans = DjangoTxn("SID Transaction", self, self.dji.Source) - self.oid_trans = DjangoTxn("OID Transaction", self, self.dji.Media) - self.rid_trans = DjangoTxn("RID Transaction", self, self.dji.Repository) - self.nid_trans = DjangoTxn("NID Transaction", self, self.dji.Note) - self.eid_trans = DjangoTxn("EID Transaction", self, self.dji.Event) - 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 = Environment(self) - self.person_map = Map(Table(self._tables["Person"])) - self.family_map = Map(Table(self._tables["Family"])) - self.place_map = Map(Table(self._tables["Place"])) - self.citation_map = Map(Table(self._tables["Citation"])) - self.source_map = Map(Table(self._tables["Source"])) - self.repository_map = Map(Table(self._tables["Repository"])) - self.note_map = Map(Table(self._tables["Note"])) - self.media_map = Map(Table(self._tables["Media"])) - self.event_map = Map(Table(self._tables["Event"])) - self.tag_map = Map(Table(self._tables["Tag"])) - self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) - self.name_group = {} - self.event_names = set() - self.individual_attributes = set() - self.family_attributes = set() - self.source_attributes = set() - self.child_ref_types = set() - self.family_rel_types = set() - self.event_role_names = set() - self.name_types = set() - self.origin_types = set() - self.repository_types = set() - self.note_types = set() - self.source_media_types = set() - self.url_types = set() - self.media_attributes = set() - self.place_types = set() - - def prepare_import(self): - """ - DbDjango does not commit data on gedcom import, but saves them - for later commit. - """ - self.use_import_cache = True - self.import_cache = {} - - @transaction.atomic - def commit_import(self): - """ - Commits the items that were queued up during the last gedcom - import for two step adding. - """ - # First we add the primary objects: - for key in list(self.import_cache.keys()): - obj = self.import_cache[key] - if isinstance(obj, Person): - self.dji.add_person(obj.serialize()) - elif isinstance(obj, Family): - self.dji.add_family(obj.serialize()) - elif isinstance(obj, Event): - self.dji.add_event(obj.serialize()) - elif isinstance(obj, Place): - self.dji.add_place(obj.serialize()) - elif isinstance(obj, Repository): - self.dji.add_repository(obj.serialize()) - elif isinstance(obj, Citation): - self.dji.add_citation(obj.serialize()) - elif isinstance(obj, Source): - self.dji.add_source(obj.serialize()) - elif isinstance(obj, Note): - self.dji.add_note(obj.serialize()) - elif isinstance(obj, MediaObject): - self.dji.add_media(obj.serialize()) - elif isinstance(obj, Tag): - self.dji.add_tag(obj.serialize()) - # Next we add the links: - for key in list(self.import_cache.keys()): - obj = self.import_cache[key] - if isinstance(obj, Person): - self.dji.add_person_detail(obj.serialize()) - elif isinstance(obj, Family): - self.dji.add_family_detail(obj.serialize()) - elif isinstance(obj, Event): - self.dji.add_event_detail(obj.serialize()) - elif isinstance(obj, Place): - self.dji.add_place_detail(obj.serialize()) - elif isinstance(obj, Repository): - self.dji.add_repository_detail(obj.serialize()) - elif isinstance(obj, Citation): - self.dji.add_citation_detail(obj.serialize()) - elif isinstance(obj, Source): - self.dji.add_source_detail(obj.serialize()) - elif isinstance(obj, Note): - self.dji.add_note_detail(obj.serialize()) - elif isinstance(obj, MediaObject): - self.dji.add_media_detail(obj.serialize()) - elif isinstance(obj, Tag): - self.dji.add_tag_detail(obj.serialize()) - self.use_import_cache = False - self.import_cache = {} - self.dji.update_publics() - - def transaction_commit(self, txn): - pass - - def request_rebuild(self): - # caches are ok, but let's compute public's - self.dji.update_publics() - self.emit('person-rebuild') - self.emit('family-rebuild') - self.emit('place-rebuild') - self.emit('source-rebuild') - self.emit('citation-rebuild') - self.emit('media-rebuild') - self.emit('event-rebuild') - self.emit('repository-rebuild') - self.emit('note-rebuild') - self.emit('tag-rebuild') - - def get_undodb(self): - return None - - def transaction_abort(self, txn): - pass - - @staticmethod - def _validated_id_prefix(val, default): - if isinstance(val, str) 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_<object>_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 = Researcher() - return obj - - def get_tag_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Tag.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Tag.all()] - - def get_person_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Person.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Person.all()] - - def get_family_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Family.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Family.all()] - - def get_event_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Event.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Event.all()] - - def get_citation_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Citation.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Citation.all()] - - def get_source_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Source.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Source.all()] - - def get_place_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Place.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Place.all()] - - def get_repository_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Repository.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Repository.all()] - - def get_media_object_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Media.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Media.all()] - - def get_note_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Note.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Note.all()] - - def get_media_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - media = self.dji.Media.get(handle=handle) - except: - return None - return self.make_media(media) - - def get_event_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - event = self.dji.Event.get(handle=handle) - except: - return None - return self.make_event(event) - - def get_family_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - family = self.dji.Family.get(handle=handle) - except: - return None - return self.make_family(family) - - def get_repository_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - repository = self.dji.Repository.get(handle=handle) - except: - return None - return self.make_repository(repository) - - def get_person_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - person = self.dji.Person.get(handle=handle) - except: - return None - return self.make_person(person) - - def get_tag_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - tag = self.dji.Tag.get(handle=handle) - except: - return None - return self.make_tag(tag) - - def make_repository(self, repository): - if self.use_db_cache and repository.cache: - data = repository.from_cache() - else: - data = self.dji.get_repository(repository) - return Repository.create(data) - - def make_citation(self, citation): - if self.use_db_cache and citation.cache: - data = citation.from_cache() - else: - data = self.dji.get_citation(citation) - return Citation.create(data) - - def make_source(self, source): - if self.use_db_cache and source.cache: - data = source.from_cache() - else: - data = self.dji.get_source(source) - return Source.create(data) - - def make_family(self, family): - if self.use_db_cache and family.cache: - data = family.from_cache() - else: - data = self.dji.get_family(family) - return Family.create(data) - - def make_person(self, person): - if self.use_db_cache and person.cache: - data = person.from_cache() - else: - data = self.dji.get_person(person) - return Person.create(data) - - def make_event(self, event): - if self.use_db_cache and event.cache: - data = event.from_cache() - else: - data = self.dji.get_event(event) - return Event.create(data) - - def make_note(self, note): - if self.use_db_cache and note.cache: - data = note.from_cache() - else: - data = self.dji.get_note(note) - return Note.create(data) - - def make_tag(self, tag): - data = self.dji.get_tag(tag) - return Tag.create(data) - - def make_place(self, place): - if self.use_db_cache and place.cache: - data = place.from_cache() - else: - data = self.dji.get_place(place) - return Place.create(data) - - def make_media(self, media): - if self.use_db_cache and media.cache: - data = media.from_cache() - else: - data = self.dji.get_media(media) - return MediaObject.create(data) - - def get_place_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - place = self.dji.Place.get(handle=handle) - except: - return None - return self.make_place(place) - - def get_citation_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - citation = self.dji.Citation.get(handle=handle) - except: - return None - return self.make_citation(citation) - - def get_source_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - source = self.dji.Source.get(handle=handle) - except: - return None - return self.make_source(source) - - def get_note_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - note = self.dji.Note.get(handle=handle) - except: - return None - return self.make_note(note) - - def get_object_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - media = self.dji.Media.get(handle=handle) - except: - return None - return self.make_media(media) - - def get_default_person(self): - people = self.dji.Person.all() - if people.count() > 0: - return self.make_person(people[0]) - return None - - def iter_people(self): - return (self.get_person_from_handle(person.handle) - for person in self.dji.Person.all()) - - def iter_person_handles(self): - return (person.handle for person in self.dji.Person.all()) - - def iter_families(self): - return (self.get_family_from_handle(family.handle) - for family in self.dji.Family.all()) - - def iter_family_handles(self): - return (family.handle for family in self.dji.Family.all()) - - def iter_notes(self): - return (self.get_note_from_handle(note.handle) - for note in self.dji.Note.all()) - - def iter_note_handles(self): - return (note.handle for note in self.dji.Note.all()) - - def iter_events(self): - return (self.get_event_from_handle(event.handle) - for event in self.dji.Event.all()) - - def iter_event_handles(self): - return (event.handle for event in self.dji.Event.all()) - - def iter_places(self): - return (self.get_place_from_handle(place.handle) - for place in self.dji.Place.all()) - - def iter_place_handles(self): - return (place.handle for place in self.dji.Place.all()) - - def iter_repositories(self): - return (self.get_repository_from_handle(repository.handle) - for repository in self.dji.Repository.all()) - - def iter_repository_handles(self): - return (repository.handle for repository in self.dji.Repository.all()) - - def iter_sources(self): - return (self.get_source_from_handle(source.handle) - for source in self.dji.Source.all()) - - def iter_source_handles(self): - return (source.handle for source in self.dji.Source.all()) - - def iter_citations(self): - return (self.get_citation_from_handle(citation.handle) - for citation in self.dji.Citation.all()) - - def iter_citation_handles(self): - return (citation.handle for citation in self.dji.Citation.all()) - - def iter_tags(self): - return (self.get_tag_from_handle(tag.handle) - for tag in self.dji.Tag.all()) - - def iter_tag_handles(self): - return (tag.handle for tag in self.dji.Tag.all()) - - def iter_media_objects(self): - return (self.get_media_from_handle(media.handle) - for media in self.dji.Media.all()) - - def get_tag_from_name(self, name): - try: - tag = self.dji.Tag.filter(name=name) - return self.make_tag(tag[0]) - except: - return None - - def get_person_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Person.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_person(match_list[0]) - else: - return None - - def get_family_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - try: - family = self.dji.Family.get(gramps_id=gramps_id) - except: - return None - return self.make_family(family) - - def get_source_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Source.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_source(match_list[0]) - else: - return None - - def get_citation_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Citation.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_citation(match_list[0]) - else: - return None - - def get_event_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Event.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_event(match_list[0]) - else: - return None - - def get_object_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Media.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_media(match_list[0]) - else: - return None - - def get_place_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Place.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_place(match_list[0]) - else: - return None - - def get_repository_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Repsoitory.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_repository(match_list[0]) - else: - return None - - def get_note_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Note.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_note(match_list[0]) - else: - return None - - def get_number_of_people(self): - return self.dji.Person.count() - - def get_number_of_events(self): - return self.dji.Event.count() - - def get_number_of_places(self): - return self.dji.Place.count() - - def get_number_of_tags(self): - return self.dji.Tag.count() - - def get_number_of_families(self): - return self.dji.Family.count() - - def get_number_of_notes(self): - return self.dji.Note.count() - - def get_number_of_citations(self): - return self.dji.Citation.count() - - def get_number_of_sources(self): - return self.dji.Source.count() - - def get_number_of_media_objects(self): - return self.dji.Media.count() - - def get_number_of_repositories(self): - return self.dji.Repository.count() - - def get_place_cursor(self): - return Cursor(self.dji.Place, self.get_raw_place_data) - - def get_person_cursor(self): - return Cursor(self.dji.Person, self.get_raw_person_data) - - def get_family_cursor(self): - return Cursor(self.dji.Family, self.get_raw_family_data) - - def get_event_cursor(self): - return Cursor(self.dji.Event, self.get_raw_event_data) - - def get_citation_cursor(self): - return Cursor(self.dji.Citation, self.get_raw_citation_data) - - def get_source_cursor(self): - return Cursor(self.dji.Source, self.get_raw_source_data) - - def get_note_cursor(self): - return Cursor(self.dji.Note, self.get_raw_note_data) - - def get_tag_cursor(self): - return Cursor(self.dji.Tag, self.get_raw_tag_data) - - def get_repository_cursor(self): - return Cursor(self.dji.Repository, self.get_raw_repository_data) - - def get_media_cursor(self): - return Cursor(self.dji.Media, self.get_raw_object_data) - - def has_gramps_id(self, obj_key, gramps_id): - key2table = { - PERSON_KEY: self.dji.Person, - FAMILY_KEY: self.dji.Family, - SOURCE_KEY: self.dji.Source, - CITATION_KEY: self.dji.Citation, - EVENT_KEY: self.dji.Event, - MEDIA_KEY: self.dji.Media, - PLACE_KEY: self.dji.Place, - REPOSITORY_KEY: self.dji.Repository, - NOTE_KEY: self.dji.Note, - } - table = key2table[obj_key] - return table.filter(gramps_id=gramps_id).count() > 0 - - def has_person_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Person.filter(handle=handle).count() == 1 - - def has_family_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Family.filter(handle=handle).count() == 1 - - def has_citation_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Citation.filter(handle=handle).count() == 1 - - def has_source_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Source.filter(handle=handle).count() == 1 - - def has_repository_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Repository.filter(handle=handle).count() == 1 - - def has_note_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Note.filter(handle=handle).count() == 1 - - def has_place_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Place.filter(handle=handle).count() == 1 - - def has_event_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Event.filter(handle=handle).count() == 1 - - def has_tag_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Tag.filter(handle=handle).count() == 1 - - def has_object_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Media.filter(handle=handle).count() == 1 - - 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): - try: - return self.dji.get_person(self.dji.Person.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_family_data(self, handle): - try: - return self.dji.get_family(self.dji.Family.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_citation_data(self, handle): - try: - return self.dji.get_citation(self.dji.Citation.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_source_data(self, handle): - try: - return self.dji.get_source(self.dji.Source.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_repository_data(self, handle): - try: - return self.dji.get_repository(self.dji.Repository.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_note_data(self, handle): - try: - return self.dji.get_note(self.dji.Note.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_place_data(self, handle): - try: - return self.dji.get_place(self.dji.Place.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_object_data(self, handle): - try: - return self.dji.get_media(self.dji.Media.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_tag_data(self, handle): - try: - return self.dji.get_tag(self.dji.Tag.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_event_data(self, handle): - try: - return self.dji.get_event(self.dji.Event.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def add_person(self, person, trans, set_gid=True): - if not person.handle: - person.handle = create_id() - if not person.gramps_id and set_gid: - person.gramps_id = self.find_next_person_gramps_id() - self.commit_person(person, trans) - self.emit("person-add", ([person.handle],)) - return person.handle - - def add_family(self, family, trans, set_gid=True): - if not family.handle: - family.handle = create_id() - if not family.gramps_id and set_gid: - family.gramps_id = self.find_next_family_gramps_id() - self.commit_family(family, trans) - self.emit("family-add", ([family.handle],)) - return family.handle - - def add_citation(self, citation, trans, set_gid=True): - if not citation.handle: - citation.handle = create_id() - if not citation.gramps_id and set_gid: - citation.gramps_id = self.find_next_citation_gramps_id() - self.commit_citation(citation, trans) - self.emit("citation-add", ([citation.handle],)) - return citation.handle - - def add_source(self, source, trans, set_gid=True): - if not source.handle: - source.handle = create_id() - if not source.gramps_id and set_gid: - source.gramps_id = self.find_next_source_gramps_id() - self.commit_source(source, trans) - self.emit("source-add", ([source.handle],)) - return source.handle - - def add_repository(self, repository, trans, set_gid=True): - if not repository.handle: - repository.handle = create_id() - if not repository.gramps_id and set_gid: - repository.gramps_id = self.find_next_repository_gramps_id() - self.commit_repository(repository, trans) - self.emit("repository-add", ([repository.handle],)) - return repository.handle - - def add_note(self, note, trans, set_gid=True): - if not note.handle: - note.handle = create_id() - if not note.gramps_id and set_gid: - note.gramps_id = self.find_next_note_gramps_id() - self.commit_note(note, trans) - self.emit("note-add", ([note.handle],)) - return note.handle - - def add_place(self, place, trans, set_gid=True): - if not place.handle: - place.handle = create_id() - if not place.gramps_id and 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 = create_id() - if not event.gramps_id and 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 = 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 = create_id() - if not obj.gramps_id and 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): - if self.use_import_cache: - self.import_cache[person.handle] = person - else: - raw = person.serialize() - items = self.dji.Person.filter(handle=person.handle) - if items.count() > 0: - # Hack, for the moment: delete and re-add - items[0].delete() - self.dji.add_person(person.serialize()) - self.dji.add_person_detail(person.serialize()) - if items.count() > 0: - self.emit("person-update", ([person.handle],)) - else: - self.emit("person-add", ([person.handle],)) - - def commit_family(self, family, trans, change_time=None): - if self.use_import_cache: - self.import_cache[family.handle] = family - else: - raw = family.serialize() - items = self.dji.Family.filter(handle=family.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_family(family.serialize()) - self.dji.add_family_detail(family.serialize()) - if items.count() > 0: - self.emit("family-update", ([family.handle],)) - else: - self.emit("family-add", ([family.handle],)) - - def commit_citation(self, citation, trans, change_time=None): - if self.use_import_cache: - self.import_cache[citation.handle] = citation - else: - raw = citation.serialize() - items = self.dji.Citation.filter(handle=citation.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_citation(citation.serialize()) - self.dji.add_citation_detail(citation.serialize()) - if items.count() > 0: - self.emit("citation-update", ([citation.handle],)) - else: - self.emit("citation-add", ([citation.handle],)) - - def commit_source(self, source, trans, change_time=None): - if self.use_import_cache: - self.import_cache[source.handle] = source - else: - raw = source.serialize() - items = self.dji.Source.filter(handle=source.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_source(source.serialize()) - self.dji.add_source_detail(source.serialize()) - if items.count() > 0: - self.emit("source-update", ([source.handle],)) - else: - self.emit("source-add", ([source.handle],)) - - def commit_repository(self, repository, trans, change_time=None): - if self.use_import_cache: - self.import_cache[repository.handle] = repository - else: - raw = repository.serialize() - items = self.dji.Repository.filter(handle=repository.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_repository(repository.serialize()) - self.dji.add_repository_detail(repository.serialize()) - if items.count() > 0: - self.emit("repository-update", ([repository.handle],)) - else: - self.emit("repository-add", ([repository.handle],)) - - def commit_note(self, note, trans, change_time=None): - if self.use_import_cache: - self.import_cache[note.handle] = note - else: - raw = note.serialize() - items = self.dji.Note.filter(handle=note.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_note(note.serialize()) - self.dji.add_note_detail(note.serialize()) - if items.count() > 0: - self.emit("note-update", ([note.handle],)) - else: - self.emit("note-add", ([note.handle],)) - - def commit_place(self, place, trans, change_time=None): - if self.use_import_cache: - self.import_cache[place.handle] = place - else: - raw = place.serialize() - items = self.dji.Place.filter(handle=place.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_place(place.serialize()) - self.dji.add_place_detail(place.serialize()) - if items.count() > 0: - self.emit("place-update", ([place.handle],)) - else: - self.emit("place-add", ([place.handle],)) - - def commit_event(self, event, trans, change_time=None): - if self.use_import_cache: - self.import_cache[event.handle] = event - else: - raw = event.serialize() - items = self.dji.Event.filter(handle=event.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_event(event.serialize()) - self.dji.add_event_detail(event.serialize()) - if items.count() > 0: - self.emit("event-update", ([event.handle],)) - else: - self.emit("event-add", ([event.handle],)) - - def commit_tag(self, tag, trans, change_time=None): - if self.use_import_cache: - self.import_cache[tag.handle] = tag - else: - raw = tag.serialize() - items = self.dji.Tag.filter(handle=tag.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_tag(tag.serialize()) - self.dji.add_tag_detail(tag.serialize()) - if items.count() > 0: - self.emit("tag-update", ([tag.handle],)) - else: - self.emit("tag-add", ([tag.handle],)) - - def commit_media_object(self, media, transaction, change_time=None): - """ - Commit the specified MediaObject to the database, storing the changes - as part of the transaction. - """ - if self.use_import_cache: - self.import_cache[obj.handle] = media - else: - raw = media.serialize() - items = self.dji.Media.filter(handle=media.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_media(media.serialize()) - self.dji.add_media_detail(media.serialize()) - if items.count() > 0: - self.emit("media-update", ([media.handle],)) - else: - self.emit("media-add", ([media.handle],)) - - def get_gramps_ids(self, obj_key): - key2table = { - PERSON_KEY: self.id_trans, - FAMILY_KEY: self.fid_trans, - CITATION_KEY: self.cid_trans, - SOURCE_KEY: self.sid_trans, - EVENT_KEY: self.eid_trans, - MEDIA_KEY: self.oid_trans, - PLACE_KEY: self.pid_trans, - REPOSITORY_KEY: self.rid_trans, - NOTE_KEY: self.nid_trans, - } - - table = key2table[obj_key] - return list(table.keys()) - - def transaction_begin(self, transaction): - return - - def set_researcher(self, owner): - pass - - def copy_from_db(self, db): - """ - A (possibily) implementation-specific method to get data from - db into this database. - """ - # First we add the primary objects: - for key in db._tables.keys(): - cursor = db._tables[key]["cursor_func"] - for (handle, data) in cursor(): - if key == "Person": - self.dji.add_person(data) - elif key == "Family": - self.dji.add_family(data) - elif key == "Event": - self.dji.add_event(data) - elif key == "Place": - self.dji.add_place(data) - elif key == "Repository": - self.dji.add_repository(data) - elif key == "Citation": - self.dji.add_citation(data) - elif key == "Source": - self.dji.add_source(data) - elif key == "Note": - self.dji.add_note(data) - elif key == "Media": - self.dji.add_media(data) - elif key == "Tag": - self.dji.add_tag(data) - for key in db._tables.keys(): - cursor = db._tables[key]["cursor_func"] - for (handle, data) in cursor(): - if key == "Person": - self.dji.add_person_detail(data) - elif key == "Family": - self.dji.add_family_detail(data) - elif key == "Event": - self.dji.add_event_detail(data) - elif key == "Place": - self.dji.add_place_detail(data) - elif key == "Repository": - self.dji.add_repository_detail(data) - elif key == "Citation": - self.dji.add_citation_detail(data) - elif key == "Source": - self.dji.add_source_detail(data) - elif key == "Note": - self.dji.add_note_detail(data) - elif key == "Media": - self.dji.add_media_detail(data) - elif key == "Tag": - self.dji.add_tag_detail(data) - # Next we add the links: - self.dji.update_publics() - - def get_from_name_and_handle(self, table_name, handle): - """ - Returns a gen.lib object (or None) given table_name and - handle. - - Examples: - - >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") - >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") - """ - if table_name in self._tables: - return self._tables[table_name]["handle_func"](handle) - return None - - def is_empty(self): - """ - Is the database empty? - """ - return (self.get_number_of_people() == 0 and - self.get_number_of_events() == 0 and - self.get_number_of_places() == 0 and - self.get_number_of_tags() == 0 and - self.get_number_of_families() == 0 and - self.get_number_of_notes() == 0 and - self.get_number_of_citations() == 0 and - self.get_number_of_sources() == 0 and - self.get_number_of_media_objects() == 0 and - self.get_number_of_repositories() == 0) - - __callback_map = {} - - def set_prefixes(self, person, media, family, source, citation, - place, event, repository, note): - self.set_person_id_prefix(person) - self.set_object_id_prefix(media) - self.set_family_id_prefix(family) - self.set_source_id_prefix(source) - self.set_citation_id_prefix(citation) - self.set_place_id_prefix(place) - self.set_event_id_prefix(event) - self.set_repository_id_prefix(repository) - self.set_note_id_prefix(note) - - def has_changed(self): - return False - - def find_backlink_handles(self, handle, include_classes=None): - ## FIXME: figure out how to get objects that refer - ## to this handle - return [] - - def get_note_bookmarks(self): - return self.note_bookmarks - - def get_media_bookmarks(self): - return self.media_bookmarks - - def get_repo_bookmarks(self): - return self.repo_bookmarks - - def get_citation_bookmarks(self): - return self.citation_bookmarks - - def get_source_bookmarks(self): - return self.source_bookmarks - - def get_place_bookmarks(self): - return self.place_bookmarks - - def get_event_bookmarks(self): - return self.event_bookmarks - - def get_bookmarks(self): - return self.bookmarks - - def get_family_bookmarks(self): - return self.family_bookmarks - - def get_save_path(self): - return "/tmp/" - - ## Get types: - def get_event_attribute_types(self): - """ - Return a list of all Attribute types assocated with Event instances - in the database. - """ - return list(self.event_attributes) - - def get_event_types(self): - """ - Return a list of all event types in the database. - """ - return list(self.event_names) - - def get_person_event_types(self): - """ - Deprecated: Use get_event_types - """ - return list(self.event_names) - - def get_person_attribute_types(self): - """ - Return a list of all Attribute types assocated with Person instances - in the database. - """ - return list(self.individual_attributes) - - def get_family_attribute_types(self): - """ - Return a list of all Attribute types assocated with Family instances - in the database. - """ - return list(self.family_attributes) - - def get_family_event_types(self): - """ - Deprecated: Use get_event_types - """ - return list(self.event_names) - - def get_media_attribute_types(self): - """ - Return a list of all Attribute types assocated with Media and MediaRef - instances in the database. - """ - return list(self.media_attributes) - - def get_family_relation_types(self): - """ - Return a list of all relationship types assocated with Family - instances in the database. - """ - return list(self.family_rel_types) - - def get_child_reference_types(self): - """ - Return a list of all child reference types assocated with Family - instances in the database. - """ - return list(self.child_ref_types) - - def get_event_roles(self): - """ - Return a list of all custom event role names assocated with Event - instances in the database. - """ - return list(self.event_role_names) - - def get_name_types(self): - """ - Return a list of all custom names types assocated with Person - instances in the database. - """ - return list(self.name_types) - - def get_origin_types(self): - """ - Return a list of all custom origin types assocated with Person/Surname - instances in the database. - """ - return list(self.origin_types) - - def get_repository_types(self): - """ - Return a list of all custom repository types assocated with Repository - instances in the database. - """ - return list(self.repository_types) - - def get_note_types(self): - """ - Return a list of all custom note types assocated with Note instances - in the database. - """ - return list(self.note_types) - - def get_source_attribute_types(self): - """ - Return a list of all Attribute types assocated with Source/Citation - instances in the database. - """ - return list(self.source_attributes) - - def get_source_media_types(self): - """ - Return a list of all custom source media types assocated with Source - instances in the database. - """ - return list(self.source_media_types) - - def get_url_types(self): - """ - Return a list of all custom names types assocated with Url instances - in the database. - """ - return list(self.url_types) - - def get_place_types(self): - """ - Return a list of all custom place types assocated with Place instances - in the database. - """ - return list(self.place_types) - - def get_default_handle(self): - people = self.dji.Person.all() - if people.count() > 0: - return people[0].handle - return None - - def close(self): - pass - - def get_surname_list(self): - return [] - - def is_open(self): - return True - - def get_table_names(self): - """Return a list of valid table names.""" - return list(self._tables.keys()) - - def find_initial_person(self): - return self.get_default_person() - - # Removals: - def remove_person(self, handle, txn): - self.dji.Person.filter(handle=handle)[0].delete() - self.emit("person-delete", ([handle],)) - - def remove_source(self, handle, transaction): - self.dji.Source.filter(handle=handle)[0].delete() - self.emit("source-delete", ([handle],)) - - def remove_citation(self, handle, transaction): - self.dji.Citation.filter(handle=handle)[0].delete() - self.emit("citation-delete", ([handle],)) - - def remove_event(self, handle, transaction): - self.dji.Event.filter(handle=handle)[0].delete() - self.emit("event-delete", ([handle],)) - - def remove_object(self, handle, transaction): - self.dji.Media.filter(handle=handle)[0].delete() - self.emit("media-delete", ([handle],)) - - def remove_place(self, handle, transaction): - self.dji.Place.filter(handle=handle)[0].delete() - self.emit("place-delete", ([handle],)) - - def remove_family(self, handle, transaction): - self.dji.Family.filter(handle=handle)[0].delete() - self.emit("family-delete", ([handle],)) - - def remove_repository(self, handle, transaction): - self.dji.Repository.filter(handle=handle)[0].delete() - self.emit("repository-delete", ([handle],)) - - def remove_note(self, handle, transaction): - self.dji.Note.filter(handle=handle)[0].delete() - self.emit("note-delete", ([handle],)) - - def remove_tag(self, handle, transaction): - self.dji.Tag.filter(handle=handle)[0].delete() - self.emit("tag-delete", ([handle],)) - - def remove_from_surname_list(self, person): - ## FIXME - pass - - def get_dbname(self): - return "Django Database" - - ## missing - - def find_place_child_handles(self, handle): - pass - - def get_cursor(self, table, txn=None, update=False, commit=False): - pass - - def get_from_name_and_handle(self, table_name, handle): - """ - Returns a gen.lib object (or None) given table_name and - handle. - - Examples: - - >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") - >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") - """ - if table_name in self._tables: - return self._tables[table_name]["handle_func"](handle) - return None - - def get_number_of_records(self, table): - pass - - def get_place_parent_cursor(self): - pass - - def get_place_tree_cursor(self): - pass - - def get_table_metadata(self, table_name): - """Return the metadata for a valid table name.""" - if table_name in self._tables: - return self._tables[table_name] - return None - - def get_transaction_class(self): - pass - - def undo(self, update_history=True): - # FIXME: - return self.undodb.undo(update_history) - - def redo(self, update_history=True): - # FIXME: - return self.undodb.redo(update_history) - - def backup(self): - pass - - def restore(self): - pass diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 936d8f534..1542f4c93 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -130,27 +130,33 @@ class Cursor(object): def __init__(self, map, func): self.map = map self.func = func + self._iter = self.__iter__() def __enter__(self): return self def __iter__(self): - return self.__next__() - def __next__(self): for item in self.map.keys(): yield (bytes(item, "utf-8"), self.func(item)) + def __next__(self): + try: + return self._iter.__next__() + except StopIteration: + return None def __exit__(self, *args, **kwargs): pass def iter(self): for item in self.map.keys(): yield (bytes(item, "utf-8"), self.func(item)) - yield None def first(self): self._iter = self.__iter__() - return self.next() + try: + return next(self._iter) + except: + return def next(self): try: return next(self._iter) except: - return None + return def close(self): pass @@ -1099,73 +1105,83 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return obj.handle def commit_person(self, person, trans, change_time=None): - if person.handle in self.person_map: - self.emit("person-update", ([person.handle],)) - else: - self.emit("person-add", ([person.handle],)) + if not trans.batch: + if person.handle in self.person_map: + self.emit("person-update", ([person.handle],)) + else: + self.emit("person-add", ([person.handle],)) self.person_map[person.handle] = person def commit_family(self, family, trans, change_time=None): - if family.handle in self.family_map: - self.emit("family-update", ([family.handle],)) - else: - self.emit("family-add", ([family.handle],)) + if not trans.batch: + if family.handle in self.family_map: + self.emit("family-update", ([family.handle],)) + else: + self.emit("family-add", ([family.handle],)) self.family_map[family.handle] = family def commit_citation(self, citation, trans, change_time=None): - if citation.handle in self.citation_map: - self.emit("citation-update", ([citation.handle],)) - else: - self.emit("citation-add", ([citation.handle],)) + if not trans.batch: + if citation.handle in self.citation_map: + self.emit("citation-update", ([citation.handle],)) + else: + self.emit("citation-add", ([citation.handle],)) self.citation_map[citation.handle] = citation def commit_source(self, source, trans, change_time=None): - if source.handle in self.source_map: - self.emit("source-update", ([source.handle],)) - else: - self.emit("source-add", ([source.handle],)) + if not trans.batch: + if source.handle in self.source_map: + self.emit("source-update", ([source.handle],)) + else: + self.emit("source-add", ([source.handle],)) self.source_map[source.handle] = source def commit_repository(self, repository, trans, change_time=None): - if repository.handle in self.repository_map: - self.emit("repository-update", ([repository.handle],)) - else: - self.emit("repository-add", ([repository.handle],)) + if not trans.batch: + if repository.handle in self.repository_map: + self.emit("repository-update", ([repository.handle],)) + else: + self.emit("repository-add", ([repository.handle],)) self.repository_map[repository.handle] = repository def commit_note(self, note, trans, change_time=None): - if note.handle in self.note_map: - self.emit("note-update", ([note.handle],)) - else: - self.emit("note-add", ([note.handle],)) + if not trans.batch: + if note.handle in self.note_map: + self.emit("note-update", ([note.handle],)) + else: + self.emit("note-add", ([note.handle],)) self.note_map[note.handle] = note def commit_place(self, place, trans, change_time=None): - if place.handle in self.place_map: - self.emit("place-update", ([place.handle],)) - else: - self.emit("place-add", ([place.handle],)) + if not trans.batch: + if place.handle in self.place_map: + self.emit("place-update", ([place.handle],)) + else: + self.emit("place-add", ([place.handle],)) self.place_map[place.handle] = place def commit_event(self, event, trans, change_time=None): - if event.handle in self.event_map: - self.emit("event-update", ([event.handle],)) - else: - self.emit("event-add", ([event.handle],)) + if not trans.batch: + if event.handle in self.event_map: + self.emit("event-update", ([event.handle],)) + else: + self.emit("event-add", ([event.handle],)) self.event_map[event.handle] = event def commit_tag(self, tag, trans, change_time=None): - if tag.handle in self.tag_map: - self.emit("tag-update", ([tag.handle],)) - else: - self.emit("tag-add", ([tag.handle],)) + if not trans.batch: + if tag.handle in self.tag_map: + self.emit("tag-update", ([tag.handle],)) + else: + self.emit("tag-add", ([tag.handle],)) self.tag_map[tag.handle] = tag def commit_media_object(self, media, transaction, change_time=None): - if media.handle in self.media_map: - self.emit("media-update", ([media.handle],)) - else: - self.emit("media-add", ([media.handle],)) + if not trans.batch: + if media.handle in self.media_map: + self.emit("media-update", ([media.handle],)) + else: + self.emit("media-add", ([media.handle],)) self.media_map[media.handle] = media def get_gramps_ids(self, obj_key): diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index e380c9601..852afe779 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -127,13 +127,17 @@ class Cursor(object): def __init__(self, model, func): self.model = model self.func = func + self._iter = self.__iter__() def __enter__(self): return self def __iter__(self): - return self.__next__() - def __next__(self): for item in self.model.all(): yield (bytes(item.handle, "utf-8"), self.func(item.handle)) + def __next__(self): + try: + return self._iter.__next__() + except StopIteration: + return None def __exit__(self, *args, **kwargs): pass def iter(self): @@ -142,7 +146,10 @@ class Cursor(object): yield None def first(self): self._iter = self.__iter__() - return self.next() + try: + return next(self._iter) + except: + return def next(self): try: return next(self._iter) @@ -1494,10 +1501,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_person(person.serialize()) self.dji.add_person_detail(person.serialize()) - if items.count() > 0: - self.emit("person-update", ([person.handle],)) - else: - self.emit("person-add", ([person.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("person-update", ([person.handle],)) + else: + self.emit("person-add", ([person.handle],)) def commit_family(self, family, trans, change_time=None): if self.use_import_cache: @@ -1509,10 +1517,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_family(family.serialize()) self.dji.add_family_detail(family.serialize()) - if items.count() > 0: - self.emit("family-update", ([family.handle],)) - else: - self.emit("family-add", ([family.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("family-update", ([family.handle],)) + else: + self.emit("family-add", ([family.handle],)) def commit_citation(self, citation, trans, change_time=None): if self.use_import_cache: @@ -1524,10 +1533,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_citation(citation.serialize()) self.dji.add_citation_detail(citation.serialize()) - if items.count() > 0: - self.emit("citation-update", ([citation.handle],)) - else: - self.emit("citation-add", ([citation.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("citation-update", ([citation.handle],)) + else: + self.emit("citation-add", ([citation.handle],)) def commit_source(self, source, trans, change_time=None): if self.use_import_cache: @@ -1539,10 +1549,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_source(source.serialize()) self.dji.add_source_detail(source.serialize()) - if items.count() > 0: - self.emit("source-update", ([source.handle],)) - else: - self.emit("source-add", ([source.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("source-update", ([source.handle],)) + else: + self.emit("source-add", ([source.handle],)) def commit_repository(self, repository, trans, change_time=None): if self.use_import_cache: @@ -1554,10 +1565,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_repository(repository.serialize()) self.dji.add_repository_detail(repository.serialize()) - if items.count() > 0: - self.emit("repository-update", ([repository.handle],)) - else: - self.emit("repository-add", ([repository.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("repository-update", ([repository.handle],)) + else: + self.emit("repository-add", ([repository.handle],)) def commit_note(self, note, trans, change_time=None): if self.use_import_cache: @@ -1569,10 +1581,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_note(note.serialize()) self.dji.add_note_detail(note.serialize()) - if items.count() > 0: - self.emit("note-update", ([note.handle],)) - else: - self.emit("note-add", ([note.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("note-update", ([note.handle],)) + else: + self.emit("note-add", ([note.handle],)) def commit_place(self, place, trans, change_time=None): if self.use_import_cache: @@ -1584,10 +1597,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_place(place.serialize()) self.dji.add_place_detail(place.serialize()) - if items.count() > 0: - self.emit("place-update", ([place.handle],)) - else: - self.emit("place-add", ([place.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("place-update", ([place.handle],)) + else: + self.emit("place-add", ([place.handle],)) def commit_event(self, event, trans, change_time=None): if self.use_import_cache: @@ -1599,10 +1613,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_event(event.serialize()) self.dji.add_event_detail(event.serialize()) - if items.count() > 0: - self.emit("event-update", ([event.handle],)) - else: - self.emit("event-add", ([event.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("event-update", ([event.handle],)) + else: + self.emit("event-add", ([event.handle],)) def commit_tag(self, tag, trans, change_time=None): if self.use_import_cache: @@ -1614,10 +1629,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_tag(tag.serialize()) self.dji.add_tag_detail(tag.serialize()) - if items.count() > 0: - self.emit("tag-update", ([tag.handle],)) - else: - self.emit("tag-add", ([tag.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("tag-update", ([tag.handle],)) + else: + self.emit("tag-add", ([tag.handle],)) def commit_media_object(self, media, transaction, change_time=None): """ @@ -1633,10 +1649,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_media(media.serialize()) self.dji.add_media_detail(media.serialize()) - if items.count() > 0: - self.emit("media-update", ([media.handle],)) - else: - self.emit("media-add", ([media.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("media-update", ([media.handle],)) + else: + self.emit("media-add", ([media.handle],)) def get_gramps_ids(self, obj_key): key2table = { From 06d0db3b6a034fe7049f10be76eee2e40f42470e Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 11:49:58 -0400 Subject: [PATCH 014/105] DictionaryDb: emit add after actually adding --- gramps/plugins/database/dictionarydb.py | 80 ++++++++++++++++++------- 1 file changed, 60 insertions(+), 20 deletions(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 1542f4c93..15009d584 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -1105,84 +1105,124 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return obj.handle def commit_person(self, person, trans, change_time=None): + emit = None if not trans.batch: if person.handle in self.person_map: - self.emit("person-update", ([person.handle],)) + emit = "person-update" else: - self.emit("person-add", ([person.handle],)) + emit = "person-add" self.person_map[person.handle] = person + # Emit after added: + if emit: + self.emit(emit, ([person.handle],)) def commit_family(self, family, trans, change_time=None): + emit = None if not trans.batch: if family.handle in self.family_map: - self.emit("family-update", ([family.handle],)) + emit = "family-update" else: - self.emit("family-add", ([family.handle],)) + emit = "family-add" self.family_map[family.handle] = family + # Emit after added: + if emit: + self.emit(emit, ([family.handle],)) def commit_citation(self, citation, trans, change_time=None): + emit = None if not trans.batch: if citation.handle in self.citation_map: - self.emit("citation-update", ([citation.handle],)) + emit = "citation-update" else: - self.emit("citation-add", ([citation.handle],)) + emit = "citation-add" self.citation_map[citation.handle] = citation + # Emit after added: + if emit: + self.emit(emit, ([citation.handle],)) def commit_source(self, source, trans, change_time=None): + emit = None if not trans.batch: if source.handle in self.source_map: - self.emit("source-update", ([source.handle],)) + emit = "source-update" else: - self.emit("source-add", ([source.handle],)) + emit = "source-add" self.source_map[source.handle] = source + # Emit after added: + if emit: + self.emit(emit, ([source.handle],)) def commit_repository(self, repository, trans, change_time=None): + emit = None if not trans.batch: if repository.handle in self.repository_map: - self.emit("repository-update", ([repository.handle],)) + emit = "repository-update" else: - self.emit("repository-add", ([repository.handle],)) + emit = "repository-add" self.repository_map[repository.handle] = repository + # Emit after added: + if emit: + self.emit(emit, ([repository.handle],)) def commit_note(self, note, trans, change_time=None): + emit = None if not trans.batch: if note.handle in self.note_map: - self.emit("note-update", ([note.handle],)) + emit "note-update" else: - self.emit("note-add", ([note.handle],)) + emit = "note-add" self.note_map[note.handle] = note + # Emit after added: + if emit: + self.emit(emit, ([note.handle],)) def commit_place(self, place, trans, change_time=None): + emit = None if not trans.batch: if place.handle in self.place_map: - self.emit("place-update", ([place.handle],)) + emit = "place-update" else: - self.emit("place-add", ([place.handle],)) + emit = "place-add" self.place_map[place.handle] = place + # Emit after added: + if emit: + self.emit(emit, ([place.handle],)) def commit_event(self, event, trans, change_time=None): + emit = None if not trans.batch: if event.handle in self.event_map: - self.emit("event-update", ([event.handle],)) + emit = "event-update" else: - self.emit("event-add", ([event.handle],)) + emit = "event-add" self.event_map[event.handle] = event + # Emit after added: + if emit: + self.emit(emit, ([event.handle],)) def commit_tag(self, tag, trans, change_time=None): + emit = None if not trans.batch: if tag.handle in self.tag_map: - self.emit("tag-update", ([tag.handle],)) + emit = "tag-update" else: - self.emit("tag-add", ([tag.handle],)) + emit = "tag-add" self.tag_map[tag.handle] = tag + # Emit after added: + if emit: + self.emit(emit, ([tag.handle],)) def commit_media_object(self, media, transaction, change_time=None): + emit = None if not trans.batch: if media.handle in self.media_map: - self.emit("media-update", ([media.handle],)) + emit = "media-update" else: - self.emit("media-add", ([media.handle],)) + emit = "media-add" self.media_map[media.handle] = media + # Emit after added: + if emit: + self.emit(emit, ([media.handle],)) def get_gramps_ids(self, obj_key): key2table = { From 6e0b8ccf86c5b3b8f8f59c10596e92bad63ab9a4 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 11:51:56 -0400 Subject: [PATCH 015/105] DictionaryDb: emit add after actually adding (fixed typo) --- gramps/plugins/database/dictionarydb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 15009d584..5c167fe48 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -1168,7 +1168,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = None if not trans.batch: if note.handle in self.note_map: - emit "note-update" + emit = "note-update" else: emit = "note-add" self.note_map[note.handle] = note From 74330122bd90dffbc502b10d6fd07afb5aa8130d Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 12:30:30 -0400 Subject: [PATCH 016/105] Basic infrastructure for Undo/Redo --- gramps/gen/db/undoredo.py | 136 ++++++++++++++++++++++++ gramps/plugins/database/dictionarydb.py | 5 +- gramps/plugins/database/djangodb.py | 3 + 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 gramps/gen/db/undoredo.py diff --git a/gramps/gen/db/undoredo.py b/gramps/gen/db/undoredo.py new file mode 100644 index 000000000..b12735093 --- /dev/null +++ b/gramps/gen/db/undoredo.py @@ -0,0 +1,136 @@ +import time +from collections import deque + +class DbUndo(object): + """ + Base class for the Gramps undo/redo manager. Needs to be subclassed + for use with a real backend. + """ + + __slots__ = ('undodb', 'db', 'mapbase', 'undo_history_timestamp', + 'txn', 'undoq', 'redoq') + + def __init__(self, grampsdb): + """ + Class constructor. Set up main instance variables + """ + self.db = grampsdb + self.undoq = deque() + self.redoq = deque() + self.undo_history_timestamp = time.time() + self.txn = None + + def clear(self): + """ + Clear the undo/redo list (but not the backing storage) + """ + self.undoq.clear() + self.redoq.clear() + self.undo_history_timestamp = time.time() + self.txn = None + + def __enter__(self, value): + """ + Context manager method to establish the context + """ + self.open(value) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """ + Context manager method to finish the context + """ + if exc_type is None: + self.close() + return exc_type is None + + def open(self, value): + """ + Open the backing storage. Needs to be overridden in the derived + class. + """ + raise NotImplementedError + + def close(self): + """ + Close the backing storage. Needs to be overridden in the derived + class. + """ + raise NotImplementedError + + def append(self, value): + """ + Add a new entry on the end. Needs to be overridden in the derived + class. + """ + raise NotImplementedError + + def __getitem__(self, index): + """ + Returns an entry by index number. Needs to be overridden in the + derived class. + """ + raise NotImplementedError + + def __setitem__(self, index, value): + """ + Set an entry to a value. Needs to be overridden in the derived class. + """ + raise NotImplementedError + + def __len__(self): + """ + Returns the number of entries. Needs to be overridden in the derived + class. + """ + raise NotImplementedError + + def commit(self, txn, msg): + """ + Commit the transaction to the undo/redo database. "txn" should be + an instance of Gramps transaction class + """ + txn.set_description(msg) + txn.timestamp = time.time() + self.undoq.append(txn) + + def undo(self, update_history=True): + """ + Undo a previously committed transaction + """ + if self.db.readonly or self.undo_count == 0: + return False + return self.__undo(update_history) + + def redo(self, update_history=True): + """ + Redo a previously committed, then undone, transaction + """ + if self.db.readonly or self.redo_count == 0: + return False + return self.__redo(update_history) + + def undoredo(func): + """ + Decorator function to wrap undo and redo operations within a bsddb + transaction. It also catches bsddb errors and raises an exception + as appropriate + """ + pass + + def __redo(self, update_history=True): + """ + Access the last undone transaction, and revert the data to the state + before the transaction was undone. + """ + pass + + def __undo(self, db=None, update_history=True): + """ + Access the last committed transaction, and revert the data to the + state before the transaction was committed. + """ + pass + + undo_count = property(lambda self:len(self.undoq)) + redo_count = property(lambda self:len(self.redoq)) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 5c167fe48..09748295a 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -37,6 +37,7 @@ import logging # #------------------------------------------------------------------------ from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP +from gramps.gen.db.undoredo import DbUndo from gramps.gen.db.dbconst import * from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback @@ -390,6 +391,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.modified = 0 self.txn = DictionaryTxn("DbDictionary Transaction", self) self.transaction = None + self.undodb = DbUndo(self) + self.abort_possible = False self._directory = directory if directory: self.load(directory) @@ -1212,7 +1215,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): if emit: self.emit(emit, ([tag.handle],)) - def commit_media_object(self, media, transaction, change_time=None): + def commit_media_object(self, media, trans, change_time=None): emit = None if not trans.batch: if media.handle in self.media_map: diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 852afe779..0a88733cd 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -44,6 +44,7 @@ from gramps.gen.lib import (Person, Family, Event, Place, Repository, Citation, Source, Note, MediaObject, Tag, Researcher) from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn +from gramps.gen.db.undoredo import DbUndo from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback from gramps.gen.db import (PERSON_KEY, @@ -335,6 +336,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.import_cache = {} self.use_import_cache = False self.use_db_cache = True + self.undodb = DbUndo(self) + self.abort_possible = False self._directory = directory if directory: self.load(directory) From ad83d8477889e837b289cd8d685ecf2029c8bb62 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 12:43:24 -0400 Subject: [PATCH 017/105] DjangoDb: send proper object-add signal on new objects --- gramps/plugins/database/djangodb.py | 50 +++++++++++++++++------------ 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 0a88733cd..305cde2a8 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -1499,13 +1499,14 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = person.serialize() items = self.dji.Person.filter(handle=person.handle) - if items.count() > 0: + count = items.count() + if count > 0: # Hack, for the moment: delete and re-add items[0].delete() self.dji.add_person(person.serialize()) self.dji.add_person_detail(person.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("person-update", ([person.handle],)) else: self.emit("person-add", ([person.handle],)) @@ -1516,12 +1517,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = family.serialize() items = self.dji.Family.filter(handle=family.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_family(family.serialize()) self.dji.add_family_detail(family.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("family-update", ([family.handle],)) else: self.emit("family-add", ([family.handle],)) @@ -1532,12 +1534,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = citation.serialize() items = self.dji.Citation.filter(handle=citation.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_citation(citation.serialize()) self.dji.add_citation_detail(citation.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("citation-update", ([citation.handle],)) else: self.emit("citation-add", ([citation.handle],)) @@ -1548,12 +1551,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = source.serialize() items = self.dji.Source.filter(handle=source.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_source(source.serialize()) self.dji.add_source_detail(source.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("source-update", ([source.handle],)) else: self.emit("source-add", ([source.handle],)) @@ -1564,12 +1568,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = repository.serialize() items = self.dji.Repository.filter(handle=repository.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_repository(repository.serialize()) self.dji.add_repository_detail(repository.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("repository-update", ([repository.handle],)) else: self.emit("repository-add", ([repository.handle],)) @@ -1580,12 +1585,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = note.serialize() items = self.dji.Note.filter(handle=note.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_note(note.serialize()) self.dji.add_note_detail(note.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("note-update", ([note.handle],)) else: self.emit("note-add", ([note.handle],)) @@ -1596,12 +1602,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = place.serialize() items = self.dji.Place.filter(handle=place.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_place(place.serialize()) self.dji.add_place_detail(place.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("place-update", ([place.handle],)) else: self.emit("place-add", ([place.handle],)) @@ -1612,12 +1619,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = event.serialize() items = self.dji.Event.filter(handle=event.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_event(event.serialize()) self.dji.add_event_detail(event.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("event-update", ([event.handle],)) else: self.emit("event-add", ([event.handle],)) @@ -1628,12 +1636,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = tag.serialize() items = self.dji.Tag.filter(handle=tag.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_tag(tag.serialize()) self.dji.add_tag_detail(tag.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("tag-update", ([tag.handle],)) else: self.emit("tag-add", ([tag.handle],)) @@ -1648,12 +1657,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = media.serialize() items = self.dji.Media.filter(handle=media.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_media(media.serialize()) self.dji.add_media_detail(media.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("media-update", ([media.handle],)) else: self.emit("media-add", ([media.handle],)) From 37e7ead1e2a07a45577d2bd069132159c886602b Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 12:48:55 -0400 Subject: [PATCH 018/105] Fixed About dialog to show proper BSDDB version --- gramps/gui/aboutdialog.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/gramps/gui/aboutdialog.py b/gramps/gui/aboutdialog.py index 7ab181cc2..a80e3f26b 100644 --- a/gramps/gui/aboutdialog.py +++ b/gramps/gui/aboutdialog.py @@ -29,12 +29,6 @@ import os import sys import io -try: - import bsddb3 as bsddb ## ok, in try/except - BSDDB_STR = ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version())) -except: - BSDDB_STR = 'not found' - ##import logging ##_LOG = logging.getLogger(".GrampsAboutDialog") @@ -65,6 +59,20 @@ _ = glocale.translation.gettext from gramps.gen.constfunc import get_env_var from .display import display_url +def ellipses(text): + """ + Ellipsize text on length 40 + """ + if len(text) > 40: + return text[:40] + "..." + return text + +try: + import bsddb3 as bsddb ## ok, in try/except + BSDDB_STR = ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version())) +except: + BSDDB_STR = 'not found' + #------------------------------------------------------------------------- # # GrampsAboutDialog @@ -135,14 +143,6 @@ class GrampsAboutDialog(Gtk.AboutDialog): ellipses(operatingsystem), ellipses(distribution))) -def ellipses(text): - """ - Ellipsize text on length 40 - """ - if len(text) > 40: - return text[:40] + "..." - return text - #------------------------------------------------------------------------- # # AuthorParser From c1345ca64c155b0d31ddec21c45e187bcdebd0e4 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 22:35:50 -0400 Subject: [PATCH 019/105] Removed hardcoded database backend types --- gramps/gui/dbman.py | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py index 8054b41b8..2473569f6 100644 --- a/gramps/gui/dbman.py +++ b/gramps/gui/dbman.py @@ -69,6 +69,7 @@ from gi.repository import Pango # #------------------------------------------------------------------------- from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.plug import BasePluginManager _ = glocale.translation.gettext from gramps.gen.const import URL_WIKISTRING from .user import User @@ -104,7 +105,10 @@ STOCK_COL = 6 RCS_BUTTON = { True : _('_Extract'), False : _('_Archive') } class DatabaseDialog(Gtk.MessageDialog): - def __init__(self, parent=None): + def __init__(self, parent, options): + """ + options = [(pdata, number), ...] + """ Gtk.MessageDialog.__init__(self, parent, flags=Gtk.DialogFlags.MODAL, @@ -112,15 +116,12 @@ class DatabaseDialog(Gtk.MessageDialog): ) self.set_icon(ICON) self.set_title('') - self.set_markup('<span size="larger" weight="bold">%s</span>' % - _('Database Backend')) + _('Database Backend for New Tree')) self.format_secondary_text( - _("Please select a database backend type")) - - self.add_button("BSDDB Database (standard)", 1) - self.add_button("Dictionary (in-memory)", 2) - self.add_button("Django Database", 3) + _("Please select a database backend type:")) + for option, number in options: + self.add_button(option.name, number) class DbManager(CLIDbManager): """ @@ -779,14 +780,24 @@ class DbManager(CLIDbManager): message. """ self.new.set_sensitive(False) - # popup window and ask for dbid types, if more than one - ## FIXME: autoload from plugins - dbid = "bsddb" - d = DatabaseDialog(self.top) - database = d.run() - d.destroy() - if database >= 0: - dbid = {1:"bsddb",2:"dictionarydb",3:"djangodb"}[database] + dbid = None + pmgr = BasePluginManager.get_instance() + pdata = pmgr.get_reg_databases() + # If just one database backend, just use it: + if len(pdata) == 0: + DbManager.ERROR(_("No available database backends"), + _("Please check your dependencies.")) + elif len(pdata) == 1: + dbid = pdata[0].id + elif len(pdata) > 1: + options = sorted(list(zip(pdata, range(1, len(pdata) + 1))), key=lambda items: items[0].name) + d = DatabaseDialog(self.top, options) + number = d.run() + d.destroy() + if number >= 0: + dbid = [option[0].id for option in options if option[1] == number][0] + ### Now, let's load it up + if dbid: try: self._create_new_db(dbid=dbid) except (OSError, IOError) as msg: From b6fb46b760b905ddbd5c800e965309dc931b78e9 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 22:52:57 -0400 Subject: [PATCH 020/105] Database plugin type support reset_system, to reset modules --- gramps/gen/dbstate.py | 20 +++++++++----------- gramps/gen/plug/_pluginreg.py | 13 +++++++++++++ gramps/plugins/database/djangodb.gpr.py | 1 + 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py index 069934a13..0d9c30d4a 100644 --- a/gramps/gen/dbstate.py +++ b/gramps/gen/dbstate.py @@ -140,18 +140,16 @@ class DbState(Callback): pmgr.reg_plugins(USER_PLUGINS, self, None, load_on_reg=True) pdata = pmgr.get_plugin(id) - ### FIXME: Currently Django needs to reset modules - if pdata.id == "djangodb": - if self.modules_is_set(): - self.reset_modules() - else: - self.save_modules() + if pdata: + if pdata.reset_system: + if self.modules_is_set(): + self.reset_modules() + else: + self.save_modules() + mod = pmgr.load_plugin(pdata) + database = getattr(mod, pdata.databaseclass) + return database() - mod = pmgr.load_plugin(pdata) - database = getattr(mod, pdata.databaseclass) - return database() - - ## FIXME: ## Work-around for databases that need sys refresh (django): def modules_is_set(self): if hasattr(self, "_modules"): diff --git a/gramps/gen/plug/_pluginreg.py b/gramps/gen/plug/_pluginreg.py index 5619f18ed..70a93f0f2 100644 --- a/gramps/gen/plug/_pluginreg.py +++ b/gramps/gen/plug/_pluginreg.py @@ -356,6 +356,9 @@ class PluginData(object): .. attribute:: databaseclass The class in the module that is the database class + .. attribute:: reset_system + Boolean to indicate that the system (sys.modules) should + be reset. """ def __init__(self): @@ -430,6 +433,7 @@ class PluginData(object): self._order = END #DATABASE attr self._databaseclass = None + self._reset_system = False #GENERAL attr self._data = [] self._process = None @@ -949,7 +953,16 @@ class PluginData(object): def _get_databaseclass(self): return self._databaseclass + def _set_reset_system(self, reset_system): + if not self._ptype == DATABASE: + raise ValueError('reset_system may only be set for DATABASE plugins') + self._reset_system = reset_system + + def _get_reset_system(self): + return self._reset_system + databaseclass = property(_get_databaseclass, _set_databaseclass) + reset_system = property(_get_reset_system, _set_reset_system) #GENERAL attr def _set_data(self, data): diff --git a/gramps/plugins/database/djangodb.gpr.py b/gramps/plugins/database/djangodb.gpr.py index 57b30f2f5..2d0d0797b 100644 --- a/gramps/plugins/database/djangodb.gpr.py +++ b/gramps/plugins/database/djangodb.gpr.py @@ -29,3 +29,4 @@ plg.status = STABLE plg.fname = 'djangodb.py' plg.ptype = DATABASE plg.databaseclass = 'DbDjango' +plg.reset_system = True From c2fb186edcf5e21d6ef1a589bd8cd22452776dd1 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 23:15:30 -0400 Subject: [PATCH 021/105] Added missing bookmark count methods to djangodb and dictionarydb --- gramps/plugins/database/dictionarydb.py | 13 +++++++++++++ gramps/plugins/database/djangodb.py | 14 ++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 09748295a..2a15c28b1 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -393,6 +393,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.transaction = None self.undodb = DbUndo(self) self.abort_possible = False + self._bm_changes = 0 self._directory = directory if directory: self.load(directory) @@ -1689,3 +1690,15 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): with open(versionpath, "w") as version_file: version_file.write("dictionarydb") + def report_bm_change(self): + """ + Add 1 to the number of bookmark changes during this session. + """ + self._bm_changes += 1 + + def db_has_bm_changes(self): + """ + Return whethere there were bookmark changes during the session. + """ + return self._bm_changes > 0 + diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 305cde2a8..543243c99 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -338,6 +338,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.use_db_cache = True self.undodb = DbUndo(self) self.abort_possible = False + self._bm_changes = 0 self._directory = directory if directory: self.load(directory) @@ -2088,3 +2089,16 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): for filename in os.listdir(defaults): fullpath = os.path.abspath(os.path.join(defaults, filename)) shutil.copy2(fullpath, directory) + + def report_bm_change(self): + """ + Add 1 to the number of bookmark changes during this session. + """ + self._bm_changes += 1 + + def db_has_bm_changes(self): + """ + Return whethere there were bookmark changes during the session. + """ + return self._bm_changes > 0 + From ae11d8b48440270fc8eaf93ed1c3394c0dfaf1f1 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Fri, 15 May 2015 06:42:13 -0400 Subject: [PATCH 022/105] Alternative DBs: touch meta_data.db to record last access time --- gramps/plugins/database/dictionarydb.py | 9 +++++++++ gramps/plugins/database/djangodb.py | 11 ++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 2a15c28b1..ff1b8f745 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -67,6 +67,13 @@ from gramps.gen.lib.tag import Tag _LOG = logging.getLogger(DBLOGNAME) +def touch(fname, mode=0o666, dir_fd=None, **kwargs): + ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python + flags = os.O_CREAT | os.O_APPEND + with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f: + os.utime(f.fileno() if os.utime in os.supports_fd else fname, + dir_fd=None if os.supports_fd else dir_fd, **kwargs) + class Environment(object): """ Implements the Environment API. @@ -1483,6 +1490,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): writer = XmlWriter(self, User(), strip_photos=0, compress=1) filename = os.path.join(self._directory, "data.gramps") writer.write(filename) + filename = os.path.join(self._directory, "meta_data.db") + touch(filename) def find_backlink_handles(self, handle, include_classes=None): return [] diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 543243c99..759d366b8 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -64,6 +64,13 @@ sys.path.append(os.path.dirname(os.path.abspath(__file__))) _LOG = logging.getLogger(DBLOGNAME) +def touch(fname, mode=0o666, dir_fd=None, **kwargs): + ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python + flags = os.O_CREAT | os.O_APPEND + with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f: + os.utime(f.fileno() if os.utime in os.supports_fd else fname, + dir_fd=None if os.supports_fd else dir_fd, **kwargs) + class Environment(object): """ Implements the Environment API. @@ -1959,7 +1966,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def close(self): - pass + if self._directory: + filename = os.path.join(self._directory, "meta_data.db") + touch(filename) def get_surname_list(self): return [] From a36a8b72b80a231f14a7660a486f61f419fcbaa1 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Fri, 15 May 2015 07:32:03 -0400 Subject: [PATCH 023/105] Database API, -L: database reports summary, if possible --- gramps/cli/clidbman.py | 54 ++++++++++++------- gramps/plugins/database/bsddb_support/read.py | 13 +++++ gramps/plugins/database/dictionarydb.py | 29 ++++++++++ gramps/plugins/database/djangodb.py | 32 +++++++++-- 4 files changed, 107 insertions(+), 21 deletions(-) diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py index eb7a59ffa..b8ee1d294 100644 --- a/gramps/cli/clidbman.py +++ b/gramps/cli/clidbman.py @@ -133,12 +133,42 @@ class CLIDbManager(object): def get_dbdir_summary(self, dirpath, name): """ - Returns (people_count, bsddb_version, schema_version) of - current DB. - Returns ("Unknown", "Unknown", "Unknown") if invalid DB or other error. + dirpath: full path to database + name: proper name of family tree + + Returns dictionary of summary item. + Should include at least, if possible: + + _("Path") + _("Family Tree") + _("Last accessed") + _("Database backend") + _("Locked?") + + and these details: + + _("Number of people") + _("Version") + _("Schema version") """ - ## Maybe return the txt file contents, for now - return ("Unknown", "Unknown", "Unknown") + dbid = "bsddb" + dbid_path = os.path.join(dirpath, "database.txt") + if os.path.isfile(dbid_path): + dbid = open(dbid_path).read().strip() + try: + database = self.dbstate.make_database(dbid) + database.load(dirpath, None) + retval = database.get_summary() + except Exception as msg: + retval = {"Unavailable": str(msg)[:74] + "..."} + retval.update({ + _("Family Tree"): name, + _("Path"): dirpath, + _("Database backend"): dbid, + _("Last accessed"): time_val(dirpath)[1], + _("Locked?"): self.is_locked(dirpath), + }) + return retval def family_tree_summary(self): """ @@ -149,19 +179,7 @@ class CLIDbManager(object): for item in self.current_names: (name, dirpath, path_name, last, tval, enable, stock_id) = item - count, bsddb_version, schema_version = self.get_dbdir_summary(dirpath, name) - retval = {} - retval[_("Number of people")] = count - if enable: - retval[_("Locked?")] = _("yes") - else: - retval[_("Locked?")] = _("no") - retval[_("Bsddb version")] = bsddb_version - retval[_("Schema version")] = schema_version - retval[_("Family Tree")] = name - retval[_("Path")] = dirpath - retval[_("Last accessed")] = time.strftime('%x %X', - time.localtime(tval)) + retval = self.get_dbdir_summary(dirpath, name) summary_list.append( retval ) return summary_list diff --git a/gramps/plugins/database/bsddb_support/read.py b/gramps/plugins/database/bsddb_support/read.py index d51810581..9280dbc09 100644 --- a/gramps/plugins/database/bsddb_support/read.py +++ b/gramps/plugins/database/bsddb_support/read.py @@ -1976,3 +1976,16 @@ class DbBsddbRead(DbReadBase, Callback): self.__log_error() name = None return name + + def get_summary(self): + """ + Returns dictionary of summary item. + Should include, if possible: + + _("Number of people") + _("Version") + _("Schema version") + """ + return { + _("Number of people"): self.get_number_of_people(), + } diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index ff1b8f745..72a383469 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -36,6 +36,8 @@ import logging # Gramps Modules # #------------------------------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP from gramps.gen.db.undoredo import DbUndo from gramps.gen.db.dbconst import * @@ -1711,3 +1713,30 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): """ return self._bm_changes > 0 + def get_summary(self): + """ + Returns dictionary of summary item. + Should include, if possible: + + _("Number of people") + _("Version") + _("Schema version") + """ + return { + _("Number of people"): self.get_number_of_people(), + } + + def get_dbname(self): + """ + In DictionaryDb, the database is in a text file at the path + """ + filepath = os.path.join(self._directory, "name.txt") + try: + name_file = open(filepath, "r") + name = name_file.readline().strip() + name_file.close() + except (OSError, IOError) as msg: + _LOG.error(str(msg)) + name = None + return name + diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 759d366b8..1c6e3a6a1 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -40,6 +40,8 @@ from django.db import transaction # #------------------------------------------------------------------------ import gramps +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext from gramps.gen.lib import (Person, Family, Event, Place, Repository, Citation, Source, Note, MediaObject, Tag, Researcher) @@ -2028,9 +2030,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): ## FIXME pass - def get_dbname(self): - return "Django Database" - ## missing def find_place_child_handles(self, handle): @@ -2111,3 +2110,30 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): """ return self._bm_changes > 0 + def get_summary(self): + """ + Returns dictionary of summary item. + Should include, if possible: + + _("Number of people") + _("Version") + _("Schema version") + """ + return { + _("Number of people"): self.get_number_of_people(), + } + + def get_dbname(self): + """ + In Django, the database is in a text file at the path + """ + filepath = os.path.join(self._directory, "name.txt") + try: + name_file = open(filepath, "r") + name = name_file.readline().strip() + name_file.close() + except (OSError, IOError) as msg: + _LOG.error(str(msg)) + name = None + return name + From 16c2843073e0b2ea55de4cd4b5b533d1cca1f986 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Fri, 15 May 2015 09:12:21 -0400 Subject: [PATCH 024/105] bsddb backend: supply version details in get_summary --- .../plugins/database/bsddb_support/write.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py index 836fcc364..cca39eae7 100644 --- a/gramps/plugins/database/bsddb_support/write.py +++ b/gramps/plugins/database/bsddb_support/write.py @@ -2448,6 +2448,28 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): except (OSError, IOError) as msg: raise DbException(str(msg)) + def get_summary(self): + """ + Returns dictionary of summary item. + Should include, if possible: + + _("Number of people") + _("Version") + _("Schema version") + """ + schema_version = self.metadata.get(b'version', default=None) + bdbversion_file = os.path.join(self.path, BDBVERSFN) + if os.path.isfile(bdbversion_file): + vers_file = open(bdbversion_file) + bsddb_version = vers_file.readline().strip() + else: + bsddb_version = _("Unknown") + return { + _("Number of people"): self.get_number_of_people(), + _("Schema version"): schema_version, + _("Version"): bsddb_version, + } + def mk_backup_name(database, base): """ Return the backup name of the database table From 1c05879f62fe95ae6dfb9ed3411fca989a9164a3 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Fri, 15 May 2015 10:48:14 -0400 Subject: [PATCH 025/105] Database API: expore name, full_name, and brief_name --- gramps/plugins/database/dictionarydb.py | 9 +++++++++ gramps/plugins/database/djangodb.py | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 72a383469..ecdf5a112 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -404,6 +404,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.abort_possible = False self._bm_changes = 0 self._directory = directory + self.full_name = None + self.path = None + self.brief_name = None if directory: self.load(directory) @@ -1671,6 +1674,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): from gramps.plugins.importer.importxml import importData from gramps.cli.user import User self._directory = directory + self.full_name = os.path.abspath(self._directory) + self.path = self.full_name + self.brief_name = os.path.basename(self._directory) filename = os.path.join(directory, "data.gramps") if os.path.isfile(filename): importData(self, filename, User()) @@ -1690,6 +1696,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): def set_save_path(self, directory): self._directory = directory + self.full_name = os.path.abspath(self._directory) + self.path = self.full_name + self.brief_name = os.path.basename(self._directory) def undo(self, update_history=True): pass diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 1c6e3a6a1..182295645 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -349,6 +349,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.abort_possible = False self._bm_changes = 0 self._directory = directory + self.full_name = None + self.path = None + self.brief_name = None if directory: self.load(directory) @@ -358,6 +361,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): force_bsddb_downgrade=False, force_python_upgrade=False): self._directory = directory + self.full_name = os.path.abspath(self._directory) + self.path = self.full_name + self.brief_name = os.path.basename(self._directory) from django.conf import settings default_settings = {"__file__": os.path.join(directory, "default_settings.py")} @@ -1836,6 +1842,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def set_save_path(self, directory): self._directory = directory + self.full_name = os.path.abspath(self._directory) + self.path = self.full_name + self.brief_name = os.path.basename(self._directory) ## Get types: def get_event_attribute_types(self): From 87d2cfb3016a25ab5112d397f5803afe9b9879ad Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Fri, 15 May 2015 22:50:54 -0400 Subject: [PATCH 026/105] DictionaryDb: reworked internal reprs; updated gender stats, researcher --- gramps/plugins/database/dictionarydb.py | 341 ++++++++++++++---------- gramps/plugins/database/djangodb.py | 17 +- 2 files changed, 213 insertions(+), 145 deletions(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index ecdf5a112..38ad398ac 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -38,7 +38,8 @@ import logging #------------------------------------------------------------------------ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP +from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, + KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP) from gramps.gen.db.undoredo import DbUndo from gramps.gen.db.dbconst import * from gramps.gen.utils.callback import Callback @@ -66,6 +67,7 @@ from gramps.gen.lib.place import Place from gramps.gen.lib.repo import Repository from gramps.gen.lib.note import Note from gramps.gen.lib.tag import Tag +from gramps.gen.lib.genderstats import GenderStats _LOG = logging.getLogger(DBLOGNAME) @@ -99,8 +101,8 @@ class Table(object): """ return self.funcs["cursor_func"]() - def put(key, data, txn=None): - self[key] = data + def put(self, key, data, txn=None): + self.funcs["add_func"](data, txn) class Map(dict): """ @@ -137,15 +139,14 @@ class MetaCursor(object): pass class Cursor(object): - def __init__(self, map, func): + def __init__(self, map): self.map = map - self.func = func self._iter = self.__iter__() def __enter__(self): return self def __iter__(self): for item in self.map.keys(): - yield (bytes(item, "utf-8"), self.func(item)) + yield (bytes(item, "utf-8"), self.map[item]) def __next__(self): try: return self._iter.__next__() @@ -155,7 +156,7 @@ class Cursor(object): pass def iter(self): for item in self.map.keys(): - yield (bytes(item, "utf-8"), self.func(item)) + yield (bytes(item, "utf-8"), self.map[item]) def first(self): self._iter = self.__iter__() try: @@ -383,15 +384,25 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.nmap_index = 0 self.env = Environment(self) self.person_map = Map(Table(self._tables["Person"])) + self.person_id_map = {} self.family_map = Map(Table(self._tables["Family"])) + self.family_id_map = {} self.place_map = Map(Table(self._tables["Place"])) + self.place_id_map = {} self.citation_map = Map(Table(self._tables["Citation"])) + self.citation_id_map = {} self.source_map = Map(Table(self._tables["Source"])) + self.source_id_map = {} self.repository_map = Map(Table(self._tables["Repository"])) + self.repository_id_map = {} self.note_map = Map(Table(self._tables["Note"])) + self.note_id_map = {} self.media_map = Map(Table(self._tables["Media"])) + self.media_id_map = {} self.event_map = Map(Table(self._tables["Event"])) + self.event_id_map = {} self.tag_map = Map(Table(self._tables["Tag"])) + self.tag_id_map = {} self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) self.name_group = {} self.undo_callback = None @@ -407,6 +418,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.full_name = None self.path = None self.brief_name = None + self.genderStats = GenderStats() # can pass in loaded stats as dict + self.owner = Researcher() if directory: self.load(directory) @@ -425,12 +438,15 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def transaction_commit(self, txn): + ## FIXME pass def get_undodb(self): + ## FIXME return None def transaction_abort(self, txn): + ## FIXME pass @staticmethod @@ -687,10 +703,12 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): ## Fixme: implement sort return self.person_map.keys() - def get_family_handles(self): + def get_family_handles(self, sort_handles=False): + ## Fixme: implement sort return self.family_map.keys() def get_event_handles(self, sort_handles=False): + ## Fixme: implement sort return self.event_map.keys() def get_citation_handles(self, sort_handles=False): @@ -705,78 +723,80 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): ## Fixme: implement sort return self.place_map.keys() - def get_repository_handles(self): + def get_repository_handles(self, sort_handles=False): + ## Fixme: implement sort return self.repository_map.keys() def get_media_object_handles(self, sort_handles=False): ## Fixme: implement sort return self.media_map.keys() - def get_note_handles(self): + def get_note_handles(self, sort_handles=False): + ## Fixme: implement sort return self.note_map.keys() def get_tag_handles(self, sort_handles=False): - # FIXME: sort + # FIXME: implement sort return self.tag_map.keys() def get_event_from_handle(self, handle): event = None if handle in self.event_map: - event = self.event_map[handle] + event = Event.create(self.event_map[handle]) return event def get_family_from_handle(self, handle): family = None if handle in self.family_map: - family = self.family_map[handle] + family = Family.create(self.family_map[handle]) return family def get_repository_from_handle(self, handle): repository = None if handle in self.repository_map: - repository = self.repository_map[handle] + repository = Repository.create(self.repository_map[handle]) return repository def get_person_from_handle(self, handle): person = None if handle in self.person_map: - person = self.person_map[handle] + person = Person.create(self.person_map[handle]) return person def get_place_from_handle(self, handle): place = None if handle in self.place_map: - place = self.place_map[handle] + place = Place.create(self.place_map[handle]) return place def get_citation_from_handle(self, handle): citation = None if handle in self.citation_map: - citation = self.citation_map[handle] + citation = Citation.create(self.citation_map[handle]) return citation def get_source_from_handle(self, handle): source = None if handle in self.source_map: - source = self.source_map[handle] + source = Source.create(self.source_map[handle]) return source def get_note_from_handle(self, handle): note = None if handle in self.note_map: - note = self.note_map[handle] + note = Note.create(self.note_map[handle]) return note def get_object_from_handle(self, handle): media = None if handle in self.media_map: - media = self.media_map[handle] + media = MediaObject.create(self.media_map[handle]) return media def get_tag_from_handle(self, handle): tag = None if handle in self.tag_map: - tag = self.tag_map[handle] + tag = Tag.create(self.tag_map[handle]) return tag def get_default_person(self): @@ -787,81 +807,73 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def iter_people(self): - return (person for person in self.person_map.values()) + return (Person.create(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()) + return (Family.create(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(): + ## Slow, but typically not too many tags: + for data in self.tag_map.values(): + tag = Tag.create(data) if tag.name == name: return tag 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 + if gramps_id in self.person_id_map: + return Person.create(self.person_id_map[gramps_id]) 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 + if gramps_id in self.family_id_map: + return Family.create(self.family_id_map[gramps_id]) return None def get_citation_from_gramps_id(self, gramps_id): - for citation in self.citation_map.values(): - if citation.gramps_id == gramps_id: - return citation + if gramps_id in self.citation_id_map: + return Citation.create(self.citation_id_map[gramps_id]) return None def get_source_from_gramps_id(self, gramps_id): - for source in self.source_map.values(): - if source.gramps_id == gramps_id: - return source + if gramps_id in self.source_id_map: + return Source.create(self.source_id_map[gramps_id]) return None def get_event_from_gramps_id(self, gramps_id): - for event in self.event_map.values(): - if event.gramps_id == gramps_id: - return event + if gramps_id in self.event_id_map: + return Event.create(self.event_id_map[gramps_id]) return None def get_media_from_gramps_id(self, gramps_id): - for media in self.media_map.values(): - if media.gramps_id == gramps_id: - return media + if gramps_id in self.media_id_map: + return MediaObject.create(self.media_id_map[gramps_id]) return None def get_place_from_gramps_id(self, gramps_id): - for place in self.place_map.values(): - if place.gramps_id == gramps_id: - return place + if gramps_id in self.place_id_map: + return Place.create(self.place_id_map[gramps_id]) return None def get_repository_from_gramps_id(self, gramps_id): - for repository in self.repository_map.values(): - if repository.gramps_id == gramps_id: - return repository + if gramps_id in self.repository_id_map: + return Repository.create(self.repository_id_map[gramps_id]) return None def get_note_from_gramps_id(self, gramps_id): - for note in self.note_map.values(): - if note.gramps_id == gramps_id: - return note + if gramps_id in self.note_id_map: + return Note.create(self.note_id_map[gramps_id]) return None def get_tag_from_gramps_id(self, gramps_id): - for tag in self.tag_map.values(): - if tag.gramps_id == gramps_id: - return tag + if gramps_id in self.tag_id_map: + return Tag.create(self.tag_id_map[gramps_id]) return None def get_number_of_people(self): @@ -874,7 +886,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return len(self.place_map) def get_number_of_tags(self): - return 0 # FIXME + return len(self.tag_map) def get_number_of_families(self): return len(self.family_map) @@ -895,52 +907,48 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return len(self.repository_map) def get_place_cursor(self): - return Cursor(self.place_map, self.get_raw_place_data) + return Cursor(self.place_map) def get_person_cursor(self): - return Cursor(self.person_map, self.get_raw_person_data) + return Cursor(self.person_map) def get_family_cursor(self): - return Cursor(self.family_map, self.get_raw_family_data) + return Cursor(self.family_map) def get_event_cursor(self): - return Cursor(self.event_map, self.get_raw_event_data) + return Cursor(self.event_map) def get_note_cursor(self): - return Cursor(self.note_map, self.get_raw_note_data) + return Cursor(self.note_map) def get_tag_cursor(self): - return Cursor(self.tag_map, self.get_raw_tag_data) + return Cursor(self.tag_map) def get_repository_cursor(self): - return Cursor(self.repository_map, self.get_raw_repository_data) + return Cursor(self.repository_map) def get_media_cursor(self): - return Cursor(self.media_map, self.get_raw_object_data) + return Cursor(self.media_map) def get_citation_cursor(self): - return Cursor(self.citation_map, self.get_raw_citation_data) + return Cursor(self.citation_map) def get_source_cursor(self): - return Cursor(self.source_map, self.get_raw_source_data) + return Cursor(self.source_map) 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, + PERSON_KEY: self.person_id_map, + FAMILY_KEY: self.family_id_map, + SOURCE_KEY: self.source_id_map, + CITATION_KEY: self.citation_id_map, + EVENT_KEY: self.event_id_map, + MEDIA_KEY: self.media_id_map, + PLACE_KEY: self.place_id_map, + REPOSITORY_KEY: self.repository_id_map, + NOTE_KEY: self.note_id_map, } - map = key2table[obj_key] - for item in map.values(): - if item.gramps_id == gramps_id: - return True - return False + return gramps_id in key2table[obj_key] def has_person_handle(self, handle): return handle in self.person_map @@ -981,59 +989,61 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): pass def set_default_person_handle(self, handle): + ## FIXME pass def set_mediapath(self, mediapath): + ## FIXME pass def get_raw_person_data(self, handle): if handle in self.person_map: - return self.person_map[handle].serialize() + return self.person_map[handle] return None def get_raw_family_data(self, handle): if handle in self.family_map: - return self.family_map[handle].serialize() + return self.family_map[handle] return None def get_raw_citation_data(self, handle): if handle in self.citation_map: - return self.citation_map[handle].serialize() + return self.citation_map[handle] return None def get_raw_source_data(self, handle): if handle in self.source_map: - return self.source_map[handle].serialize() + return self.source_map[handle] return None def get_raw_repository_data(self, handle): if handle in self.repository_map: - return self.repository_map[handle].serialize() + return self.repository_map[handle] return None def get_raw_note_data(self, handle): if handle in self.note_map: - return self.note_map[handle].serialize() + return self.note_map[handle] return None def get_raw_place_data(self, handle): if handle in self.place_map: - return self.place_map[handle].serialize() + return self.place_map[handle] return None def get_raw_object_data(self, handle): if handle in self.media_map: - return self.media_map[handle].serialize() + return self.media_map[handle] return None def get_raw_tag_data(self, handle): if handle in self.tag_map: - return self.tag_map[handle].serialize() + return self.tag_map[handle] return None def get_raw_event_data(self, handle): if handle in self.event_map: - return self.event_map[handle].serialize() + return self.event_map[handle] return None def add_person(self, person, trans, set_gid=True): @@ -1127,7 +1137,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "person-update" else: emit = "person-add" - self.person_map[person.handle] = person + self.person_map[person.handle] = person.serialize() + self.person_id_map[person.gramps_id] = self.person_map[person.handle] # Emit after added: if emit: self.emit(emit, ([person.handle],)) @@ -1139,7 +1150,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "family-update" else: emit = "family-add" - self.family_map[family.handle] = family + self.family_map[family.handle] = family.serialize() + self.family_id_map[family.gramps_id] = self.family_map[family.handle] # Emit after added: if emit: self.emit(emit, ([family.handle],)) @@ -1151,7 +1163,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "citation-update" else: emit = "citation-add" - self.citation_map[citation.handle] = citation + self.citation_map[citation.handle] = citation.serialize() + self.citation_id_map[citation.gramps_id] = self.citation_map[citation.handle] # Emit after added: if emit: self.emit(emit, ([citation.handle],)) @@ -1163,7 +1176,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "source-update" else: emit = "source-add" - self.source_map[source.handle] = source + self.source_map[source.handle] = source.serialize() + self.source_id_map[source.gramps_id] = self.source_map[source.handle] # Emit after added: if emit: self.emit(emit, ([source.handle],)) @@ -1175,7 +1189,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "repository-update" else: emit = "repository-add" - self.repository_map[repository.handle] = repository + self.repository_map[repository.handle] = repository.serialize() + self.repository_id_map[repository.gramps_id] = self.repository_map[repository.handle] # Emit after added: if emit: self.emit(emit, ([repository.handle],)) @@ -1187,7 +1202,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "note-update" else: emit = "note-add" - self.note_map[note.handle] = note + self.note_map[note.handle] = note.serialize() + self.note_id_map[note.gramps_id] = self.note_map[note.handle] # Emit after added: if emit: self.emit(emit, ([note.handle],)) @@ -1199,7 +1215,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "place-update" else: emit = "place-add" - self.place_map[place.handle] = place + self.place_map[place.handle] = place.serialize() + self.place_id_map[place.gramps_id] = self.place_map[place.handle] # Emit after added: if emit: self.emit(emit, ([place.handle],)) @@ -1211,7 +1228,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "event-update" else: emit = "event-add" - self.event_map[event.handle] = event + self.event_map[event.handle] = event.serialize() + self.event_id_map[event.gramps_id] = self.event_map[event.handle] # Emit after added: if emit: self.emit(emit, ([event.handle],)) @@ -1223,7 +1241,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "tag-update" else: emit = "tag-add" - self.tag_map[tag.handle] = tag + self.tag_map[tag.handle] = tag.serialize() + self.tag_id_map[tag.gramps_id] = self.tag_map[tag.handle] # Emit after added: if emit: self.emit(emit, ([tag.handle],)) @@ -1235,34 +1254,35 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "media-update" else: emit = "media-add" - self.media_map[media.handle] = media + self.media_map[media.handle] = media.serialize() + self.media_id_map[media.gramps_id] = self.media_map[media.handle] # Emit after added: if emit: self.emit(emit, ([media.handle],)) 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, + PERSON_KEY: self.person_id_map, + FAMILY_KEY: self.family_id_map, + CITATION_KEY: self.citation_id_map, + SOURCE_KEY: self.source_id_map, + EVENT_KEY: self.event_id_map, + MEDIA_KEY: self.media_id_map, + PLACE_KEY: self.place_id_map, + REPOSITORY_KEY: self.repository_id_map, + NOTE_KEY: self.note_id_map, } - table = key2table[obj_key] - return [item.gramps_id for item in table.values()] + return list(key2table[obj_key].keys()) def transaction_begin(self, transaction): + ## FIXME return - def disable_signals(self): - pass - def set_researcher(self, owner): - pass + self.owner.set_from(owner) + + def get_researcher(self): + return self.owner def request_rebuild(self): self.emit('person-rebuild') @@ -1332,7 +1352,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): if self.readonly or not handle: return if handle in self.person_map: + person = Person.create(self.person_map[handle]) del self.person_map[handle] + del self.person_id_map[person.gramps_id] self.emit("person-delete", ([handle],)) def remove_source(self, handle, transaction): @@ -1341,7 +1363,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.source_map, - SOURCE_KEY) + self.source_id_map, SOURCE_KEY) def remove_citation(self, handle, transaction): """ @@ -1349,7 +1371,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.citation_map, - CITATION_KEY) + self.citation_id_map, CITATION_KEY) def remove_event(self, handle, transaction): """ @@ -1357,7 +1379,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.event_map, - EVENT_KEY) + self.event_id_map, EVENT_KEY) def remove_object(self, handle, transaction): """ @@ -1365,7 +1387,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.media_map, - MEDIA_KEY) + self.media_id_map, MEDIA_KEY) def remove_place(self, handle, transaction): """ @@ -1373,7 +1395,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.place_map, - PLACE_KEY) + self.place_id_map, PLACE_KEY) def remove_family(self, handle, transaction): """ @@ -1381,7 +1403,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.family_map, - FAMILY_KEY) + self.family_id_map, FAMILY_KEY) def remove_repository(self, handle, transaction): """ @@ -1389,7 +1411,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.repository_map, - REPOSITORY_KEY) + self.repository_id_map, REPOSITORY_KEY) def remove_note(self, handle, transaction): """ @@ -1397,7 +1419,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.note_map, - NOTE_KEY) + self.note_id_map, NOTE_KEY) def remove_tag(self, handle, transaction): """ @@ -1405,7 +1427,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.tag_map, - TAG_KEY) + self.tag_id_map, TAG_KEY) def is_empty(self): """ @@ -1416,13 +1438,13 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return False return True - def __do_remove(self, handle, transaction, data_map, key): + def __do_remove(self, handle, transaction, data_map, data_id_map, key): if self.readonly or not handle: return - if isinstance(handle, str): - handle = handle.encode('utf-8') if handle in data_map: + obj = self._tables[KEY_TO_CLASS_MAP[key]]["class_func"].create(data_map[handle]) del data_map[handle] + del data_id_map[obj.gramps_id] self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) def delete_primary_from_reference_map(self, handle, transaction, txn=None): @@ -1486,6 +1508,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): ## Missing: def backup(self): + ## FIXME pass def close(self): @@ -1499,6 +1522,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): touch(filename) def find_backlink_handles(self, handle, include_classes=None): + ## FIXME return [] def find_initial_person(self): @@ -1508,22 +1532,30 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def find_place_child_handles(self, handle): + ## FIXME return [] def get_bookmarks(self): return self.bookmarks def get_child_reference_types(self): + ## FIXME return [] def get_citation_bookmarks(self): return self.citation_bookmarks def get_cursor(self, table, txn=None, update=False, commit=False): + ## FIXME + ## called from a complete find_back_ref pass - def get_dbname(self): - return "DictionaryDb" + # 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_default_handle(self): items = self.person_map.keys() @@ -1532,124 +1564,141 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def get_event_attribute_types(self): + ## FIXME return [] def get_event_bookmarks(self): return self.event_bookmarks def get_event_roles(self): + ## FIXME return [] def get_event_types(self): + ## FIXME return [] def get_family_attribute_types(self): + ## FIXME return [] def get_family_bookmarks(self): return self.family_bookmarks def get_family_event_types(self): + ## FIXME return [] def get_family_relation_types(self): + ## FIXME return [] def get_media_attribute_types(self): + ## FIXME return [] def get_media_bookmarks(self): return self.media_bookmarks def get_name_types(self): + ## FIXME return [] def get_note_bookmarks(self): return self.note_bookmarks def get_note_types(self): + ## FIXME return [] - def get_number_of_records(self, table): - return 0 - def get_origin_types(self): + ## FIXME return [] def get_person_attribute_types(self): + ## FIXME return [] def get_person_event_types(self): + ## FIXME return [] def get_place_bookmarks(self): return self.place_bookmarks def get_place_tree_cursor(self): + ## FIXME return [] def get_place_types(self): + ## FIXME return [] def get_repo_bookmarks(self): return self.repo_bookmarks def get_repository_types(self): + ## FIXME return [] def get_save_path(self): return self._directory def get_source_attribute_types(self): + ## FIXME return [] def get_source_bookmarks(self): return self.source_bookmarks def get_source_media_types(self): + ## FIXME return [] def get_surname_list(self): + ## FIXME return [] def get_url_types(self): + ## FIXME return [] def has_changed(self): + ## FIXME return True def is_open(self): - return True + return self._directory is not None def iter_citation_handles(self): return (key for key in self.citation_map.keys()) def iter_citations(self): - return (key for key in self.citation_map.values()) + return (Citation.create(key) for key in self.citation_map.values()) def iter_event_handles(self): return (key for key in self.event_map.keys()) def iter_events(self): - return (key for key in self.event_map.values()) + return (Events.create(key) for key in self.event_map.values()) def iter_media_objects(self): - return (key for key in self.media_map.values()) + return (MediaObject.create(key) for key in self.media_map.values()) def iter_note_handles(self): return (key for key in self.note_map.keys()) def iter_notes(self): - return (key for key in self.note_map.values()) + return (Note.create(key) for key in self.note_map.values()) def iter_place_handles(self): return (key for key in self.place_map.keys()) def iter_places(self): - return (key for key in self.place_map.values()) + return (Place.create(key) for key in self.place_map.values()) def iter_repositories(self): - return (key for key in self.repositories_map.values()) + return (Repository.create(key) for key in self.repositories_map.values()) def iter_repository_handles(self): return (key for key in self.repositories_map.keys()) @@ -1658,13 +1707,13 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return (key for key in self.source_map.keys()) def iter_sources(self): - return (key for key in self.source_map.values()) + return (Source.create(key) for key in self.source_map.values()) def iter_tag_handles(self): return (key for key in self.tag_map.keys()) def iter_tags(self): - return (key for key in self.tag_map.values()) + return (Tag.create(key) for key in self.tag_map.values()) def load(self, directory, pulse_progress=None, mode=None, force_schema_upgrade=False, @@ -1682,16 +1731,20 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): importData(self, filename, User()) def prepare_import(self): + ## FIXME pass def redo(self, update_history=True): + ## FIXME pass def restore(self): + ## FIXME pass def set_prefixes(self, person, media, family, source, citation, place, event, repository, note): + ## FIXME pass def set_save_path(self, directory): @@ -1701,6 +1754,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.brief_name = os.path.basename(self._directory) def undo(self, update_history=True): + ## FIXME pass def write_version(self, directory): @@ -1749,3 +1803,10 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): name = None return name + def reindex_reference_map(self): + ## FIXME + pass + + def rebuild_secondary(self, update): + ## FIXME + pass diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 182295645..f0d1faf44 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -44,7 +44,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext from gramps.gen.lib import (Person, Family, Event, Place, Repository, Citation, Source, Note, MediaObject, Tag, - Researcher) + Researcher, GenderStats) from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn from gramps.gen.db.undoredo import DbUndo from gramps.gen.utils.callback import Callback @@ -352,6 +352,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.full_name = None self.path = None self.brief_name = None + self.genderStats = GenderStats() # can pass in loaded stats as dict + self.owner = Researcher() if directory: self.load(directory) @@ -788,8 +790,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def get_researcher(self): - obj = Researcher() - return obj + return self.owner def get_tag_handles(self, sort_handles=False): if sort_handles: @@ -1704,7 +1705,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): return def set_researcher(self, owner): - pass + self.owner.set_from(owner) def copy_from_db(self, db): """ @@ -2037,9 +2038,10 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def remove_from_surname_list(self, person): ## FIXME + ## called by a complete commit_person pass - ## missing + ## was missing def find_place_child_handles(self, handle): pass @@ -2146,3 +2148,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): name = None return name + def reindex_reference_map(self): + pass + + def rebuild_secondary(self, update): + pass From d8346a705fd54920c43e6baaa9db01c33c6fb87c Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Fri, 15 May 2015 23:26:22 -0400 Subject: [PATCH 027/105] DjangoDb: always force a gramps_id; typo fix --- gramps/plugins/database/djangodb.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index f0d1faf44..ca55eb0b1 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -1429,7 +1429,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_person(self, person, trans, set_gid=True): if not person.handle: person.handle = create_id() - if not person.gramps_id and set_gid: + if not person.gramps_id: person.gramps_id = self.find_next_person_gramps_id() self.commit_person(person, trans) return person.handle @@ -1437,7 +1437,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_family(self, family, trans, set_gid=True): if not family.handle: family.handle = create_id() - if not family.gramps_id and set_gid: + if not family.gramps_id: family.gramps_id = self.find_next_family_gramps_id() self.commit_family(family, trans) return family.handle @@ -1445,7 +1445,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_citation(self, citation, trans, set_gid=True): if not citation.handle: citation.handle = create_id() - if not citation.gramps_id and set_gid: + if not citation.gramps_id: citation.gramps_id = self.find_next_citation_gramps_id() self.commit_citation(citation, trans) return citation.handle @@ -1453,7 +1453,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_source(self, source, trans, set_gid=True): if not source.handle: source.handle = create_id() - if not source.gramps_id and set_gid: + if not source.gramps_id: source.gramps_id = self.find_next_source_gramps_id() self.commit_source(source, trans) return source.handle @@ -1461,7 +1461,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_repository(self, repository, trans, set_gid=True): if not repository.handle: repository.handle = create_id() - if not repository.gramps_id and set_gid: + if not repository.gramps_id: repository.gramps_id = self.find_next_repository_gramps_id() self.commit_repository(repository, trans) return repository.handle @@ -1469,7 +1469,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_note(self, note, trans, set_gid=True): if not note.handle: note.handle = create_id() - if not note.gramps_id and set_gid: + if not note.gramps_id: note.gramps_id = self.find_next_note_gramps_id() self.commit_note(note, trans) return note.handle @@ -1477,7 +1477,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_place(self, place, trans, set_gid=True): if not place.handle: place.handle = create_id() - if not place.gramps_id and set_gid: + if not place.gramps_id: place.gramps_id = self.find_next_place_gramps_id() self.commit_place(place, trans) return place.handle @@ -1485,7 +1485,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_event(self, event, trans, set_gid=True): if not event.handle: event.handle = create_id() - if not event.gramps_id and set_gid: + if not event.gramps_id: event.gramps_id = self.find_next_event_gramps_id() self.commit_event(event, trans) return event.handle @@ -1493,7 +1493,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_tag(self, tag, trans): if not tag.handle: tag.handle = create_id() - self.commit_event(tag, trans) + self.commit_tag(tag, trans) return tag.handle def add_object(self, obj, transaction, set_gid=True): @@ -1505,7 +1505,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): """ if not obj.handle: obj.handle = create_id() - if not obj.gramps_id and set_gid: + if not obj.gramps_id: obj.gramps_id = self.find_next_object_gramps_id() self.commit_media_object(obj, transaction) return obj.handle @@ -1664,7 +1664,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: self.emit("tag-add", ([tag.handle],)) - def commit_media_object(self, media, transaction, change_time=None): + def commit_media_object(self, media, trans, change_time=None): """ Commit the specified MediaObject to the database, storing the changes as part of the transaction. From 2147f721768feb4fe16d436cdd29042d7d39ddca Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 16 May 2015 00:16:33 -0400 Subject: [PATCH 028/105] Importers: added db.prepare_import/db.commit_import to wrap imports --- gramps/plugins/database/bsddb_support/write.py | 12 ++++++++++++ gramps/plugins/database/dictionarydb.py | 13 +++++++++++++ gramps/plugins/database/djangodb.py | 2 +- gramps/plugins/importer/importcsv.py | 2 ++ gramps/plugins/importer/importgedcom.py | 2 ++ gramps/plugins/importer/importgeneweb.py | 2 ++ gramps/plugins/importer/importgpkg.py | 2 ++ gramps/plugins/importer/importprogen.py | 2 ++ gramps/plugins/importer/importvcard.py | 2 ++ gramps/plugins/importer/importxml.py | 2 ++ gramps/webapp/reports.py | 4 ++-- 11 files changed, 42 insertions(+), 3 deletions(-) diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py index cca39eae7..9630b3ce2 100644 --- a/gramps/plugins/database/bsddb_support/write.py +++ b/gramps/plugins/database/bsddb_support/write.py @@ -2470,6 +2470,18 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): _("Version"): bsddb_version, } + def prepare_import(self): + """ + Initialization before imports + """ + pass + + def commit_import(self): + """ + Post process after imports + """ + pass + def mk_backup_name(database, base): """ Return the backup name of the database table diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 38ad398ac..44e3721fa 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -1810,3 +1810,16 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): def rebuild_secondary(self, update): ## FIXME pass + + def prepare_import(self): + """ + Initialization before imports + """ + pass + + def commit_import(self): + """ + Post process after imports + """ + pass + diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index ca55eb0b1..f7fe47892 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -518,7 +518,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dji.add_tag_detail(obj.serialize()) self.use_import_cache = False self.import_cache = {} - self.dji.update_publics() + self.request_rebuild() def transaction_commit(self, txn): pass diff --git a/gramps/plugins/importer/importcsv.py b/gramps/plugins/importer/importcsv.py index 2b25a588f..c39362425 100644 --- a/gramps/plugins/importer/importcsv.py +++ b/gramps/plugins/importer/importcsv.py @@ -151,8 +151,10 @@ def importData(dbase, filename, user): parser = CSVParser(dbase, user, (config.get('preferences.tag-on-import-format') if config.get('preferences.tag-on-import') else None)) try: + dbase.prepare_import() with OpenFileOrStdin(filename, 'b') as filehandle: parser.parse(filehandle) + dbase.commit_import() except EnvironmentError as err: user.notify_error(_("%s could not be opened\n") % filename, str(err)) return diff --git a/gramps/plugins/importer/importgedcom.py b/gramps/plugins/importer/importgedcom.py index da257a764..72db2d29d 100644 --- a/gramps/plugins/importer/importgedcom.py +++ b/gramps/plugins/importer/importgedcom.py @@ -130,7 +130,9 @@ def importData(database, filename, user): try: read_only = database.readonly database.readonly = False + database.prepare_import() gedparse.parse_gedcom_file(False) + database.commit_import() database.readonly = read_only ifile.close() except IOError as msg: diff --git a/gramps/plugins/importer/importgeneweb.py b/gramps/plugins/importer/importgeneweb.py index 01707577a..b4505efd3 100644 --- a/gramps/plugins/importer/importgeneweb.py +++ b/gramps/plugins/importer/importgeneweb.py @@ -86,7 +86,9 @@ def importData(database, filename, user): return try: + database.prepare_import() status = g.parse_geneweb_file() + database.commit_import() except IOError as msg: errmsg = _("%s could not be opened\n") % filename user.notify_error(errmsg,str(msg)) diff --git a/gramps/plugins/importer/importgpkg.py b/gramps/plugins/importer/importgpkg.py index 3c7f1b4c7..2a164b09d 100644 --- a/gramps/plugins/importer/importgpkg.py +++ b/gramps/plugins/importer/importgpkg.py @@ -93,7 +93,9 @@ def impData(database, name, user): imp_db_name = os.path.join(tmpdir_path, XMLFILE) importer = importData + database.prepare_import() info = importer(database, imp_db_name, user) + database.commit_import() newmediapath = database.get_mediapath() #import of gpkg should not change media path as all media has new paths! diff --git a/gramps/plugins/importer/importprogen.py b/gramps/plugins/importer/importprogen.py index 584a3281e..202f56118 100644 --- a/gramps/plugins/importer/importprogen.py +++ b/gramps/plugins/importer/importprogen.py @@ -74,7 +74,9 @@ def _importData(database, filename, user): return try: + database.prepare_import() status = g.parse_progen_file() + database.commit_import() except ProgenError as msg: user.notify_error(_("Pro-Gen data error"), str(msg)) return diff --git a/gramps/plugins/importer/importvcard.py b/gramps/plugins/importer/importvcard.py index 15cf804e5..4d504931f 100644 --- a/gramps/plugins/importer/importvcard.py +++ b/gramps/plugins/importer/importvcard.py @@ -62,8 +62,10 @@ def importData(database, filename, user): """Function called by Gramps to import data on persons in VCard format.""" parser = VCardParser(database) try: + database.prepare_import() with OpenFileOrStdin(filename) as filehandle: parser.parse(filehandle) + database.commit_import() except EnvironmentError as msg: user.notify_error(_("%s could not be opened\n") % filename, str(msg)) return diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py index 51525dbd6..21ce6a7a0 100644 --- a/gramps/plugins/importer/importxml.py +++ b/gramps/plugins/importer/importxml.py @@ -122,6 +122,7 @@ def importData(database, filename, user): line_cnt = 0 person_cnt = 0 + database.prepare_import() with ImportOpenFileContextManager(filename, user) as xml_file: if xml_file is None: return @@ -162,6 +163,7 @@ def importData(database, filename, user): "valid Gramps database.")) return + database.commit_import() database.readonly = read_only return info diff --git a/gramps/webapp/reports.py b/gramps/webapp/reports.py index c0c7f480e..35cc66356 100644 --- a/gramps/webapp/reports.py +++ b/gramps/webapp/reports.py @@ -85,9 +85,9 @@ def import_file(db, filename, user): print("ERROR:", name, exception) return False import_function = getattr(mod, pdata.import_function) - db.prepare_import() + #db.prepare_import() retval = import_function(db, filename, user) - db.commit_import() + #db.commit_import() return retval return False From bb9403e2b2f4fc97b1a558db8aa38be5d31f73ab Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 16 May 2015 00:46:22 -0400 Subject: [PATCH 029/105] DjangoDb: typo, added logger --- gramps/gen/dbstate.py | 11 +++++++++++ gramps/plugins/database/djangodb.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py index 0d9c30d4a..85722464d 100644 --- a/gramps/gen/dbstate.py +++ b/gramps/gen/dbstate.py @@ -29,6 +29,14 @@ from .proxy.proxybase import ProxyDbBase from .utils.callback import Callback from .config import config +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +LOG = logging.getLogger(".dbstate") + class DbState(Callback): """ Provide a class to encapsulate the state of the database. @@ -152,6 +160,7 @@ class DbState(Callback): ## Work-around for databases that need sys refresh (django): def modules_is_set(self): + LOG.warn("modules_is_set?") if hasattr(self, "_modules"): return self._modules != None else: @@ -159,6 +168,7 @@ class DbState(Callback): return False def reset_modules(self): + LOG.warn("reset_modules!") # First, clear out old modules: for key in list(sys.modules.keys()): del(sys.modules[key]) @@ -167,5 +177,6 @@ class DbState(Callback): sys.modules[key] = self._modules[key] def save_modules(self): + LOG.warn("save_modules!") self._modules = sys.modules.copy() diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index f7fe47892..c28cc9265 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -1670,7 +1670,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): as part of the transaction. """ if self.use_import_cache: - self.import_cache[obj.handle] = media + self.import_cache[media.handle] = media else: raw = media.serialize() items = self.dji.Media.filter(handle=media.handle) From d3bd2b669207da575b91b76e1916aa39d32bae83 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 16 May 2015 01:40:05 -0400 Subject: [PATCH 030/105] DjangoDb: force load when write_version/create to make work with reset modules --- gramps/gen/dbstate.py | 6 +++--- gramps/plugins/database/djangodb.py | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py index 85722464d..9d1ce8071 100644 --- a/gramps/gen/dbstate.py +++ b/gramps/gen/dbstate.py @@ -160,7 +160,7 @@ class DbState(Callback): ## Work-around for databases that need sys refresh (django): def modules_is_set(self): - LOG.warn("modules_is_set?") + LOG.info("modules_is_set?") if hasattr(self, "_modules"): return self._modules != None else: @@ -168,7 +168,7 @@ class DbState(Callback): return False def reset_modules(self): - LOG.warn("reset_modules!") + LOG.info("reset_modules!") # First, clear out old modules: for key in list(sys.modules.keys()): del(sys.modules[key]) @@ -177,6 +177,6 @@ class DbState(Callback): sys.modules[key] = self._modules[key] def save_modules(self): - LOG.warn("save_modules!") + LOG.info("save_modules!") self._modules = sys.modules.copy() diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index c28cc9265..cb8f70238 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -362,6 +362,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): force_bsddb_upgrade=False, force_bsddb_downgrade=False, force_python_upgrade=False): + _LOG.info("Django loading...") self._directory = directory self.full_name = os.path.abspath(self._directory) self.path = self.full_name @@ -380,9 +381,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def __getattr__(self, item): return self.dictionary[item] + _LOG.info("Django loading defaults from: " + directory) try: settings.configure(Module(default_settings)) except RuntimeError: + _LOG.info("Django already configured error! Shouldn't happen!") # already configured; ignore pass @@ -455,6 +458,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.url_types = set() self.media_attributes = set() self.place_types = set() + _LOG.info("Django loading... done!") def prepare_import(self): """ @@ -2108,6 +2112,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): for filename in os.listdir(defaults): fullpath = os.path.abspath(os.path.join(defaults, filename)) shutil.copy2(fullpath, directory) + # force load, to get all modules loaded because of reset issue + self.load(directory) def report_bm_change(self): """ From 276052d231d7bbeae71d008b87c9399539d2cf8b Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 16 May 2015 02:15:02 -0400 Subject: [PATCH 031/105] Added missing function; allow failed plugin message to show --- gramps/gen/db/__init__.py | 21 +++++++++++++++++++++ gramps/gui/viewmanager.py | 7 ++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py index 066571e38..10f751303 100644 --- a/gramps/gen/db/__init__.py +++ b/gramps/gen/db/__init__.py @@ -88,3 +88,24 @@ from .base import * from .dbconst import * from .txn import * from .exceptions import * + +def find_surname_name(key, data): + """ + Creating a surname from raw name, to use for sort and index + returns a byte string + """ + return __index_surname(data[5]) + +def __index_surname(surn_list): + """ + All non pa/matronymic surnames are used in indexing. + pa/matronymic not as they change for every generation! + returns a byte string + """ + from gramps.gen.lib import NameOriginType + if surn_list: + surn = " ".join([x[0] for x in surn_list if not (x[3][0] in [ + NameOriginType.PATRONYMIC, NameOriginType.MATRONYMIC]) ]) + else: + surn = "" + return surn diff --git a/gramps/gui/viewmanager.py b/gramps/gui/viewmanager.py index a296f0122..a595eb803 100644 --- a/gramps/gui/viewmanager.py +++ b/gramps/gui/viewmanager.py @@ -1591,6 +1591,11 @@ def run_plugin(pdata, dbstate, uistate): mod = pmgr.load_plugin(pdata) if not mod: #import of plugin failed + failed = pmgr.get_fail_list() + if failed: + error_msg = failed[-1][1][1] + else: + error_msg = "(no error message)" ErrorDialog( _('Failed Loading Plugin'), _('The plugin %(name)s did not load and reported an error.\n\n' @@ -1605,7 +1610,7 @@ def run_plugin(pdata, dbstate, uistate): 'gramps_bugtracker_url' : URL_BUGHOME, 'firstauthoremail': pdata.authors_email[0] if pdata.authors_email else '...', - 'error_msg': pmgr.get_fail_list()[-1][1][1]}) + 'error_msg': error_msg}) return if pdata.ptype == REPORT: From acc6a652ebbe3ec785a504bceace806904b65573 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 16 May 2015 09:18:41 -0400 Subject: [PATCH 032/105] Removed duplicate methods --- gramps/plugins/database/dictionarydb.py | 8 -------- gramps/plugins/database/djangodb.py | 21 +++++---------------- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 44e3721fa..878afa880 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -695,10 +695,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): def get_name_group_mapping(self, key): return None - def get_researcher(self): - obj = Researcher() - return obj - def get_person_handles(self, sort_handles=False): ## Fixme: implement sort return self.person_map.keys() @@ -1730,10 +1726,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): if os.path.isfile(filename): importData(self, filename, User()) - def prepare_import(self): - ## FIXME - pass - def redo(self, update_history=True): ## FIXME pass diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index cb8f70238..0091d033a 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -96,8 +96,10 @@ class Table(object): """ return self.funcs["cursor_func"]() - def put(key, data, txn=None): - self[key] = data + def put(self, key, data, txn=None): + ## FIXME: probably needs implementing? + #self[key] = data + pass class Map(dict): """ @@ -196,6 +198,7 @@ class DjangoTxn(DbTxn): def put(self, handle, new_data, txn): """ """ + ## FIXME: probably not correct? txn[handle] = new_data def commit(self): @@ -2053,20 +2056,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def get_cursor(self, table, txn=None, update=False, commit=False): pass - def get_from_name_and_handle(self, table_name, handle): - """ - Returns a gen.lib object (or None) given table_name and - handle. - - Examples: - - >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") - >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") - """ - if table_name in self._tables: - return self._tables[table_name]["handle_func"](handle) - return None - def get_number_of_records(self, table): pass From 92f435d45b15be2a758c387b1206d42cc6a91a5f Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sun, 17 May 2015 11:38:31 -0400 Subject: [PATCH 033/105] Diff: fixed import of DictionaryDb; removed mistaken tag.gramps_id references in DictionartDb --- gramps/gen/merge/diff.py | 2 +- gramps/plugins/database/dictionarydb.py | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/gramps/gen/merge/diff.py b/gramps/gen/merge/diff.py index 67539a0bb..a0d4bebad 100644 --- a/gramps/gen/merge/diff.py +++ b/gramps/gen/merge/diff.py @@ -28,7 +28,7 @@ from gramps.cli.user import User from ..dbstate import DbState from gramps.cli.grampscli import CLIManager from ..plug import BasePluginManager -from ..db.dictionary import DictionaryDb +from gramps.plugins.database.dictionarydb import DictionaryDb from gramps.gen.lib.handle import HandleClass, Handle from gramps.gen.lib import * from gramps.gen.lib.personref import PersonRef diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 878afa880..549732b34 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -402,7 +402,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.event_map = Map(Table(self._tables["Event"])) self.event_id_map = {} self.tag_map = Map(Table(self._tables["Tag"])) - self.tag_id_map = {} self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) self.name_group = {} self.undo_callback = None @@ -867,11 +866,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return Note.create(self.note_id_map[gramps_id]) return None - def get_tag_from_gramps_id(self, gramps_id): - if gramps_id in self.tag_id_map: - return Tag.create(self.tag_id_map[gramps_id]) - return None - def get_number_of_people(self): return len(self.person_map) @@ -1238,7 +1232,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: emit = "tag-add" self.tag_map[tag.handle] = tag.serialize() - self.tag_id_map[tag.gramps_id] = self.tag_map[tag.handle] # Emit after added: if emit: self.emit(emit, ([tag.handle],)) @@ -1423,7 +1416,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.tag_map, - self.tag_id_map, TAG_KEY) + None, TAG_KEY) def is_empty(self): """ @@ -1440,7 +1433,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): if handle in data_map: obj = self._tables[KEY_TO_CLASS_MAP[key]]["class_func"].create(data_map[handle]) del data_map[handle] - del data_id_map[obj.gramps_id] + if data_id_map: + del data_id_map[obj.gramps_id] self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) def delete_primary_from_reference_map(self, handle, transaction, txn=None): From a9b7b43ffbe8240fb656c1a12a5be44e20c1f4b7 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sun, 17 May 2015 19:01:21 -0400 Subject: [PATCH 034/105] DictionaryDb: give handle in bytes, handle as str internally in dict --- gramps/plugins/database/dictionarydb.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 549732b34..2eda2460d 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -735,60 +735,80 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return self.tag_map.keys() def get_event_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") event = None if handle in self.event_map: event = Event.create(self.event_map[handle]) return event def get_family_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") family = None if handle in self.family_map: family = Family.create(self.family_map[handle]) return family def get_repository_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") repository = None if handle in self.repository_map: repository = Repository.create(self.repository_map[handle]) return repository def get_person_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") person = None if handle in self.person_map: person = Person.create(self.person_map[handle]) return person def get_place_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") place = None if handle in self.place_map: place = Place.create(self.place_map[handle]) return place def get_citation_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") citation = None if handle in self.citation_map: citation = Citation.create(self.citation_map[handle]) return citation def get_source_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") source = None if handle in self.source_map: source = Source.create(self.source_map[handle]) return source def get_note_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") note = None if handle in self.note_map: note = Note.create(self.note_map[handle]) return note def get_object_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") media = None if handle in self.media_map: media = MediaObject.create(self.media_map[handle]) return media def get_tag_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") tag = None if handle in self.tag_map: tag = Tag.create(self.tag_map[handle]) From e1c33c4d3ec0beb4c4cec66796764c189f6f31a3 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 21 May 2015 10:51:36 -0400 Subject: [PATCH 035/105] Added DbState.open_database() for opening without DbManager --- gramps/gen/dbstate.py | 56 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py index 9d1ce8071..b232d612e 100644 --- a/gramps/gen/dbstate.py +++ b/gramps/gen/dbstate.py @@ -23,6 +23,8 @@ Provide the database state class """ import sys +import os +import io from .db import DbReadBase from .proxy.proxybase import ProxyDbBase @@ -62,9 +64,10 @@ class DbState(Callback): Closes the existing db, and opens a new one. Retained for backward compatibility. """ - self.emit('no-database', ()) - self.db.close() - self.change_database_noclose(database) + if database: + self.emit('no-database', ()) + self.db.close() + self.change_database_noclose(database) def change_database_noclose(self, database): """ @@ -158,6 +161,53 @@ class DbState(Callback): database = getattr(mod, pdata.databaseclass) return database() + def open_database(self, dbname, force_unlock=False, callback=None): + """ + Open a database by name and return the database. + """ + data = self.lookup_family_tree(dbname) + database = None + if data: + dbpath, locked, locked_by, backend = data + if (not locked) or (locked and force_unlock): + database = self.make_database(backend) + database.load(dbpath, callback=callback) + return database + + def lookup_family_tree(self, dbname): + """ + Find a Family Tree given its name, and return properties. + """ + dbdir = os.path.expanduser(config.get('behavior.database-path')) + for dpath in os.listdir(dbdir): + dirpath = os.path.join(dbdir, dpath) + path_name = os.path.join(dirpath, "name.txt") + if os.path.isfile(path_name): + file = io.open(path_name, 'r', encoding='utf8') + name = file.readline().strip() + file.close() + if dbname == name: + locked = False + locked_by = None + backend = None + fname = os.path.join(dirpath, "database.txt") + if os.path.isfile(fname): + ifile = io.open(fname, 'r', encoding='utf8') + backend = ifile.read().strip() + ifile.close() + else: + backend = "bsddb" + try: + fname = os.path.join(dirpath, "lock") + ifile = io.open(fname, 'r', encoding='utf8') + locked_by = ifile.read().strip() + locked = True + ifile.close() + except (OSError, IOError): + pass + return (dirpath, locked, locked_by, backend) + return None + ## Work-around for databases that need sys refresh (django): def modules_is_set(self): LOG.info("modules_is_set?") From 720664818f3c5c427c8e38a1db19d7951f5c8daf Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 01:34:16 -0400 Subject: [PATCH 036/105] WIP: Added DB-API 2.0 interface; needs to load/save details from dbdir; currently using sqlite :memory: database. But could use any DB-API 2.0 compatible layers. --- gramps/plugins/database/dbapi.gpr.py | 31 + gramps/plugins/database/dbapi.py | 2231 +++++++++++++++++++++++ gramps/plugins/database/dictionarydb.py | 2 +- 3 files changed, 2263 insertions(+), 1 deletion(-) create mode 100644 gramps/plugins/database/dbapi.gpr.py create mode 100644 gramps/plugins/database/dbapi.py diff --git a/gramps/plugins/database/dbapi.gpr.py b/gramps/plugins/database/dbapi.gpr.py new file mode 100644 index 000000000..2749e7a50 --- /dev/null +++ b/gramps/plugins/database/dbapi.gpr.py @@ -0,0 +1,31 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com> +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +plg = newplugin() +plg.id = 'dbapi' +plg.name = _("DB-API 2.0") +plg.name_accell = _("DB-_API 2.0") +plg.description = _("DB-API 2.0 Database Backend") +plg.version = '1.0' +plg.gramps_target_version = "4.2" +plg.status = STABLE +plg.fname = 'dbapi.py' +plg.ptype = DATABASE +plg.databaseclass = 'DBAPI' diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py new file mode 100644 index 000000000..b5d61e5dc --- /dev/null +++ b/gramps/plugins/database/dbapi.py @@ -0,0 +1,2231 @@ +#------------------------------------------------------------------------ +# +# Python Modules +# +#------------------------------------------------------------------------ +import pickle +import base64 +import time +import re +import os +import logging + +#------------------------------------------------------------------------ +# +# Gramps Modules +# +#------------------------------------------------------------------------ +import gramps +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext +from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, + KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP) +from gramps.gen.utils.callback import Callback +from gramps.gen.updatecallback import UpdateCallback +from gramps.gen.db.undoredo import DbUndo +from gramps.gen.db.dbconst import * +from gramps.gen.db import (PERSON_KEY, + FAMILY_KEY, + CITATION_KEY, + SOURCE_KEY, + EVENT_KEY, + MEDIA_KEY, + PLACE_KEY, + REPOSITORY_KEY, + NOTE_KEY, + TAG_KEY) + +from gramps.gen.utils.id import create_id +from gramps.gen.lib.researcher import Researcher +from gramps.gen.lib.mediaobj import MediaObject +from gramps.gen.lib.person import Person +from gramps.gen.lib.family import Family +from gramps.gen.lib.src import Source +from gramps.gen.lib.citation import Citation +from gramps.gen.lib.event import Event +from gramps.gen.lib.place import Place +from gramps.gen.lib.repo import Repository +from gramps.gen.lib.note import Note +from gramps.gen.lib.tag import Tag +from gramps.gen.lib.genderstats import GenderStats + +_LOG = logging.getLogger(DBLOGNAME) + +def touch(fname, mode=0o666, dir_fd=None, **kwargs): + ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python + flags = os.O_CREAT | os.O_APPEND + with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f: + os.utime(f.fileno() if os.utime in os.supports_fd else fname, + dir_fd=None if os.supports_fd else dir_fd, **kwargs) + +class Environment(object): + """ + Implements the Environment API. + """ + def __init__(self, db): + self.db = db + + def txn_begin(self): + return DBAPITxn("DBAPIDb Transaction", self.db) + +class Table(object): + """ + Implements Table interface. + """ + def __init__(self, funcs): + self.funcs = funcs + + def cursor(self): + """ + Returns a Cursor for this Table. + """ + return self.funcs["cursor_func"]() + + def put(self, key, data, txn=None): + self.funcs["add_func"](data, txn) + +class Map(object): + """ + Implements the map API for person_map, etc. + + Takes a Table() as argument. + """ + def __init__(self, table, + keys_func="handles_func", + contains_func="has_handle_func", + *args, **kwargs): + super().__init__(*args, **kwargs) + self.table = table + self.keys_func = keys_func + self.contains_func = contains_func + + def keys(self): + return self.table.funcs[self.keys_func]() + + def values(self): + return self.table.funcs["cursor_func"]() + + def __contains__(self, key): + return self.table.funcs[self.contains_func](key) + + def __getitem__(self, key): + if self.table.funcs[self.contains_func](key): + return self.table.funcs["raw_func"](key) + + def __len__(self): + return self.table.funcs["count_func"]() + +class MetaCursor(object): + def __init__(self): + pass + def __enter__(self): + return self + def __iter__(self): + return self.__next__() + def __next__(self): + yield None + def __exit__(self, *args, **kwargs): + pass + def iter(self): + yield None + def first(self): + self._iter = self.__iter__() + return self.next() + def next(self): + try: + return next(self._iter) + except: + return None + def close(self): + pass + +class Cursor(object): + def __init__(self, map): + self.map = map + self._iter = self.__iter__() + def __enter__(self): + return self + def __iter__(self): + for item in self.map.keys(): + yield (bytes(item, "utf-8"), self.map[item]) + def __next__(self): + try: + return self._iter.__next__() + except StopIteration: + return None + def __exit__(self, *args, **kwargs): + pass + def iter(self): + for item in self.map.keys(): + yield (bytes(item, "utf-8"), self.map[item]) + def first(self): + self._iter = self.__iter__() + try: + return next(self._iter) + except: + return + def next(self): + try: + return next(self._iter) + except: + return + def close(self): + pass + +class Bookmarks(object): + def __init__(self): + self.handles = [] + def get(self): + return self.handles + def append(self, handle): + self.handles.append(handle) + +class DBAPITxn(DbTxn): + def __init__(self, message, db, batch=False): + DbTxn.__init__(self, message, db, batch) + + 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 DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): + """ + A Gramps Database Backend. This replicates the grampsdb functions. + """ + __signals__ = dict((obj+'-'+op, signal) + for obj in + ['person', 'family', 'event', 'place', + 'source', 'citation', 'media', 'note', 'repository', 'tag'] + for op, signal in zip( + ['add', 'update', 'delete', 'rebuild'], + [(list,), (list,), (list,), None] + ) + ) + + # 2. Signals for long operations + __signals__.update(('long-op-'+op, signal) for op, signal in zip( + ['start', 'heartbeat', 'end'], + [(object,), None, None] + )) + + # 3. Special signal for change in home person + __signals__['home-person-changed'] = None + + # 4. Signal for change in person group name, parameters are + __signals__['person-groupname-rebuild'] = (str, str) + + __callback_map = {} + + def __init__(self, directory=None): + DbReadBase.__init__(self) + DbWriteBase.__init__(self) + Callback.__init__(self) + self._tables['Person'].update( + { + "handle_func": self.get_person_from_handle, + "gramps_id_func": self.get_person_from_gramps_id, + "class_func": Person, + "cursor_func": self.get_person_cursor, + "handles_func": self.get_person_handles, + "add_func": self.add_person, + "commit_func": self.commit_person, + "iter_func": self.iter_people, + "ids_func": self.get_person_gramps_ids, + "has_handle_func": self.has_handle_for_person, + "has_gramps_id_func": self.has_gramps_id_for_person, + "count": self.get_number_of_people, + "raw_func": self._get_raw_person_data, + }) + self._tables['Family'].update( + { + "handle_func": self.get_family_from_handle, + "gramps_id_func": self.get_family_from_gramps_id, + "class_func": Family, + "cursor_func": self.get_family_cursor, + "handles_func": self.get_family_handles, + "add_func": self.add_family, + "commit_func": self.commit_family, + "iter_func": self.iter_families, + "ids_func": self.get_family_gramps_ids, + "has_handle_func": self.has_handle_for_family, + "has_gramps_id_func": self.has_gramps_id_for_family, + "count": self.get_number_of_families, + "raw_func": self._get_raw_family_data, + }) + self._tables['Source'].update( + { + "handle_func": self.get_source_from_handle, + "gramps_id_func": self.get_source_from_gramps_id, + "class_func": Source, + "cursor_func": self.get_source_cursor, + "handles_func": self.get_source_handles, + "add_func": self.add_source, + "commit_func": self.commit_source, + "iter_func": self.iter_sources, + "ids_func": self.get_source_gramps_ids, + "has_handle_func": self.has_handle_for_source, + "has_gramps_id_func": self.has_gramps_id_for_source, + "count": self.get_number_of_sources, + "raw_func": self._get_raw_source_data, + }) + self._tables['Citation'].update( + { + "handle_func": self.get_citation_from_handle, + "gramps_id_func": self.get_citation_from_gramps_id, + "class_func": Citation, + "cursor_func": self.get_citation_cursor, + "handles_func": self.get_citation_handles, + "add_func": self.add_citation, + "commit_func": self.commit_citation, + "iter_func": self.iter_citations, + "ids_func": self.get_citation_gramps_ids, + "has_handle_func": self.has_handle_for_citation, + "has_gramps_id_func": self.has_gramps_id_for_citation, + "count": self.get_number_of_citations, + "raw_func": self._get_raw_citation_data, + }) + self._tables['Event'].update( + { + "handle_func": self.get_event_from_handle, + "gramps_id_func": self.get_event_from_gramps_id, + "class_func": Event, + "cursor_func": self.get_event_cursor, + "handles_func": self.get_event_handles, + "add_func": self.add_event, + "commit_func": self.commit_event, + "iter_func": self.iter_events, + "ids_func": self.get_event_gramps_ids, + "has_handle_func": self.has_handle_for_event, + "has_gramps_id_func": self.has_gramps_id_for_event, + "count": self.get_number_of_events, + "raw_func": self._get_raw_event_data, + }) + self._tables['Media'].update( + { + "handle_func": self.get_object_from_handle, + "gramps_id_func": self.get_object_from_gramps_id, + "class_func": MediaObject, + "cursor_func": self.get_media_cursor, + "handles_func": self.get_media_object_handles, + "add_func": self.add_object, + "commit_func": self.commit_media_object, + "iter_func": self.iter_media_objects, + "ids_func": self.get_media_gramps_ids, + "has_handle_func": self.has_handle_for_media, + "has_gramps_id_func": self.has_gramps_id_for_media, + "count": self.get_number_of_media_objects, + "raw_func": self._get_raw_media_data, + }) + self._tables['Place'].update( + { + "handle_func": self.get_place_from_handle, + "gramps_id_func": self.get_place_from_gramps_id, + "class_func": Place, + "cursor_func": self.get_place_cursor, + "handles_func": self.get_place_handles, + "add_func": self.add_place, + "commit_func": self.commit_place, + "iter_func": self.iter_places, + "ids_func": self.get_place_gramps_ids, + "has_handle_func": self.has_handle_for_place, + "has_gramps_id_func": self.has_gramps_id_for_place, + "count": self.get_number_of_places, + "raw_func": self._get_raw_place_data, + }) + self._tables['Repository'].update( + { + "handle_func": self.get_repository_from_handle, + "gramps_id_func": self.get_repository_from_gramps_id, + "class_func": Repository, + "cursor_func": self.get_repository_cursor, + "handles_func": self.get_repository_handles, + "add_func": self.add_repository, + "commit_func": self.commit_repository, + "iter_func": self.iter_repositories, + "ids_func": self.get_repository_gramps_ids, + "has_handle_func": self.has_handle_for_repository, + "has_gramps_id_func": self.has_gramps_id_for_repository, + "count": self.get_number_of_repositories, + "raw_func": self._get_raw_repository_data, + }) + self._tables['Note'].update( + { + "handle_func": self.get_note_from_handle, + "gramps_id_func": self.get_note_from_gramps_id, + "class_func": Note, + "cursor_func": self.get_note_cursor, + "handles_func": self.get_note_handles, + "add_func": self.add_note, + "commit_func": self.commit_note, + "iter_func": self.iter_notes, + "ids_func": self.get_note_gramps_ids, + "has_handle_func": self.has_handle_for_note, + "has_gramps_id_func": self.has_gramps_id_for_note, + "count": self.get_number_of_notes, + "raw_func": self._get_raw_note_data, + }) + self._tables['Tag'].update( + { + "handle_func": self.get_tag_from_handle, + "gramps_id_func": None, + "class_func": Tag, + "cursor_func": self.get_tag_cursor, + "handles_func": self.get_tag_handles, + "add_func": self.add_tag, + "commit_func": self.commit_tag, + "iter_func": self.iter_tags, + "count": self.get_number_of_tags, + }) + # skip GEDCOM cross-ref check for now: + self.set_feature("skip-check-xref", True) + self.set_feature("skip-import-additions", 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 = DBAPITxn("ID Transaction", self) + self.fid_trans = DBAPITxn("FID Transaction", self) + self.pid_trans = DBAPITxn("PID Transaction", self) + self.cid_trans = DBAPITxn("CID Transaction", self) + self.sid_trans = DBAPITxn("SID Transaction", self) + self.oid_trans = DBAPITxn("OID Transaction", self) + self.rid_trans = DBAPITxn("RID Transaction", self) + self.nid_trans = DBAPITxn("NID Transaction", self) + self.eid_trans = DBAPITxn("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 = Environment(self) + self.person_map = Map(Table(self._tables["Person"])) + self.person_id_map = Map(Table(self._tables["Person"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.family_map = Map(Table(self._tables["Family"])) + self.family_id_map = Map(Table(self._tables["Family"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.place_map = Map(Table(self._tables["Place"])) + self.place_id_map = Map(Table(self._tables["Place"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.citation_map = Map(Table(self._tables["Citation"])) + self.citation_id_map = Map(Table(self._tables["Citation"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.source_map = Map(Table(self._tables["Source"])) + self.source_id_map = Map(Table(self._tables["Source"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.repository_map = Map(Table(self._tables["Repository"])) + self.repository_id_map = Map(Table(self._tables["Repository"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.note_map = Map(Table(self._tables["Note"])) + self.note_id_map = Map(Table(self._tables["Note"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.media_map = Map(Table(self._tables["Media"])) + self.media_id_map = Map(Table(self._tables["Media"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.event_map = Map(Table(self._tables["Event"])) + self.event_id_map = Map(Table(self._tables["Event"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.tag_map = Map(Table(self._tables["Tag"])) + self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) + self.name_group = {} + self.undo_callback = None + self.redo_callback = None + self.undo_history_callback = None + self.modified = 0 + self.txn = DBAPITxn("DBAPI Transaction", self) + self.transaction = None + self.undodb = DbUndo(self) + self.abort_possible = False + self._bm_changes = 0 + self._directory = directory + self.full_name = None + self.path = None + self.brief_name = None + self.genderStats = GenderStats() # can pass in loaded stats as dict + self.owner = Researcher() + if directory: + self.load(directory) + + def version_supported(self): + """Return True when the file has a supported version.""" + return True + + def get_table_names(self): + """Return a list of valid table names.""" + return list(self._tables.keys()) + + def get_table_metadata(self, table_name): + """Return the metadata for a valid table name.""" + if table_name in self._tables: + return self._tables[table_name] + return None + + def transaction_commit(self, txn): + ## FIXME + pass + + def get_undodb(self): + ## FIXME + return None + + def transaction_abort(self, txn): + ## FIXME + pass + + @staticmethod + def _validated_id_prefix(val, default): + if isinstance(val, str) 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 = int(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_<object>_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_person_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from person;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_family_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from family;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_event_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from event;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_citation_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from citation;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_source_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from source;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_place_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from place;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_repository_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from repository;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_media_object_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from media;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_note_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from note;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_tag_handles(self, sort_handles=False): + # FIXME: implement sort + cur = self.dbapi.execute("select handle from tag;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_event_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + event = None + if handle in self.event_map: + event = Event.create(self._get_raw_event_data(handle)) + return event + + def get_family_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + family = None + if handle in self.family_map: + family = Family.create(self._get_raw_family_data(handle)) + return family + + def get_repository_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + repository = None + if handle in self.repository_map: + repository = Repository.create(self._get_raw_repository_data(handle)) + return repository + + def get_person_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + person = None + if handle in self.person_map: + person = Person.create(self._get_raw_person_data(handle)) + return person + + def get_place_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + place = None + if handle in self.place_map: + place = Place.create(self._get_raw_place_data(handle)) + return place + + def get_citation_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + citation = None + if handle in self.citation_map: + citation = Citation.create(self._get_raw_citation_data(handle)) + return citation + + def get_source_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + source = None + if handle in self.source_map: + source = Source.create(self._get_raw_source_data(handle)) + return source + + def get_note_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + note = None + if handle in self.note_map: + note = Note.create(self._get_raw_note_data(handle)) + return note + + def get_object_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + media = None + if handle in self.media_map: + media = MediaObject.create(self._get_raw_media_data(handle)) + return media + + def get_tag_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + tag = None + if handle in self.tag_map: + tag = Tag.create(self._get_raw_tag_data(handle)) + return tag + + def get_default_person(self): + handle = self.get_default_handle() + if handle: + return self.get_person_from_handle(handle) + else: + return None + + def iter_people(self): + return (Person.create(data[1]) for data in self.get_person_cursor()) + + def iter_person_handles(self): + return (data[0] for data in self.get_person_cursor()) + + def iter_families(self): + return (Family.create(data[1]) for data in self.get_family_cursor()) + + def iter_family_handles(self): + return (handle for handle in self.family_map.keys()) + + def get_tag_from_name(self, name): + ## Slow, but typically not too many tags: + for data in self.tag_map.values(): + tag = Tag.create(data) + if tag.name == name: + return tag + return None + + def get_person_from_gramps_id(self, gramps_id): + if gramps_id in self.person_id_map: + return Person.create(self.person_id_map[gramps_id]) + return None + + def get_family_from_gramps_id(self, gramps_id): + if gramps_id in self.family_id_map: + return Family.create(self.family_id_map[gramps_id]) + return None + + def get_citation_from_gramps_id(self, gramps_id): + if gramps_id in self.citation_id_map: + return Citation.create(self.citation_id_map[gramps_id]) + return None + + def get_source_from_gramps_id(self, gramps_id): + if gramps_id in self.source_id_map: + return Source.create(self.source_id_map[gramps_id]) + return None + + def get_event_from_gramps_id(self, gramps_id): + if gramps_id in self.event_id_map: + return Event.create(self.event_id_map[gramps_id]) + return None + + def get_media_from_gramps_id(self, gramps_id): + if gramps_id in self.media_id_map: + return MediaObject.create(self.media_id_map[gramps_id]) + return None + + def get_place_from_gramps_id(self, gramps_id): + if gramps_id in self.place_id_map: + return Place.create(self.place_id_map[gramps_id]) + return None + + def get_repository_from_gramps_id(self, gramps_id): + if gramps_id in self.repository_id_map: + return Repository.create(self.repository_id_map[gramps_id]) + return None + + def get_note_from_gramps_id(self, gramps_id): + if gramps_id in self.note_id_map: + return Note.create(self.note_id_map[gramps_id]) + return None + + def get_number_of_people(self): + cur = self.dbapi.execute("select count(handle) from person;") + row = cur.fetchone() + return row[0] + + def get_number_of_events(self): + cur = self.dbapi.execute("select count(handle) from event;") + row = cur.fetchone() + return row[0] + + def get_number_of_places(self): + cur = self.dbapi.execute("select count(handle) from place;") + row = cur.fetchone() + return row[0] + + def get_number_of_tags(self): + cur = self.dbapi.execute("select count(handle) from tag;") + row = cur.fetchone() + return row[0] + + def get_number_of_families(self): + cur = self.dbapi.execute("select count(handle) from family;") + row = cur.fetchone() + return row[0] + + def get_number_of_notes(self): + cur = self.dbapi.execute("select count(handle) from note;") + row = cur.fetchone() + return row[0] + + def get_number_of_citations(self): + cur = self.dbapi.execute("select count(handle) from citation;") + row = cur.fetchone() + return row[0] + + def get_number_of_sources(self): + cur = self.dbapi.execute("select count(handle) from source;") + row = cur.fetchone() + return row[0] + + def get_number_of_media_objects(self): + cur = self.dbapi.execute("select count(handle) from media;") + row = cur.fetchone() + return row[0] + + def get_number_of_repositories(self): + cur = self.dbapi.execute("select count(handle) from repository;") + row = cur.fetchone() + return row[0] + + def get_place_cursor(self): + return Cursor(self.place_map) + + def get_person_cursor(self): + return Cursor(self.person_map) + + def get_family_cursor(self): + return Cursor(self.family_map) + + def get_event_cursor(self): + return Cursor(self.event_map) + + def get_note_cursor(self): + return Cursor(self.note_map) + + def get_tag_cursor(self): + return Cursor(self.tag_map) + + def get_repository_cursor(self): + return Cursor(self.repository_map) + + def get_media_cursor(self): + return Cursor(self.media_map) + + def get_citation_cursor(self): + return Cursor(self.citation_map) + + def get_source_cursor(self): + return Cursor(self.source_map) + + def has_gramps_id(self, obj_key, gramps_id): + key2table = { + PERSON_KEY: self.person_id_map, + FAMILY_KEY: self.family_id_map, + SOURCE_KEY: self.source_id_map, + CITATION_KEY: self.citation_id_map, + EVENT_KEY: self.event_id_map, + MEDIA_KEY: self.media_id_map, + PLACE_KEY: self.place_id_map, + REPOSITORY_KEY: self.repository_id_map, + NOTE_KEY: self.note_id_map, + } + return gramps_id in key2table[obj_key] + + 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): + ## FIXME + pass + + def set_mediapath(self, mediapath): + ## FIXME + pass + + def get_raw_person_data(self, handle): + if handle in self.person_map: + return self.person_map[handle] + return None + + def get_raw_family_data(self, handle): + if handle in self.family_map: + return self.family_map[handle] + return None + + def get_raw_citation_data(self, handle): + if handle in self.citation_map: + return self.citation_map[handle] + return None + + def get_raw_source_data(self, handle): + if handle in self.source_map: + return self.source_map[handle] + return None + + def get_raw_repository_data(self, handle): + if handle in self.repository_map: + return self.repository_map[handle] + return None + + def get_raw_note_data(self, handle): + if handle in self.note_map: + return self.note_map[handle] + return None + + def get_raw_place_data(self, handle): + if handle in self.place_map: + return self.place_map[handle] + return None + + def get_raw_object_data(self, handle): + if handle in self.media_map: + return self.media_map[handle] + return None + + def get_raw_tag_data(self, handle): + if handle in self.tag_map: + return self.tag_map[handle] + return None + + def get_raw_event_data(self, handle): + if handle in self.event_map: + return self.event_map[handle] + return None + + def add_person(self, person, trans, set_gid=True): + if not person.handle: + person.handle = create_id() + if not person.gramps_id and 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 = create_id() + if not family.gramps_id and 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 = create_id() + if not citation.gramps_id and 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 = create_id() + if not source.gramps_id and 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 = create_id() + if not repository.gramps_id and 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 = create_id() + if not note.gramps_id and 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 = create_id() + if not place.gramps_id and 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 = create_id() + if not event.gramps_id and 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 = create_id() + self.commit_tag(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 = create_id() + if not obj.gramps_id and 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): + emit = None + if True or not trans.batch: + if person.handle in self.person_map: + emit = "person-update" + self.dbapi.execute("""UPDATE person SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [person.gramps_id, + pickle.dumps(person.serialize()), + person.handle]) + else: + emit = "person-add" + self.dbapi.execute("""insert into person(handle, gramps_id, blob) + values(?, ?, ?);""", + [person.handle, person.gramps_id, pickle.dumps(person.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([person.handle],)) + + def commit_family(self, family, trans, change_time=None): + emit = None + if True or not trans.batch: + if family.handle in self.family_map: + emit = "family-update" + self.dbapi.execute("""UPDATE family SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [family.gramps_id, + pickle.dumps(family.serialize()), + family.handle]) + else: + emit = "family-add" + self.dbapi.execute("insert into family values(?, ?, ?);", + [family.handle, family.gramps_id, + pickle.dumps(family.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([family.handle],)) + + def commit_citation(self, citation, trans, change_time=None): + emit = None + if True or not trans.batch: + if citation.handle in self.citation_map: + emit = "citation-update" + self.dbapi.execute("""UPDATE citation SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [citation.gramps_id, + pickle.dumps(citation.serialize()), + citation.handle]) + else: + emit = "citation-add" + self.dbapi.execute("insert into citation values(?, ?, ?);", + [citation.handle, citation.gramps_id, pickle.dumps(citation.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([citation.handle],)) + + def commit_source(self, source, trans, change_time=None): + emit = None + if True or not trans.batch: + if source.handle in self.source_map: + emit = "source-update" + self.dbapi.execute("""UPDATE source SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [source.gramps_id, + pickle.dumps(source.serialize()), + source.handle]) + else: + emit = "source-add" + self.dbapi.execute("insert into source values(?, ?, ?);", + [source.handle, source.gramps_id, pickle.dumps(source.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([source.handle],)) + + def commit_repository(self, repository, trans, change_time=None): + emit = None + if True or not trans.batch: + if repository.handle in self.repository_map: + emit = "repository-update" + self.dbapi.execute("""UPDATE repository SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [repository.gramps_id, + pickle.dumps(repository.serialize()), + repository.handle]) + else: + emit = "repository-add" + self.dbapi.execute("insert into repository values(?, ?, ?);", + [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([repository.handle],)) + + def commit_note(self, note, trans, change_time=None): + emit = None + if True or not trans.batch: + if note.handle in self.note_map: + emit = "note-update" + self.dbapi.execute("""UPDATE note SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [note.gramps_id, + pickle.dumps(note.serialize()), + note.handle]) + else: + emit = "note-add" + self.dbapi.execute("insert into note values(?, ?, ?);", + [note.handle, note.gramps_id, pickle.dumps(note.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([note.handle],)) + + def commit_place(self, place, trans, change_time=None): + emit = None + if True or not trans.batch: + if place.handle in self.place_map: + emit = "place-update" + self.dbapi.execute("""UPDATE place SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [place.gramps_id, + pickle.dumps(place.serialize()), + place.handle]) + else: + emit = "place-add" + self.dbapi.execute("insert into place values(?, ?, ?);", + [place.handle, place.gramps_id, pickle.dumps(place.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([place.handle],)) + + def commit_event(self, event, trans, change_time=None): + emit = None + if True or not trans.batch: + if event.handle in self.event_map: + emit = "event-update" + self.dbapi.execute("""UPDATE event SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [event.gramps_id, + pickle.dumps(event.serialize()), + event.handle]) + else: + emit = "event-add" + self.dbapi.execute("insert into event values(?, ?, ?);", + [event.handle, event.gramps_id, pickle.dumps(event.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([event.handle],)) + + def commit_tag(self, tag, trans, change_time=None): + emit = None + if True or not trans.batch: + if tag.handle in self.tag_map: + emit = "tag-update" + self.dbapi.execute("""UPDATE tag SET blob = ? + WHERE handle = ?;""", + [pickle.dumps(tag.serialize()), + tag.handle]) + else: + emit = "tag-add" + self.dbapi.execute("insert into tag values(?, ?);", + [tag.handle, pickle.dumps(tag.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([tag.handle],)) + + def commit_media_object(self, media, trans, change_time=None): + emit = None + if True or not trans.batch: + if media.handle in self.media_map: + emit = "media-update" + self.dbapi.execute("""UPDATE media SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [media.gramps_id, + pickle.dumps(media.serialize()), + media.handle]) + else: + emit = "media-add" + self.dbapi.execute("insert into media values(?, ?, ?);", + [media.handle, media.gramps_id, pickle.dumps(media.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([media.handle],)) + + def get_gramps_ids(self, obj_key): + key2table = { + PERSON_KEY: self.person_id_map, + FAMILY_KEY: self.family_id_map, + CITATION_KEY: self.citation_id_map, + SOURCE_KEY: self.source_id_map, + EVENT_KEY: self.event_id_map, + MEDIA_KEY: self.media_id_map, + PLACE_KEY: self.place_id_map, + REPOSITORY_KEY: self.repository_id_map, + NOTE_KEY: self.note_id_map, + } + return list(key2table[obj_key].keys()) + + def transaction_begin(self, transaction): + ## FIXME + return + + def set_researcher(self, owner): + self.owner.set_from(owner) + + def get_researcher(self): + return self.owner + + def request_rebuild(self): + self.emit('person-rebuild') + self.emit('family-rebuild') + self.emit('place-rebuild') + self.emit('source-rebuild') + self.emit('citation-rebuild') + self.emit('media-rebuild') + self.emit('event-rebuild') + self.emit('repository-rebuild') + self.emit('note-rebuild') + self.emit('tag-rebuild') + + def copy_from_db(self, db): + """ + A (possibily) implementation-specific method to get data from + db into this database. + """ + for key in db._tables.keys(): + cursor = db._tables[key]["cursor_func"] + class_ = db._tables[key]["class_func"] + for (handle, data) in cursor(): + map = getattr(self, "%s_map" % key.lower()) + map[handle] = class_.create(data) + + def get_transaction_class(self): + """ + Get the transaction class associated with this database backend. + """ + return DBAPITxn + + def get_from_name_and_handle(self, table_name, handle): + """ + Returns a gen.lib object (or None) given table_name and + handle. + + Examples: + + >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") + >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") + """ + if table_name in self._tables: + return self._tables[table_name]["handle_func"](handle) + return None + + def get_from_name_and_gramps_id(self, table_name, gramps_id): + """ + Returns a gen.lib object (or None) given table_name and + Gramps ID. + + Examples: + + >>> self.get_from_name_and_gramps_id("Person", "I00002") + >>> self.get_from_name_and_gramps_id("Family", "F056") + >>> self.get_from_name_and_gramps_id("Media", "M00012") + """ + if table_name in self._tables: + return self._tables[table_name]["gramps_id_func"](gramps_id) + return None + + def remove_person(self, handle, transaction): + """ + Remove the Person specified by the database handle from the database, + preserving the change in the passed transaction. + """ + + if self.readonly or not handle: + return + if handle in self.person_map: + person = Person.create(self.person_map[handle]) + #del self.person_map[handle] + #del self.person_id_map[person.gramps_id] + self.dbapi.execute("DELETE from person WHERE handle = ?;", [handle]) + self.emit("person-delete", ([handle],)) + + def remove_source(self, handle, transaction): + """ + Remove the Source specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.source_map, + self.source_id_map, SOURCE_KEY) + + def remove_citation(self, handle, transaction): + """ + Remove the Citation specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.citation_map, + self.citation_id_map, CITATION_KEY) + + def remove_event(self, handle, transaction): + """ + Remove the Event specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.event_map, + self.event_id_map, EVENT_KEY) + + def remove_object(self, handle, transaction): + """ + Remove the MediaObjectPerson specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.media_map, + self.media_id_map, MEDIA_KEY) + + def remove_place(self, handle, transaction): + """ + Remove the Place specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.place_map, + self.place_id_map, PLACE_KEY) + + def remove_family(self, handle, transaction): + """ + Remove the Family specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.family_map, + self.family_id_map, FAMILY_KEY) + + def remove_repository(self, handle, transaction): + """ + Remove the Repository specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.repository_map, + self.repository_id_map, REPOSITORY_KEY) + + def remove_note(self, handle, transaction): + """ + Remove the Note specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.note_map, + self.note_id_map, NOTE_KEY) + + def remove_tag(self, handle, transaction): + """ + Remove the Tag specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.tag_map, + None, TAG_KEY) + + def is_empty(self): + """ + Return true if there are no [primary] records in the database + """ + for table in self._tables: + if len(self._tables[table]["handles_func"]()) > 0: + return False + return True + + def __do_remove(self, handle, transaction, data_map, data_id_map, key): + key2table = { + PERSON_KEY: "person", + FAMILY_KEY: "family", + SOURCE_KEY: "source", + CITATION_KEY: "citation", + EVENT_KEY: "event", + MEDIA_KEY: "media", + PLACE_KEY: "place", + REPOSITORY_KEY: "repository", + NOTE_KEY: "note", + } + if self.readonly or not handle: + return + if handle in data_map: + self.dbapi.execute("DELETE from %s WHERE handle = ?;" % key2table[key], + [handle]) + self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) + + def delete_primary_from_reference_map(self, handle, transaction, txn=None): + """ + Remove all references to the primary object from the reference_map. + handle should be utf-8 + """ + 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.decode('utf-8'), 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 __remove_reference(self, key, transaction, txn): + """ + Remove the reference specified by the key, preserving the change in + the passed transaction. + """ + if isinstance(key, tuple): + #create a byte string key, first validity check in python 3! + for val in key: + if isinstance(val, bytes): + raise DbError(_('An attempt is made to save a reference key ' + 'which is partly bytecode, this is not allowed.\n' + 'Key is %s') % str(key)) + key = str(key) + if isinstance(key, str): + key = key.encode('utf-8') + if not self.readonly: + if not transaction.batch: + old_data = self.reference_map.get(key, txn=txn) + transaction.add(REFERENCE_KEY, TXNDEL, key, old_data, None) + #transaction.reference_del.append(str(key)) + self.reference_map.delete(key, txn=txn) + + ## Missing: + + def backup(self): + ## FIXME + pass + + def close(self): + if self._directory: + from gramps.plugins.export.exportxml import XmlWriter + from gramps.cli.user import User + writer = XmlWriter(self, User(), strip_photos=0, compress=1) + filename = os.path.join(self._directory, "data.gramps") + writer.write(filename) + filename = os.path.join(self._directory, "meta_data.db") + touch(filename) + + def find_backlink_handles(self, handle, include_classes=None): + ## FIXME + return [] + + def find_initial_person(self): + items = self.person_map.keys() + if len(items) > 0: + return self.get_person_from_handle(list(items)[0]) + return None + + def find_place_child_handles(self, handle): + ## FIXME + return [] + + def get_bookmarks(self): + return self.bookmarks + + def get_child_reference_types(self): + ## FIXME + return [] + + def get_citation_bookmarks(self): + return self.citation_bookmarks + + def get_cursor(self, table, txn=None, update=False, commit=False): + ## FIXME + ## called from a complete find_back_ref + pass + + # 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_default_handle(self): + items = self.person_map.keys() + if len(items) > 0: + return list(items)[0] + return None + + def get_event_attribute_types(self): + ## FIXME + return [] + + def get_event_bookmarks(self): + return self.event_bookmarks + + def get_event_roles(self): + ## FIXME + return [] + + def get_event_types(self): + ## FIXME + return [] + + def get_family_attribute_types(self): + ## FIXME + return [] + + def get_family_bookmarks(self): + return self.family_bookmarks + + def get_family_event_types(self): + ## FIXME + return [] + + def get_family_relation_types(self): + ## FIXME + return [] + + def get_media_attribute_types(self): + ## FIXME + return [] + + def get_media_bookmarks(self): + return self.media_bookmarks + + def get_name_types(self): + ## FIXME + return [] + + def get_note_bookmarks(self): + return self.note_bookmarks + + def get_note_types(self): + ## FIXME + return [] + + def get_origin_types(self): + ## FIXME + return [] + + def get_person_attribute_types(self): + ## FIXME + return [] + + def get_person_event_types(self): + ## FIXME + return [] + + def get_place_bookmarks(self): + return self.place_bookmarks + + def get_place_tree_cursor(self): + ## FIXME + return [] + + def get_place_types(self): + ## FIXME + return [] + + def get_repo_bookmarks(self): + return self.repo_bookmarks + + def get_repository_types(self): + ## FIXME + return [] + + def get_save_path(self): + return self._directory + + def get_source_attribute_types(self): + ## FIXME + return [] + + def get_source_bookmarks(self): + return self.source_bookmarks + + def get_source_media_types(self): + ## FIXME + return [] + + def get_surname_list(self): + ## FIXME + return [] + + def get_url_types(self): + ## FIXME + return [] + + def has_changed(self): + ## FIXME + return True + + def is_open(self): + return self._directory is not None + + def iter_citation_handles(self): + return (data[0] for data in self.get_citation_cursor()) + + def iter_citations(self): + return (Citation.create(data[1]) for data in self.get_citation_cursor()) + + def iter_event_handles(self): + return (data[0] for data in self.get_event_cursor()) + + def iter_events(self): + return (Event.create(data[1]) for data in self.get_event_cursor()) + + def iter_media_objects(self): + return (MediaObject.create(data[1]) for data in self.get_media_cursor()) + + def iter_note_handles(self): + return (data[0] for data in self.get_note_cursor()) + + def iter_notes(self): + return (Note.create(data[1]) for data in self.get_note_cursor()) + + def iter_place_handles(self): + return (data[0] for data in self.get_place_cursor()) + + def iter_places(self): + return (Place.create(data[1]) for data in self.get_place_cursor()) + + def iter_repositories(self): + return (Repository.create(data[1]) for data in self.get_repository_cursor()) + + def iter_repository_handles(self): + return (data[0] for data in self.get_repository_cursor()) + + def iter_source_handles(self): + return (data[0] for data in self.get_source_cursor()) + + def iter_sources(self): + return (Source.create(data[1]) for data in self.get_source_cursor()) + + def iter_tag_handles(self): + return (data[0] for data in self.get_tag_cursor()) + + def iter_tags(self): + return (Tag.create(data[1]) for data in self.get_tag_cursor()) + + def load(self, directory, pulse_progress=None, mode=None, + force_schema_upgrade=False, + force_bsddb_upgrade=False, + force_bsddb_downgrade=False, + force_python_upgrade=False): + # Run code from directory + import sqlite3 + self.dbapi = sqlite3.connect(':memory:') + self.dbapi.row_factory = sqlite3.Row # allows access by name + # make sure schema is up to date: + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS person ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS family ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS source ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS citation ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS event ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS media ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS place ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS repository ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS note ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS tag ( + handle TEXT PRIMARY KEY NOT NULL, + blob TEXT + );""") + + def redo(self, update_history=True): + ## FIXME + pass + + def restore(self): + ## FIXME + pass + + def set_prefixes(self, person, media, family, source, citation, + place, event, repository, note): + ## FIXME + pass + + def set_save_path(self, directory): + self._directory = directory + self.full_name = os.path.abspath(self._directory) + self.path = self.full_name + self.brief_name = os.path.basename(self._directory) + + def undo(self, update_history=True): + ## FIXME + pass + + def write_version(self, directory): + """Write files for a newly created DB.""" + versionpath = os.path.join(directory, str(DBBACKEND)) + _LOG.debug("Write database backend file to 'dbapi'") + with open(versionpath, "w") as version_file: + version_file.write("dbapi") + + def report_bm_change(self): + """ + Add 1 to the number of bookmark changes during this session. + """ + self._bm_changes += 1 + + def db_has_bm_changes(self): + """ + Return whethere there were bookmark changes during the session. + """ + return self._bm_changes > 0 + + def get_summary(self): + """ + Returns dictionary of summary item. + Should include, if possible: + + _("Number of people") + _("Version") + _("Schema version") + """ + return { + _("Number of people"): self.get_number_of_people(), + } + + def get_dbname(self): + """ + In DBAPI, the database is in a text file at the path + """ + filepath = os.path.join(self._directory, "name.txt") + try: + name_file = open(filepath, "r") + name = name_file.readline().strip() + name_file.close() + except (OSError, IOError) as msg: + _LOG.error(str(msg)) + name = None + return name + + def reindex_reference_map(self): + ## FIXME + pass + + def rebuild_secondary(self, update): + ## FIXME + pass + + def prepare_import(self): + """ + DBAPI does not commit data on gedcom import, but saves them + for later commit. + """ + pass + + def commit_import(self): + """ + Commits the items that were queued up during the last gedcom + import for two step adding. + """ + pass + + def has_handle_for_person(self, key): + cur = self.dbapi.execute("select * from person where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_family(self, key): + cur = self.dbapi.execute("select * from family where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_source(self, key): + cur = self.dbapi.execute("select * from source where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_citation(self, key): + cur = self.dbapi.execute("select * from citation where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_event(self, key): + cur = self.dbapi.execute("select * from event where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_media(self, key): + cur = self.dbapi.execute("select * from media where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_place(self, key): + cur = self.dbapi.execute("select * from place where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_repository(self, key): + cur = self.dbapi.execute("select * from repository where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_note(self, key): + cur = self.dbapi.execute("select * from note where handle = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_person(self, key): + cur = self.dbapi.execute("select * from person where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_family(self, key): + cur = self.dbapi.execute("select * from family where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_source(self, key): + cur = self.dbapi.execute("select * from source where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_citation(self, key): + cur = self.dbapi.execute("select * from citation where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_event(self, key): + cur = self.dbapi.execute("select * from event where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_media(self, key): + cur = self.dbapi.execute("select * from media where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_place(self, key): + cur = self.dbapi.execute("select * from place where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_repository(self, key): + cur = self.dbapi.execute("select * from repository where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_note(self, key): + cur = self.dbapi.execute("select * from note where gramps_id = ?", [key]) + return cur.fetchone() != None + + def get_person_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from person;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_family_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from family;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_source_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from source;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_citation_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from citation;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_event_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from event;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_media_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from media;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_place_gramps_ids(self): + cur = self.dbapi.execute("select gramps from place;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_repository_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from repository;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_note_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from note;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def _get_raw_person_data(self, key): + cur = self.dbapi.execute("select blob from person where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_family_data(self, key): + cur = self.dbapi.execute("select blob from family where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_source_data(self, key): + cur = self.dbapi.execute("select blob from source where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_citation_data(self, key): + cur = self.dbapi.execute("select blob from citation where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_event_data(self, key): + cur = self.dbapi.execute("select blob from event where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_media_data(self, key): + cur = self.dbapi.execute("select blob from media where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_place_data(self, key): + cur = self.dbapi.execute("select blob from place where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_repository_data(self, key): + cur = self.dbapi.execute("select blob from repository where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_note_data(self, key): + cur = self.dbapi.execute("select blob from note where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_tag_data(self, key): + cur = self.dbapi.execute("select blob from tag where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 2eda2460d..8b8d8beb7 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -1690,7 +1690,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return (key for key in self.event_map.keys()) def iter_events(self): - return (Events.create(key) for key in self.event_map.values()) + return (Event.create(key) for key in self.event_map.values()) def iter_media_objects(self): return (MediaObject.create(key) for key in self.media_map.values()) From 83c853726de9f31c9fb87177c0c91c0acacfc1c0 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 02:15:44 -0400 Subject: [PATCH 037/105] DB-API 2.0 can now load/save from file; need to load/save metadata --- gramps/plugins/database/dbapi.py | 31 +++++++++++++++++-- .../defaults/default_settings.py | 7 +++++ 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 gramps/plugins/database/dbapi_support/defaults/default_settings.py diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index b5d61e5dc..c99146b6d 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -9,6 +9,7 @@ import time import re import os import logging +import shutil #------------------------------------------------------------------------ # @@ -1261,6 +1262,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dbapi.execute("""insert into person(handle, gramps_id, blob) values(?, ?, ?);""", [person.handle, person.gramps_id, pickle.dumps(person.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([person.handle],)) @@ -1281,6 +1283,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dbapi.execute("insert into family values(?, ?, ?);", [family.handle, family.gramps_id, pickle.dumps(family.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([family.handle],)) @@ -1300,6 +1303,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "citation-add" self.dbapi.execute("insert into citation values(?, ?, ?);", [citation.handle, citation.gramps_id, pickle.dumps(citation.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([citation.handle],)) @@ -1319,6 +1323,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "source-add" self.dbapi.execute("insert into source values(?, ?, ?);", [source.handle, source.gramps_id, pickle.dumps(source.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([source.handle],)) @@ -1338,6 +1343,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "repository-add" self.dbapi.execute("insert into repository values(?, ?, ?);", [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([repository.handle],)) @@ -1357,6 +1363,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "note-add" self.dbapi.execute("insert into note values(?, ?, ?);", [note.handle, note.gramps_id, pickle.dumps(note.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([note.handle],)) @@ -1376,6 +1383,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "place-add" self.dbapi.execute("insert into place values(?, ?, ?);", [place.handle, place.gramps_id, pickle.dumps(place.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([place.handle],)) @@ -1395,6 +1403,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "event-add" self.dbapi.execute("insert into event values(?, ?, ?);", [event.handle, event.gramps_id, pickle.dumps(event.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([event.handle],)) @@ -1412,6 +1421,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "tag-add" self.dbapi.execute("insert into tag values(?, ?);", [tag.handle, pickle.dumps(tag.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([tag.handle],)) @@ -1431,6 +1441,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "media-add" self.dbapi.execute("insert into media values(?, ?, ?);", [media.handle, media.gramps_id, pickle.dumps(media.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([media.handle],)) @@ -1706,6 +1717,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): writer.write(filename) filename = os.path.join(self._directory, "meta_data.db") touch(filename) + self.dbapi.close() def find_backlink_handles(self, handle, include_classes=None): ## FIXME @@ -1907,9 +1919,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): force_bsddb_downgrade=False, force_python_upgrade=False): # Run code from directory - import sqlite3 - self.dbapi = sqlite3.connect(':memory:') - self.dbapi.row_factory = sqlite3.Row # allows access by name + default_settings = {"__file__": + os.path.join(directory, "default_settings.py")} + settings_file = os.path.join(directory, "default_settings.py") + with open(settings_file) as f: + code = compile(f.read(), settings_file, 'exec') + exec(code, globals(), default_settings) + + self.dbapi = default_settings["dbapi"] + # make sure schema is up to date: self.dbapi.execute("""CREATE TABLE IF NOT EXISTS person ( handle TEXT PRIMARY KEY NOT NULL, @@ -1990,6 +2008,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): _LOG.debug("Write database backend file to 'dbapi'") with open(versionpath, "w") as version_file: version_file.write("dbapi") + # Write default_settings, sqlite.db + defaults = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "dbapi_support", "defaults") + _LOG.debug("Copy defaults from: " + defaults) + for filename in os.listdir(defaults): + fullpath = os.path.abspath(os.path.join(defaults, filename)) + shutil.copy2(fullpath, directory) def report_bm_change(self): """ diff --git a/gramps/plugins/database/dbapi_support/defaults/default_settings.py b/gramps/plugins/database/dbapi_support/defaults/default_settings.py new file mode 100644 index 000000000..8931c8e89 --- /dev/null +++ b/gramps/plugins/database/dbapi_support/defaults/default_settings.py @@ -0,0 +1,7 @@ +import os +import sqlite3 + +path_to_db = os.path.join(os.path.dirname(os.path.realpath(__file__)), + 'sqlite.db') +dbapi = sqlite3.connect(path_to_db) +dbapi.row_factory = sqlite3.Row # allows access by name From 3cd6622c8fce463c1fdee262afe25b128eaef3c0 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 09:54:04 -0400 Subject: [PATCH 038/105] Added support for sort_handles --- gramps/plugins/database/dbapi.py | 162 ++++++++++++++++++++++--------- 1 file changed, 114 insertions(+), 48 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index c99146b6d..3dd90ac49 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -38,16 +38,8 @@ from gramps.gen.db import (PERSON_KEY, from gramps.gen.utils.id import create_id from gramps.gen.lib.researcher import Researcher -from gramps.gen.lib.mediaobj import MediaObject -from gramps.gen.lib.person import Person -from gramps.gen.lib.family import Family -from gramps.gen.lib.src import Source -from gramps.gen.lib.citation import Citation -from gramps.gen.lib.event import Event -from gramps.gen.lib.place import Place -from gramps.gen.lib.repo import Repository -from gramps.gen.lib.note import Note -from gramps.gen.lib.tag import Tag +from gramps.gen.lib import (Tag, MediaObject, Person, Family, Source, Citation, Event, + Place, Repository, Note, NameOriginType) from gramps.gen.lib.genderstats import GenderStats _LOG = logging.getLogger(DBLOGNAME) @@ -761,62 +753,70 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def get_person_handles(self, sort_handles=False): - ## Fixme: implement sort - cur = self.dbapi.execute("select handle from person;") + if sort_handles: + cur = self.dbapi.execute("SELECT handle FROM person ORDER BY order_by;") + else: + cur = self.dbapi.execute("SELECT handle FROM person;") rows = cur.fetchall() return [row[0] for row in rows] - def get_family_handles(self, sort_handles=False): - ## Fixme: implement sort + def get_family_handles(self): cur = self.dbapi.execute("select handle from family;") rows = cur.fetchall() return [row[0] for row in rows] - def get_event_handles(self, sort_handles=False): - ## Fixme: implement sort + def get_event_handles(self): cur = self.dbapi.execute("select handle from event;") rows = cur.fetchall() return [row[0] for row in rows] def get_citation_handles(self, sort_handles=False): - ## Fixme: implement sort - cur = self.dbapi.execute("select handle from citation;") + if sort_handles: + cur = self.dbapi.execute("select handle from citation ORDER BY order_by;") + else: + cur = self.dbapi.execute("select handle from citation;") rows = cur.fetchall() return [row[0] for row in rows] def get_source_handles(self, sort_handles=False): - ## Fixme: implement sort - cur = self.dbapi.execute("select handle from source;") + if sort_handles: + cur = self.dbapi.execute("select handle from source ORDER BY order_by;") + else: + cur = self.dbapi.execute("select handle from source;") rows = cur.fetchall() return [row[0] for row in rows] def get_place_handles(self, sort_handles=False): - ## Fixme: implement sort - cur = self.dbapi.execute("select handle from place;") + if sort_handles: + cur = self.dbapi.execute("select handle from place ORDER BY order_by;") + else: + cur = self.dbapi.execute("select handle from place;") rows = cur.fetchall() return [row[0] for row in rows] - def get_repository_handles(self, sort_handles=False): - ## Fixme: implement sort + def get_repository_handles(self): cur = self.dbapi.execute("select handle from repository;") rows = cur.fetchall() return [row[0] for row in rows] def get_media_object_handles(self, sort_handles=False): - ## Fixme: implement sort - cur = self.dbapi.execute("select handle from media;") + if sort_handles: + cur = self.dbapi.execute("select handle from media ORDER BY order_by;") + else: + cur = self.dbapi.execute("select handle from media;") rows = cur.fetchall() return [row[0] for row in rows] - def get_note_handles(self, sort_handles=False): - ## Fixme: implement sort + def get_note_handles(self): cur = self.dbapi.execute("select handle from note;") rows = cur.fetchall() return [row[0] for row in rows] def get_tag_handles(self, sort_handles=False): - # FIXME: implement sort - cur = self.dbapi.execute("select handle from tag;") + if sort_handles: + cur = self.dbapi.execute("select handle from tag ORDER BY order_by;") + else: + cur = self.dbapi.execute("select handle from tag;") rows = cur.fetchall() return [row[0] for row in rows] @@ -1252,16 +1252,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if person.handle in self.person_map: emit = "person-update" self.dbapi.execute("""UPDATE person SET gramps_id = ?, + order_by = ?, blob = ? WHERE handle = ?;""", [person.gramps_id, + self._order_by_person_key(person), pickle.dumps(person.serialize()), person.handle]) else: emit = "person-add" - self.dbapi.execute("""insert into person(handle, gramps_id, blob) - values(?, ?, ?);""", - [person.handle, person.gramps_id, pickle.dumps(person.serialize())]) + self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) + values(?, ?, ?, ?);""", + [person.handle, + self._order_by_person_key(person), + person.gramps_id, + pickle.dumps(person.serialize())]) self.dbapi.commit() # Emit after added: if emit: @@ -1294,15 +1299,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if citation.handle in self.citation_map: emit = "citation-update" self.dbapi.execute("""UPDATE citation SET gramps_id = ?, - blob = ? + order_by = ?, + blob = ? WHERE handle = ?;""", [citation.gramps_id, + self._order_by_citation_key(citation), pickle.dumps(citation.serialize()), citation.handle]) else: emit = "citation-add" - self.dbapi.execute("insert into citation values(?, ?, ?);", - [citation.handle, citation.gramps_id, pickle.dumps(citation.serialize())]) + self.dbapi.execute("insert into citation values(?, ?, ?, ?);", + [citation.handle, + self._order_by_citation_key(citation), + citation.gramps_id, + pickle.dumps(citation.serialize())]) self.dbapi.commit() # Emit after added: if emit: @@ -1314,15 +1324,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if source.handle in self.source_map: emit = "source-update" self.dbapi.execute("""UPDATE source SET gramps_id = ?, + order_by = ?, blob = ? WHERE handle = ?;""", [source.gramps_id, + self._order_by_source_key(source), pickle.dumps(source.serialize()), source.handle]) else: emit = "source-add" - self.dbapi.execute("insert into source values(?, ?, ?);", - [source.handle, source.gramps_id, pickle.dumps(source.serialize())]) + self.dbapi.execute("insert into source values(?, ?, ?, ?);", + [source.handle, + self._order_by_source_key(source), + source.gramps_id, + pickle.dumps(source.serialize())]) self.dbapi.commit() # Emit after added: if emit: @@ -1374,15 +1389,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if place.handle in self.place_map: emit = "place-update" self.dbapi.execute("""UPDATE place SET gramps_id = ?, - blob = ? + order_by = ?, + blob = ? WHERE handle = ?;""", [place.gramps_id, + self._order_by_place_key(place), pickle.dumps(place.serialize()), place.handle]) else: emit = "place-add" - self.dbapi.execute("insert into place values(?, ?, ?);", - [place.handle, place.gramps_id, pickle.dumps(place.serialize())]) + self.dbapi.execute("insert into place values(?, ?, ?, ?);", + [place.handle, + self._order_by_place_key(place), + place.gramps_id, + pickle.dumps(place.serialize())]) self.dbapi.commit() # Emit after added: if emit: @@ -1402,7 +1422,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: emit = "event-add" self.dbapi.execute("insert into event values(?, ?, ?);", - [event.handle, event.gramps_id, pickle.dumps(event.serialize())]) + [event.handle, + event.gramps_id, + pickle.dumps(event.serialize())]) self.dbapi.commit() # Emit after added: if emit: @@ -1413,14 +1435,18 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if True or not trans.batch: if tag.handle in self.tag_map: emit = "tag-update" - self.dbapi.execute("""UPDATE tag SET blob = ? + self.dbapi.execute("""UPDATE tag SET blob = ?, + order_by = ? WHERE handle = ?;""", [pickle.dumps(tag.serialize()), + self._order_by_tag_key(tag), tag.handle]) else: emit = "tag-add" - self.dbapi.execute("insert into tag values(?, ?);", - [tag.handle, pickle.dumps(tag.serialize())]) + self.dbapi.execute("insert into tag values(?, ?, ?);", + [tag.handle, + self._order_by_tag_key(tag), + pickle.dumps(tag.serialize())]) self.dbapi.commit() # Emit after added: if emit: @@ -1432,15 +1458,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if media.handle in self.media_map: emit = "media-update" self.dbapi.execute("""UPDATE media SET gramps_id = ?, - blob = ? + order_by = ?, + blob = ? WHERE handle = ?;""", [media.gramps_id, + self._order_by_media_key(media), pickle.dumps(media.serialize()), media.handle]) else: emit = "media-add" - self.dbapi.execute("insert into media values(?, ?, ?);", - [media.handle, media.gramps_id, pickle.dumps(media.serialize())]) + self.dbapi.execute("insert into media values(?, ?, ?, ?);", + [media.handle, + self._order_by_media_key(media), + media.gramps_id, + pickle.dumps(media.serialize())]) self.dbapi.commit() # Emit after added: if emit: @@ -1931,6 +1962,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # make sure schema is up to date: self.dbapi.execute("""CREATE TABLE IF NOT EXISTS person ( handle TEXT PRIMARY KEY NOT NULL, + order_by TEXT , gramps_id TEXT , blob TEXT );""") @@ -1941,11 +1973,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): );""") self.dbapi.execute("""CREATE TABLE IF NOT EXISTS source ( handle TEXT PRIMARY KEY NOT NULL, + order_by TEXT , gramps_id TEXT , blob TEXT );""") self.dbapi.execute("""CREATE TABLE IF NOT EXISTS citation ( handle TEXT PRIMARY KEY NOT NULL, + order_by TEXT , gramps_id TEXT , blob TEXT );""") @@ -1956,11 +1990,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): );""") self.dbapi.execute("""CREATE TABLE IF NOT EXISTS media ( handle TEXT PRIMARY KEY NOT NULL, + order_by TEXT , gramps_id TEXT , blob TEXT );""") self.dbapi.execute("""CREATE TABLE IF NOT EXISTS place ( handle TEXT PRIMARY KEY NOT NULL, + order_by TEXT , gramps_id TEXT , blob TEXT );""") @@ -1976,6 +2012,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): );""") self.dbapi.execute("""CREATE TABLE IF NOT EXISTS tag ( handle TEXT PRIMARY KEY NOT NULL, + order_by TEXT , blob TEXT );""") @@ -2254,3 +2291,32 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if row: return pickle.loads(row[0]) + def _order_by_person_key(self, person): + """ + All non pa/matronymic surnames are used in indexing. + pa/matronymic not as they change for every generation! + returns a byte string + """ + if person.primary_name and person.primary_name.surname_list: + order_by = " ".join([x.surname for x in person.primary_name.surname_list if not + (int(x.origintype) in [NameOriginType.PATRONYMIC, + NameOriginType.MATRONYMIC]) ]) + else: + order_by = "" + return glocale.sort_key(order_by) + + def _order_by_place_key(self, place): + return glocale.sort_key(place.title) + + def _order_by_source_key(self, source): + return glocale.sort_key(source.title) + + def _order_by_citation_key(self, citation): + return glocale.sort_key(citation.page) + + def _order_by_media_key(self, media): + return glocale.sort_key(media.desc) + + def _order_by_tag_key(self, tag): + return glocale.sort_key(tag.get_name()) + From f275843556d687ee79414af316ff0c7f29c41b13 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 10:07:57 -0400 Subject: [PATCH 039/105] Now using batch transactions --- gramps/plugins/database/dbapi.py | 328 +++++++++++++++---------------- 1 file changed, 163 insertions(+), 165 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 3dd90ac49..774a4bdda 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -495,16 +495,14 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def transaction_commit(self, txn): - ## FIXME - pass + self.dbapi.commit() def get_undodb(self): ## FIXME return None def transaction_abort(self, txn): - ## FIXME - pass + self.dbapi.rollback() @staticmethod def _validated_id_prefix(val, default): @@ -1248,25 +1246,25 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_person(self, person, trans, change_time=None): emit = None - if True or not trans.batch: - if person.handle in self.person_map: - emit = "person-update" - self.dbapi.execute("""UPDATE person SET gramps_id = ?, - order_by = ?, - blob = ? - WHERE handle = ?;""", - [person.gramps_id, - self._order_by_person_key(person), - pickle.dumps(person.serialize()), - person.handle]) - else: - emit = "person-add" - self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) - values(?, ?, ?, ?);""", - [person.handle, - self._order_by_person_key(person), - person.gramps_id, - pickle.dumps(person.serialize())]) + if person.handle in self.person_map: + emit = "person-update" + self.dbapi.execute("""UPDATE person SET gramps_id = ?, + order_by = ?, + blob = ? + WHERE handle = ?;""", + [person.gramps_id, + self._order_by_person_key(person), + pickle.dumps(person.serialize()), + person.handle]) + else: + emit = "person-add" + self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) + values(?, ?, ?, ?);""", + [person.handle, + self._order_by_person_key(person), + person.gramps_id, + pickle.dumps(person.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1274,20 +1272,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_family(self, family, trans, change_time=None): emit = None - if True or not trans.batch: - if family.handle in self.family_map: - emit = "family-update" - self.dbapi.execute("""UPDATE family SET gramps_id = ?, - blob = ? - WHERE handle = ?;""", - [family.gramps_id, - pickle.dumps(family.serialize()), - family.handle]) - else: - emit = "family-add" - self.dbapi.execute("insert into family values(?, ?, ?);", - [family.handle, family.gramps_id, - pickle.dumps(family.serialize())]) + if family.handle in self.family_map: + emit = "family-update" + self.dbapi.execute("""UPDATE family SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [family.gramps_id, + pickle.dumps(family.serialize()), + family.handle]) + else: + emit = "family-add" + self.dbapi.execute("insert into family values(?, ?, ?);", + [family.handle, family.gramps_id, + pickle.dumps(family.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1295,24 +1293,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_citation(self, citation, trans, change_time=None): emit = None - if True or not trans.batch: - if citation.handle in self.citation_map: - emit = "citation-update" - self.dbapi.execute("""UPDATE citation SET gramps_id = ?, - order_by = ?, - blob = ? - WHERE handle = ?;""", - [citation.gramps_id, - self._order_by_citation_key(citation), - pickle.dumps(citation.serialize()), - citation.handle]) - else: - emit = "citation-add" - self.dbapi.execute("insert into citation values(?, ?, ?, ?);", - [citation.handle, - self._order_by_citation_key(citation), - citation.gramps_id, - pickle.dumps(citation.serialize())]) + if citation.handle in self.citation_map: + emit = "citation-update" + self.dbapi.execute("""UPDATE citation SET gramps_id = ?, + order_by = ?, + blob = ? + WHERE handle = ?;""", + [citation.gramps_id, + self._order_by_citation_key(citation), + pickle.dumps(citation.serialize()), + citation.handle]) + else: + emit = "citation-add" + self.dbapi.execute("insert into citation values(?, ?, ?, ?);", + [citation.handle, + self._order_by_citation_key(citation), + citation.gramps_id, + pickle.dumps(citation.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1320,24 +1318,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_source(self, source, trans, change_time=None): emit = None - if True or not trans.batch: - if source.handle in self.source_map: - emit = "source-update" - self.dbapi.execute("""UPDATE source SET gramps_id = ?, - order_by = ?, - blob = ? - WHERE handle = ?;""", - [source.gramps_id, - self._order_by_source_key(source), - pickle.dumps(source.serialize()), - source.handle]) - else: - emit = "source-add" - self.dbapi.execute("insert into source values(?, ?, ?, ?);", - [source.handle, - self._order_by_source_key(source), - source.gramps_id, - pickle.dumps(source.serialize())]) + if source.handle in self.source_map: + emit = "source-update" + self.dbapi.execute("""UPDATE source SET gramps_id = ?, + order_by = ?, + blob = ? + WHERE handle = ?;""", + [source.gramps_id, + self._order_by_source_key(source), + pickle.dumps(source.serialize()), + source.handle]) + else: + emit = "source-add" + self.dbapi.execute("insert into source values(?, ?, ?, ?);", + [source.handle, + self._order_by_source_key(source), + source.gramps_id, + pickle.dumps(source.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1345,19 +1343,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_repository(self, repository, trans, change_time=None): emit = None - if True or not trans.batch: - if repository.handle in self.repository_map: - emit = "repository-update" - self.dbapi.execute("""UPDATE repository SET gramps_id = ?, - blob = ? - WHERE handle = ?;""", - [repository.gramps_id, - pickle.dumps(repository.serialize()), - repository.handle]) - else: - emit = "repository-add" - self.dbapi.execute("insert into repository values(?, ?, ?);", - [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())]) + if repository.handle in self.repository_map: + emit = "repository-update" + self.dbapi.execute("""UPDATE repository SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [repository.gramps_id, + pickle.dumps(repository.serialize()), + repository.handle]) + else: + emit = "repository-add" + self.dbapi.execute("insert into repository values(?, ?, ?);", + [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1365,19 +1363,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_note(self, note, trans, change_time=None): emit = None - if True or not trans.batch: - if note.handle in self.note_map: - emit = "note-update" - self.dbapi.execute("""UPDATE note SET gramps_id = ?, - blob = ? - WHERE handle = ?;""", - [note.gramps_id, - pickle.dumps(note.serialize()), - note.handle]) - else: - emit = "note-add" - self.dbapi.execute("insert into note values(?, ?, ?);", - [note.handle, note.gramps_id, pickle.dumps(note.serialize())]) + if note.handle in self.note_map: + emit = "note-update" + self.dbapi.execute("""UPDATE note SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [note.gramps_id, + pickle.dumps(note.serialize()), + note.handle]) + else: + emit = "note-add" + self.dbapi.execute("insert into note values(?, ?, ?);", + [note.handle, note.gramps_id, pickle.dumps(note.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1385,24 +1383,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_place(self, place, trans, change_time=None): emit = None - if True or not trans.batch: - if place.handle in self.place_map: - emit = "place-update" - self.dbapi.execute("""UPDATE place SET gramps_id = ?, - order_by = ?, - blob = ? - WHERE handle = ?;""", - [place.gramps_id, - self._order_by_place_key(place), - pickle.dumps(place.serialize()), - place.handle]) - else: - emit = "place-add" - self.dbapi.execute("insert into place values(?, ?, ?, ?);", - [place.handle, - self._order_by_place_key(place), - place.gramps_id, - pickle.dumps(place.serialize())]) + if place.handle in self.place_map: + emit = "place-update" + self.dbapi.execute("""UPDATE place SET gramps_id = ?, + order_by = ?, + blob = ? + WHERE handle = ?;""", + [place.gramps_id, + self._order_by_place_key(place), + pickle.dumps(place.serialize()), + place.handle]) + else: + emit = "place-add" + self.dbapi.execute("insert into place values(?, ?, ?, ?);", + [place.handle, + self._order_by_place_key(place), + place.gramps_id, + pickle.dumps(place.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1410,21 +1408,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_event(self, event, trans, change_time=None): emit = None - if True or not trans.batch: - if event.handle in self.event_map: - emit = "event-update" - self.dbapi.execute("""UPDATE event SET gramps_id = ?, - blob = ? - WHERE handle = ?;""", - [event.gramps_id, - pickle.dumps(event.serialize()), - event.handle]) - else: - emit = "event-add" - self.dbapi.execute("insert into event values(?, ?, ?);", - [event.handle, - event.gramps_id, - pickle.dumps(event.serialize())]) + if event.handle in self.event_map: + emit = "event-update" + self.dbapi.execute("""UPDATE event SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [event.gramps_id, + pickle.dumps(event.serialize()), + event.handle]) + else: + emit = "event-add" + self.dbapi.execute("insert into event values(?, ?, ?);", + [event.handle, + event.gramps_id, + pickle.dumps(event.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1432,21 +1430,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_tag(self, tag, trans, change_time=None): emit = None - if True or not trans.batch: - if tag.handle in self.tag_map: - emit = "tag-update" - self.dbapi.execute("""UPDATE tag SET blob = ?, - order_by = ? - WHERE handle = ?;""", - [pickle.dumps(tag.serialize()), - self._order_by_tag_key(tag), - tag.handle]) - else: - emit = "tag-add" - self.dbapi.execute("insert into tag values(?, ?, ?);", - [tag.handle, - self._order_by_tag_key(tag), - pickle.dumps(tag.serialize())]) + if tag.handle in self.tag_map: + emit = "tag-update" + self.dbapi.execute("""UPDATE tag SET blob = ?, + order_by = ? + WHERE handle = ?;""", + [pickle.dumps(tag.serialize()), + self._order_by_tag_key(tag), + tag.handle]) + else: + emit = "tag-add" + self.dbapi.execute("insert into tag values(?, ?, ?);", + [tag.handle, + self._order_by_tag_key(tag), + pickle.dumps(tag.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1454,24 +1452,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_media_object(self, media, trans, change_time=None): emit = None - if True or not trans.batch: - if media.handle in self.media_map: - emit = "media-update" - self.dbapi.execute("""UPDATE media SET gramps_id = ?, - order_by = ?, - blob = ? - WHERE handle = ?;""", - [media.gramps_id, - self._order_by_media_key(media), - pickle.dumps(media.serialize()), - media.handle]) - else: - emit = "media-add" - self.dbapi.execute("insert into media values(?, ?, ?, ?);", - [media.handle, - self._order_by_media_key(media), - media.gramps_id, - pickle.dumps(media.serialize())]) + if media.handle in self.media_map: + emit = "media-update" + self.dbapi.execute("""UPDATE media SET gramps_id = ?, + order_by = ?, + blob = ? + WHERE handle = ?;""", + [media.gramps_id, + self._order_by_media_key(media), + pickle.dumps(media.serialize()), + media.handle]) + else: + emit = "media-add" + self.dbapi.execute("insert into media values(?, ?, ?, ?);", + [media.handle, + self._order_by_media_key(media), + media.gramps_id, + pickle.dumps(media.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: From aa7928d35d3797e8740b7d1cfa514a950e277b0a Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 10:27:49 -0400 Subject: [PATCH 040/105] Added indices on order_by fields --- gramps/plugins/database/dbapi.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 774a4bdda..436ba87d5 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -1490,7 +1490,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return list(key2table[obj_key].keys()) def transaction_begin(self, transaction): - ## FIXME + """ + Transactions are handled automatically by the db layer. + """ return def set_researcher(self, owner): @@ -2013,6 +2015,25 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): order_by TEXT , blob TEXT );""") + ## Indices: + self.dbapi.execute("""CREATE INDEX IF NOT EXISTS + order_by ON person (order_by); + """) + self.dbapi.execute("""CREATE INDEX IF NOT EXISTS + order_by ON source (order_by); + """) + self.dbapi.execute("""CREATE INDEX IF NOT EXISTS + order_by ON citation (order_by); + """) + self.dbapi.execute("""CREATE INDEX IF NOT EXISTS + order_by ON media (order_by); + """) + self.dbapi.execute("""CREATE INDEX IF NOT EXISTS + order_by ON place (order_by); + """) + self.dbapi.execute("""CREATE INDEX IF NOT EXISTS + order_by ON tag (order_by); + """) def redo(self, update_history=True): ## FIXME From 56cf1b02abb4819a86a4cbb68cca52eebf6eba0d Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 14:52:43 -0400 Subject: [PATCH 041/105] Basics for back references now work, although doesn't update with edits yet --- gramps/plugins/database/dbapi.py | 145 +++++++++++++++++-------------- 1 file changed, 79 insertions(+), 66 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 436ba87d5..31e50534c 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -20,7 +20,8 @@ import gramps from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, - KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP) + KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP, + CLASS_TO_KEY_MAP) from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback from gramps.gen.db.undoredo import DbUndo @@ -1245,6 +1246,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return obj.handle def commit_person(self, person, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if person.handle in self.person_map: emit = "person-update" @@ -1271,6 +1273,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([person.handle],)) def commit_family(self, family, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if family.handle in self.family_map: emit = "family-update" @@ -1292,6 +1295,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([family.handle],)) def commit_citation(self, citation, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if citation.handle in self.citation_map: emit = "citation-update" @@ -1317,6 +1321,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([citation.handle],)) def commit_source(self, source, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if source.handle in self.source_map: emit = "source-update" @@ -1342,6 +1347,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([source.handle],)) def commit_repository(self, repository, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if repository.handle in self.repository_map: emit = "repository-update" @@ -1362,6 +1368,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([repository.handle],)) def commit_note(self, note, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if note.handle in self.note_map: emit = "note-update" @@ -1382,6 +1389,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([note.handle],)) def commit_place(self, place, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if place.handle in self.place_map: emit = "place-update" @@ -1407,6 +1415,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([place.handle],)) def commit_event(self, event, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if event.handle in self.event_map: emit = "event-update" @@ -1429,6 +1438,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([event.handle],)) def commit_tag(self, tag, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if tag.handle in self.tag_map: emit = "tag-update" @@ -1451,6 +1461,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([tag.handle],)) def commit_media_object(self, media, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if media.handle in self.media_map: emit = "media-update" @@ -1675,64 +1686,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): [handle]) self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) - def delete_primary_from_reference_map(self, handle, transaction, txn=None): - """ - Remove all references to the primary object from the reference_map. - handle should be utf-8 - """ - 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.decode('utf-8'), 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 __remove_reference(self, key, transaction, txn): - """ - Remove the reference specified by the key, preserving the change in - the passed transaction. - """ - if isinstance(key, tuple): - #create a byte string key, first validity check in python 3! - for val in key: - if isinstance(val, bytes): - raise DbError(_('An attempt is made to save a reference key ' - 'which is partly bytecode, this is not allowed.\n' - 'Key is %s') % str(key)) - key = str(key) - if isinstance(key, str): - key = key.encode('utf-8') - if not self.readonly: - if not transaction.batch: - old_data = self.reference_map.get(key, txn=txn) - transaction.add(REFERENCE_KEY, TXNDEL, key, old_data, None) - #transaction.reference_del.append(str(key)) - self.reference_map.delete(key, txn=txn) - ## Missing: def backup(self): @@ -1751,8 +1704,28 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dbapi.close() def find_backlink_handles(self, handle, include_classes=None): - ## FIXME - return [] + """ + 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 = list(find_backlink_handles(handle)) + """ + cur = self.dbapi.execute("SELECT * from reference WHERE ref_handle = ?;", + [handle]) + rows = cur.fetchall() + for row in rows: + if (include_classes is None) or (row["obj_class"] in include_classes): + yield (row["obj_class"], row["obj_handle"]) def find_initial_person(self): items = self.person_map.keys() @@ -2015,6 +1988,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): order_by TEXT , blob TEXT );""") + # Reference + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS reference ( + obj_handle TEXT, + obj_class TEXT, + ref_handle TEXT, + ref_class TEXT + );""") ## Indices: self.dbapi.execute("""CREATE INDEX IF NOT EXISTS order_by ON person (order_by); @@ -2034,6 +2014,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dbapi.execute("""CREATE INDEX IF NOT EXISTS order_by ON tag (order_by); """) + self.dbapi.execute("""CREATE INDEX IF NOT EXISTS + ref_handle ON reference (ref_handle); + """) def redo(self, update_history=True): ## FIXME @@ -2111,13 +2094,43 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): name = None return name - def reindex_reference_map(self): - ## FIXME - pass + def reindex_reference_map(self, callback): + callback(4) + self.dbapi.execute("DELETE FROM reference;") + primary_table = ( + (self.get_person_cursor, Person), + (self.get_family_cursor, Family), + (self.get_event_cursor, Event), + (self.get_place_cursor, Place), + (self.get_source_cursor, Source), + (self.get_citation_cursor, Citation), + (self.get_media_cursor, MediaObject), + (self.get_repository_cursor, Repository), + (self.get_note_cursor, Note), + (self.get_tag_cursor, Tag), + ) + # Now we use the functions and classes defined above + # to loop through each of the primary object tables. + for cursor_func, class_func in primary_table: + logging.info("Rebuilding %s reference map" % + class_func.__name__) + with cursor_func() as cursor: + for found_handle, val in cursor: + obj = class_func.create(val) + references = set(obj.get_referenced_handles_recursively()) + # handle addition of new references + for (ref_class_name, ref_handle) in references: + self.dbapi.execute("INSERT into reference VALUES(?, ?, ?, ?);", + [obj.handle, + obj.__class__.__name__, + ref_handle, + ref_class_name]) + + self.dbapi.commit() + callback(5) def rebuild_secondary(self, update): - ## FIXME - pass + self.reindex_reference_map(update) def prepare_import(self): """ From e4898df12d920c68dc3e4e714152d9848b3ee248 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 20:25:04 -0400 Subject: [PATCH 042/105] WIP: name_group; clean up of SQL --- gramps/plugins/database/dbapi.py | 89 ++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 33 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 31e50534c..d501dbdd3 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -462,7 +462,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): contains_func="has_gramps_id_func") self.tag_map = Map(Table(self._tables["Tag"])) self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) - self.name_group = {} + ## FIXME: add appropriate methods: + self.name_group = Map(Table({"handles_func": self.get_name_group_keys, # keys + "has_handle_func": self.has_name_group_key, # key in table + "cursor_func": None, # create a cursor, values + "add_func": self.set_name_group_mapping, # add a key, value + "count_func": None})) # how many items? self.undo_callback = None self.redo_callback = None self.undo_history_callback = None @@ -746,10 +751,16 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def get_name_group_keys(self): - return [] + cur = self.dbapi.execute("select name from name_group order by name;") + rows = cur.fetchall() + return [row[0] for row in rows] def get_name_group_mapping(self, key): - return None + cur = self.dbapi.execute("select grouping from name_group where name = ?;", + [key]) + row = cur.fetchone() + if row: + return row[0] def get_person_handles(self, sort_handles=False): if sort_handles: @@ -1096,12 +1107,22 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return handle in self.media_map def has_name_group_key(self, key): - # FIXME: - return False + cur = self.dbapi.execute("select grouping from name_group where name = ?;", + [key]) + row = cur.fetchone() + return True if row else False - def set_name_group_mapping(self, key, value): - # FIXME: - pass + def set_name_group_mapping(self, name, grouping): + sname = name.encode("utf-8") + cur = self.dbapi.execute("SELECT * from name_group where name = ?;", + [sname]) + row = cur.fetchone() + if row: + cur = self.dbapi.execute("DELETE from name_group where name = ?;", + [sname]) + cur = self.dbapi.execute("INSERT into name_group (name, grouping) VALUES(?, ?);", + [sname, grouping]) + self.dbapi.commit() def set_default_person_handle(self, handle): ## FIXME @@ -1260,8 +1281,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): person.handle]) else: emit = "person-add" - self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) - values(?, ?, ?, ?);""", + self.dbapi.execute("""insert into person (handle, order_by, gramps_id, blob) + values(?, ?, ?, ?);""", [person.handle, self._order_by_person_key(person), person.gramps_id, @@ -1285,7 +1306,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): family.handle]) else: emit = "family-add" - self.dbapi.execute("insert into family values(?, ?, ?);", + self.dbapi.execute("""insert into family (handle, gramps_id, blob) + values(?, ?, ?);""", [family.handle, family.gramps_id, pickle.dumps(family.serialize())]) if not trans.batch: @@ -1309,7 +1331,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): citation.handle]) else: emit = "citation-add" - self.dbapi.execute("insert into citation values(?, ?, ?, ?);", + self.dbapi.execute("""insert into citation (handle, order_by, gramps_id, blob) + values(?, ?, ?, ?);""", [citation.handle, self._order_by_citation_key(citation), citation.gramps_id, @@ -1335,7 +1358,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): source.handle]) else: emit = "source-add" - self.dbapi.execute("insert into source values(?, ?, ?, ?);", + self.dbapi.execute("""insert into source (handle, order_by, gramps_id, blob) + values(?, ?, ?, ?);""", [source.handle, self._order_by_source_key(source), source.gramps_id, @@ -1359,7 +1383,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): repository.handle]) else: emit = "repository-add" - self.dbapi.execute("insert into repository values(?, ?, ?);", + self.dbapi.execute("""insert into repository (handle, gramps_id, blob) + values(?, ?, ?);""", [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())]) if not trans.batch: self.dbapi.commit() @@ -1380,7 +1405,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): note.handle]) else: emit = "note-add" - self.dbapi.execute("insert into note values(?, ?, ?);", + self.dbapi.execute("""insert into note (handle, gramps_id, blob) + values(?, ?, ?);""", [note.handle, note.gramps_id, pickle.dumps(note.serialize())]) if not trans.batch: self.dbapi.commit() @@ -1403,7 +1429,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): place.handle]) else: emit = "place-add" - self.dbapi.execute("insert into place values(?, ?, ?, ?);", + self.dbapi.execute("""insert into place (handle, order_by, gramps_id, blob) + values(?, ?, ?, ?);""", [place.handle, self._order_by_place_key(place), place.gramps_id, @@ -1427,7 +1454,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): event.handle]) else: emit = "event-add" - self.dbapi.execute("insert into event values(?, ?, ?);", + self.dbapi.execute("""insert into event (handle, gramps_id, blob) + values(?, ?, ?);""", [event.handle, event.gramps_id, pickle.dumps(event.serialize())]) @@ -1450,7 +1478,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): tag.handle]) else: emit = "tag-add" - self.dbapi.execute("insert into tag values(?, ?, ?);", + self.dbapi.execute("""insert into tag (handle, order_by, blob) + values(?, ?, ?);""", [tag.handle, self._order_by_tag_key(tag), pickle.dumps(tag.serialize())]) @@ -1475,7 +1504,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): media.handle]) else: emit = "media-add" - self.dbapi.execute("insert into media values(?, ?, ?, ?);", + self.dbapi.execute("""insert into media (handle, order_by, gramps_id, blob) + values(?, ?, ?, ?);""", [media.handle, self._order_by_media_key(media), media.gramps_id, @@ -1747,18 +1777,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def get_citation_bookmarks(self): return self.citation_bookmarks - def get_cursor(self, table, txn=None, update=False, commit=False): - ## FIXME - ## called from a complete find_back_ref - pass - - # 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_default_handle(self): items = self.person_map.keys() if len(items) > 0: @@ -1988,13 +2006,17 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): order_by TEXT , blob TEXT );""") - # Reference + # Secondary: self.dbapi.execute("""CREATE TABLE IF NOT EXISTS reference ( obj_handle TEXT, obj_class TEXT, ref_handle TEXT, ref_class TEXT );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS name_group ( + name TEXT PRIMARY KEY NOT NULL, + grouping TEXT + );""") ## Indices: self.dbapi.execute("""CREATE INDEX IF NOT EXISTS order_by ON person (order_by); @@ -2120,7 +2142,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): references = set(obj.get_referenced_handles_recursively()) # handle addition of new references for (ref_class_name, ref_handle) in references: - self.dbapi.execute("INSERT into reference VALUES(?, ?, ?, ?);", + self.dbapi.execute("""INSERT into reference (obj_handle, obj_class, ref_handle, ref_class) + VALUES(?, ?, ?, ?);""", [obj.handle, obj.__class__.__name__, ref_handle, From e85b4be7d662e427ff687aeb15080dd5f827bde7 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 21:27:51 -0400 Subject: [PATCH 043/105] Added metadata table and setting/value --- gramps/plugins/database/dbapi.py | 48 ++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index d501dbdd3..503194bf9 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -748,7 +748,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return gid def get_mediapath(self): - return None + cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"]) + row = cur.fetchone() + if row: + return row["value"] + else: + return None def get_name_group_keys(self): cur = self.dbapi.execute("select name from name_group order by name;") @@ -1125,12 +1130,27 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dbapi.commit() def set_default_person_handle(self, handle): - ## FIXME - pass + cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"]) + row = cur.fetchone() + if row: + cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", + [handle, "default-person"]) + else: + cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", + ["default-person", handle]) + self.emit('home-person-changed') + self.dbapi.commit() def set_mediapath(self, mediapath): - ## FIXME - pass + cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"]) + row = cur.fetchone() + if row: + cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", + [mediapath, "media-path"]) + else: + cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", + ["media-path", mediapath]) + self.dbapi.commit() def get_raw_person_data(self, handle): if handle in self.person_map: @@ -1615,6 +1635,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): #del self.person_id_map[person.gramps_id] self.dbapi.execute("DELETE from person WHERE handle = ?;", [handle]) self.emit("person-delete", ([handle],)) + if not transaction.batch: + self.dbapi.commit() def remove_source(self, handle, transaction): """ @@ -1715,6 +1737,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dbapi.execute("DELETE from %s WHERE handle = ?;" % key2table[key], [handle]) self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) + if not transaction.batch: + self.dbapi.commit() ## Missing: @@ -1778,10 +1802,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return self.citation_bookmarks def get_default_handle(self): - items = self.person_map.keys() - if len(items) > 0: - return list(items)[0] - return None + cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"]) + row = cur.fetchone() + if row: + return row["value"] + else: + return None def get_event_attribute_types(self): ## FIXME @@ -2017,6 +2043,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): name TEXT PRIMARY KEY NOT NULL, grouping TEXT );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS metadata ( + setting TEXT PRIMARY KEY NOT NULL, + value TEXT + );""") ## Indices: self.dbapi.execute("""CREATE INDEX IF NOT EXISTS order_by ON person (order_by); From 80b2b351e696f8da6f282e870766f8291e612f27 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 22:46:05 -0400 Subject: [PATCH 044/105] Update backlinks --- gramps/plugins/database/dbapi.py | 55 +++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 503194bf9..d4a2bf09e 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -1287,7 +1287,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return obj.handle def commit_person(self, person, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if person.handle in self.person_map: emit = "person-update" @@ -1309,12 +1308,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): pickle.dumps(person.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(person) # Emit after added: if emit: self.emit(emit, ([person.handle],)) def commit_family(self, family, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if family.handle in self.family_map: emit = "family-update" @@ -1332,12 +1331,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): pickle.dumps(family.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(family) # Emit after added: if emit: self.emit(emit, ([family.handle],)) def commit_citation(self, citation, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if citation.handle in self.citation_map: emit = "citation-update" @@ -1359,12 +1358,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): pickle.dumps(citation.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(citation) # Emit after added: if emit: self.emit(emit, ([citation.handle],)) def commit_source(self, source, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if source.handle in self.source_map: emit = "source-update" @@ -1386,12 +1385,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): pickle.dumps(source.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(source) # Emit after added: if emit: self.emit(emit, ([source.handle],)) def commit_repository(self, repository, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if repository.handle in self.repository_map: emit = "repository-update" @@ -1408,12 +1407,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(repository) # Emit after added: if emit: self.emit(emit, ([repository.handle],)) def commit_note(self, note, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if note.handle in self.note_map: emit = "note-update" @@ -1430,12 +1429,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): [note.handle, note.gramps_id, pickle.dumps(note.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(note) # Emit after added: if emit: self.emit(emit, ([note.handle],)) def commit_place(self, place, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if place.handle in self.place_map: emit = "place-update" @@ -1457,12 +1456,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): pickle.dumps(place.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(place) # Emit after added: if emit: self.emit(emit, ([place.handle],)) def commit_event(self, event, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if event.handle in self.event_map: emit = "event-update" @@ -1479,14 +1478,47 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): [event.handle, event.gramps_id, pickle.dumps(event.serialize())]) + + self.update_event_attributes( + [str(attr.type) for attr in event.attribute_list + if attr.type.is_custom() and str(attr.type)]) + + if event.type.is_custom(): + self.update_event_names(str(event.type)) + if not trans.batch: self.dbapi.commit() + self.update_backlinks(event) + # Emit after added: if emit: self.emit(emit, ([event.handle],)) + def update_backlinks(self, obj): + # First, delete the current references: + self.dbapi.execute("DELETE FROM reference where obj_handle = ?;", + [obj.handle]) + # Now, add the current ones: + references = set(obj.get_referenced_handles_recursively()) + for (ref_class_name, ref_handle) in references: + self.dbapi.execute("""INSERT into reference + (obj_handle, obj_class, ref_handle, ref_class) + VALUES(?, ?, ?, ?);""", + [obj.handle, + obj.__class__.__name__, + ref_handle, + ref_class_name]) + # Will commit later + + def update_event_attributes(self, attr_list): + # FIXME + pass + + def update_event_names(self, event_type): + # FIXME + pass + def commit_tag(self, tag, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if tag.handle in self.tag_map: emit = "tag-update" @@ -1505,12 +1537,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): pickle.dumps(tag.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(tag) # Emit after added: if emit: self.emit(emit, ([tag.handle],)) def commit_media_object(self, media, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if media.handle in self.media_map: emit = "media-update" @@ -1532,6 +1564,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): pickle.dumps(media.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(media) # Emit after added: if emit: self.emit(emit, ([media.handle],)) @@ -2197,7 +2230,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): Commits the items that were queued up during the last gedcom import for two step adding. """ - pass + self.reindex_reference_map(lambda n: n) def has_handle_for_person(self, key): cur = self.dbapi.execute("select * from person where handle = ?", [key]) From 72cfc3f8264ddae6c5ef151153e6a5d9ca9bbb79 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Mon, 25 May 2015 13:33:04 -0400 Subject: [PATCH 045/105] All metadata functionality now implemented --- .../plugins/database/bsddb_support/write.py | 2 +- gramps/plugins/database/dbapi.py | 491 ++++++++++++------ gramps/plugins/database/dictionarydb.py | 2 +- gramps/plugins/database/djangodb.py | 2 +- 4 files changed, 327 insertions(+), 170 deletions(-) diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py index 9630b3ce2..1a3ad039a 100644 --- a/gramps/plugins/database/bsddb_support/write.py +++ b/gramps/plugins/database/bsddb_support/write.py @@ -653,7 +653,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): return False @catch_db_error - def load(self, name, callback, mode=DBMODE_W, force_schema_upgrade=False, + def load(self, name, callback=None, mode=DBMODE_W, force_schema_upgrade=False, force_bsddb_upgrade=False, force_bsddb_downgrade=False, force_python_upgrade=False): diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index d4a2bf09e..aad21039e 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -167,13 +167,34 @@ class Cursor(object): pass class Bookmarks(object): - def __init__(self): - self.handles = [] + def __init__(self, default=[]): + self.handles = list(default) + + def set(self, handles): + self.handles = list(handles) + def get(self): return self.handles + def append(self, handle): self.handles.append(handle) + def append_list(self, handles): + self.handles += handles + + def remove(self, handle): + self.handles.remove(handle) + + def pop(self, item): + return self.handles.pop(item) + + def insert(self, pos, item): + self.handles.insert(pos, item) + + def close(self): + del self.handles + + class DBAPITxn(DbTxn): def __init__(self, message, db, batch=False): DbTxn.__init__(self, message, db, batch) @@ -386,6 +407,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.readonly = False self.db_is_open = True self.name_formats = [] + # Bookmarks: self.bookmarks = Bookmarks() self.family_bookmarks = Bookmarks() self.event_bookmarks = Bookmarks() @@ -443,7 +465,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.source_map = Map(Table(self._tables["Source"])) self.source_id_map = Map(Table(self._tables["Source"]), keys_func="ids_func", - contains_func="has_gramps_id_func") + contains_func="has_gramps_id_func") self.repository_map = Map(Table(self._tables["Repository"])) self.repository_id_map = Map(Table(self._tables["Repository"]), keys_func="ids_func", @@ -478,6 +500,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.abort_possible = False self._bm_changes = 0 self._directory = directory + self._has_changed = False self.full_name = None self.path = None self.brief_name = None @@ -503,10 +526,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def transaction_commit(self, txn): self.dbapi.commit() - def get_undodb(self): - ## FIXME - return None - def transaction_abort(self, txn): self.dbapi.rollback() @@ -748,12 +767,42 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return gid def get_mediapath(self): - cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"]) + return self.get_metadata("media-path", "") + + def set_mediapath(self, mediapath): + return self.set_metadata("media-path", mediapath) + + def get_metadata(self, key, default=[]): + """ + Get an item from the database. + """ + cur = self.dbapi.execute("SELECT * FROM metadata WHERE setting = ?;", [key]) row = cur.fetchone() if row: - return row["value"] + return pickle.loads(row["value"]) + elif default == []: + return [] else: - return None + return default + + def set_metadata(self, key, value): + """ + key: string + value: item, will be serialized here + """ + cur = self.dbapi.execute("SELECT * FROM metadata WHERE setting = ?;", [key]) + row = cur.fetchone() + if row: + cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", + [pickle.dumps(value), key]) + else: + cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", + [key, pickle.dumps(value)]) + self.dbapi.commit() + + def set_default_person_handle(self, handle): + self.set_metadata("default-person-handle", handle) + self.emit('home-person-changed') def get_name_group_keys(self): cur = self.dbapi.execute("select name from name_group order by name;") @@ -1129,29 +1178,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): [sname, grouping]) self.dbapi.commit() - def set_default_person_handle(self, handle): - cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"]) - row = cur.fetchone() - if row: - cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", - [handle, "default-person"]) - else: - cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", - ["default-person", handle]) - self.emit('home-person-changed') - self.dbapi.commit() - - def set_mediapath(self, mediapath): - cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"]) - row = cur.fetchone() - if row: - cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", - [mediapath, "media-path"]) - else: - cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", - ["media-path", mediapath]) - self.dbapi.commit() - def get_raw_person_data(self, handle): if handle in self.person_map: return self.person_map[handle] @@ -1312,6 +1338,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([person.handle],)) + self._has_changed = True def commit_family(self, family, trans, change_time=None): emit = None @@ -1335,6 +1362,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([family.handle],)) + self._has_changed = True def commit_citation(self, citation, trans, change_time=None): emit = None @@ -1362,6 +1390,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([citation.handle],)) + self._has_changed = True def commit_source(self, source, trans, change_time=None): emit = None @@ -1389,6 +1418,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([source.handle],)) + self._has_changed = True def commit_repository(self, repository, trans, change_time=None): emit = None @@ -1411,6 +1441,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([repository.handle],)) + self._has_changed = True def commit_note(self, note, trans, change_time=None): emit = None @@ -1433,6 +1464,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([note.handle],)) + self._has_changed = True def commit_place(self, place, trans, change_time=None): emit = None @@ -1460,6 +1492,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([place.handle],)) + self._has_changed = True def commit_event(self, event, trans, change_time=None): emit = None @@ -1493,6 +1526,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([event.handle],)) + self._has_changed = True def update_backlinks(self, obj): # First, delete the current references: @@ -1773,12 +1807,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not transaction.batch: self.dbapi.commit() - ## Missing: - - def backup(self): - ## FIXME - pass - def close(self): if self._directory: from gramps.plugins.export.exportxml import XmlWriter @@ -1788,6 +1816,39 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): writer.write(filename) filename = os.path.join(self._directory, "meta_data.db") touch(filename) + # Save metadata + self.set_metadata('bookmarks', self.bookmarks.get()) + self.set_metadata('family_bookmarks', self.family_bookmarks.get()) + self.set_metadata('event_bookmarks', self.event_bookmarks.get()) + self.set_metadata('source_bookmarks', self.source_bookmarks.get()) + self.set_metadata('citation_bookmarks', self.citation_bookmarks.get()) + self.set_metadata('repo_bookmarks', self.repo_bookmarks.get()) + self.set_metadata('media_bookmarks', self.media_bookmarks.get()) + self.set_metadata('place_bookmarks', self.place_bookmarks.get()) + self.set_metadata('note_bookmarks', self.note_bookmarks.get()) + + # Custom type values, sets + self.set_metadata('event_names', self.event_names) + self.set_metadata('fattr_names', self.family_attributes) + self.set_metadata('pattr_names', self.individual_attributes) + self.set_metadata('sattr_names', self.source_attributes) + self.set_metadata('marker_names', self.marker_names) + self.set_metadata('child_refs', self.child_ref_types) + self.set_metadata('family_rels', self.family_rel_types) + self.set_metadata('event_roles', self.event_role_names) + self.set_metadata('name_types', self.name_types) + self.set_metadata('origin_types', self.origin_types) + self.set_metadata('repo_types', self.repository_types) + self.set_metadata('note_types', self.note_types) + self.set_metadata('sm_types', self.source_media_types) + self.set_metadata('url_types', self.url_types) + self.set_metadata('mattr_names', self.media_attributes) + self.set_metadata('eattr_names', self.event_attributes) + self.set_metadata('place_types', self.place_types) + + # surname list + self.set_metadata('surname_list', self.surname_list) + self.dbapi.close() def find_backlink_handles(self, handle, include_classes=None): @@ -1815,136 +1876,185 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): yield (row["obj_class"], row["obj_handle"]) def find_initial_person(self): - items = self.person_map.keys() - if len(items) > 0: - return self.get_person_from_handle(list(items)[0]) - return None - - def find_place_child_handles(self, handle): - ## FIXME - return [] + handle = self.get_default_handle() + person = None + if handle: + person = self.get_person_from_handle(handle) + if person: + return person + cur = self.dbapi.execute("SELECT handle FROM person;") + row = cur.fetchone() + if row: + return row[0] def get_bookmarks(self): return self.bookmarks - def get_child_reference_types(self): - ## FIXME - return [] - def get_citation_bookmarks(self): return self.citation_bookmarks def get_default_handle(self): - cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"]) - row = cur.fetchone() - if row: - return row["value"] - else: - return None + return self.get_metadata("default-person-handle", None) - def get_event_attribute_types(self): - ## FIXME - return [] - - def get_event_bookmarks(self): - return self.event_bookmarks - - def get_event_roles(self): - ## FIXME - return [] - - def get_event_types(self): - ## FIXME - return [] - - def get_family_attribute_types(self): - ## FIXME - return [] - - def get_family_bookmarks(self): - return self.family_bookmarks - - def get_family_event_types(self): - ## FIXME - return [] - - def get_family_relation_types(self): - ## FIXME - return [] - - def get_media_attribute_types(self): - ## FIXME - return [] - - def get_media_bookmarks(self): - return self.media_bookmarks - - def get_name_types(self): - ## FIXME - return [] - - def get_note_bookmarks(self): - return self.note_bookmarks - - def get_note_types(self): - ## FIXME - return [] - - def get_origin_types(self): - ## FIXME - return [] - - def get_person_attribute_types(self): - ## FIXME - return [] - - def get_person_event_types(self): - ## FIXME - return [] - - def get_place_bookmarks(self): - return self.place_bookmarks - - def get_place_tree_cursor(self): - ## FIXME - return [] - - def get_place_types(self): - ## FIXME - return [] - - def get_repo_bookmarks(self): - return self.repo_bookmarks - - def get_repository_types(self): - ## FIXME - return [] - - def get_save_path(self): - return self._directory - - def get_source_attribute_types(self): - ## FIXME - return [] - - def get_source_bookmarks(self): - return self.source_bookmarks - - def get_source_media_types(self): + def find_place_child_handles(self, handle): ## FIXME return [] def get_surname_list(self): - ## FIXME - return [] + """ + Return the list of locale-sorted surnames contained in the database. + """ + return self.surname_list + + def get_event_attribute_types(self): + """ + Return a list of all Attribute types assocated with Event instances + in the database. + """ + return list(self.event_attributes) + + def get_event_types(self): + """ + Return a list of all event types in the database. + """ + return list(self.event_names) + + def get_person_event_types(self): + """ + Deprecated: Use get_event_types + """ + return list(self.event_names) + + def get_person_attribute_types(self): + """ + Return a list of all Attribute types assocated with Person instances + in the database. + """ + return list(self.individual_attributes) + + def get_family_attribute_types(self): + """ + Return a list of all Attribute types assocated with Family instances + in the database. + """ + return list(self.family_attributes) + + def get_family_event_types(self): + """ + Deprecated: Use get_event_types + """ + return list(self.event_names) + + def get_media_attribute_types(self): + """ + Return a list of all Attribute types assocated with Media and MediaRef + instances in the database. + """ + return list(self.media_attributes) + + def get_family_relation_types(self): + """ + Return a list of all relationship types assocated with Family + instances in the database. + """ + return list(self.family_rel_types) + + def get_child_reference_types(self): + """ + Return a list of all child reference types assocated with Family + instances in the database. + """ + return list(self.child_ref_types) + + def get_event_roles(self): + """ + Return a list of all custom event role names assocated with Event + instances in the database. + """ + return list(self.event_role_names) + + def get_name_types(self): + """ + Return a list of all custom names types assocated with Person + instances in the database. + """ + return list(self.name_types) + + def get_origin_types(self): + """ + Return a list of all custom origin types assocated with Person/Surname + instances in the database. + """ + return list(self.origin_types) + + def get_repository_types(self): + """ + Return a list of all custom repository types assocated with Repository + instances in the database. + """ + return list(self.repository_types) + + def get_note_types(self): + """ + Return a list of all custom note types assocated with Note instances + in the database. + """ + return list(self.note_types) + + def get_source_attribute_types(self): + """ + Return a list of all Attribute types assocated with Source/Citation + instances in the database. + """ + return list(self.source_attributes) + + def get_source_media_types(self): + """ + Return a list of all custom source media types assocated with Source + instances in the database. + """ + return list(self.source_media_types) def get_url_types(self): - ## FIXME - return [] + """ + Return a list of all custom names types assocated with Url instances + in the database. + """ + return list(self.url_types) + + def get_place_types(self): + """ + Return a list of all custom place types assocated with Place instances + in the database. + """ + return list(self.place_types) + + def get_event_bookmarks(self): + return self.event_bookmarks + + def get_family_bookmarks(self): + return self.family_bookmarks + + def get_media_bookmarks(self): + return self.media_bookmarks + + def get_note_bookmarks(self): + return self.note_bookmarks + + def get_place_bookmarks(self): + return self.place_bookmarks + + def get_repo_bookmarks(self): + return self.repo_bookmarks + + def get_save_path(self): + return self._directory + + def get_source_bookmarks(self): + return self.source_bookmarks def has_changed(self): - ## FIXME - return True + return self._has_changed def is_open(self): return self._directory is not None @@ -1994,7 +2104,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def iter_tags(self): return (Tag.create(data[1]) for data in self.get_tag_cursor()) - def load(self, directory, pulse_progress=None, mode=None, + def load(self, directory, callback=None, mode=None, force_schema_upgrade=False, force_bsddb_upgrade=False, force_bsddb_downgrade=False, @@ -2102,19 +2212,50 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dbapi.execute("""CREATE INDEX IF NOT EXISTS ref_handle ON reference (ref_handle); """) + # Load metadata + self.bookmarks.set(self.get_metadata('bookmarks')) + self.family_bookmarks.set(self.get_metadata('family_bookmarks')) + self.event_bookmarks.set(self.get_metadata('event_bookmarks')) + self.source_bookmarks.set(self.get_metadata('source_bookmarks')) + self.citation_bookmarks.set(self.get_metadata('citation_bookmarks')) + self.repo_bookmarks.set(self.get_metadata('repo_bookmarks')) + self.media_bookmarks.set(self.get_metadata('media_bookmarks')) + self.place_bookmarks.set(self.get_metadata('place_bookmarks')) + self.note_bookmarks.set(self.get_metadata('note_bookmarks')) - def redo(self, update_history=True): - ## FIXME - pass - - def restore(self): - ## FIXME - pass - + # Custom type values + self.event_names = self.get_metadata('event_names', set()) + self.family_attributes = self.get_metadata('fattr_names', set()) + self.individual_attributes = self.get_metadata('pattr_names', set()) + self.source_attributes = self.get_metadata('sattr_names', set()) + self.marker_names = self.get_metadata('marker_names', set()) + self.child_ref_types = self.get_metadata('child_refs', set()) + self.family_rel_types = self.get_metadata('family_rels', set()) + self.event_role_names = self.get_metadata('event_roles', set()) + self.name_types = self.get_metadata('name_types', set()) + self.origin_types = self.get_metadata('origin_types', set()) + self.repository_types = self.get_metadata('repo_types', set()) + self.note_types = self.get_metadata('note_types', set()) + self.source_media_types = self.get_metadata('sm_types', set()) + self.url_types = self.get_metadata('url_types', set()) + self.media_attributes = self.get_metadata('mattr_names', set()) + self.event_attributes = self.get_metadata('eattr_names', set()) + self.place_types = self.get_metadata('place_types', set()) + + # surname list + self.surname_list = self.get_metadata('surname_list') + def set_prefixes(self, person, media, family, source, citation, place, event, repository, note): - ## FIXME - pass + self.set_person_id_prefix(person) + self.set_object_id_prefix(media) + self.set_family_id_prefix(family) + self.set_source_id_prefix(source) + self.set_citation_id_prefix(citation) + self.set_place_id_prefix(place) + self.set_event_id_prefix(event) + self.set_repository_id_prefix(repository) + self.set_note_id_prefix(note) def set_save_path(self, directory): self._directory = directory @@ -2122,10 +2263,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.path = self.full_name self.brief_name = os.path.basename(self._directory) - def undo(self, update_history=True): - ## FIXME - pass - def write_version(self, directory): """Write files for a newly created DB.""" versionpath = os.path.join(directory, str(DBBACKEND)) @@ -2438,3 +2575,23 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def _order_by_tag_key(self, tag): return glocale.sort_key(tag.get_name()) + def backup(self): + ## FIXME + pass + + def restore(self): + ## FIXME + pass + + def get_undodb(self): + ## FIXME + return None + + def undo(self, update_history=True): + ## FIXME + pass + + def redo(self, update_history=True): + ## FIXME + pass + diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 8b8d8beb7..3fe7e0da5 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -1725,7 +1725,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): def iter_tags(self): return (Tag.create(key) for key in self.tag_map.values()) - def load(self, directory, pulse_progress=None, mode=None, + def load(self, directory, callback=None, mode=None, force_schema_upgrade=False, force_bsddb_upgrade=False, force_bsddb_downgrade=False, diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 0091d033a..a7f64f13e 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -360,7 +360,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if directory: self.load(directory) - def load(self, directory, pulse_progress=None, mode=None, + def load(self, directory, callback=None, mode=None, force_schema_upgrade=False, force_bsddb_upgrade=False, force_bsddb_downgrade=False, From 5e96f8a72e610b3800d5af1f1da4f437f65f7342 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Mon, 25 May 2015 17:53:14 -0400 Subject: [PATCH 046/105] DB-API: added undo-redo infrastructure --- gramps/gen/db/__init__.py | 1 + gramps/gen/db/undoredo.py | 73 +++++++++++++++++----- gramps/plugins/database/dbapi.py | 101 +++++++++++++++++++++++++------ 3 files changed, 141 insertions(+), 34 deletions(-) diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py index 10f751303..d9fcf1ad1 100644 --- a/gramps/gen/db/__init__.py +++ b/gramps/gen/db/__init__.py @@ -88,6 +88,7 @@ from .base import * from .dbconst import * from .txn import * from .exceptions import * +from .undoredo import * def find_surname_name(key, data): """ diff --git a/gramps/gen/db/undoredo.py b/gramps/gen/db/undoredo.py index b12735093..bfcd652d6 100644 --- a/gramps/gen/db/undoredo.py +++ b/gramps/gen/db/undoredo.py @@ -1,4 +1,10 @@ +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- import time +import pickle from collections import deque class DbUndo(object): @@ -19,6 +25,20 @@ class DbUndo(object): self.redoq = deque() self.undo_history_timestamp = time.time() self.txn = None + # N.B. the databases have to be in the same order as the numbers in + # xxx_KEY in gen/db/dbconst.py + self.mapbase = ( + self.db.person_map, + self.db.family_map, + self.db.source_map, + self.db.event_map, + self.db.media_map, + self.db.place_map, + self.db.repository_map, + self.db.note_map, + self.db.tag_map, + self.db.citation_map, + ) def clear(self): """ @@ -85,6 +105,16 @@ class DbUndo(object): """ raise NotImplementedError + def __redo(self, update_history): + """ + """ + raise NotImplementedError + + def __undo(self, update_history): + """ + """ + raise NotImplementedError + def commit(self, txn, msg): """ Commit the transaction to the undo/redo database. "txn" should be @@ -110,27 +140,40 @@ class DbUndo(object): return False return self.__redo(update_history) - def undoredo(func): + def undo_reference(self, data, handle, db_map): """ - Decorator function to wrap undo and redo operations within a bsddb - transaction. It also catches bsddb errors and raises an exception - as appropriate + Helper method to undo a reference map entry """ - pass + try: + if data is None: + db_map.delete(handle, txn=self.txn) + else: + db_map.put(handle, data, txn=self.txn) - def __redo(self, update_history=True): - """ - Access the last undone transaction, and revert the data to the state - before the transaction was undone. - """ - pass + except DBERRS as msg: + self.db._log_error() + raise DbError(msg) - def __undo(self, db=None, update_history=True): + def undo_data(self, data, handle, db_map, emit, signal_root): """ - Access the last committed transaction, and revert the data to the - state before the transaction was committed. + Helper method to undo/redo the changes made """ - pass + try: + if data is None: + emit(signal_root + '-delete', ([handle2internal(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) + emit(signal, ([handle2internal(handle)],)) + + except DBERRS as msg: + self.db._log_error() + raise DbError(msg) undo_count = property(lambda self:len(self.undoq)) redo_count = property(lambda self:len(self.redoq)) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index aad21039e..9df140e32 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -19,12 +19,11 @@ import shutil import gramps from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, +from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, DbUndo, KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP, CLASS_TO_KEY_MAP) from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback -from gramps.gen.db.undoredo import DbUndo from gramps.gen.db.dbconst import * from gramps.gen.db import (PERSON_KEY, FAMILY_KEY, @@ -52,6 +51,71 @@ def touch(fname, mode=0o666, dir_fd=None, **kwargs): os.utime(f.fileno() if os.utime in os.supports_fd else fname, dir_fd=None if os.supports_fd else dir_fd, **kwargs) +class DBAPIUndo(DbUndo): + def __init__(self, grampsdb, path): + super(DBAPIUndo, self).__init__(grampsdb) + self.undodb = grampsdb + self.path = path + + def open(self, value=None): + """ + Open the backing storage. Needs to be overridden in the derived + class. + """ + pass + # FIXME + + def close(self): + """ + Close the backing storage. Needs to be overridden in the derived + class. + """ + pass + # FIXME + + def append(self, value): + """ + Add a new entry on the end. Needs to be overridden in the derived + class. + """ + pass + # FIXME + + def __getitem__(self, index): + """ + Returns an entry by index number. Needs to be overridden in the + derived class. + """ + return None + # FIXME + + def __setitem__(self, index, value): + """ + Set an entry to a value. Needs to be overridden in the derived class. + """ + pass + # FIXME + + def __len__(self): + """ + Returns the number of entries. Needs to be overridden in the derived + class. + """ + return 0 + # FIXME + + def __redo(self, update_history): + """ + """ + pass + # FIXME + + def __undo(self, update_history): + """ + """ + pass + # FIXME + class Environment(object): """ Implements the Environment API. @@ -427,6 +491,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.set_repository_id_prefix('R%04d') self.set_note_id_prefix('N%04d') # ---------------------------------- + self.undodb = None self.id_trans = DBAPITxn("ID Transaction", self) self.fid_trans = DBAPITxn("FID Transaction", self) self.pid_trans = DBAPITxn("PID Transaction", self) @@ -484,19 +549,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): contains_func="has_gramps_id_func") self.tag_map = Map(Table(self._tables["Tag"])) self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) - ## FIXME: add appropriate methods: - self.name_group = Map(Table({"handles_func": self.get_name_group_keys, # keys - "has_handle_func": self.has_name_group_key, # key in table - "cursor_func": None, # create a cursor, values - "add_func": self.set_name_group_mapping, # add a key, value - "count_func": None})) # how many items? self.undo_callback = None self.redo_callback = None self.undo_history_callback = None self.modified = 0 self.txn = DBAPITxn("DBAPI Transaction", self) self.transaction = None - self.undodb = DbUndo(self) self.abort_possible = False self._bm_changes = 0 self._directory = directory @@ -984,7 +1042,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return (handle for handle in self.family_map.keys()) def get_tag_from_name(self, name): - ## Slow, but typically not too many tags: + ## FIXME: Slow, but typically not too many tags: for data in self.tag_map.values(): tag = Tag.create(data) if tag.name == name: @@ -2244,6 +2302,11 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # surname list self.surname_list = self.get_metadata('surname_list') + + self._directory = directory + self.undolog = os.path.join(self._directory, DBUNDOFN) + self.undodb = DBAPIUndo(self, self.undolog) + self.undodb.open() def set_prefixes(self, person, media, family, source, citation, place, event, repository, note): @@ -2576,22 +2639,22 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return glocale.sort_key(tag.get_name()) def backup(self): - ## FIXME + """ + If you wish to support an optional backup routine, put it here. + """ pass def restore(self): - ## FIXME + """ + If you wish to support an optional restore routine, put it here. + """ pass def get_undodb(self): - ## FIXME - return None + return self.undodb def undo(self, update_history=True): - ## FIXME - pass + return self.undodb.undo(update_history) def redo(self, update_history=True): - ## FIXME - pass - + return self.undodb.redo(update_history) From 986022cee155f4618736746066b2e692b0ec3f33 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Mon, 25 May 2015 19:04:27 -0400 Subject: [PATCH 047/105] DB-API: committing objects updates secondary items --- gramps/plugins/database/dbapi.py | 173 ++++++++++++++++++++++++++++--- 1 file changed, 160 insertions(+), 13 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 9df140e32..c8673104f 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -10,6 +10,7 @@ import re import os import logging import shutil +import bisect #------------------------------------------------------------------------ # @@ -1374,6 +1375,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = None if person.handle in self.person_map: emit = "person-update" + old_person = self.get_person_from_handle(person.handle) + # Update gender statistics if necessary + if (old_person.gender != person.gender or + old_person.primary_name.first_name != + person.primary_name.first_name): + + self.genderStats.uncount_person(old_person) + self.genderStats.count_person(person) + # Update surname list if necessary + if (self._order_by_person_key(person) != + self._order_by_person_key(old_person)): + self.remove_from_surname_list(old_person) + self.add_to_surname_list(person, trans.batch) + # update the person: self.dbapi.execute("""UPDATE person SET gramps_id = ?, order_by = ?, blob = ? @@ -1384,6 +1399,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): person.handle]) else: emit = "person-add" + self.genderStats.count_person(person) + self.add_to_surname_list(person, trans.batch) + # Insert the person: self.dbapi.execute("""insert into person (handle, order_by, gramps_id, blob) values(?, ?, ?, ?);""", [person.handle, @@ -1393,11 +1411,68 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(person) + # Other misc update tasks: + self.individual_attributes.update( + [str(attr.type) for attr in person.attribute_list + if attr.type.is_custom() and str(attr.type)]) + + self.event_role_names.update([str(eref.role) + for eref in person.event_ref_list + if eref.role.is_custom()]) + + self.name_types.update([str(name.type) + for name in ([person.primary_name] + + person.alternate_names) + if name.type.is_custom()]) + all_surn = [] # new list we will use for storage + all_surn += person.primary_name.get_surname_list() + for asurname in person.alternate_names: + all_surn += asurname.get_surname_list() + self.origin_types.update([str(surn.origintype) for surn in all_surn + if surn.origintype.is_custom()]) + all_surn = None + self.url_types.update([str(url.type) for url in person.urls + if url.type.is_custom()]) + attr_list = [] + for mref in person.media_list: + attr_list += [str(attr.type) for attr in mref.attribute_list + if attr.type.is_custom() and str(attr.type)] + self.media_attributes.update(attr_list) # Emit after added: if emit: self.emit(emit, ([person.handle],)) self._has_changed = True + def add_to_surname_list(self, person, batch_transaction): + """ + Add surname to surname list + """ + if batch_transaction: + return + name = conv_to_unicode(self._order_by_person_key(person), 'utf-8') + i = bisect.bisect(self.surname_list, name) + if 0 < i <= len(self.surname_list): + if self.surname_list[i-1] != name: + self.surname_list.insert(i, name) + else: + self.surname_list.insert(i, name) + + def remove_from_surname_list(self, person): + """ + Check whether there are persons with the same surname left in + the database. + + If not then we need to remove the name from the list. + The function must be overridden in the derived class. + """ + name = self._order_by_person_key(person) + if isinstance(name, str): + uname = name + name = name.encode('utf-8') + else: + uname = str(name) + # FIXME: check database + def commit_family(self, family, trans, change_time=None): emit = None if family.handle in self.family_map: @@ -1417,6 +1492,31 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(family) + # Misc updates: + self.family_attributes.update( + [str(attr.type) for attr in family.attribute_list + if attr.type.is_custom() and str(attr.type)]) + + rel_list = [] + for ref in family.child_ref_list: + if ref.frel.is_custom(): + rel_list.append(str(ref.frel)) + if ref.mrel.is_custom(): + rel_list.append(str(ref.mrel)) + self.child_ref_types.update(rel_list) + + self.event_role_names.update( + [str(eref.role) for eref in family.event_ref_list + if eref.role.is_custom()]) + + if family.type.is_custom(): + self.family_rel_types.add(str(family.type)) + + attr_list = [] + for mref in family.media_list: + attr_list += [str(attr.type) for attr in mref.attribute_list + if attr.type.is_custom() and str(attr.type)] + self.media_attributes.update(attr_list) # Emit after added: if emit: self.emit(emit, ([family.handle],)) @@ -1445,6 +1545,17 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(citation) + # Misc updates: + attr_list = [] + for mref in citation.media_list: + attr_list += [str(attr.type) for attr in mref.attribute_list + if attr.type.is_custom() and str(attr.type)] + self.media_attributes.update(attr_list) + + self.source_attributes.update( + [str(attr.type) for attr in citation.attribute_list + if attr.type.is_custom() and str(attr.type)]) + # Emit after added: if emit: self.emit(emit, ([citation.handle],)) @@ -1473,6 +1584,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(source) + # Misc updates: + self.source_media_types.update( + [str(ref.media_type) for ref in source.reporef_list + if ref.media_type.is_custom()]) + + attr_list = [] + for mref in source.media_list: + attr_list += [str(attr.type) for attr in mref.attribute_list + if attr.type.is_custom() and str(attr.type)] + self.media_attributes.update(attr_list) + self.source_attributes.update( + [str(attr.type) for attr in source.attribute_list + if attr.type.is_custom() and str(attr.type)]) # Emit after added: if emit: self.emit(emit, ([source.handle],)) @@ -1496,6 +1620,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(repository) + # Misc updates: + if repository.type.is_custom(): + self.repository_types.add(str(repository.type)) + + self.url_types.update([str(url.type) for url in repository.urls + if url.type.is_custom()]) # Emit after added: if emit: self.emit(emit, ([repository.handle],)) @@ -1519,6 +1649,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(note) + # Misc updates: + if note.type.is_custom(): + self.note_types.add(str(note.type)) # Emit after added: if emit: self.emit(emit, ([note.handle],)) @@ -1547,6 +1680,18 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(place) + # Misc updates: + if place.get_type().is_custom(): + self.place_types.add(str(place.get_type())) + + self.url_types.update([str(url.type) for url in place.urls + if url.type.is_custom()]) + + attr_list = [] + for mref in place.media_list: + attr_list += [str(attr.type) for attr in mref.attribute_list + if attr.type.is_custom() and str(attr.type)] + self.media_attributes.update(attr_list) # Emit after added: if emit: self.emit(emit, ([place.handle],)) @@ -1569,18 +1714,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): [event.handle, event.gramps_id, pickle.dumps(event.serialize())]) - - self.update_event_attributes( - [str(attr.type) for attr in event.attribute_list - if attr.type.is_custom() and str(attr.type)]) - - if event.type.is_custom(): - self.update_event_names(str(event.type)) - if not trans.batch: self.dbapi.commit() self.update_backlinks(event) - + # Misc updates: + self.event_attributes.update( + [str(attr.type) for attr in event.attribute_list + if attr.type.is_custom() and str(attr.type)]) + if event.type.is_custom(): + self.event_names.add(str(event.type)) + attr_list = [] + for mref in event.media_list: + attr_list += [str(attr.type) for attr in mref.attribute_list + if attr.type.is_custom() and str(attr.type)] + self.media_attributes.update(attr_list) # Emit after added: if emit: self.emit(emit, ([event.handle],)) @@ -1657,6 +1804,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(media) + # Misc updates: + self.media_attributes.update( + [str(attr.type) for attr in media.attribute_list + if attr.type.is_custom() and str(attr.type)]) # Emit after added: if emit: self.emit(emit, ([media.handle],)) @@ -1954,10 +2105,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def get_default_handle(self): return self.get_metadata("default-person-handle", None) - def find_place_child_handles(self, handle): - ## FIXME - return [] - def get_surname_list(self): """ Return the list of locale-sorted surnames contained in the database. From 54cc6cbff3bb5c3302211a9d9757174934992b08 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Mon, 25 May 2015 21:06:44 -0400 Subject: [PATCH 048/105] DB-API: surname_list not working; added tag map support --- gramps/plugins/database/dbapi.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index c8673104f..36e4e6a51 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -10,7 +10,6 @@ import re import os import logging import shutil -import bisect #------------------------------------------------------------------------ # @@ -463,8 +462,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): "handles_func": self.get_tag_handles, "add_func": self.add_tag, "commit_func": self.commit_tag, + "has_handle_func": self.has_handle_for_tag, "iter_func": self.iter_tags, "count": self.get_number_of_tags, + "raw_func": self._get_raw_tag_data, }) # skip GEDCOM cross-ref check for now: self.set_feature("skip-check-xref", True) @@ -1449,13 +1450,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): """ if batch_transaction: return - name = conv_to_unicode(self._order_by_person_key(person), 'utf-8') - i = bisect.bisect(self.surname_list, name) - if 0 < i <= len(self.surname_list): - if self.surname_list[i-1] != name: - self.surname_list.insert(i, name) - else: - self.surname_list.insert(i, name) + #name = self._order_by_person_key(person) + #i = bisect.bisect(self.surname_list, name) + #if 0 < i <= len(self.surname_list): + # if self.surname_list[i-1] != name: + # self.surname_list.insert(i, name) + #else: + # self.surname_list.insert(i, name) def remove_from_surname_list(self, person): """ @@ -1466,11 +1467,11 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): The function must be overridden in the derived class. """ name = self._order_by_person_key(person) - if isinstance(name, str): - uname = name - name = name.encode('utf-8') - else: - uname = str(name) + #if isinstance(name, str): + # uname = name + # name = name.encode('utf-8') + #else: + # uname = str(name) # FIXME: check database def commit_family(self, family, trans, change_time=None): @@ -2615,6 +2616,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): cur = self.dbapi.execute("select * from note where handle = ?", [key]) return cur.fetchone() != None + def has_handle_for_tag(self, key): + cur = self.dbapi.execute("select * from tag where handle = ?", [key]) + return cur.fetchone() != None + def has_gramps_id_for_person(self, key): cur = self.dbapi.execute("select * from person where gramps_id = ?", [key]) return cur.fetchone() != None From 3cbc012f6fd74ae3b6c1986a6d1b1ce719d8668c Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Mon, 25 May 2015 22:31:36 -0400 Subject: [PATCH 049/105] DB-API: sql clean up; some FIXME's still left --- gramps/plugins/database/dbapi.py | 286 +++++++++++++++++-------------- 1 file changed, 154 insertions(+), 132 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 36e4e6a51..51a409240 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -10,6 +10,7 @@ import re import os import logging import shutil +import bisect #------------------------------------------------------------------------ # @@ -865,12 +866,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit('home-person-changed') def get_name_group_keys(self): - cur = self.dbapi.execute("select name from name_group order by name;") + cur = self.dbapi.execute("SELECT name FROM name_group ORDER BY name;") rows = cur.fetchall() return [row[0] for row in rows] def get_name_group_mapping(self, key): - cur = self.dbapi.execute("select grouping from name_group where name = ?;", + cur = self.dbapi.execute("SELECT grouping FROM name_group WHERE name = ?;", [key]) row = cur.fetchone() if row: @@ -885,62 +886,62 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return [row[0] for row in rows] def get_family_handles(self): - cur = self.dbapi.execute("select handle from family;") + cur = self.dbapi.execute("SELECT handle FROM family;") rows = cur.fetchall() return [row[0] for row in rows] def get_event_handles(self): - cur = self.dbapi.execute("select handle from event;") + cur = self.dbapi.execute("SELECT handle FROM event;") rows = cur.fetchall() return [row[0] for row in rows] def get_citation_handles(self, sort_handles=False): if sort_handles: - cur = self.dbapi.execute("select handle from citation ORDER BY order_by;") + cur = self.dbapi.execute("SELECT handle FROM citation ORDER BY order_by;") else: - cur = self.dbapi.execute("select handle from citation;") + cur = self.dbapi.execute("SELECT handle FROM citation;") rows = cur.fetchall() return [row[0] for row in rows] def get_source_handles(self, sort_handles=False): if sort_handles: - cur = self.dbapi.execute("select handle from source ORDER BY order_by;") + cur = self.dbapi.execute("SELECT handle FROM source ORDER BY order_by;") else: - cur = self.dbapi.execute("select handle from source;") + cur = self.dbapi.execute("SELECT handle from source;") rows = cur.fetchall() return [row[0] for row in rows] def get_place_handles(self, sort_handles=False): if sort_handles: - cur = self.dbapi.execute("select handle from place ORDER BY order_by;") + cur = self.dbapi.execute("SELECT handle FROM place ORDER BY order_by;") else: - cur = self.dbapi.execute("select handle from place;") + cur = self.dbapi.execute("SELECT handle FROM place;") rows = cur.fetchall() return [row[0] for row in rows] def get_repository_handles(self): - cur = self.dbapi.execute("select handle from repository;") + cur = self.dbapi.execute("SELECT handle FROM repository;") rows = cur.fetchall() return [row[0] for row in rows] def get_media_object_handles(self, sort_handles=False): if sort_handles: - cur = self.dbapi.execute("select handle from media ORDER BY order_by;") + cur = self.dbapi.execute("SELECT handle FROM media ORDER BY order_by;") else: - cur = self.dbapi.execute("select handle from media;") + cur = self.dbapi.execute("SELECT handle FROM media;") rows = cur.fetchall() return [row[0] for row in rows] def get_note_handles(self): - cur = self.dbapi.execute("select handle from note;") + cur = self.dbapi.execute("SELECT handle FROM note;") rows = cur.fetchall() return [row[0] for row in rows] def get_tag_handles(self, sort_handles=False): if sort_handles: - cur = self.dbapi.execute("select handle from tag ORDER BY order_by;") + cur = self.dbapi.execute("SELECT handle FROM tag ORDER BY order_by;") else: - cur = self.dbapi.execute("select handle from tag;") + cur = self.dbapi.execute("SELECT handle FROM tag;") rows = cur.fetchall() return [row[0] for row in rows] @@ -1097,52 +1098,52 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def get_number_of_people(self): - cur = self.dbapi.execute("select count(handle) from person;") + cur = self.dbapi.execute("SELECT count(handle) FROM person;") row = cur.fetchone() return row[0] def get_number_of_events(self): - cur = self.dbapi.execute("select count(handle) from event;") + cur = self.dbapi.execute("SELECT count(handle) FROM event;") row = cur.fetchone() return row[0] def get_number_of_places(self): - cur = self.dbapi.execute("select count(handle) from place;") + cur = self.dbapi.execute("SELECT count(handle) FROM place;") row = cur.fetchone() return row[0] def get_number_of_tags(self): - cur = self.dbapi.execute("select count(handle) from tag;") + cur = self.dbapi.execute("SELECT count(handle) FROM tag;") row = cur.fetchone() return row[0] def get_number_of_families(self): - cur = self.dbapi.execute("select count(handle) from family;") + cur = self.dbapi.execute("SELECT count(handle) FROM family;") row = cur.fetchone() return row[0] def get_number_of_notes(self): - cur = self.dbapi.execute("select count(handle) from note;") + cur = self.dbapi.execute("SELECT count(handle) FROM note;") row = cur.fetchone() return row[0] def get_number_of_citations(self): - cur = self.dbapi.execute("select count(handle) from citation;") + cur = self.dbapi.execute("SELECT count(handle) FROM citation;") row = cur.fetchone() return row[0] def get_number_of_sources(self): - cur = self.dbapi.execute("select count(handle) from source;") + cur = self.dbapi.execute("SELECT count(handle) FROM source;") row = cur.fetchone() return row[0] def get_number_of_media_objects(self): - cur = self.dbapi.execute("select count(handle) from media;") + cur = self.dbapi.execute("SELECT count(handle) FROM media;") row = cur.fetchone() return row[0] def get_number_of_repositories(self): - cur = self.dbapi.execute("select count(handle) from repository;") + cur = self.dbapi.execute("SELECT count(handle) FROM repository;") row = cur.fetchone() return row[0] @@ -1221,20 +1222,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return handle in self.media_map def has_name_group_key(self, key): - cur = self.dbapi.execute("select grouping from name_group where name = ?;", + cur = self.dbapi.execute("SELECT grouping FROM name_group WHERE name = ?;", [key]) row = cur.fetchone() return True if row else False def set_name_group_mapping(self, name, grouping): sname = name.encode("utf-8") - cur = self.dbapi.execute("SELECT * from name_group where name = ?;", + cur = self.dbapi.execute("SELECT * FROM name_group WHERE name = ?;", [sname]) row = cur.fetchone() if row: - cur = self.dbapi.execute("DELETE from name_group where name = ?;", + cur = self.dbapi.execute("DELETE FROM name_group WHERE name = ?;", [sname]) - cur = self.dbapi.execute("INSERT into name_group (name, grouping) VALUES(?, ?);", + cur = self.dbapi.execute("INSERT INTO name_group (name, grouping) VALUES(?, ?);", [sname, grouping]) self.dbapi.commit() @@ -1403,15 +1404,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.genderStats.count_person(person) self.add_to_surname_list(person, trans.batch) # Insert the person: - self.dbapi.execute("""insert into person (handle, order_by, gramps_id, blob) - values(?, ?, ?, ?);""", + self.dbapi.execute("""INSERT INTO person (handle, order_by, gramps_id, blob) + VALUES(?, ?, ?, ?);""", [person.handle, self._order_by_person_key(person), person.gramps_id, pickle.dumps(person.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(person) + self.dbapi.commit() # Other misc update tasks: self.individual_attributes.update( [str(attr.type) for attr in person.attribute_list @@ -1450,13 +1451,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): """ if batch_transaction: return - #name = self._order_by_person_key(person) - #i = bisect.bisect(self.surname_list, name) - #if 0 < i <= len(self.surname_list): - # if self.surname_list[i-1] != name: - # self.surname_list.insert(i, name) - #else: - # self.surname_list.insert(i, name) + # TODO: check to see if this is correct + name = None + primary_name = person.get_primary_name() + if primary_name: + surname_list = primary_name.get_surname_list() + if len(surname_list) > 0: + name = surname_list[0].surname + if name is None: + return + i = bisect.bisect(self.surname_list, name) + if 0 < i <= len(self.surname_list): + if self.surname_list[i-1] != name: + self.surname_list.insert(i, name) + else: + self.surname_list.insert(i, name) def remove_from_surname_list(self, person): """ @@ -1466,13 +1475,16 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): If not then we need to remove the name from the list. The function must be overridden in the derived class. """ - name = self._order_by_person_key(person) - #if isinstance(name, str): - # uname = name - # name = name.encode('utf-8') - #else: - # uname = str(name) - # FIXME: check database + name = None + primary_name = person.get_primary_name() + if primary_name: + surname_list = primary_name.get_surname_list() + if len(surname_list) > 0: + name = surname_list[0].surname + if name is None: + return + if name in self.surname_list: + self.surname_list.remove(name) def commit_family(self, family, trans, change_time=None): emit = None @@ -1486,13 +1498,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): family.handle]) else: emit = "family-add" - self.dbapi.execute("""insert into family (handle, gramps_id, blob) - values(?, ?, ?);""", + self.dbapi.execute("""INSERT INTO family (handle, gramps_id, blob) + VALUES(?, ?, ?);""", [family.handle, family.gramps_id, pickle.dumps(family.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(family) + self.dbapi.commit() # Misc updates: self.family_attributes.update( [str(attr.type) for attr in family.attribute_list @@ -1537,15 +1549,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): citation.handle]) else: emit = "citation-add" - self.dbapi.execute("""insert into citation (handle, order_by, gramps_id, blob) - values(?, ?, ?, ?);""", + self.dbapi.execute("""INSERT INTO citation (handle, order_by, gramps_id, blob) + VALUES(?, ?, ?, ?);""", [citation.handle, self._order_by_citation_key(citation), citation.gramps_id, pickle.dumps(citation.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(citation) + self.dbapi.commit() # Misc updates: attr_list = [] for mref in citation.media_list: @@ -1576,15 +1588,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): source.handle]) else: emit = "source-add" - self.dbapi.execute("""insert into source (handle, order_by, gramps_id, blob) - values(?, ?, ?, ?);""", + self.dbapi.execute("""INSERT INTO source (handle, order_by, gramps_id, blob) + VALUES(?, ?, ?, ?);""", [source.handle, self._order_by_source_key(source), source.gramps_id, pickle.dumps(source.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(source) + self.dbapi.commit() # Misc updates: self.source_media_types.update( [str(ref.media_type) for ref in source.reporef_list @@ -1615,12 +1627,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): repository.handle]) else: emit = "repository-add" - self.dbapi.execute("""insert into repository (handle, gramps_id, blob) - values(?, ?, ?);""", + self.dbapi.execute("""INSERT INTO repository (handle, gramps_id, blob) + VALUES(?, ?, ?);""", [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(repository) + self.dbapi.commit() # Misc updates: if repository.type.is_custom(): self.repository_types.add(str(repository.type)) @@ -1644,12 +1656,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): note.handle]) else: emit = "note-add" - self.dbapi.execute("""insert into note (handle, gramps_id, blob) - values(?, ?, ?);""", + self.dbapi.execute("""INSERT INTO note (handle, gramps_id, blob) + VALUES(?, ?, ?);""", [note.handle, note.gramps_id, pickle.dumps(note.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(note) + self.dbapi.commit() # Misc updates: if note.type.is_custom(): self.note_types.add(str(note.type)) @@ -1672,15 +1684,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): place.handle]) else: emit = "place-add" - self.dbapi.execute("""insert into place (handle, order_by, gramps_id, blob) - values(?, ?, ?, ?);""", + self.dbapi.execute("""INSERT INTO place (handle, order_by, gramps_id, blob) + VALUES(?, ?, ?, ?);""", [place.handle, self._order_by_place_key(place), place.gramps_id, pickle.dumps(place.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(place) + self.dbapi.commit() # Misc updates: if place.get_type().is_custom(): self.place_types.add(str(place.get_type())) @@ -1710,14 +1722,14 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): event.handle]) else: emit = "event-add" - self.dbapi.execute("""insert into event (handle, gramps_id, blob) - values(?, ?, ?);""", + self.dbapi.execute("""INSERT INTO event (handle, gramps_id, blob) + VALUES(?, ?, ?);""", [event.handle, event.gramps_id, pickle.dumps(event.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(event) + self.dbapi.commit() # Misc updates: self.event_attributes.update( [str(attr.type) for attr in event.attribute_list @@ -1736,27 +1748,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def update_backlinks(self, obj): # First, delete the current references: - self.dbapi.execute("DELETE FROM reference where obj_handle = ?;", + self.dbapi.execute("DELETE FROM reference WHERE obj_handle = ?;", [obj.handle]) # Now, add the current ones: references = set(obj.get_referenced_handles_recursively()) for (ref_class_name, ref_handle) in references: - self.dbapi.execute("""INSERT into reference + self.dbapi.execute("""INSERT INTO reference (obj_handle, obj_class, ref_handle, ref_class) VALUES(?, ?, ?, ?);""", [obj.handle, obj.__class__.__name__, ref_handle, ref_class_name]) - # Will commit later - - def update_event_attributes(self, attr_list): - # FIXME - pass - - def update_event_names(self, event_type): - # FIXME - pass + # This function is followed by a commit. def commit_tag(self, tag, trans, change_time=None): emit = None @@ -1770,14 +1774,14 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): tag.handle]) else: emit = "tag-add" - self.dbapi.execute("""insert into tag (handle, order_by, blob) - values(?, ?, ?);""", + self.dbapi.execute("""INSERT INTO tag (handle, order_by, blob) + VALUES(?, ?, ?);""", [tag.handle, self._order_by_tag_key(tag), pickle.dumps(tag.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(tag) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([tag.handle],)) @@ -1796,15 +1800,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): media.handle]) else: emit = "media-add" - self.dbapi.execute("""insert into media (handle, order_by, gramps_id, blob) - values(?, ?, ?, ?);""", + self.dbapi.execute("""INSERT INTO media (handle, order_by, gramps_id, blob) + VALUES(?, ?, ?, ?);""", [media.handle, self._order_by_media_key(media), media.gramps_id, pickle.dumps(media.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(media) + self.dbapi.commit() # Misc updates: self.media_attributes.update( [str(attr.type) for attr in media.attribute_list @@ -1910,7 +1914,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): person = Person.create(self.person_map[handle]) #del self.person_map[handle] #del self.person_id_map[person.gramps_id] - self.dbapi.execute("DELETE from person WHERE handle = ?;", [handle]) + self.dbapi.execute("DELETE FROM person WHERE handle = ?;", [handle]) self.emit("person-delete", ([handle],)) if not transaction.batch: self.dbapi.commit() @@ -2011,7 +2015,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if self.readonly or not handle: return if handle in data_map: - self.dbapi.execute("DELETE from %s WHERE handle = ?;" % key2table[key], + self.dbapi.execute("DELETE FROM %s WHERE handle = ?;" % key2table[key], [handle]) self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) if not transaction.batch: @@ -2078,7 +2082,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): result_list = list(find_backlink_handles(handle)) """ - cur = self.dbapi.execute("SELECT * from reference WHERE ref_handle = ?;", + cur = self.dbapi.execute("SELECT * FROM reference WHERE ref_handle = ?;", [handle]) rows = cur.fetchall() for row in rows: @@ -2553,7 +2557,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): references = set(obj.get_referenced_handles_recursively()) # handle addition of new references for (ref_class_name, ref_handle) in references: - self.dbapi.execute("""INSERT into reference (obj_handle, obj_class, ref_handle, ref_class) + self.dbapi.execute("""INSERT INTO reference (obj_handle, obj_class, ref_handle, ref_class) VALUES(?, ?, ?, ?);""", [obj.handle, obj.__class__.__name__, @@ -2564,199 +2568,217 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): callback(5) def rebuild_secondary(self, update): - self.reindex_reference_map(update) + pass + # FIXME: rebuild the secondary databases/maps: + ## gender stats + ## event_names + ## fattr_names + ## pattr_names + ## sattr_names + ## marker_names + ## child_refs + ## family_rels + ## event_roles + ## name_types + ## origin_types + ## repo_types + ## note_types + ## sm_types + ## url_types + ## mattr_names + ## eattr_names + ## place_types + # surname list def prepare_import(self): """ - DBAPI does not commit data on gedcom import, but saves them - for later commit. + Do anything needed before an import. """ pass def commit_import(self): """ - Commits the items that were queued up during the last gedcom - import for two step adding. + Do anything needed after an import. """ self.reindex_reference_map(lambda n: n) def has_handle_for_person(self, key): - cur = self.dbapi.execute("select * from person where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM person WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_family(self, key): - cur = self.dbapi.execute("select * from family where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM family WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_source(self, key): - cur = self.dbapi.execute("select * from source where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM source WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_citation(self, key): - cur = self.dbapi.execute("select * from citation where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM citation WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_event(self, key): - cur = self.dbapi.execute("select * from event where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM event WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_media(self, key): - cur = self.dbapi.execute("select * from media where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM media WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_place(self, key): - cur = self.dbapi.execute("select * from place where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM place WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_repository(self, key): - cur = self.dbapi.execute("select * from repository where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM repository WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_note(self, key): - cur = self.dbapi.execute("select * from note where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM note WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_tag(self, key): - cur = self.dbapi.execute("select * from tag where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM tag WHERE handle = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_person(self, key): - cur = self.dbapi.execute("select * from person where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM person WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_family(self, key): - cur = self.dbapi.execute("select * from family where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM family WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_source(self, key): - cur = self.dbapi.execute("select * from source where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM source WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_citation(self, key): - cur = self.dbapi.execute("select * from citation where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM citation WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_event(self, key): - cur = self.dbapi.execute("select * from event where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM event WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_media(self, key): - cur = self.dbapi.execute("select * from media where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM media WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_place(self, key): - cur = self.dbapi.execute("select * from place where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM place WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_repository(self, key): - cur = self.dbapi.execute("select * from repository where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM repository WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_note(self, key): - cur = self.dbapi.execute("select * from note where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM note WHERE gramps_id = ?", [key]) return cur.fetchone() != None def get_person_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from person;") + cur = self.dbapi.execute("SELECT gramps_id FROM person;") rows = cur.fetchall() return [row[0] for row in rows] def get_family_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from family;") + cur = self.dbapi.execute("SELECT gramps_id FROM family;") rows = cur.fetchall() return [row[0] for row in rows] def get_source_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from source;") + cur = self.dbapi.execute("SELECT gramps_id FROM source;") rows = cur.fetchall() return [row[0] for row in rows] def get_citation_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from citation;") + cur = self.dbapi.execute("SELECT gramps_id FROM citation;") rows = cur.fetchall() return [row[0] for row in rows] def get_event_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from event;") + cur = self.dbapi.execute("SELECT gramps_id FROM event;") rows = cur.fetchall() return [row[0] for row in rows] def get_media_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from media;") + cur = self.dbapi.execute("SELECT gramps_id FROM media;") rows = cur.fetchall() return [row[0] for row in rows] def get_place_gramps_ids(self): - cur = self.dbapi.execute("select gramps from place;") + cur = self.dbapi.execute("SELECT gramps FROM place;") rows = cur.fetchall() return [row[0] for row in rows] def get_repository_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from repository;") + cur = self.dbapi.execute("SELECT gramps_id FROM repository;") rows = cur.fetchall() return [row[0] for row in rows] def get_note_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from note;") + cur = self.dbapi.execute("SELECT gramps_id FROM note;") rows = cur.fetchall() return [row[0] for row in rows] def _get_raw_person_data(self, key): - cur = self.dbapi.execute("select blob from person where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM person WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_family_data(self, key): - cur = self.dbapi.execute("select blob from family where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM family WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_source_data(self, key): - cur = self.dbapi.execute("select blob from source where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM source WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_citation_data(self, key): - cur = self.dbapi.execute("select blob from citation where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM citation WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_event_data(self, key): - cur = self.dbapi.execute("select blob from event where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM event WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_media_data(self, key): - cur = self.dbapi.execute("select blob from media where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM media WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_place_data(self, key): - cur = self.dbapi.execute("select blob from place where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM place WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_repository_data(self, key): - cur = self.dbapi.execute("select blob from repository where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM repository WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_note_data(self, key): - cur = self.dbapi.execute("select blob from note where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM note WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_tag_data(self, key): - cur = self.dbapi.execute("select blob from tag where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM tag WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) From 90488ce14b755aaf91e1a7b84e7a174b017b3f8e Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Tue, 26 May 2015 11:35:56 -0400 Subject: [PATCH 050/105] DB-API: fixed error in find_initial_person --- gramps/plugins/database/dbapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 51a409240..54ca72cea 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -2099,7 +2099,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): cur = self.dbapi.execute("SELECT handle FROM person;") row = cur.fetchone() if row: - return row[0] + return self.get_person_from_handle(row[0]) def get_bookmarks(self): return self.bookmarks From b059bdec66361d9e89f28b5dc215a31ce1bcf8fb Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Tue, 12 May 2015 16:30:46 -0400 Subject: [PATCH 051/105] Database backend as a plugin: this set of changes moves most or all of Bsddb from gramps.gen.db to gramps.plugins.database. The id of the plugin is 'bsddb' which can be loaded using the make_database(id, dbstate) API (for now). Next step is to add an identifying text in the directory to indicate which database backend to use. --- gramps/cli/arghandler.py | 5 +- gramps/cli/clidbman.py | 9 +- gramps/cli/grampscli.py | 6 +- gramps/gen/db/__init__.py | 27 ++- gramps/gen/db/backup.py | 209 +---------------- gramps/gen/dbstate.py | 6 +- gramps/gen/plug/_gramplet.py | 1 - gramps/gen/plug/_manager.py | 5 + gramps/gen/plug/_pluginreg.py | 31 ++- gramps/gen/utils/callman.py | 3 +- gramps/gui/dbloader.py | 4 +- gramps/gui/dbman.py | 11 +- gramps/gui/pluginmanager.py | 7 +- gramps/plugins/database/bsddb.gpr.py | 31 +++ gramps/plugins/database/bsddb.py | 3 + .../database/bsddb_support/__init__.py | 96 ++++++++ .../plugins/database/bsddb_support/backup.py | 213 ++++++++++++++++++ .../database/bsddb_support}/bsddbtxn.py | 0 .../database/bsddb_support}/cursor.py | 0 .../database/bsddb_support}/dictionary.py | 27 +-- .../database/bsddb_support}/read.py | 45 ++-- .../database/bsddb_support}/undoredo.py | 8 +- .../database/bsddb_support}/upgrade.py | 31 +-- .../database/bsddb_support}/write.py | 45 ++-- 24 files changed, 507 insertions(+), 316 deletions(-) create mode 100644 gramps/plugins/database/bsddb.gpr.py create mode 100644 gramps/plugins/database/bsddb.py create mode 100644 gramps/plugins/database/bsddb_support/__init__.py create mode 100644 gramps/plugins/database/bsddb_support/backup.py rename gramps/{gen/db => plugins/database/bsddb_support}/bsddbtxn.py (100%) rename gramps/{gen/db => plugins/database/bsddb_support}/cursor.py (100%) rename gramps/{gen/db => plugins/database/bsddb_support}/dictionary.py (98%) rename gramps/{gen/db => plugins/database/bsddb_support}/read.py (98%) rename gramps/{gen/db => plugins/database/bsddb_support}/undoredo.py (98%) rename gramps/{gen/db => plugins/database/bsddb_support}/upgrade.py (98%) rename gramps/{gen/db => plugins/database/bsddb_support}/write.py (99%) diff --git a/gramps/cli/arghandler.py b/gramps/cli/arghandler.py index 0db169d30..c7a5f5a4a 100644 --- a/gramps/cli/arghandler.py +++ b/gramps/cli/arghandler.py @@ -44,7 +44,7 @@ import sys #------------------------------------------------------------------------- from gramps.gen.recentfiles import recent_files from gramps.gen.utils.file import rm_tempdir, get_empty_tempdir -from gramps.gen.db import DbBsddb +from gramps.gen.db import make_database from .clidbman import CLIDbManager, NAME_FILE, find_locker_name from gramps.gen.plug import BasePluginManager @@ -491,7 +491,8 @@ class ArgHandler(object): self.imp_db_path, title = self.dbman.create_new_db_cli() else: self.imp_db_path = get_empty_tempdir("import_dbdir") - newdb = DbBsddb() + + newdb = make_database("bsddb", self.dbstate) newdb.write_version(self.imp_db_path) try: diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py index 404104afe..52b183013 100644 --- a/gramps/cli/clidbman.py +++ b/gramps/cli/clidbman.py @@ -54,7 +54,7 @@ _LOG = logging.getLogger(DBLOGNAME) #------------------------------------------------------------------------- from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -from gramps.gen.db import DbBsddb +from gramps.gen.db import make_database from gramps.gen.plug import BasePluginManager from gramps.gen.config import config from gramps.gen.constfunc import win, conv_to_unicode @@ -294,7 +294,8 @@ class CLIDbManager(object): if create_db: # write the version number into metadata - newdb = DbBsddb() + + newdb = make_database("bsddb", self.dbstate) newdb.write_version(new_path) (tval, last) = time_val(new_path) @@ -360,8 +361,8 @@ class CLIDbManager(object): # Create a new database self.__start_cursor(_("Importing data...")) - dbclass = DbBsddb - dbase = dbclass() + + dbase = make_database("bsddb", self.dbstate) dbase.load(new_path, user.callback) import_function = plugin.get_import_function() diff --git a/gramps/cli/grampscli.py b/gramps/cli/grampscli.py index a45564a19..00b66d352 100644 --- a/gramps/cli/grampscli.py +++ b/gramps/cli/grampscli.py @@ -49,7 +49,7 @@ from gramps.gen.config import config from gramps.gen.const import PLUGINS_DIR, USER_PLUGINS from gramps.gen.errors import DbError from gramps.gen.dbstate import DbState -from gramps.gen.db import DbBsddb +from gramps.gen.db import make_database from gramps.gen.db.exceptions import (DbUpgradeRequiredError, BsddbDowngradeError, DbVersionError, @@ -152,9 +152,9 @@ class CLIDbLoader(object): else: mode = 'w' - dbclass = DbBsddb + db = make_database("bsddb", self.dbstate) - self.dbstate.change_database(dbclass()) + self.dbstate.change_database(db) self.dbstate.db.disable_signals() self._begin_progress() diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py index 5b79ce334..b977887b7 100644 --- a/gramps/gen/db/__init__.py +++ b/gramps/gen/db/__init__.py @@ -86,11 +86,26 @@ More details can be found in the manual's from .base import * from .dbconst import * -from .cursor import * -from .read import * -from .bsddbtxn import * +#from .cursor import * +#from .read import * +#from .bsddbtxn import * from .txn import * -from .undoredo import * +#from .undoredo import * from .exceptions import * -from .write import * -from .backup import backup, restore +#from .write import * +#from .backup import backup, restore + +def make_database(id, dbstate): + from gramps.cli.grampscli import CLIManager + from gramps.gen.plug import BasePluginManager + from gramps.cli.user import User + + climanager = CLIManager(dbstate, setloader=False, user=User()) # do not load db_loader + climanager.do_reg_plugins(dbstate, None) + pmgr = BasePluginManager.get_instance() + pdata = pmgr.get_plugin(id) + + mod = pmgr.load_plugin(pdata) + database = getattr(mod, pdata.databaseclass) + return database() + diff --git a/gramps/gen/db/backup.py b/gramps/gen/db/backup.py index 9329caaff..52d4d5f05 100644 --- a/gramps/gen/db/backup.py +++ b/gramps/gen/db/backup.py @@ -1,213 +1,8 @@ -# -# Gramps - a GTK+/GNOME based genealogy program -# -# Copyright (C) 2007 Donald N. Allingham -# Copyright (C) 2011 Tim G L Lyons -# -# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# -# gen/db/backup.py - -""" -Description -=========== - -This module Provides backup and restore functions for a database. The -backup function saves the data into backup files, while the restore -function loads the data back into a database. - -You should only restore the data into an empty database. - -Implementation -============== - -Not all of the database tables need to be backed up, since many are -automatically generated from the others. The tables that are backed up -are the primary tables and the metadata table. - -The database consists of a table of "pickled" tuples. Each of the -primary tables is "walked", and the pickled tuple is extracted, and -written to the backup file. - -Restoring the data is just as simple. The backup file is parsed an -entry at a time, and inserted into the associated database table. The -derived tables are built automatically as the items are entered into -db. -""" - -#------------------------------------------------------------------------- -# -# load standard python libraries -# -#------------------------------------------------------------------------- -import os -import pickle - -#------------------------------------------------------------------------ -# -# Gramps libs -# -#------------------------------------------------------------------------ -from .exceptions import DbException -from .write import FAMILY_TBL, PLACES_TBL, SOURCES_TBL, MEDIA_TBL, \ - EVENTS_TBL, PERSON_TBL, REPO_TBL, NOTE_TBL, TAG_TBL, META, CITATIONS_TBL - -#------------------------------------------------------------------------ -# -# Set up logging -# -#------------------------------------------------------------------------ import logging LOG = logging.getLogger(".Backup") def backup(database): - """ - Exports the database to a set of backup files. These files consist - of the pickled database tables, one file for each table. - - The heavy lifting is done by the private :py:func:`__do__export` function. - The purpose of this function is to catch any exceptions that occur. - - :param database: database instance to backup - :type database: DbDir - """ - try: - __do_export(database) - except (OSError, IOError) as msg: - raise DbException(str(msg)) - -def __mk_backup_name(database, base): - """ - Return the backup name of the database table - - :param database: database instance - :type database: DbDir - :param base: base name of the table - :type base: str - """ - return os.path.join(database.get_save_path(), base + ".gbkp") - -def __mk_tmp_name(database, base): - """ - Return the temporary backup name of the database table - - :param database: database instance - :type database: DbDir - :param base: base name of the table - :type base: str - """ - return os.path.join(database.get_save_path(), base + ".gbkp.new") - -def __do_export(database): - """ - Loop through each table of the database, saving the pickled data - a file. - - :param database: database instance to backup - :type database: DbDir - """ - try: - for (base, tbl) in __build_tbl_map(database): - backup_name = __mk_tmp_name(database, base) - backup_table = open(backup_name, 'wb') - - cursor = tbl.cursor() - data = cursor.first() - while data: - pickle.dump(data, backup_table, 2) - data = cursor.next() - cursor.close() - backup_table.close() - except (IOError,OSError): - return - - for (base, tbl) in __build_tbl_map(database): - new_name = __mk_backup_name(database, base) - old_name = __mk_tmp_name(database, base) - if os.path.isfile(new_name): - os.unlink(new_name) - os.rename(old_name, new_name) + print("FIXME") def restore(database): - """ - Restores the database to a set of backup files. These files consist - of the pickled database tables, one file for each table. - - The heavy lifting is done by the private :py:func:`__do__restore` function. - The purpose of this function is to catch any exceptions that occur. - - :param database: database instance to restore - :type database: DbDir - """ - try: - __do_restore(database) - except (OSError, IOError) as msg: - raise DbException(str(msg)) - -def __do_restore(database): - """ - Loop through each table of the database, restoring the pickled data - to the appropriate database file. - - :param database: database instance to backup - :type database: DbDir - """ - for (base, tbl) in __build_tbl_map(database): - backup_name = __mk_backup_name(database, base) - backup_table = open(backup_name, 'rb') - __load_tbl_txn(database, backup_table, tbl) - - database.rebuild_secondary() - -def __load_tbl_txn(database, backup_table, tbl): - """ - Return the temporary backup name of the database table - - :param database: database instance - :type database: DbDir - :param backup_table: file containing the backup data - :type backup_table: file - :param tbl: Berkeley db database table - :type tbl: Berkeley db database table - """ - try: - while True: - data = pickle.load(backup_table) - txn = database.env.txn_begin() - tbl.put(data[0], data[1], txn=txn) - txn.commit() - except EOFError: - backup_table.close() - -def __build_tbl_map(database): - """ - Builds a table map of names to database tables. - - :param database: database instance to backup - :type database: DbDir - """ - return [ - ( PERSON_TBL, database.person_map.db), - ( FAMILY_TBL, database.family_map.db), - ( PLACES_TBL, database.place_map.db), - ( SOURCES_TBL, database.source_map.db), - ( CITATIONS_TBL, database.citation_map.db), - ( REPO_TBL, database.repository_map.db), - ( NOTE_TBL, database.note_map.db), - ( MEDIA_TBL, database.media_map.db), - ( EVENTS_TBL, database.event_map.db), - ( TAG_TBL, database.tag_map.db), - ( META, database.metadata.db), - ] + print("FIXME") diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py index 839d05cd2..12ee68ea9 100644 --- a/gramps/gen/dbstate.py +++ b/gramps/gen/dbstate.py @@ -23,8 +23,8 @@ Provide the database state class """ -from .db import DbBsddbRead from .db import DbReadBase +from .db import make_database from .proxy.proxybase import ProxyDbBase from .utils.callback import Callback from .config import config @@ -45,7 +45,7 @@ class DbState(Callback): just a place holder until a real DB is assigned. """ Callback.__init__(self) - self.db = DbBsddbRead() + self.db = make_database("bsddb", self) self.open = False self.stack = [] @@ -88,7 +88,7 @@ class DbState(Callback): """ self.emit('no-database', ()) self.db.close() - self.db = DbBsddbRead() + self.db = make_database("bsddb", self) self.db.db_is_open = False self.open = False self.emit('database-changed', (self.db, )) diff --git a/gramps/gen/plug/_gramplet.py b/gramps/gen/plug/_gramplet.py index c040bae7d..b812a7fb7 100644 --- a/gramps/gen/plug/_gramplet.py +++ b/gramps/gen/plug/_gramplet.py @@ -70,7 +70,6 @@ class Gramplet(object): self.connect(self.gui.textview, "motion-notify-event", self.gui.on_motion) self.connect_signal('Person', self._active_changed) - self._db_changed(self.dbstate.db) active_person = self.get_active('Person') if active_person: # already changed diff --git a/gramps/gen/plug/_manager.py b/gramps/gen/plug/_manager.py index e047f6166..e30a089ae 100644 --- a/gramps/gen/plug/_manager.py +++ b/gramps/gen/plug/_manager.py @@ -412,6 +412,11 @@ class BasePluginManager(object): """ return self.__pgr.sidebar_plugins() + def get_reg_databases(self): + """ Return list of registered database backends + """ + return self.__pgr.database_plugins() + def get_external_opt_dict(self): """ Return the dictionary of external options. """ return self.__external_opt_dict diff --git a/gramps/gen/plug/_pluginreg.py b/gramps/gen/plug/_pluginreg.py index 04167cafc..5619f18ed 100644 --- a/gramps/gen/plug/_pluginreg.py +++ b/gramps/gen/plug/_pluginreg.py @@ -70,8 +70,9 @@ VIEW = 8 RELCALC = 9 GRAMPLET = 10 SIDEBAR = 11 +DATABASE = 12 PTYPE = [REPORT , QUICKREPORT, TOOL, IMPORT, EXPORT, DOCGEN, GENERAL, - MAPSERVICE, VIEW, RELCALC, GRAMPLET, SIDEBAR] + MAPSERVICE, VIEW, RELCALC, GRAMPLET, SIDEBAR, DATABASE] PTYPE_STR = { REPORT: _('Report') , QUICKREPORT: _('Quickreport'), @@ -85,6 +86,7 @@ PTYPE_STR = { RELCALC: _('Relationships'), GRAMPLET: _('Gramplet'), SIDEBAR: _('Sidebar'), + DATABASE: _('Database'), } #possible report categories @@ -206,7 +208,7 @@ class PluginData(object): The python path where the plugin implementation can be found .. attribute:: ptype The plugin type. One of REPORT , QUICKREPORT, TOOL, IMPORT, - EXPORT, DOCGEN, GENERAL, MAPSERVICE, VIEW, GRAMPLET + EXPORT, DOCGEN, GENERAL, MAPSERVICE, VIEW, GRAMPLET, DATABASE .. attribute:: authors List of authors of the plugin, default=[] .. attribute:: authors_email @@ -349,6 +351,11 @@ class PluginData(object): the plugin is appended to the list of plugins. If START, then the plugin is prepended. Only set START if you want a plugin to be the first in the order of plugins + + Attributes for DATABASE plugins + + .. attribute:: databaseclass + The class in the module that is the database class """ def __init__(self): @@ -421,6 +428,8 @@ class PluginData(object): self._menu_label = '' #VIEW and SIDEBAR attr self._order = END + #DATABASE attr + self._databaseclass = None #GENERAL attr self._data = [] self._process = None @@ -931,6 +940,17 @@ class PluginData(object): order = property(_get_order, _set_order) + #DATABASE attributes + def _set_databaseclass(self, databaseclass): + if not self._ptype == DATABASE: + raise ValueError('databaseclass may only be set for DATABASE plugins') + self._databaseclass = databaseclass + + def _get_databaseclass(self): + return self._databaseclass + + databaseclass = property(_get_databaseclass, _set_databaseclass) + #GENERAL attr def _set_data(self, data): if not self._ptype in (GENERAL,): @@ -1032,6 +1052,7 @@ def make_environment(**kwargs): 'REPORT_MODE_CLI': REPORT_MODE_CLI, 'TOOL_MODE_GUI': TOOL_MODE_GUI, 'TOOL_MODE_CLI': TOOL_MODE_CLI, + 'DATABASE': DATABASE, 'GRAMPSVERSION': GRAMPSVERSION, 'START': START, 'END': END, @@ -1297,6 +1318,12 @@ class PluginRegister(object): """ return self.type_plugins(SIDEBAR) + def database_plugins(self): + """ + Return a list of :class:`PluginData` that are of type DATABASE + """ + return self.type_plugins(DATABASE) + def filter_load_on_reg(self): """ Return a list of :class:`PluginData` that have load_on_reg == True diff --git a/gramps/gen/utils/callman.py b/gramps/gen/utils/callman.py index ca6a94d86..939a0f6fa 100644 --- a/gramps/gen/utils/callman.py +++ b/gramps/gen/utils/callman.py @@ -303,7 +303,8 @@ class CallbackManager(object): Do a custom db connect signal outside of the primary object ones managed automatically. """ - self.custom_signal_keys.append(self.database.connect(name, callback)) + if self.database: + self.custom_signal_keys.append(self.database.connect(name, callback)) def __callbackcreator(self, signal, noarg=False): """ diff --git a/gramps/gui/dbloader.py b/gramps/gui/dbloader.py index 6e7f85d5e..18a1c3281 100644 --- a/gramps/gui/dbloader.py +++ b/gramps/gui/dbloader.py @@ -55,7 +55,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext from gramps.cli.grampscli import CLIDbLoader from gramps.gen.config import config -from gramps.gen.db import DbBsddb +from gramps.gen.db import make_database from gramps.gen.db.exceptions import (DbUpgradeRequiredError, BsddbDowngradeError, DbVersionError, @@ -305,7 +305,7 @@ class DbLoader(CLIDbLoader): else: mode = 'w' - db = DbBsddb() + db = make_database("bsddb", self.dbstate) db.disable_signals() self.dbstate.no_database() diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py index f3e6214e3..c5ccbe657 100644 --- a/gramps/gui/dbman.py +++ b/gramps/gui/dbman.py @@ -73,7 +73,7 @@ _ = glocale.translation.gettext from gramps.gen.const import URL_WIKISTRING from .user import User from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2 -from gramps.gen.db import DbBsddb +from gramps.gen.db import make_database from .pluginmanager import GuiPluginManager from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val from .ddtargets import DdTargets @@ -531,8 +531,8 @@ class DbManager(CLIDbManager): new_path, newname = self._create_new_db("%s : %s" % (parent_name, name)) self.__start_cursor(_("Extracting archive...")) - dbclass = DbBsddb - dbase = dbclass() + + dbase = make_database("bsddb", self.dbstate) dbase.load(new_path, None) self.__start_cursor(_("Importing archive...")) @@ -719,11 +719,10 @@ class DbManager(CLIDbManager): fname = os.path.join(dirname, filename) os.unlink(fname) - newdb = DbBsddb() + newdb = make_database("bsddb", self.dbstate) newdb.write_version(dirname) - dbclass = DbBsddb - dbase = dbclass() + dbase = make_database("bsddb", self.dbstate) dbase.set_save_path(dirname) dbase.load(dirname, None) diff --git a/gramps/gui/pluginmanager.py b/gramps/gui/pluginmanager.py index edde82dac..bb609e984 100644 --- a/gramps/gui/pluginmanager.py +++ b/gramps/gui/pluginmanager.py @@ -205,7 +205,12 @@ class GuiPluginManager(Callback): return [plg for plg in self.basemgr.get_reg_docgens() if plg.id not in self.__hidden_plugins] + def get_reg_databases(self): + """ Return list of non hidden registered database backends + """ + return [plg for plg in self.basemgr.get_reg_databases() + if plg.id not in self.__hidden_plugins] + def get_reg_general(self, category=None): return [plg for plg in self.basemgr.get_reg_general(category) if plg.id not in self.__hidden_plugins] - diff --git a/gramps/plugins/database/bsddb.gpr.py b/gramps/plugins/database/bsddb.gpr.py new file mode 100644 index 000000000..20f2e9fda --- /dev/null +++ b/gramps/plugins/database/bsddb.gpr.py @@ -0,0 +1,31 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com> +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +plg = newplugin() +plg.id = 'bsddb' +plg.name = _("BSDDB Database Backend") +plg.name_accell = _("_BSDDB Database Backend") +plg.description = _("Berkeley Software Distribution Database Backend") +plg.version = '1.0' +plg.gramps_target_version = "4.2" +plg.status = STABLE +plg.fname = 'bsddb.py' +plg.ptype = DATABASE +plg.databaseclass = 'DbBsddb' diff --git a/gramps/plugins/database/bsddb.py b/gramps/plugins/database/bsddb.py new file mode 100644 index 000000000..9cbca25ce --- /dev/null +++ b/gramps/plugins/database/bsddb.py @@ -0,0 +1,3 @@ + +from bsddb_support import DbBsddb + diff --git a/gramps/plugins/database/bsddb_support/__init__.py b/gramps/plugins/database/bsddb_support/__init__.py new file mode 100644 index 000000000..524ecb660 --- /dev/null +++ b/gramps/plugins/database/bsddb_support/__init__.py @@ -0,0 +1,96 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" +Gramps Database API. + +Database Architecture +===================== + +Access to the database is made through Python classes. Exactly +what functionality you have is dependent on the properties of the +database. For example, if you are accessing a read-only view, then +you will only have access to a subset of the methods available. + +At the root of any database interface is either :py:class:`.DbReadBase` and/or +:py:class:`.DbWriteBase`. These define the methods to read and write to a +database, respectively. + +The full database hierarchy is: + +- :py:class:`.DbBsddb` - read and write implementation to BSDDB databases + + * :py:class:`.DbWriteBase` - virtual and implementation-independent methods + for reading data + + * :py:class:`.DbBsddbRead` - read-only (accessors, getters) implementation + to BSDDB databases + + + :py:class:`.DbReadBase` - virtual and implementation-independent + methods for reading data + + + :py:class:`.Callback` - callback and signal functions + + * :py:class:`.UpdateCallback` - callback functionality + +- :py:class:`.DbDjango` - read and write implementation to Django-based + databases + + * :py:class:`.DbWriteBase` - virtual and implementation-independent methods + for reading data + + * :py:class:`.DbReadBase` - virtual and implementation-independent methods + for reading data + +DbBsddb +======= + +The :py:class:`.DbBsddb` interface defines a hierarchical database +(non-relational) written in +`PyBSDDB <http://www.jcea.es/programacion/pybsddb.htm>`_. There is no +such thing as a database schema, and the meaning of the data is +defined in the Python classes above. The data is stored as pickled +tuples and unserialized into the primary data types (below). + +DbDjango +======== + +The DbDjango interface defines the Gramps data in terms of +*models* and *relations* from the +`Django project <http://www.djangoproject.com/>`_. The database +backend can be any implementation that supports Django, including +such popular SQL implementations as sqlite, MySQL, Postgresql, and +Oracle. The data is retrieved from the SQL fields, serialized and +then unserialized into the primary data types (below). + +More details can be found in the manual's +`Using database API <http://www.gramps-project.org/wiki/index.php?title=Using_database_API>`_. +""" + +from gramps.gen.db.base import * +from gramps.gen.db.dbconst import * +from .cursor import * +from .read import * +from .bsddbtxn import * +from gramps.gen.db.txn import * +from .undoredo import * +from gramps.gen.db.exceptions import * +from .write import * +from .backup import backup, restore diff --git a/gramps/plugins/database/bsddb_support/backup.py b/gramps/plugins/database/bsddb_support/backup.py new file mode 100644 index 000000000..29dd46c54 --- /dev/null +++ b/gramps/plugins/database/bsddb_support/backup.py @@ -0,0 +1,213 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2007 Donald N. Allingham +# Copyright (C) 2011 Tim G L Lyons +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# gen/db/backup.py + +""" +Description +=========== + +This module Provides backup and restore functions for a database. The +backup function saves the data into backup files, while the restore +function loads the data back into a database. + +You should only restore the data into an empty database. + +Implementation +============== + +Not all of the database tables need to be backed up, since many are +automatically generated from the others. The tables that are backed up +are the primary tables and the metadata table. + +The database consists of a table of "pickled" tuples. Each of the +primary tables is "walked", and the pickled tuple is extracted, and +written to the backup file. + +Restoring the data is just as simple. The backup file is parsed an +entry at a time, and inserted into the associated database table. The +derived tables are built automatically as the items are entered into +db. +""" + +#------------------------------------------------------------------------- +# +# load standard python libraries +# +#------------------------------------------------------------------------- +import os +import pickle + +#------------------------------------------------------------------------ +# +# Gramps libs +# +#------------------------------------------------------------------------ +from gramps.gen.db.exceptions import DbException +from .write import FAMILY_TBL, PLACES_TBL, SOURCES_TBL, MEDIA_TBL, \ + EVENTS_TBL, PERSON_TBL, REPO_TBL, NOTE_TBL, TAG_TBL, META, CITATIONS_TBL + +#------------------------------------------------------------------------ +# +# Set up logging +# +#------------------------------------------------------------------------ +import logging +LOG = logging.getLogger(".Backup") + +def backup(database): + """ + Exports the database to a set of backup files. These files consist + of the pickled database tables, one file for each table. + + The heavy lifting is done by the private :py:func:`__do__export` function. + The purpose of this function is to catch any exceptions that occur. + + :param database: database instance to backup + :type database: DbDir + """ + try: + __do_export(database) + except (OSError, IOError) as msg: + raise DbException(str(msg)) + +def __mk_backup_name(database, base): + """ + Return the backup name of the database table + + :param database: database instance + :type database: DbDir + :param base: base name of the table + :type base: str + """ + return os.path.join(database.get_save_path(), base + ".gbkp") + +def __mk_tmp_name(database, base): + """ + Return the temporary backup name of the database table + + :param database: database instance + :type database: DbDir + :param base: base name of the table + :type base: str + """ + return os.path.join(database.get_save_path(), base + ".gbkp.new") + +def __do_export(database): + """ + Loop through each table of the database, saving the pickled data + a file. + + :param database: database instance to backup + :type database: DbDir + """ + try: + for (base, tbl) in __build_tbl_map(database): + backup_name = __mk_tmp_name(database, base) + backup_table = open(backup_name, 'wb') + + cursor = tbl.cursor() + data = cursor.first() + while data: + pickle.dump(data, backup_table, 2) + data = cursor.next() + cursor.close() + backup_table.close() + except (IOError,OSError): + return + + for (base, tbl) in __build_tbl_map(database): + new_name = __mk_backup_name(database, base) + old_name = __mk_tmp_name(database, base) + if os.path.isfile(new_name): + os.unlink(new_name) + os.rename(old_name, new_name) + +def restore(database): + """ + Restores the database to a set of backup files. These files consist + of the pickled database tables, one file for each table. + + The heavy lifting is done by the private :py:func:`__do__restore` function. + The purpose of this function is to catch any exceptions that occur. + + :param database: database instance to restore + :type database: DbDir + """ + try: + __do_restore(database) + except (OSError, IOError) as msg: + raise DbException(str(msg)) + +def __do_restore(database): + """ + Loop through each table of the database, restoring the pickled data + to the appropriate database file. + + :param database: database instance to backup + :type database: DbDir + """ + for (base, tbl) in __build_tbl_map(database): + backup_name = __mk_backup_name(database, base) + backup_table = open(backup_name, 'rb') + __load_tbl_txn(database, backup_table, tbl) + + database.rebuild_secondary() + +def __load_tbl_txn(database, backup_table, tbl): + """ + Return the temporary backup name of the database table + + :param database: database instance + :type database: DbDir + :param backup_table: file containing the backup data + :type backup_table: file + :param tbl: Berkeley db database table + :type tbl: Berkeley db database table + """ + try: + while True: + data = pickle.load(backup_table) + txn = database.env.txn_begin() + tbl.put(data[0], data[1], txn=txn) + txn.commit() + except EOFError: + backup_table.close() + +def __build_tbl_map(database): + """ + Builds a table map of names to database tables. + + :param database: database instance to backup + :type database: DbDir + """ + return [ + ( PERSON_TBL, database.person_map.db), + ( FAMILY_TBL, database.family_map.db), + ( PLACES_TBL, database.place_map.db), + ( SOURCES_TBL, database.source_map.db), + ( CITATIONS_TBL, database.citation_map.db), + ( REPO_TBL, database.repository_map.db), + ( NOTE_TBL, database.note_map.db), + ( MEDIA_TBL, database.media_map.db), + ( EVENTS_TBL, database.event_map.db), + ( TAG_TBL, database.tag_map.db), + ( META, database.metadata.db), + ] diff --git a/gramps/gen/db/bsddbtxn.py b/gramps/plugins/database/bsddb_support/bsddbtxn.py similarity index 100% rename from gramps/gen/db/bsddbtxn.py rename to gramps/plugins/database/bsddb_support/bsddbtxn.py diff --git a/gramps/gen/db/cursor.py b/gramps/plugins/database/bsddb_support/cursor.py similarity index 100% rename from gramps/gen/db/cursor.py rename to gramps/plugins/database/bsddb_support/cursor.py diff --git a/gramps/gen/db/dictionary.py b/gramps/plugins/database/bsddb_support/dictionary.py similarity index 98% rename from gramps/gen/db/dictionary.py rename to gramps/plugins/database/bsddb_support/dictionary.py index 36a8cd5fa..2d785ef6e 100644 --- a/gramps/gen/db/dictionary.py +++ b/gramps/plugins/database/bsddb_support/dictionary.py @@ -40,21 +40,18 @@ from . import (PERSON_KEY, NOTE_KEY, TAG_KEY) -from ..const import GRAMPS_LOCALE as glocale -_ = glocale.translation.gettext -from ..errors import DbError -from ..utils.id import create_id -from ..lib.researcher import Researcher -from ..lib.mediaobj import MediaObject -from ..lib.person import Person -from ..lib.family import Family -from ..lib.src import Source -from ..lib.citation import Citation -from ..lib.event import Event -from ..lib.place import Place -from ..lib.repo import Repository -from ..lib.note import Note -from ..lib.tag import Tag +from gramps.gen.utils.id import create_id +from gramps.gen.lib.researcher import Researcher +from gramps.gen.lib.mediaobj import MediaObject +from gramps.gen.lib.person import Person +from gramps.gen.lib.family import Family +from gramps.gen.lib.src import Source +from gramps.gen.lib.citation import Citation +from gramps.gen.lib.event import Event +from gramps.gen.lib.place import Place +from gramps.gen.lib.repo import Repository +from gramps.gen.lib.note import Note +from gramps.gen.lib.tag import Tag class Cursor(object): """ diff --git a/gramps/gen/db/read.py b/gramps/plugins/database/bsddb_support/read.py similarity index 98% rename from gramps/gen/db/read.py rename to gramps/plugins/database/bsddb_support/read.py index 87dc112d6..d51810581 100644 --- a/gramps/gen/db/read.py +++ b/gramps/plugins/database/bsddb_support/read.py @@ -53,30 +53,32 @@ import logging # GRAMPS libraries # #------------------------------------------------------------------------- -from ..lib.mediaobj import MediaObject -from ..lib.person import Person -from ..lib.family import Family -from ..lib.src import Source -from ..lib.citation import Citation -from ..lib.event import Event -from ..lib.place import Place -from ..lib.repo import Repository -from ..lib.note import Note -from ..lib.tag import Tag -from ..lib.genderstats import GenderStats -from ..lib.researcher import Researcher -from ..lib.nameorigintype import NameOriginType +from gramps.gen.lib.mediaobj import MediaObject +from gramps.gen.lib.person import Person +from gramps.gen.lib.family import Family +from gramps.gen.lib.src import Source +from gramps.gen.lib.citation import Citation +from gramps.gen.lib.event import Event +from gramps.gen.lib.place import Place +from gramps.gen.lib.repo import Repository +from gramps.gen.lib.note import Note +from gramps.gen.lib.tag import Tag +from gramps.gen.lib.genderstats import GenderStats +from gramps.gen.lib.researcher import Researcher +from gramps.gen.lib.nameorigintype import NameOriginType -from .dbconst import * -from ..utils.callback import Callback -from ..utils.cast import conv_dbstr_to_unicode -from . import (BsddbBaseCursor, DbReadBase) -from ..utils.id import create_id -from ..errors import DbError -from ..constfunc import handle2internal, get_env_var -from ..const import GRAMPS_LOCALE as glocale +from gramps.gen.utils.callback import Callback +from gramps.gen.utils.cast import conv_dbstr_to_unicode +from . import BsddbBaseCursor +from gramps.gen.db.base import DbReadBase +from gramps.gen.utils.id import create_id +from gramps.gen.errors import DbError +from gramps.gen.constfunc import handle2internal, get_env_var +from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext +from gramps.gen.db.dbconst import * + LOG = logging.getLogger(DBLOGNAME) LOG = logging.getLogger(".citation") #------------------------------------------------------------------------- @@ -84,7 +86,6 @@ LOG = logging.getLogger(".citation") # constants # #------------------------------------------------------------------------- -from .dbconst import * _SIGBASE = ('person', 'family', 'source', 'citation', 'event', 'media', 'place', 'repository', diff --git a/gramps/gen/db/undoredo.py b/gramps/plugins/database/bsddb_support/undoredo.py similarity index 98% rename from gramps/gen/db/undoredo.py rename to gramps/plugins/database/bsddb_support/undoredo.py index 7fed4d876..da6b3368e 100644 --- a/gramps/gen/db/undoredo.py +++ b/gramps/plugins/database/bsddb_support/undoredo.py @@ -43,7 +43,7 @@ except: DBPageNotFoundError = 0 DBInvalidArgError = 0 -from ..const import GRAMPS_LOCALE as glocale +from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext #------------------------------------------------------------------------- @@ -51,10 +51,10 @@ _ = glocale.translation.gettext # Gramps modules # #------------------------------------------------------------------------- -from ..constfunc import conv_to_unicode, handle2internal, win -from .dbconst import * +from gramps.gen.constfunc import conv_to_unicode, handle2internal, win +from gramps.gen.db.dbconst import * from . import BSDDBTxn -from ..errors import DbError +from gramps.gen.errors import DbError #------------------------------------------------------------------------- # diff --git a/gramps/gen/db/upgrade.py b/gramps/plugins/database/bsddb_support/upgrade.py similarity index 98% rename from gramps/gen/db/upgrade.py rename to gramps/plugins/database/bsddb_support/upgrade.py index c720a9547..1f1cd6719 100644 --- a/gramps/gen/db/upgrade.py +++ b/gramps/plugins/database/bsddb_support/upgrade.py @@ -39,23 +39,24 @@ from bsddb3 import db # Gramps modules # #------------------------------------------------------------------------- -from ..const import GRAMPS_LOCALE as glocale +from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -from ..constfunc import handle2internal -from ..lib.markertype import MarkerType -from ..lib.nameorigintype import NameOriginType -from ..lib.place import Place -from ..lib.placeref import PlaceRef -from ..lib.placetype import PlaceType -from ..lib.placename import PlaceName -from ..lib.eventtype import EventType -from ..lib.tag import Tag -from ..utils.file import create_checksum -from ..utils.id import create_id +from gramps.gen.constfunc import handle2internal +from gramps.gen.lib.markertype import MarkerType +from gramps.gen.lib.nameorigintype import NameOriginType +from gramps.gen.lib.place import Place +from gramps.gen.lib.placeref import PlaceRef +from gramps.gen.lib.placetype import PlaceType +from gramps.gen.lib.placename import PlaceName +from gramps.gen.lib.eventtype import EventType +from gramps.gen.lib.tag import Tag +from gramps.gen.utils.file import create_checksum +from gramps.gen.utils.id import create_id from . import BSDDBTxn from .write import _mkname, SURNAMES -from .dbconst import (PERSON_KEY, FAMILY_KEY, EVENT_KEY, - MEDIA_KEY, PLACE_KEY, REPOSITORY_KEY, SOURCE_KEY) +from gramps.gen.db.dbconst import (PERSON_KEY, FAMILY_KEY, EVENT_KEY, + MEDIA_KEY, PLACE_KEY, REPOSITORY_KEY, + SOURCE_KEY) from gramps.gui.dialog import (InfoDialog) LOG = logging.getLogger(".upgrade") @@ -359,7 +360,7 @@ def upgrade_datamap_17(datamap): """ new_srcattr_list = [] private = False - from ..lib.srcattrtype import SrcAttributeType + from gramps.gen.lib.srcattrtype import SrcAttributeType for (key, value) in datamap.items(): the_type = SrcAttributeType(key).serialize() new_srcattr_list.append((private, the_type, value)) diff --git a/gramps/gen/db/write.py b/gramps/plugins/database/bsddb_support/write.py similarity index 99% rename from gramps/gen/db/write.py rename to gramps/plugins/database/bsddb_support/write.py index 607735ec8..768db02d9 100644 --- a/gramps/gen/db/write.py +++ b/gramps/plugins/database/bsddb_support/write.py @@ -56,33 +56,34 @@ except: # Gramps modules # #------------------------------------------------------------------------- -from ..lib.person import Person -from ..lib.family import Family -from ..lib.src import Source -from ..lib.citation import Citation -from ..lib.event import Event -from ..lib.place import Place -from ..lib.repo import Repository -from ..lib.mediaobj import MediaObject -from ..lib.note import Note -from ..lib.tag import Tag -from ..lib.genderstats import GenderStats -from ..lib.researcher import Researcher +from gramps.gen.lib.person import Person +from gramps.gen.lib.family import Family +from gramps.gen.lib.src import Source +from gramps.gen.lib.citation import Citation +from gramps.gen.lib.event import Event +from gramps.gen.lib.place import Place +from gramps.gen.lib.repo import Repository +from gramps.gen.lib.mediaobj import MediaObject +from gramps.gen.lib.note import Note +from gramps.gen.lib.tag import Tag +from gramps.gen.lib.genderstats import GenderStats +from gramps.gen.lib.researcher import Researcher from . import (DbBsddbRead, DbWriteBase, BSDDBTxn, DbTxn, BsddbBaseCursor, BsddbDowngradeError, DbVersionError, DbEnvironmentError, DbUpgradeRequiredError, find_surname, - find_byte_surname, find_surname_name, DbUndoBSDDB as DbUndo, - exceptions) -from .dbconst import * -from ..utils.callback import Callback -from ..utils.cast import conv_dbstr_to_unicode -from ..utils.id import create_id -from ..updatecallback import UpdateCallback -from ..errors import DbError -from ..constfunc import (win, conv_to_unicode, handle2internal, + find_byte_surname, find_surname_name, DbUndoBSDDB as DbUndo) + +from gramps.gen.db import exceptions +from gramps.gen.db.dbconst import * +from gramps.gen.utils.callback import Callback +from gramps.gen.utils.cast import conv_dbstr_to_unicode +from gramps.gen.utils.id import create_id +from gramps.gen.updatecallback import UpdateCallback +from gramps.gen.errors import DbError +from gramps.gen.constfunc import (win, conv_to_unicode, handle2internal, get_env_var) -from ..const import HOME_DIR, GRAMPS_LOCALE as glocale +from gramps.gen.const import HOME_DIR, GRAMPS_LOCALE as glocale _ = glocale.translation.gettext _LOG = logging.getLogger(DBLOGNAME) From 2d6a319c1399b833fe0c34596dc30d50df25b821 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Tue, 12 May 2015 19:09:17 -0400 Subject: [PATCH 052/105] Moved make_database to DbState --- gramps/cli/arghandler.py | 3 +-- gramps/cli/clidbman.py | 5 ++--- gramps/cli/grampscli.py | 3 +-- gramps/gen/db/__init__.py | 15 --------------- gramps/gen/dbstate.py | 26 +++++++++++++++++++++++--- gramps/gui/dbloader.py | 3 +-- gramps/gui/dbman.py | 7 +++---- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/gramps/cli/arghandler.py b/gramps/cli/arghandler.py index c7a5f5a4a..47e9b8da6 100644 --- a/gramps/cli/arghandler.py +++ b/gramps/cli/arghandler.py @@ -44,7 +44,6 @@ import sys #------------------------------------------------------------------------- from gramps.gen.recentfiles import recent_files from gramps.gen.utils.file import rm_tempdir, get_empty_tempdir -from gramps.gen.db import make_database from .clidbman import CLIDbManager, NAME_FILE, find_locker_name from gramps.gen.plug import BasePluginManager @@ -492,7 +491,7 @@ class ArgHandler(object): else: self.imp_db_path = get_empty_tempdir("import_dbdir") - newdb = make_database("bsddb", self.dbstate) + newdb = self.dbstate.make_database("bsddb") newdb.write_version(self.imp_db_path) try: diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py index 52b183013..e73bd71d6 100644 --- a/gramps/cli/clidbman.py +++ b/gramps/cli/clidbman.py @@ -54,7 +54,6 @@ _LOG = logging.getLogger(DBLOGNAME) #------------------------------------------------------------------------- from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -from gramps.gen.db import make_database from gramps.gen.plug import BasePluginManager from gramps.gen.config import config from gramps.gen.constfunc import win, conv_to_unicode @@ -295,7 +294,7 @@ class CLIDbManager(object): if create_db: # write the version number into metadata - newdb = make_database("bsddb", self.dbstate) + newdb = self.dbstate.make_database("bsddb") newdb.write_version(new_path) (tval, last) = time_val(new_path) @@ -362,7 +361,7 @@ class CLIDbManager(object): # Create a new database self.__start_cursor(_("Importing data...")) - dbase = make_database("bsddb", self.dbstate) + dbase = self.dbstate.make_database("bsddb") dbase.load(new_path, user.callback) import_function = plugin.get_import_function() diff --git a/gramps/cli/grampscli.py b/gramps/cli/grampscli.py index 00b66d352..d1d3cfc07 100644 --- a/gramps/cli/grampscli.py +++ b/gramps/cli/grampscli.py @@ -49,7 +49,6 @@ from gramps.gen.config import config from gramps.gen.const import PLUGINS_DIR, USER_PLUGINS from gramps.gen.errors import DbError from gramps.gen.dbstate import DbState -from gramps.gen.db import make_database from gramps.gen.db.exceptions import (DbUpgradeRequiredError, BsddbDowngradeError, DbVersionError, @@ -152,7 +151,7 @@ class CLIDbLoader(object): else: mode = 'w' - db = make_database("bsddb", self.dbstate) + db = self.dbstate.make_database("bsddb") self.dbstate.change_database(db) self.dbstate.db.disable_signals() diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py index b977887b7..4ddaf5386 100644 --- a/gramps/gen/db/__init__.py +++ b/gramps/gen/db/__init__.py @@ -94,18 +94,3 @@ from .txn import * from .exceptions import * #from .write import * #from .backup import backup, restore - -def make_database(id, dbstate): - from gramps.cli.grampscli import CLIManager - from gramps.gen.plug import BasePluginManager - from gramps.cli.user import User - - climanager = CLIManager(dbstate, setloader=False, user=User()) # do not load db_loader - climanager.do_reg_plugins(dbstate, None) - pmgr = BasePluginManager.get_instance() - pdata = pmgr.get_plugin(id) - - mod = pmgr.load_plugin(pdata) - database = getattr(mod, pdata.databaseclass) - return database() - diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py index 12ee68ea9..04a8e683d 100644 --- a/gramps/gen/dbstate.py +++ b/gramps/gen/dbstate.py @@ -24,7 +24,6 @@ Provide the database state class """ from .db import DbReadBase -from .db import make_database from .proxy.proxybase import ProxyDbBase from .utils.callback import Callback from .config import config @@ -45,7 +44,7 @@ class DbState(Callback): just a place holder until a real DB is assigned. """ Callback.__init__(self) - self.db = make_database("bsddb", self) + self.db = self.make_database("bsddb") self.open = False self.stack = [] @@ -88,7 +87,7 @@ class DbState(Callback): """ self.emit('no-database', ()) self.db.close() - self.db = make_database("bsddb", self) + self.db = self.make_database("bsddb") self.db.db_is_open = False self.open = False self.emit('database-changed', (self.db, )) @@ -122,3 +121,24 @@ class DbState(Callback): """ self.db = self.stack.pop() self.emit('database-changed', (self.db, )) + + def make_database(self, id): + """ + Make a database, given a plugin id. + """ + from .plug import BasePluginManager + from .const import PLUGINS_DIR, USER_PLUGINS + + pmgr = BasePluginManager.get_instance() + pdata = pmgr.get_plugin(id) + + if not pdata: + # This might happen if using gramps from outside, and + # we haven't loaded plugins yet + pmgr.reg_plugins(PLUGINS_DIR, self, None) + pmgr.reg_plugins(USER_PLUGINS, self, None, load_on_reg=True) + pdata = pmgr.get_plugin(id) + + mod = pmgr.load_plugin(pdata) + database = getattr(mod, pdata.databaseclass) + return database() diff --git a/gramps/gui/dbloader.py b/gramps/gui/dbloader.py index 18a1c3281..f3d303b15 100644 --- a/gramps/gui/dbloader.py +++ b/gramps/gui/dbloader.py @@ -55,7 +55,6 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext from gramps.cli.grampscli import CLIDbLoader from gramps.gen.config import config -from gramps.gen.db import make_database from gramps.gen.db.exceptions import (DbUpgradeRequiredError, BsddbDowngradeError, DbVersionError, @@ -305,7 +304,7 @@ class DbLoader(CLIDbLoader): else: mode = 'w' - db = make_database("bsddb", self.dbstate) + db = self.dbstate.make_database("bsddb") db.disable_signals() self.dbstate.no_database() diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py index c5ccbe657..733bf9ecc 100644 --- a/gramps/gui/dbman.py +++ b/gramps/gui/dbman.py @@ -73,7 +73,6 @@ _ = glocale.translation.gettext from gramps.gen.const import URL_WIKISTRING from .user import User from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2 -from gramps.gen.db import make_database from .pluginmanager import GuiPluginManager from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val from .ddtargets import DdTargets @@ -532,7 +531,7 @@ class DbManager(CLIDbManager): self.__start_cursor(_("Extracting archive...")) - dbase = make_database("bsddb", self.dbstate) + dbase = self.dbstate.make_database("bsddb") dbase.load(new_path, None) self.__start_cursor(_("Importing archive...")) @@ -719,10 +718,10 @@ class DbManager(CLIDbManager): fname = os.path.join(dirname, filename) os.unlink(fname) - newdb = make_database("bsddb", self.dbstate) + newdb = self.dbstate.make_database("bsddb") newdb.write_version(dirname) - dbase = make_database("bsddb", self.dbstate) + dbase = self.dbstate.make_database("bsddb") dbase.set_save_path(dirname) dbase.load(dirname, None) From e7d62cf9b19ed90e97c5386e4603892760b41dc5 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Tue, 12 May 2015 22:03:10 -0400 Subject: [PATCH 053/105] Only BSDDB plugin needs bsddb3; back/restore moved to db --- gramps/cli/clidbman.py | 53 +----- gramps/gen/db/__init__.py | 6 - gramps/gen/db/backup.py | 8 - gramps/gen/db/dbconst.py | 15 +- gramps/gen/plug/_gramplet.py | 6 - gramps/grampsapp.py | 8 - gramps/gui/aboutdialog.py | 9 +- gramps/gui/dbman.py | 3 +- gramps/gui/editors/editfamily.py | 10 +- gramps/gui/logger/_errorreportassistant.py | 9 +- gramps/gui/viewmanager.py | 3 +- .../database/bsddb_support/__init__.py | 1 - .../plugins/database/bsddb_support/backup.py | 139 ---------------- .../plugins/database/bsddb_support/summary.py | 62 +++++++ .../database/bsddb_support}/test/__init__.py | 0 .../bsddb_support}/test/cursor_test.py | 0 .../database/bsddb_support}/test/db_test.py | 0 .../bsddb_support}/test/grampsdbtestbase.py | 0 .../bsddb_support}/test/reference_map_test.py | 0 .../plugins/database/bsddb_support/write.py | 156 ++++++++++++++++-- .../{bsddb_support => }/dictionary.py | 0 gramps/plugins/gramplet/leak.py | 8 +- 22 files changed, 239 insertions(+), 257 deletions(-) delete mode 100644 gramps/gen/db/backup.py create mode 100644 gramps/plugins/database/bsddb_support/summary.py rename gramps/{gen/db => plugins/database/bsddb_support}/test/__init__.py (100%) rename gramps/{gen/db => plugins/database/bsddb_support}/test/cursor_test.py (100%) rename gramps/{gen/db => plugins/database/bsddb_support}/test/db_test.py (100%) rename gramps/{gen/db => plugins/database/bsddb_support}/test/grampsdbtestbase.py (100%) rename gramps/{gen/db => plugins/database/bsddb_support}/test/reference_map_test.py (100%) rename gramps/plugins/database/{bsddb_support => }/dictionary.py (100%) diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py index e73bd71d6..a4986f615 100644 --- a/gramps/cli/clidbman.py +++ b/gramps/cli/clidbman.py @@ -137,57 +137,8 @@ class CLIDbManager(object): current DB. Returns ("Unknown", "Unknown", "Unknown") if invalid DB or other error. """ - from bsddb3 import dbshelve, db - - from gramps.gen.db import META, PERSON_TBL - from gramps.gen.db.dbconst import BDBVERSFN - - bdbversion_file = os.path.join(dirpath, BDBVERSFN) - if os.path.isfile(bdbversion_file): - vers_file = open(bdbversion_file) - bsddb_version = vers_file.readline().strip() - else: - return "Unknown", "Unknown", "Unknown" - - current_bsddb_version = str(db.version()) - if bsddb_version != current_bsddb_version: - return "Unknown", bsddb_version, "Unknown" - - env = db.DBEnv() - flags = db.DB_CREATE | db.DB_PRIVATE |\ - db.DB_INIT_MPOOL |\ - db.DB_INIT_LOG | db.DB_INIT_TXN - try: - env.open(dirpath, flags) - except Exception as msg: - LOG.warning("Error opening db environment for '%s': %s" % - (name, str(msg))) - try: - env.close() - except Exception as msg: - LOG.warning("Error closing db environment for '%s': %s" % - (name, str(msg))) - return "Unknown", bsddb_version, "Unknown" - dbmap1 = dbshelve.DBShelf(env) - fname = os.path.join(dirpath, META + ".db") - try: - dbmap1.open(fname, META, db.DB_HASH, db.DB_RDONLY) - except: - env.close() - return "Unknown", bsddb_version, "Unknown" - schema_version = dbmap1.get(b'version', default=None) - dbmap1.close() - dbmap2 = dbshelve.DBShelf(env) - fname = os.path.join(dirpath, PERSON_TBL + ".db") - try: - dbmap2.open(fname, PERSON_TBL, db.DB_HASH, db.DB_RDONLY) - except: - env.close() - return "Unknown", bsddb_version, schema_version - count = len(dbmap2) - dbmap2.close() - env.close() - return (count, bsddb_version, schema_version) + ## Maybe return the txt file contents, for now + return ("Unknown", "Unknown", "Unknown") def family_tree_summary(self): """ diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py index 4ddaf5386..066571e38 100644 --- a/gramps/gen/db/__init__.py +++ b/gramps/gen/db/__init__.py @@ -86,11 +86,5 @@ More details can be found in the manual's from .base import * from .dbconst import * -#from .cursor import * -#from .read import * -#from .bsddbtxn import * from .txn import * -#from .undoredo import * from .exceptions import * -#from .write import * -#from .backup import backup, restore diff --git a/gramps/gen/db/backup.py b/gramps/gen/db/backup.py deleted file mode 100644 index 52d4d5f05..000000000 --- a/gramps/gen/db/backup.py +++ /dev/null @@ -1,8 +0,0 @@ -import logging -LOG = logging.getLogger(".Backup") - -def backup(database): - print("FIXME") - -def restore(database): - print("FIXME") diff --git a/gramps/gen/db/dbconst.py b/gramps/gen/db/dbconst.py index ba3514afb..7b91ab7e6 100644 --- a/gramps/gen/db/dbconst.py +++ b/gramps/gen/db/dbconst.py @@ -31,8 +31,7 @@ Declare constants used by database modules __all__ = ( ('DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO', 'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN', - 'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'DBFLAGS_O', 'DBFLAGS_R', - 'DBFLAGS_D', 'SCHVERSFN', 'PCKVERSFN' + 'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN' ) + ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY', @@ -60,18 +59,6 @@ DBLOCKS = 100000 # Maximum number of locks supported DBOBJECTS = 100000 # Maximum number of simultaneously locked objects DBUNDO = 1000 # Maximum size of undo buffer -try: - from bsddb3.db import DB_CREATE, DB_AUTO_COMMIT, DB_DUP, DB_DUPSORT, DB_RDONLY - DBFLAGS_O = DB_CREATE | DB_AUTO_COMMIT # Default flags for database open - DBFLAGS_R = DB_RDONLY # Flags to open a database read-only - DBFLAGS_D = DB_DUP | DB_DUPSORT # Default flags for duplicate keys -except: - print("WARNING: no bsddb support") - # FIXME: make this more abstract to deal with other backends, or do not import - DBFLAGS_O = DB_CREATE = DB_AUTO_COMMIT = 0 - DBFLAGS_R = DB_RDONLY = 0 - DBFLAGS_D = DB_DUP = DB_DUPSORT = 0 - PERSON_KEY = 0 FAMILY_KEY = 1 SOURCE_KEY = 2 diff --git a/gramps/gen/plug/_gramplet.py b/gramps/gen/plug/_gramplet.py index b812a7fb7..0159a8d03 100644 --- a/gramps/gen/plug/_gramplet.py +++ b/gramps/gen/plug/_gramplet.py @@ -320,8 +320,6 @@ class Gramplet(object): self._idle_id = 0 LOG.debug("gramplet updater: %s : One time, done!" % self.gui.title) return False - # FIXME: find out why Data Entry has this error, or just ignore it - import bsddb3 as bsddb try: retval = next(self._generator) if not retval: @@ -332,10 +330,6 @@ class Gramplet(object): LOG.debug("gramplet updater: %s: return %s" % (self.gui.title, retval)) return retval - except bsddb.db.DBCursorClosedError: - # not sure why---caused by Data Entry Gramplet - LOG.warn("bsddb.db.DBCursorClosedError in: %s" % self.gui.title) - return False except StopIteration: self._idle_id = 0 self._generator.close() diff --git a/gramps/grampsapp.py b/gramps/grampsapp.py index b9e4a6015..e2d7215e7 100644 --- a/gramps/grampsapp.py +++ b/gramps/grampsapp.py @@ -136,14 +136,6 @@ if not sys.version_info >= MIN_PYTHON_VERSION : 'v3': MIN_PYTHON_VERSION[2]}) sys.exit(1) -try: - import bsddb3 -except ImportError: - logging.warning(_("\nYou don't have the python3 bsddb3 package installed." - " This package is needed to start Gramps.\n\n" - "Gramps will terminate now.")) - sys.exit(1) - #------------------------------------------------------------------------- # # Gramps libraries diff --git a/gramps/gui/aboutdialog.py b/gramps/gui/aboutdialog.py index 9bd977d55..9886b550f 100644 --- a/gramps/gui/aboutdialog.py +++ b/gramps/gui/aboutdialog.py @@ -28,7 +28,12 @@ import os import sys import io -import bsddb3 as bsddb + +try: + import bsddb3 as bsddb ## ok, in try/except + BSDDB_STR = ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version())) +except: + BSDDB_STR = 'not found' ##import logging ##_LOG = logging.getLogger(".GrampsAboutDialog") @@ -125,7 +130,7 @@ class GrampsAboutDialog(Gtk.AboutDialog): "Distribution: %s") % (ellipses(str(VERSION)), ellipses(str(sys.version).replace('\n','')), - ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version())), + BSDDB_STR, ellipses(get_env_var('LANG','')), ellipses(operatingsystem), ellipses(distribution))) diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py index 733bf9ecc..b105ac37b 100644 --- a/gramps/gui/dbman.py +++ b/gramps/gui/dbman.py @@ -78,7 +78,6 @@ from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val from .ddtargets import DdTargets from gramps.gen.recentfiles import rename_filename, remove_filename from .glade import Glade -from gramps.gen.db.backup import restore from gramps.gen.db.exceptions import DbException @@ -728,7 +727,7 @@ class DbManager(CLIDbManager): self.__start_cursor(_("Rebuilding database from backup files")) try: - restore(dbase) + dbase.restore() except DbException as msg: DbManager.ERROR(_("Error restoring backup data"), msg) diff --git a/gramps/gui/editors/editfamily.py b/gramps/gui/editors/editfamily.py index 707bf0853..2c5110b1b 100644 --- a/gramps/gui/editors/editfamily.py +++ b/gramps/gui/editors/editfamily.py @@ -26,7 +26,6 @@ # python modules # #------------------------------------------------------------------------- -from bsddb3 import db as bsddb_db import pickle #------------------------------------------------------------------------- @@ -1026,10 +1025,11 @@ class EditFamily(EditPrimary): ) def save(self, *obj): - try: - self.__do_save() - except bsddb_db.DBRunRecoveryError as msg: - RunDatabaseRepair(msg[1]) + ## FIXME: how to catch a specific error? + #try: + self.__do_save() + #except bsddb_db.DBRunRecoveryError as msg: + # RunDatabaseRepair(msg[1]) def __do_save(self): self.ok_button.set_sensitive(False) diff --git a/gramps/gui/logger/_errorreportassistant.py b/gramps/gui/logger/_errorreportassistant.py index e5c81597a..14c6ba1a2 100644 --- a/gramps/gui/logger/_errorreportassistant.py +++ b/gramps/gui/logger/_errorreportassistant.py @@ -30,7 +30,12 @@ from gi.repository import GdkPixbuf from gi.repository import GObject import cairo import sys, os -import bsddb3 as bsddb + +try: + import bsddb3 as bsddb # ok, in try/except + BSDDB_STR = str(bsddb.__version__) + " " + str(bsddb.db.version()) +except: + BSDDB_STR = 'not found' #------------------------------------------------------------------------- # @@ -166,7 +171,7 @@ class ErrorReportAssistant(Gtk.Assistant): "gobject version: %s\n"\ "cairo version : %s"\ % (str(sys.version).replace('\n',''), - str(bsddb.__version__) + " " + str(bsddb.db.version()), + BSDDB_STR, str(VERSION), get_env_var('LANG',''), operatingsystem, diff --git a/gramps/gui/viewmanager.py b/gramps/gui/viewmanager.py index 721706faf..cb2be6f16 100644 --- a/gramps/gui/viewmanager.py +++ b/gramps/gui/viewmanager.py @@ -87,7 +87,6 @@ from gramps.gen.utils.file import media_path_full from .dbloader import DbLoader from .display import display_help, display_url from .configure import GrampsPreferences -from gramps.gen.db.backup import backup from gramps.gen.db.exceptions import DbException from .aboutdialog import GrampsAboutDialog from .navigator import Navigator @@ -762,7 +761,7 @@ class ViewManager(CLIManager): self.uistate.progress.show() self.uistate.push_message(self.dbstate, _("Autobackup...")) try: - backup(self.dbstate.db) + self.dbstate.db.backup() except DbException as msg: ErrorDialog(_("Error saving backup data"), msg) self.uistate.set_busy_cursor(False) diff --git a/gramps/plugins/database/bsddb_support/__init__.py b/gramps/plugins/database/bsddb_support/__init__.py index 524ecb660..1dd4bce56 100644 --- a/gramps/plugins/database/bsddb_support/__init__.py +++ b/gramps/plugins/database/bsddb_support/__init__.py @@ -93,4 +93,3 @@ from gramps.gen.db.txn import * from .undoredo import * from gramps.gen.db.exceptions import * from .write import * -from .backup import backup, restore diff --git a/gramps/plugins/database/bsddb_support/backup.py b/gramps/plugins/database/bsddb_support/backup.py index 29dd46c54..94350da56 100644 --- a/gramps/plugins/database/bsddb_support/backup.py +++ b/gramps/plugins/database/bsddb_support/backup.py @@ -72,142 +72,3 @@ from .write import FAMILY_TBL, PLACES_TBL, SOURCES_TBL, MEDIA_TBL, \ import logging LOG = logging.getLogger(".Backup") -def backup(database): - """ - Exports the database to a set of backup files. These files consist - of the pickled database tables, one file for each table. - - The heavy lifting is done by the private :py:func:`__do__export` function. - The purpose of this function is to catch any exceptions that occur. - - :param database: database instance to backup - :type database: DbDir - """ - try: - __do_export(database) - except (OSError, IOError) as msg: - raise DbException(str(msg)) - -def __mk_backup_name(database, base): - """ - Return the backup name of the database table - - :param database: database instance - :type database: DbDir - :param base: base name of the table - :type base: str - """ - return os.path.join(database.get_save_path(), base + ".gbkp") - -def __mk_tmp_name(database, base): - """ - Return the temporary backup name of the database table - - :param database: database instance - :type database: DbDir - :param base: base name of the table - :type base: str - """ - return os.path.join(database.get_save_path(), base + ".gbkp.new") - -def __do_export(database): - """ - Loop through each table of the database, saving the pickled data - a file. - - :param database: database instance to backup - :type database: DbDir - """ - try: - for (base, tbl) in __build_tbl_map(database): - backup_name = __mk_tmp_name(database, base) - backup_table = open(backup_name, 'wb') - - cursor = tbl.cursor() - data = cursor.first() - while data: - pickle.dump(data, backup_table, 2) - data = cursor.next() - cursor.close() - backup_table.close() - except (IOError,OSError): - return - - for (base, tbl) in __build_tbl_map(database): - new_name = __mk_backup_name(database, base) - old_name = __mk_tmp_name(database, base) - if os.path.isfile(new_name): - os.unlink(new_name) - os.rename(old_name, new_name) - -def restore(database): - """ - Restores the database to a set of backup files. These files consist - of the pickled database tables, one file for each table. - - The heavy lifting is done by the private :py:func:`__do__restore` function. - The purpose of this function is to catch any exceptions that occur. - - :param database: database instance to restore - :type database: DbDir - """ - try: - __do_restore(database) - except (OSError, IOError) as msg: - raise DbException(str(msg)) - -def __do_restore(database): - """ - Loop through each table of the database, restoring the pickled data - to the appropriate database file. - - :param database: database instance to backup - :type database: DbDir - """ - for (base, tbl) in __build_tbl_map(database): - backup_name = __mk_backup_name(database, base) - backup_table = open(backup_name, 'rb') - __load_tbl_txn(database, backup_table, tbl) - - database.rebuild_secondary() - -def __load_tbl_txn(database, backup_table, tbl): - """ - Return the temporary backup name of the database table - - :param database: database instance - :type database: DbDir - :param backup_table: file containing the backup data - :type backup_table: file - :param tbl: Berkeley db database table - :type tbl: Berkeley db database table - """ - try: - while True: - data = pickle.load(backup_table) - txn = database.env.txn_begin() - tbl.put(data[0], data[1], txn=txn) - txn.commit() - except EOFError: - backup_table.close() - -def __build_tbl_map(database): - """ - Builds a table map of names to database tables. - - :param database: database instance to backup - :type database: DbDir - """ - return [ - ( PERSON_TBL, database.person_map.db), - ( FAMILY_TBL, database.family_map.db), - ( PLACES_TBL, database.place_map.db), - ( SOURCES_TBL, database.source_map.db), - ( CITATIONS_TBL, database.citation_map.db), - ( REPO_TBL, database.repository_map.db), - ( NOTE_TBL, database.note_map.db), - ( MEDIA_TBL, database.media_map.db), - ( EVENTS_TBL, database.event_map.db), - ( TAG_TBL, database.tag_map.db), - ( META, database.metadata.db), - ] diff --git a/gramps/plugins/database/bsddb_support/summary.py b/gramps/plugins/database/bsddb_support/summary.py new file mode 100644 index 000000000..ccc94d2dc --- /dev/null +++ b/gramps/plugins/database/bsddb_support/summary.py @@ -0,0 +1,62 @@ +## Removed from clidbman.py +## specific to bsddb + +from bsddb3 import dbshelve, db +import os + +from gramps.gen.db import META, PERSON_TBL +from gramps.gen.db.dbconst import BDBVERSFN + +def get_dbdir_summary(dirpath, name): + """ + Returns (people_count, bsddb_version, schema_version) of + current DB. + Returns ("Unknown", "Unknown", "Unknown") if invalid DB or other error. + """ + + bdbversion_file = os.path.join(dirpath, BDBVERSFN) + if os.path.isfile(bdbversion_file): + vers_file = open(bdbversion_file) + bsddb_version = vers_file.readline().strip() + else: + return "Unknown", "Unknown", "Unknown" + + current_bsddb_version = str(db.version()) + if bsddb_version != current_bsddb_version: + return "Unknown", bsddb_version, "Unknown" + + env = db.DBEnv() + flags = db.DB_CREATE | db.DB_PRIVATE |\ + db.DB_INIT_MPOOL |\ + db.DB_INIT_LOG | db.DB_INIT_TXN + try: + env.open(dirpath, flags) + except Exception as msg: + LOG.warning("Error opening db environment for '%s': %s" % + (name, str(msg))) + try: + env.close() + except Exception as msg: + LOG.warning("Error closing db environment for '%s': %s" % + (name, str(msg))) + return "Unknown", bsddb_version, "Unknown" + dbmap1 = dbshelve.DBShelf(env) + fname = os.path.join(dirpath, META + ".db") + try: + dbmap1.open(fname, META, db.DB_HASH, db.DB_RDONLY) + except: + env.close() + return "Unknown", bsddb_version, "Unknown" + schema_version = dbmap1.get(b'version', default=None) + dbmap1.close() + dbmap2 = dbshelve.DBShelf(env) + fname = os.path.join(dirpath, PERSON_TBL + ".db") + try: + dbmap2.open(fname, PERSON_TBL, db.DB_HASH, db.DB_RDONLY) + except: + env.close() + return "Unknown", bsddb_version, schema_version + count = len(dbmap2) + dbmap2.close() + env.close() + return (count, bsddb_version, schema_version) diff --git a/gramps/gen/db/test/__init__.py b/gramps/plugins/database/bsddb_support/test/__init__.py similarity index 100% rename from gramps/gen/db/test/__init__.py rename to gramps/plugins/database/bsddb_support/test/__init__.py diff --git a/gramps/gen/db/test/cursor_test.py b/gramps/plugins/database/bsddb_support/test/cursor_test.py similarity index 100% rename from gramps/gen/db/test/cursor_test.py rename to gramps/plugins/database/bsddb_support/test/cursor_test.py diff --git a/gramps/gen/db/test/db_test.py b/gramps/plugins/database/bsddb_support/test/db_test.py similarity index 100% rename from gramps/gen/db/test/db_test.py rename to gramps/plugins/database/bsddb_support/test/db_test.py diff --git a/gramps/gen/db/test/grampsdbtestbase.py b/gramps/plugins/database/bsddb_support/test/grampsdbtestbase.py similarity index 100% rename from gramps/gen/db/test/grampsdbtestbase.py rename to gramps/plugins/database/bsddb_support/test/grampsdbtestbase.py diff --git a/gramps/gen/db/test/reference_map_test.py b/gramps/plugins/database/bsddb_support/test/reference_map_test.py similarity index 100% rename from gramps/gen/db/test/reference_map_test.py rename to gramps/plugins/database/bsddb_support/test/reference_map_test.py diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py index 768db02d9..2b5648b9d 100644 --- a/gramps/plugins/database/bsddb_support/write.py +++ b/gramps/plugins/database/bsddb_support/write.py @@ -40,16 +40,12 @@ from functools import wraps import logging from sys import maxsize, getfilesystemencoding, version_info -try: - from bsddb3 import dbshelve, db -except: - # FIXME: make this more abstract to deal with other backends - class db: - DB_HASH = 0 - DBRunRecoveryError = 0 - DBAccessError = 0 - DBPageNotFoundError = 0 - DBInvalidArgError = 0 +from bsddb3 import dbshelve, db +from bsddb3.db import DB_CREATE, DB_AUTO_COMMIT, DB_DUP, DB_DUPSORT, DB_RDONLY + +DBFLAGS_O = DB_CREATE | DB_AUTO_COMMIT # Default flags for database open +DBFLAGS_R = DB_RDONLY # Flags to open a database read-only +DBFLAGS_D = DB_DUP | DB_DUPSORT # Default flags for duplicate keys #------------------------------------------------------------------------- # @@ -2450,6 +2446,146 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): """ return DbTxn + def backup(self): + """ + Exports the database to a set of backup files. These files consist + of the pickled database tables, one file for each table. + + The heavy lifting is done by the private :py:func:`__do__export` function. + The purpose of this function is to catch any exceptions that occur. + + :param database: database instance to backup + :type database: DbDir + """ + try: + do_export(self) + except (OSError, IOError) as msg: + raise DbException(str(msg)) + + def restore(self): + """ + Restores the database to a set of backup files. These files consist + of the pickled database tables, one file for each table. + + The heavy lifting is done by the private :py:func:`__do__restore` function. + The purpose of this function is to catch any exceptions that occur. + + :param database: database instance to restore + :type database: DbDir + """ + try: + do_restore(self) + except (OSError, IOError) as msg: + raise DbException(str(msg)) + +def mk_backup_name(database, base): + """ + Return the backup name of the database table + + :param database: database instance + :type database: DbDir + :param base: base name of the table + :type base: str + """ + return os.path.join(database.get_save_path(), base + ".gbkp") + +def mk_tmp_name(database, base): + """ + Return the temporary backup name of the database table + + :param database: database instance + :type database: DbDir + :param base: base name of the table + :type base: str + """ + return os.path.join(database.get_save_path(), base + ".gbkp.new") + +def do_export(database): + """ + Loop through each table of the database, saving the pickled data + a file. + + :param database: database instance to backup + :type database: DbDir + """ + try: + for (base, tbl) in build_tbl_map(database): + backup_name = mk_tmp_name(database, base) + backup_table = open(backup_name, 'wb') + + cursor = tbl.cursor() + data = cursor.first() + while data: + pickle.dump(data, backup_table, 2) + data = cursor.next() + cursor.close() + backup_table.close() + except (IOError,OSError): + return + + for (base, tbl) in build_tbl_map(database): + new_name = mk_backup_name(database, base) + old_name = mk_tmp_name(database, base) + if os.path.isfile(new_name): + os.unlink(new_name) + os.rename(old_name, new_name) + +def do_restore(database): + """ + Loop through each table of the database, restoring the pickled data + to the appropriate database file. + + :param database: database instance to backup + :type database: DbDir + """ + for (base, tbl) in build_tbl_map(database): + backup_name = mk_backup_name(database, base) + backup_table = open(backup_name, 'rb') + load_tbl_txn(database, backup_table, tbl) + + database.rebuild_secondary() + +def load_tbl_txn(database, backup_table, tbl): + """ + Return the temporary backup name of the database table + + :param database: database instance + :type database: DbDir + :param backup_table: file containing the backup data + :type backup_table: file + :param tbl: Berkeley db database table + :type tbl: Berkeley db database table + """ + try: + while True: + data = pickle.load(backup_table) + txn = database.env.txn_begin() + tbl.put(data[0], data[1], txn=txn) + txn.commit() + except EOFError: + backup_table.close() + +def build_tbl_map(database): + """ + Builds a table map of names to database tables. + + :param database: database instance to backup + :type database: DbDir + """ + return [ + ( PERSON_TBL, database.person_map.db), + ( FAMILY_TBL, database.family_map.db), + ( PLACES_TBL, database.place_map.db), + ( SOURCES_TBL, database.source_map.db), + ( CITATIONS_TBL, database.citation_map.db), + ( REPO_TBL, database.repository_map.db), + ( NOTE_TBL, database.note_map.db), + ( MEDIA_TBL, database.media_map.db), + ( EVENTS_TBL, database.event_map.db), + ( TAG_TBL, database.tag_map.db), + ( META, database.metadata.db), + ] + def _mkname(path, name): return os.path.join(path, name + DBEXT) diff --git a/gramps/plugins/database/bsddb_support/dictionary.py b/gramps/plugins/database/dictionary.py similarity index 100% rename from gramps/plugins/database/bsddb_support/dictionary.py rename to gramps/plugins/database/dictionary.py diff --git a/gramps/plugins/gramplet/leak.py b/gramps/plugins/gramplet/leak.py index 2e30e7582..3c10a1681 100644 --- a/gramps/plugins/gramplet/leak.py +++ b/gramps/plugins/gramplet/leak.py @@ -31,7 +31,6 @@ Show uncollected objects in a window. #------------------------------------------------------------------------ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -from bsddb3.db import DBError #------------------------------------------------------------------------ # @@ -155,6 +154,13 @@ class Leak(Gramplet): parent=self.uistate.window) def display(self): + try: + from bsddb3.db import DBError + except: + class DBError(Exception): + """ + Dummy. + """ gc.collect(2) self.model.clear() count = 0 From c0f9559f8cd336d520ae9f90942f879a6c2d1687 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Tue, 12 May 2015 23:08:54 -0400 Subject: [PATCH 054/105] Database backend writes its plugin id in database.txt --- gramps/gen/db/dbconst.py | 4 +++- gramps/plugins/database/bsddb_support/write.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/gramps/gen/db/dbconst.py b/gramps/gen/db/dbconst.py index 7b91ab7e6..e53535248 100644 --- a/gramps/gen/db/dbconst.py +++ b/gramps/gen/db/dbconst.py @@ -31,7 +31,8 @@ Declare constants used by database modules __all__ = ( ('DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO', 'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN', - 'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN' + 'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN', + 'DBBACKEND' ) + ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY', @@ -47,6 +48,7 @@ DBUNDOFN = "undo.db" # File name of 'undo' database DBLOCKFN = "lock" # File name of lock file DBRECOVFN = "need_recover" # File name of recovery file BDBVERSFN = "bdbversion.txt"# File name of Berkeley DB version file +DBBACKEND = "database.txt" # File name of Database backend file SCHVERSFN = "schemaversion.txt"# File name of schema version file PCKVERSFN = "pickleupgrade.txt" # Indicator that pickle has been upgrade t Python3 DBLOGNAME = ".Db" # Name of logger diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py index 2b5648b9d..606f9484a 100644 --- a/gramps/plugins/database/bsddb_support/write.py +++ b/gramps/plugins/database/bsddb_support/write.py @@ -2430,6 +2430,11 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): version = str(_DBVERSION) version_file.write(version) + versionpath = os.path.join(name, str(DBBACKEND)) + _LOG.debug("Write database backend file to 'bsddb'") + with open(versionpath, "w") as version_file: + version_file.write("bsddb") + self.metadata.close() self.env.close() From 337ba6b22a6ceb591b6d4fcd99e8e4ec667eaba3 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Wed, 13 May 2015 08:09:30 -0400 Subject: [PATCH 055/105] Added Django and Dictionary plugins, to be developed --- gramps/plugins/database/dbdjango.py | 2036 ++++++++++++++++ gramps/plugins/database/dictionarydb.gpr.py | 31 + .../{dictionary.py => dictionarydb.py} | 22 +- .../database/django_support/libdjango.py | 2063 +++++++++++++++++ gramps/plugins/database/djangodb.gpr.py | 31 + gramps/plugins/database/djangodb.py | 2036 ++++++++++++++++ 6 files changed, 6208 insertions(+), 11 deletions(-) create mode 100644 gramps/plugins/database/dbdjango.py create mode 100644 gramps/plugins/database/dictionarydb.gpr.py rename gramps/plugins/database/{dictionary.py => dictionarydb.py} (98%) create mode 100644 gramps/plugins/database/django_support/libdjango.py create mode 100644 gramps/plugins/database/djangodb.gpr.py create mode 100644 gramps/plugins/database/djangodb.py diff --git a/gramps/plugins/database/dbdjango.py b/gramps/plugins/database/dbdjango.py new file mode 100644 index 000000000..adb28dc54 --- /dev/null +++ b/gramps/plugins/database/dbdjango.py @@ -0,0 +1,2036 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2009 Douglas S. Blank <doug.blank@gmail.com> +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" Implements a Db interface """ + +#------------------------------------------------------------------------ +# +# Python Modules +# +#------------------------------------------------------------------------ +import sys +import time +import re +import base64 +import pickle +import os + +#------------------------------------------------------------------------ +# +# Gramps Modules +# +#------------------------------------------------------------------------ +import gramps +from gramps.gen.lib import (Person, Family, Event, Place, Repository, + Citation, Source, Note, MediaObject, Tag, + Researcher) +from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn +from gramps.gen.utils.callback import Callback +from gramps.gen.updatecallback import UpdateCallback +from gramps.gen.db import (PERSON_KEY, + FAMILY_KEY, + CITATION_KEY, + SOURCE_KEY, + EVENT_KEY, + MEDIA_KEY, + PLACE_KEY, + REPOSITORY_KEY, + NOTE_KEY) +from gramps.gen.utils.id import create_id +from django.db import transaction + +class Environment(object): + """ + Implements the Environment API. + """ + def __init__(self, db): + self.db = db + + def txn_begin(self): + return DjangoTxn("DbDjango Transaction", self.db) + +class Table(object): + """ + Implements Table interface. + """ + def __init__(self, funcs): + self.funcs = funcs + + def cursor(self): + """ + Returns a Cursor for this Table. + """ + return self.funcs["cursor_func"]() + + def put(key, data, txn=None): + self[key] = data + +class Map(dict): + """ + Implements the map API for person_map, etc. + + Takes a Table() as argument. + """ + def __init__(self, tbl, *args, **kwargs): + super().__init__(*args, **kwargs) + self.db = tbl + +class MetaCursor(object): + def __init__(self): + pass + def __enter__(self): + return self + def __iter__(self): + return self.__next__() + def __next__(self): + yield None + def __exit__(self, *args, **kwargs): + pass + def iter(self): + yield None + def first(self): + self._iter = self.__iter__() + return self.next() + def next(self): + try: + return next(self._iter) + except: + return None + def close(self): + pass + +class Cursor(object): + 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 (bytes(item.handle, "utf-8"), self.func(item.handle)) + def __exit__(self, *args, **kwargs): + pass + def iter(self): + for item in self.model.all(): + yield (bytes(item.handle, "utf-8"), self.func(item.handle)) + yield None + def first(self): + self._iter = self.__iter__() + return self.next() + def next(self): + try: + return next(self._iter) + except: + return None + def close(self): + pass + +class Bookmarks(object): + def __init__(self): + self.handles = [] + def get(self): + return self.handles + def append(self, handle): + self.handles.append(handle) + +class DjangoTxn(DbTxn): + def __init__(self, message, db, table=None): + DbTxn.__init__(self, message, db) + self.table = table + + def get(self, key, default=None, txn=None, **kwargs): + """ + Returns the data object associated with key + """ + try: + return self.table.objects(handle=key) + except: + if txn and key in txn: + return txn[key] + else: + return None + + def put(self, handle, new_data, txn): + """ + """ + txn[handle] = new_data + + def commit(self): + pass + +class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): + """ + A Gramps Database Backend. This replicates the grampsdb functions. + """ + # Set up dictionary for callback signal handler + # --------------------------------------------- + # 1. Signals for primary objects + __signals__ = dict((obj+'-'+op, signal) + for obj in + ['person', 'family', 'event', 'place', + 'source', 'citation', 'media', 'note', 'repository', 'tag'] + for op, signal in zip( + ['add', 'update', 'delete', 'rebuild'], + [(list,), (list,), (list,), None] + ) + ) + + # 2. Signals for long operations + __signals__.update(('long-op-'+op, signal) for op, signal in zip( + ['start', 'heartbeat', 'end'], + [(object,), None, None] + )) + + # 3. Special signal for change in home person + __signals__['home-person-changed'] = None + + # 4. Signal for change in person group name, parameters are + __signals__['person-groupname-rebuild'] = (str, str) + + def __init__(self, directory=None): + DbReadBase.__init__(self) + DbWriteBase.__init__(self) + Callback.__init__(self) + self._tables = { + 'Person': + { + "handle_func": self.get_person_from_handle, + "gramps_id_func": self.get_person_from_gramps_id, + "class_func": gramps.gen.lib.Person, + "cursor_func": self.get_person_cursor, + "handles_func": self.get_person_handles, + "iter_func": self.iter_people, + }, + 'Family': + { + "handle_func": self.get_family_from_handle, + "gramps_id_func": self.get_family_from_gramps_id, + "class_func": gramps.gen.lib.Family, + "cursor_func": self.get_family_cursor, + "handles_func": self.get_family_handles, + "iter_func": self.iter_families, + }, + 'Source': + { + "handle_func": self.get_source_from_handle, + "gramps_id_func": self.get_source_from_gramps_id, + "class_func": gramps.gen.lib.Source, + "cursor_func": self.get_source_cursor, + "handles_func": self.get_source_handles, + "iter_func": self.iter_sources, + }, + 'Citation': + { + "handle_func": self.get_citation_from_handle, + "gramps_id_func": self.get_citation_from_gramps_id, + "class_func": gramps.gen.lib.Citation, + "cursor_func": self.get_citation_cursor, + "handles_func": self.get_citation_handles, + "iter_func": self.iter_citations, + }, + 'Event': + { + "handle_func": self.get_event_from_handle, + "gramps_id_func": self.get_event_from_gramps_id, + "class_func": gramps.gen.lib.Event, + "cursor_func": self.get_event_cursor, + "handles_func": self.get_event_handles, + "iter_func": self.iter_events, + }, + 'Media': + { + "handle_func": self.get_object_from_handle, + "gramps_id_func": self.get_object_from_gramps_id, + "class_func": gramps.gen.lib.MediaObject, + "cursor_func": self.get_media_cursor, + "handles_func": self.get_media_object_handles, + "iter_func": self.iter_media_objects, + }, + 'Place': + { + "handle_func": self.get_place_from_handle, + "gramps_id_func": self.get_place_from_gramps_id, + "class_func": gramps.gen.lib.Place, + "cursor_func": self.get_place_cursor, + "handles_func": self.get_place_handles, + "iter_func": self.iter_places, + }, + 'Repository': + { + "handle_func": self.get_repository_from_handle, + "gramps_id_func": self.get_repository_from_gramps_id, + "class_func": gramps.gen.lib.Repository, + "cursor_func": self.get_repository_cursor, + "handles_func": self.get_repository_handles, + "iter_func": self.iter_repositories, + }, + 'Note': + { + "handle_func": self.get_note_from_handle, + "gramps_id_func": self.get_note_from_gramps_id, + "class_func": gramps.gen.lib.Note, + "cursor_func": self.get_note_cursor, + "handles_func": self.get_note_handles, + "iter_func": self.iter_notes, + }, + 'Tag': + { + "handle_func": self.get_tag_from_handle, + "gramps_id_func": None, + "class_func": gramps.gen.lib.Tag, + "cursor_func": self.get_tag_cursor, + "handles_func": self.get_tag_handles, + "iter_func": self.iter_tags, + }, + } + # 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.undo_callback = None + self.redo_callback = None + self.undo_history_callback = None + self.modified = 0 + self.txn = DjangoTxn("DbDjango Transaction", self) + self.transaction = None + # Import cache for gedcom import, uses transactions, and + # two step adding of objects. + self.import_cache = {} + self.use_import_cache = False + self.use_db_cache = True + self._directory = directory + if directory: + self.load(directory) + + def load(self, directory, pulse_progress=None, mode=None): + self._directory = directory + from django.conf import settings + default_settings = {} + settings_file = os.path.join(directory, "default_settings.py") + with open(settings_file) as f: + code = compile(f.read(), settings_file, 'exec') + exec(code, globals(), default_settings) + + class Module(object): + def __init__(self, dictionary): + self.dictionary = dictionary + def __getattr__(self, item): + return self.dictionary[item] + + try: + settings.configure(Module(default_settings)) + except RuntimeError: + # already configured; ignore + pass + + import django + django.setup() + + from django_support.libdjango import DjangoInterface + self.dji = DjangoInterface() + 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 = DjangoTxn("ID Transaction", self, self.dji.Person) + self.fid_trans = DjangoTxn("FID Transaction", self, self.dji.Family) + self.pid_trans = DjangoTxn("PID Transaction", self, self.dji.Place) + self.cid_trans = DjangoTxn("CID Transaction", self, self.dji.Citation) + self.sid_trans = DjangoTxn("SID Transaction", self, self.dji.Source) + self.oid_trans = DjangoTxn("OID Transaction", self, self.dji.Media) + self.rid_trans = DjangoTxn("RID Transaction", self, self.dji.Repository) + self.nid_trans = DjangoTxn("NID Transaction", self, self.dji.Note) + self.eid_trans = DjangoTxn("EID Transaction", self, self.dji.Event) + 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 = Environment(self) + self.person_map = Map(Table(self._tables["Person"])) + self.family_map = Map(Table(self._tables["Family"])) + self.place_map = Map(Table(self._tables["Place"])) + self.citation_map = Map(Table(self._tables["Citation"])) + self.source_map = Map(Table(self._tables["Source"])) + self.repository_map = Map(Table(self._tables["Repository"])) + self.note_map = Map(Table(self._tables["Note"])) + self.media_map = Map(Table(self._tables["Media"])) + self.event_map = Map(Table(self._tables["Event"])) + self.tag_map = Map(Table(self._tables["Tag"])) + self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) + self.name_group = {} + self.event_names = set() + self.individual_attributes = set() + self.family_attributes = set() + self.source_attributes = set() + self.child_ref_types = set() + self.family_rel_types = set() + self.event_role_names = set() + self.name_types = set() + self.origin_types = set() + self.repository_types = set() + self.note_types = set() + self.source_media_types = set() + self.url_types = set() + self.media_attributes = set() + self.place_types = set() + + def prepare_import(self): + """ + DbDjango does not commit data on gedcom import, but saves them + for later commit. + """ + self.use_import_cache = True + self.import_cache = {} + + @transaction.atomic + def commit_import(self): + """ + Commits the items that were queued up during the last gedcom + import for two step adding. + """ + # First we add the primary objects: + for key in list(self.import_cache.keys()): + obj = self.import_cache[key] + if isinstance(obj, Person): + self.dji.add_person(obj.serialize()) + elif isinstance(obj, Family): + self.dji.add_family(obj.serialize()) + elif isinstance(obj, Event): + self.dji.add_event(obj.serialize()) + elif isinstance(obj, Place): + self.dji.add_place(obj.serialize()) + elif isinstance(obj, Repository): + self.dji.add_repository(obj.serialize()) + elif isinstance(obj, Citation): + self.dji.add_citation(obj.serialize()) + elif isinstance(obj, Source): + self.dji.add_source(obj.serialize()) + elif isinstance(obj, Note): + self.dji.add_note(obj.serialize()) + elif isinstance(obj, MediaObject): + self.dji.add_media(obj.serialize()) + elif isinstance(obj, Tag): + self.dji.add_tag(obj.serialize()) + # Next we add the links: + for key in list(self.import_cache.keys()): + obj = self.import_cache[key] + if isinstance(obj, Person): + self.dji.add_person_detail(obj.serialize()) + elif isinstance(obj, Family): + self.dji.add_family_detail(obj.serialize()) + elif isinstance(obj, Event): + self.dji.add_event_detail(obj.serialize()) + elif isinstance(obj, Place): + self.dji.add_place_detail(obj.serialize()) + elif isinstance(obj, Repository): + self.dji.add_repository_detail(obj.serialize()) + elif isinstance(obj, Citation): + self.dji.add_citation_detail(obj.serialize()) + elif isinstance(obj, Source): + self.dji.add_source_detail(obj.serialize()) + elif isinstance(obj, Note): + self.dji.add_note_detail(obj.serialize()) + elif isinstance(obj, MediaObject): + self.dji.add_media_detail(obj.serialize()) + elif isinstance(obj, Tag): + self.dji.add_tag_detail(obj.serialize()) + self.use_import_cache = False + self.import_cache = {} + self.dji.update_publics() + + def transaction_commit(self, txn): + pass + + def request_rebuild(self): + # caches are ok, but let's compute public's + self.dji.update_publics() + self.emit('person-rebuild') + self.emit('family-rebuild') + self.emit('place-rebuild') + self.emit('source-rebuild') + self.emit('citation-rebuild') + self.emit('media-rebuild') + self.emit('event-rebuild') + self.emit('repository-rebuild') + self.emit('note-rebuild') + self.emit('tag-rebuild') + + def get_undodb(self): + return None + + def transaction_abort(self, txn): + pass + + @staticmethod + def _validated_id_prefix(val, default): + if isinstance(val, str) 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_<object>_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 = Researcher() + return obj + + def get_tag_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Tag.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Tag.all()] + + def get_person_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Person.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Person.all()] + + def get_family_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Family.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Family.all()] + + def get_event_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Event.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Event.all()] + + def get_citation_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Citation.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Citation.all()] + + def get_source_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Source.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Source.all()] + + def get_place_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Place.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Place.all()] + + def get_repository_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Repository.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Repository.all()] + + def get_media_object_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Media.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Media.all()] + + def get_note_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Note.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Note.all()] + + def get_media_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + media = self.dji.Media.get(handle=handle) + except: + return None + return self.make_media(media) + + def get_event_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + event = self.dji.Event.get(handle=handle) + except: + return None + return self.make_event(event) + + def get_family_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + family = self.dji.Family.get(handle=handle) + except: + return None + return self.make_family(family) + + def get_repository_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + repository = self.dji.Repository.get(handle=handle) + except: + return None + return self.make_repository(repository) + + def get_person_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + person = self.dji.Person.get(handle=handle) + except: + return None + return self.make_person(person) + + def get_tag_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + tag = self.dji.Tag.get(handle=handle) + except: + return None + return self.make_tag(tag) + + def make_repository(self, repository): + if self.use_db_cache and repository.cache: + data = repository.from_cache() + else: + data = self.dji.get_repository(repository) + return Repository.create(data) + + def make_citation(self, citation): + if self.use_db_cache and citation.cache: + data = citation.from_cache() + else: + data = self.dji.get_citation(citation) + return Citation.create(data) + + def make_source(self, source): + if self.use_db_cache and source.cache: + data = source.from_cache() + else: + data = self.dji.get_source(source) + return Source.create(data) + + def make_family(self, family): + if self.use_db_cache and family.cache: + data = family.from_cache() + else: + data = self.dji.get_family(family) + return Family.create(data) + + def make_person(self, person): + if self.use_db_cache and person.cache: + data = person.from_cache() + else: + data = self.dji.get_person(person) + return Person.create(data) + + def make_event(self, event): + if self.use_db_cache and event.cache: + data = event.from_cache() + else: + data = self.dji.get_event(event) + return Event.create(data) + + def make_note(self, note): + if self.use_db_cache and note.cache: + data = note.from_cache() + else: + data = self.dji.get_note(note) + return Note.create(data) + + def make_tag(self, tag): + data = self.dji.get_tag(tag) + return Tag.create(data) + + def make_place(self, place): + if self.use_db_cache and place.cache: + data = place.from_cache() + else: + data = self.dji.get_place(place) + return Place.create(data) + + def make_media(self, media): + if self.use_db_cache and media.cache: + data = media.from_cache() + else: + data = self.dji.get_media(media) + return MediaObject.create(data) + + def get_place_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + place = self.dji.Place.get(handle=handle) + except: + return None + return self.make_place(place) + + def get_citation_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + citation = self.dji.Citation.get(handle=handle) + except: + return None + return self.make_citation(citation) + + def get_source_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + source = self.dji.Source.get(handle=handle) + except: + return None + return self.make_source(source) + + def get_note_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + note = self.dji.Note.get(handle=handle) + except: + return None + return self.make_note(note) + + def get_object_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + media = self.dji.Media.get(handle=handle) + except: + return None + return self.make_media(media) + + def get_default_person(self): + people = self.dji.Person.all() + if people.count() > 0: + return self.make_person(people[0]) + return None + + def iter_people(self): + return (self.get_person_from_handle(person.handle) + for person in self.dji.Person.all()) + + def iter_person_handles(self): + return (person.handle for person in self.dji.Person.all()) + + def iter_families(self): + return (self.get_family_from_handle(family.handle) + for family in self.dji.Family.all()) + + def iter_family_handles(self): + return (family.handle for family in self.dji.Family.all()) + + def iter_notes(self): + return (self.get_note_from_handle(note.handle) + for note in self.dji.Note.all()) + + def iter_note_handles(self): + return (note.handle for note in self.dji.Note.all()) + + def iter_events(self): + return (self.get_event_from_handle(event.handle) + for event in self.dji.Event.all()) + + def iter_event_handles(self): + return (event.handle for event in self.dji.Event.all()) + + def iter_places(self): + return (self.get_place_from_handle(place.handle) + for place in self.dji.Place.all()) + + def iter_place_handles(self): + return (place.handle for place in self.dji.Place.all()) + + def iter_repositories(self): + return (self.get_repository_from_handle(repository.handle) + for repository in self.dji.Repository.all()) + + def iter_repository_handles(self): + return (repository.handle for repository in self.dji.Repository.all()) + + def iter_sources(self): + return (self.get_source_from_handle(source.handle) + for source in self.dji.Source.all()) + + def iter_source_handles(self): + return (source.handle for source in self.dji.Source.all()) + + def iter_citations(self): + return (self.get_citation_from_handle(citation.handle) + for citation in self.dji.Citation.all()) + + def iter_citation_handles(self): + return (citation.handle for citation in self.dji.Citation.all()) + + def iter_tags(self): + return (self.get_tag_from_handle(tag.handle) + for tag in self.dji.Tag.all()) + + def iter_tag_handles(self): + return (tag.handle for tag in self.dji.Tag.all()) + + def iter_media_objects(self): + return (self.get_media_from_handle(media.handle) + for media in self.dji.Media.all()) + + def get_tag_from_name(self, name): + try: + tag = self.dji.Tag.filter(name=name) + return self.make_tag(tag[0]) + except: + return None + + def get_person_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Person.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_person(match_list[0]) + else: + return None + + def get_family_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + try: + family = self.dji.Family.get(gramps_id=gramps_id) + except: + return None + return self.make_family(family) + + def get_source_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Source.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_source(match_list[0]) + else: + return None + + def get_citation_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Citation.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_citation(match_list[0]) + else: + return None + + def get_event_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Event.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_event(match_list[0]) + else: + return None + + def get_object_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Media.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_media(match_list[0]) + else: + return None + + def get_place_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Place.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_place(match_list[0]) + else: + return None + + def get_repository_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Repsoitory.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_repository(match_list[0]) + else: + return None + + def get_note_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Note.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_note(match_list[0]) + else: + return None + + def get_number_of_people(self): + return self.dji.Person.count() + + def get_number_of_events(self): + return self.dji.Event.count() + + def get_number_of_places(self): + return self.dji.Place.count() + + def get_number_of_tags(self): + return self.dji.Tag.count() + + def get_number_of_families(self): + return self.dji.Family.count() + + def get_number_of_notes(self): + return self.dji.Note.count() + + def get_number_of_citations(self): + return self.dji.Citation.count() + + def get_number_of_sources(self): + return self.dji.Source.count() + + def get_number_of_media_objects(self): + return self.dji.Media.count() + + def get_number_of_repositories(self): + return self.dji.Repository.count() + + def get_place_cursor(self): + return Cursor(self.dji.Place, self.get_raw_place_data) + + def get_person_cursor(self): + return Cursor(self.dji.Person, self.get_raw_person_data) + + def get_family_cursor(self): + return Cursor(self.dji.Family, self.get_raw_family_data) + + def get_event_cursor(self): + return Cursor(self.dji.Event, self.get_raw_event_data) + + def get_citation_cursor(self): + return Cursor(self.dji.Citation, self.get_raw_citation_data) + + def get_source_cursor(self): + return Cursor(self.dji.Source, self.get_raw_source_data) + + def get_note_cursor(self): + return Cursor(self.dji.Note, self.get_raw_note_data) + + def get_tag_cursor(self): + return Cursor(self.dji.Tag, self.get_raw_tag_data) + + def get_repository_cursor(self): + return Cursor(self.dji.Repository, self.get_raw_repository_data) + + def get_media_cursor(self): + return Cursor(self.dji.Media, self.get_raw_object_data) + + def has_gramps_id(self, obj_key, gramps_id): + key2table = { + PERSON_KEY: self.dji.Person, + FAMILY_KEY: self.dji.Family, + SOURCE_KEY: self.dji.Source, + CITATION_KEY: self.dji.Citation, + EVENT_KEY: self.dji.Event, + MEDIA_KEY: self.dji.Media, + PLACE_KEY: self.dji.Place, + REPOSITORY_KEY: self.dji.Repository, + NOTE_KEY: self.dji.Note, + } + table = key2table[obj_key] + return table.filter(gramps_id=gramps_id).count() > 0 + + def has_person_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Person.filter(handle=handle).count() == 1 + + def has_family_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Family.filter(handle=handle).count() == 1 + + def has_citation_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Citation.filter(handle=handle).count() == 1 + + def has_source_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Source.filter(handle=handle).count() == 1 + + def has_repository_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Repository.filter(handle=handle).count() == 1 + + def has_note_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Note.filter(handle=handle).count() == 1 + + def has_place_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Place.filter(handle=handle).count() == 1 + + def has_event_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Event.filter(handle=handle).count() == 1 + + def has_tag_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Tag.filter(handle=handle).count() == 1 + + def has_object_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Media.filter(handle=handle).count() == 1 + + 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): + try: + return self.dji.get_person(self.dji.Person.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_family_data(self, handle): + try: + return self.dji.get_family(self.dji.Family.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_citation_data(self, handle): + try: + return self.dji.get_citation(self.dji.Citation.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_source_data(self, handle): + try: + return self.dji.get_source(self.dji.Source.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_repository_data(self, handle): + try: + return self.dji.get_repository(self.dji.Repository.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_note_data(self, handle): + try: + return self.dji.get_note(self.dji.Note.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_place_data(self, handle): + try: + return self.dji.get_place(self.dji.Place.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_object_data(self, handle): + try: + return self.dji.get_media(self.dji.Media.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_tag_data(self, handle): + try: + return self.dji.get_tag(self.dji.Tag.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_event_data(self, handle): + try: + return self.dji.get_event(self.dji.Event.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def add_person(self, person, trans, set_gid=True): + if not person.handle: + person.handle = create_id() + if not person.gramps_id and set_gid: + person.gramps_id = self.find_next_person_gramps_id() + self.commit_person(person, trans) + self.emit("person-add", ([person.handle],)) + return person.handle + + def add_family(self, family, trans, set_gid=True): + if not family.handle: + family.handle = create_id() + if not family.gramps_id and set_gid: + family.gramps_id = self.find_next_family_gramps_id() + self.commit_family(family, trans) + self.emit("family-add", ([family.handle],)) + return family.handle + + def add_citation(self, citation, trans, set_gid=True): + if not citation.handle: + citation.handle = create_id() + if not citation.gramps_id and set_gid: + citation.gramps_id = self.find_next_citation_gramps_id() + self.commit_citation(citation, trans) + self.emit("citation-add", ([citation.handle],)) + return citation.handle + + def add_source(self, source, trans, set_gid=True): + if not source.handle: + source.handle = create_id() + if not source.gramps_id and set_gid: + source.gramps_id = self.find_next_source_gramps_id() + self.commit_source(source, trans) + self.emit("source-add", ([source.handle],)) + return source.handle + + def add_repository(self, repository, trans, set_gid=True): + if not repository.handle: + repository.handle = create_id() + if not repository.gramps_id and set_gid: + repository.gramps_id = self.find_next_repository_gramps_id() + self.commit_repository(repository, trans) + self.emit("repository-add", ([repository.handle],)) + return repository.handle + + def add_note(self, note, trans, set_gid=True): + if not note.handle: + note.handle = create_id() + if not note.gramps_id and set_gid: + note.gramps_id = self.find_next_note_gramps_id() + self.commit_note(note, trans) + self.emit("note-add", ([note.handle],)) + return note.handle + + def add_place(self, place, trans, set_gid=True): + if not place.handle: + place.handle = create_id() + if not place.gramps_id and 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 = create_id() + if not event.gramps_id and 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 = 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 = create_id() + if not obj.gramps_id and 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): + if self.use_import_cache: + self.import_cache[person.handle] = person + else: + raw = person.serialize() + items = self.dji.Person.filter(handle=person.handle) + if items.count() > 0: + # Hack, for the moment: delete and re-add + items[0].delete() + self.dji.add_person(person.serialize()) + self.dji.add_person_detail(person.serialize()) + if items.count() > 0: + self.emit("person-update", ([person.handle],)) + else: + self.emit("person-add", ([person.handle],)) + + def commit_family(self, family, trans, change_time=None): + if self.use_import_cache: + self.import_cache[family.handle] = family + else: + raw = family.serialize() + items = self.dji.Family.filter(handle=family.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_family(family.serialize()) + self.dji.add_family_detail(family.serialize()) + if items.count() > 0: + self.emit("family-update", ([family.handle],)) + else: + self.emit("family-add", ([family.handle],)) + + def commit_citation(self, citation, trans, change_time=None): + if self.use_import_cache: + self.import_cache[citation.handle] = citation + else: + raw = citation.serialize() + items = self.dji.Citation.filter(handle=citation.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_citation(citation.serialize()) + self.dji.add_citation_detail(citation.serialize()) + if items.count() > 0: + self.emit("citation-update", ([citation.handle],)) + else: + self.emit("citation-add", ([citation.handle],)) + + def commit_source(self, source, trans, change_time=None): + if self.use_import_cache: + self.import_cache[source.handle] = source + else: + raw = source.serialize() + items = self.dji.Source.filter(handle=source.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_source(source.serialize()) + self.dji.add_source_detail(source.serialize()) + if items.count() > 0: + self.emit("source-update", ([source.handle],)) + else: + self.emit("source-add", ([source.handle],)) + + def commit_repository(self, repository, trans, change_time=None): + if self.use_import_cache: + self.import_cache[repository.handle] = repository + else: + raw = repository.serialize() + items = self.dji.Repository.filter(handle=repository.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_repository(repository.serialize()) + self.dji.add_repository_detail(repository.serialize()) + if items.count() > 0: + self.emit("repository-update", ([repository.handle],)) + else: + self.emit("repository-add", ([repository.handle],)) + + def commit_note(self, note, trans, change_time=None): + if self.use_import_cache: + self.import_cache[note.handle] = note + else: + raw = note.serialize() + items = self.dji.Note.filter(handle=note.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_note(note.serialize()) + self.dji.add_note_detail(note.serialize()) + if items.count() > 0: + self.emit("note-update", ([note.handle],)) + else: + self.emit("note-add", ([note.handle],)) + + def commit_place(self, place, trans, change_time=None): + if self.use_import_cache: + self.import_cache[place.handle] = place + else: + raw = place.serialize() + items = self.dji.Place.filter(handle=place.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_place(place.serialize()) + self.dji.add_place_detail(place.serialize()) + if items.count() > 0: + self.emit("place-update", ([place.handle],)) + else: + self.emit("place-add", ([place.handle],)) + + def commit_event(self, event, trans, change_time=None): + if self.use_import_cache: + self.import_cache[event.handle] = event + else: + raw = event.serialize() + items = self.dji.Event.filter(handle=event.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_event(event.serialize()) + self.dji.add_event_detail(event.serialize()) + if items.count() > 0: + self.emit("event-update", ([event.handle],)) + else: + self.emit("event-add", ([event.handle],)) + + def commit_tag(self, tag, trans, change_time=None): + if self.use_import_cache: + self.import_cache[tag.handle] = tag + else: + raw = tag.serialize() + items = self.dji.Tag.filter(handle=tag.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_tag(tag.serialize()) + self.dji.add_tag_detail(tag.serialize()) + if items.count() > 0: + self.emit("tag-update", ([tag.handle],)) + else: + self.emit("tag-add", ([tag.handle],)) + + def commit_media_object(self, media, transaction, change_time=None): + """ + Commit the specified MediaObject to the database, storing the changes + as part of the transaction. + """ + if self.use_import_cache: + self.import_cache[obj.handle] = media + else: + raw = media.serialize() + items = self.dji.Media.filter(handle=media.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_media(media.serialize()) + self.dji.add_media_detail(media.serialize()) + if items.count() > 0: + self.emit("media-update", ([media.handle],)) + else: + self.emit("media-add", ([media.handle],)) + + def get_gramps_ids(self, obj_key): + key2table = { + PERSON_KEY: self.id_trans, + FAMILY_KEY: self.fid_trans, + CITATION_KEY: self.cid_trans, + SOURCE_KEY: self.sid_trans, + EVENT_KEY: self.eid_trans, + MEDIA_KEY: self.oid_trans, + PLACE_KEY: self.pid_trans, + REPOSITORY_KEY: self.rid_trans, + NOTE_KEY: self.nid_trans, + } + + table = key2table[obj_key] + return list(table.keys()) + + def transaction_begin(self, transaction): + return + + def set_researcher(self, owner): + pass + + def copy_from_db(self, db): + """ + A (possibily) implementation-specific method to get data from + db into this database. + """ + # First we add the primary objects: + for key in db._tables.keys(): + cursor = db._tables[key]["cursor_func"] + for (handle, data) in cursor(): + if key == "Person": + self.dji.add_person(data) + elif key == "Family": + self.dji.add_family(data) + elif key == "Event": + self.dji.add_event(data) + elif key == "Place": + self.dji.add_place(data) + elif key == "Repository": + self.dji.add_repository(data) + elif key == "Citation": + self.dji.add_citation(data) + elif key == "Source": + self.dji.add_source(data) + elif key == "Note": + self.dji.add_note(data) + elif key == "Media": + self.dji.add_media(data) + elif key == "Tag": + self.dji.add_tag(data) + for key in db._tables.keys(): + cursor = db._tables[key]["cursor_func"] + for (handle, data) in cursor(): + if key == "Person": + self.dji.add_person_detail(data) + elif key == "Family": + self.dji.add_family_detail(data) + elif key == "Event": + self.dji.add_event_detail(data) + elif key == "Place": + self.dji.add_place_detail(data) + elif key == "Repository": + self.dji.add_repository_detail(data) + elif key == "Citation": + self.dji.add_citation_detail(data) + elif key == "Source": + self.dji.add_source_detail(data) + elif key == "Note": + self.dji.add_note_detail(data) + elif key == "Media": + self.dji.add_media_detail(data) + elif key == "Tag": + self.dji.add_tag_detail(data) + # Next we add the links: + self.dji.update_publics() + + def get_from_name_and_handle(self, table_name, handle): + """ + Returns a gen.lib object (or None) given table_name and + handle. + + Examples: + + >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") + >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") + """ + if table_name in self._tables: + return self._tables[table_name]["handle_func"](handle) + return None + + def is_empty(self): + """ + Is the database empty? + """ + return (self.get_number_of_people() == 0 and + self.get_number_of_events() == 0 and + self.get_number_of_places() == 0 and + self.get_number_of_tags() == 0 and + self.get_number_of_families() == 0 and + self.get_number_of_notes() == 0 and + self.get_number_of_citations() == 0 and + self.get_number_of_sources() == 0 and + self.get_number_of_media_objects() == 0 and + self.get_number_of_repositories() == 0) + + __callback_map = {} + + def set_prefixes(self, person, media, family, source, citation, + place, event, repository, note): + self.set_person_id_prefix(person) + self.set_object_id_prefix(media) + self.set_family_id_prefix(family) + self.set_source_id_prefix(source) + self.set_citation_id_prefix(citation) + self.set_place_id_prefix(place) + self.set_event_id_prefix(event) + self.set_repository_id_prefix(repository) + self.set_note_id_prefix(note) + + def has_changed(self): + return False + + def find_backlink_handles(self, handle, include_classes=None): + ## FIXME: figure out how to get objects that refer + ## to this handle + return [] + + def get_note_bookmarks(self): + return self.note_bookmarks + + def get_media_bookmarks(self): + return self.media_bookmarks + + def get_repo_bookmarks(self): + return self.repo_bookmarks + + def get_citation_bookmarks(self): + return self.citation_bookmarks + + def get_source_bookmarks(self): + return self.source_bookmarks + + def get_place_bookmarks(self): + return self.place_bookmarks + + def get_event_bookmarks(self): + return self.event_bookmarks + + def get_bookmarks(self): + return self.bookmarks + + def get_family_bookmarks(self): + return self.family_bookmarks + + def get_save_path(self): + return "/tmp/" + + ## Get types: + def get_event_attribute_types(self): + """ + Return a list of all Attribute types assocated with Event instances + in the database. + """ + return list(self.event_attributes) + + def get_event_types(self): + """ + Return a list of all event types in the database. + """ + return list(self.event_names) + + def get_person_event_types(self): + """ + Deprecated: Use get_event_types + """ + return list(self.event_names) + + def get_person_attribute_types(self): + """ + Return a list of all Attribute types assocated with Person instances + in the database. + """ + return list(self.individual_attributes) + + def get_family_attribute_types(self): + """ + Return a list of all Attribute types assocated with Family instances + in the database. + """ + return list(self.family_attributes) + + def get_family_event_types(self): + """ + Deprecated: Use get_event_types + """ + return list(self.event_names) + + def get_media_attribute_types(self): + """ + Return a list of all Attribute types assocated with Media and MediaRef + instances in the database. + """ + return list(self.media_attributes) + + def get_family_relation_types(self): + """ + Return a list of all relationship types assocated with Family + instances in the database. + """ + return list(self.family_rel_types) + + def get_child_reference_types(self): + """ + Return a list of all child reference types assocated with Family + instances in the database. + """ + return list(self.child_ref_types) + + def get_event_roles(self): + """ + Return a list of all custom event role names assocated with Event + instances in the database. + """ + return list(self.event_role_names) + + def get_name_types(self): + """ + Return a list of all custom names types assocated with Person + instances in the database. + """ + return list(self.name_types) + + def get_origin_types(self): + """ + Return a list of all custom origin types assocated with Person/Surname + instances in the database. + """ + return list(self.origin_types) + + def get_repository_types(self): + """ + Return a list of all custom repository types assocated with Repository + instances in the database. + """ + return list(self.repository_types) + + def get_note_types(self): + """ + Return a list of all custom note types assocated with Note instances + in the database. + """ + return list(self.note_types) + + def get_source_attribute_types(self): + """ + Return a list of all Attribute types assocated with Source/Citation + instances in the database. + """ + return list(self.source_attributes) + + def get_source_media_types(self): + """ + Return a list of all custom source media types assocated with Source + instances in the database. + """ + return list(self.source_media_types) + + def get_url_types(self): + """ + Return a list of all custom names types assocated with Url instances + in the database. + """ + return list(self.url_types) + + def get_place_types(self): + """ + Return a list of all custom place types assocated with Place instances + in the database. + """ + return list(self.place_types) + + def get_default_handle(self): + people = self.dji.Person.all() + if people.count() > 0: + return people[0].handle + return None + + def close(self): + pass + + def get_surname_list(self): + return [] + + def is_open(self): + return True + + def get_table_names(self): + """Return a list of valid table names.""" + return list(self._tables.keys()) + + def find_initial_person(self): + return self.get_default_person() + + # Removals: + def remove_person(self, handle, txn): + self.dji.Person.filter(handle=handle)[0].delete() + self.emit("person-delete", ([handle],)) + + def remove_source(self, handle, transaction): + self.dji.Source.filter(handle=handle)[0].delete() + self.emit("source-delete", ([handle],)) + + def remove_citation(self, handle, transaction): + self.dji.Citation.filter(handle=handle)[0].delete() + self.emit("citation-delete", ([handle],)) + + def remove_event(self, handle, transaction): + self.dji.Event.filter(handle=handle)[0].delete() + self.emit("event-delete", ([handle],)) + + def remove_object(self, handle, transaction): + self.dji.Media.filter(handle=handle)[0].delete() + self.emit("media-delete", ([handle],)) + + def remove_place(self, handle, transaction): + self.dji.Place.filter(handle=handle)[0].delete() + self.emit("place-delete", ([handle],)) + + def remove_family(self, handle, transaction): + self.dji.Family.filter(handle=handle)[0].delete() + self.emit("family-delete", ([handle],)) + + def remove_repository(self, handle, transaction): + self.dji.Repository.filter(handle=handle)[0].delete() + self.emit("repository-delete", ([handle],)) + + def remove_note(self, handle, transaction): + self.dji.Note.filter(handle=handle)[0].delete() + self.emit("note-delete", ([handle],)) + + def remove_tag(self, handle, transaction): + self.dji.Tag.filter(handle=handle)[0].delete() + self.emit("tag-delete", ([handle],)) + + def remove_from_surname_list(self, person): + ## FIXME + pass + + def get_dbname(self): + return "Django Database" + + ## missing + + def find_place_child_handles(self, handle): + pass + + def get_cursor(self, table, txn=None, update=False, commit=False): + pass + + def get_from_name_and_handle(self, table_name, handle): + """ + Returns a gen.lib object (or None) given table_name and + handle. + + Examples: + + >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") + >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") + """ + if table_name in self._tables: + return self._tables[table_name]["handle_func"](handle) + return None + + def get_number_of_records(self, table): + pass + + def get_place_parent_cursor(self): + pass + + def get_place_tree_cursor(self): + pass + + def get_table_metadata(self, table_name): + """Return the metadata for a valid table name.""" + if table_name in self._tables: + return self._tables[table_name] + return None + + def get_transaction_class(self): + pass + + def undo(self, update_history=True): + # FIXME: + return self.undodb.undo(update_history) + + def redo(self, update_history=True): + # FIXME: + return self.undodb.redo(update_history) + + def backup(self): + pass + + def restore(self): + pass diff --git a/gramps/plugins/database/dictionarydb.gpr.py b/gramps/plugins/database/dictionarydb.gpr.py new file mode 100644 index 000000000..3b0e62eca --- /dev/null +++ b/gramps/plugins/database/dictionarydb.gpr.py @@ -0,0 +1,31 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com> +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +plg = newplugin() +plg.id = 'dictionarydb' +plg.name = _("Dictionary Database Backend") +plg.name_accell = _("Di_ctionary Database Backend") +plg.description = _("Dictionary (in-memory) Database Backend") +plg.version = '1.0' +plg.gramps_target_version = "4.2" +plg.status = STABLE +plg.fname = 'dictionarydb.py' +plg.ptype = DATABASE +plg.databaseclass = 'DictionaryDb' diff --git a/gramps/plugins/database/dictionary.py b/gramps/plugins/database/dictionarydb.py similarity index 98% rename from gramps/plugins/database/dictionary.py rename to gramps/plugins/database/dictionarydb.py index 2d785ef6e..a3dbd9586 100644 --- a/gramps/plugins/database/dictionary.py +++ b/gramps/plugins/database/dictionarydb.py @@ -28,17 +28,17 @@ import pickle import base64 import time import re -from . import DbReadBase, DbWriteBase, DbTxn -from . import (PERSON_KEY, - FAMILY_KEY, - CITATION_KEY, - SOURCE_KEY, - EVENT_KEY, - MEDIA_KEY, - PLACE_KEY, - REPOSITORY_KEY, - NOTE_KEY, - TAG_KEY) +from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn +from gramps.gen.db import (PERSON_KEY, + FAMILY_KEY, + CITATION_KEY, + SOURCE_KEY, + EVENT_KEY, + MEDIA_KEY, + PLACE_KEY, + REPOSITORY_KEY, + NOTE_KEY, + TAG_KEY) from gramps.gen.utils.id import create_id from gramps.gen.lib.researcher import Researcher diff --git a/gramps/plugins/database/django_support/libdjango.py b/gramps/plugins/database/django_support/libdjango.py new file mode 100644 index 000000000..fa0a596c8 --- /dev/null +++ b/gramps/plugins/database/django_support/libdjango.py @@ -0,0 +1,2063 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2009 Douglas S. Blank <doug.blank@gmail.com> +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" Interface to Django models """ + +#------------------------------------------------------------------------ +# +# Python Modules +# +#------------------------------------------------------------------------ +import time +import sys +import pickle +import base64 +import collections + +#------------------------------------------------------------------------ +# +# Django Modules +# +#------------------------------------------------------------------------ +from django.contrib.contenttypes.models import ContentType +from django.db import transaction + +#------------------------------------------------------------------------ +# +# Gramps Modules +# +#------------------------------------------------------------------------ +import gramps.webapp.grampsdb.models as models +from gramps.gen.lib import Name +from gramps.gen.utils.id import create_id +from gramps.gen.constfunc import conv_to_unicode + +# To get a django person from a django database: +# djperson = dji.Person.get(handle='djhgsdh324hjg234hj24') +# +# To turn the djperson into a Gramps Person: +# tuple = dji.get_person(djperson) +# gperson = lib.gen.Person(tuple) +# OR +# gperson = dbdjango.DbDjango().get_person_from_handle(handle) + +def check_diff(item, raw): + encoded = str(base64.encodebytes(pickle.dumps(raw)), "utf-8") + if item.cache != encoded: + print("Different:", item.__class__.__name__, item.gramps_id) + print("raw :", raw) + print("cache:", item.from_cache()) + # FIXING, TOO: + item.save_cache() + +#------------------------------------------------------------------------- +# +# Import functions +# +#------------------------------------------------------------------------- +def lookup_role_index(role0, event_ref_list): + """ + Find the handle in a unserialized event_ref_list and return code. + """ + if role0 is None: + return -1 + else: + count = 0 + for event_ref in event_ref_list: + (private, note_list, attribute_list, ref, erole) = event_ref + try: + event = models.Event.objects.get(handle=ref) + except: + return -1 + if event.event_type[0] == role0: + return count + count += 1 + return -1 + +def totime(dtime): + if dtime: + return int(time.mktime(dtime.timetuple())) + else: + return 0 + +#------------------------------------------------------------------------- +# +# Export functions +# +#------------------------------------------------------------------------- +def todate(t): + return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(t)) + +def lookup(index, event_ref_list): + """ + Get the unserialized event_ref in an list of them and return it. + """ + if index < 0: + return None + else: + count = 0 + for event_ref in event_ref_list: + (private, note_list, attribute_list, ref, role) = event_ref + if index == count: + return ref + count += 1 + return None + +def get_datamap(grampsclass): + return [x[0] for x in grampsclass._DATAMAP if x[0] != grampsclass.CUSTOM] + +#------------------------------------------------------------------------- +# +# Django Interface +# +#------------------------------------------------------------------------- +class DjangoInterface(object): + """ + DjangoInterface for interoperating between Gramps and Django. + + This interface comes in a number of parts: + get_ITEMS() + add_ITEMS() + + get_ITEM(ITEM) + + Given an ITEM from a Django table, construct a Gramps Raw Data tuple. + + add_ITEM(data) + + Given a Gramps Raw Data tuple, add the data to the Django tables. + + + """ + def __init__(self): + self.debug = 0 + + def __getattr__(self, name): + """ + Django Objects database interface. + + >>> self.Person.all() + >>> self.Person.get(id=1) + >>> self.Person.get(handle='gh71234dhf3746347734') + """ + if hasattr(models, name): + return getattr(models, name).objects + else: + raise AttributeError("no such model: '%s'" % name) + + def get_next_id(self, obj, prefix): + """ + Get next gramps_id + + >>> dji.get_next_id(Person, "P") + 'P0002' + >>> dji.get_next_id(Media, "M") + 'M2349' + """ + ids = [o["gramps_id"] for o in obj.objects.values("gramps_id")] + count = 1 + while "%s%04d" % (prefix, count) in ids: + count += 1 + return "%s%04d" % (prefix, count) + + def get_model(self, name): + if hasattr(models, name): + return getattr(models, name) + elif hasattr(models, name.title()): + return getattr(models, name.title()) + else: + raise AttributeError("no such model: '%s'" % name) + + # ----------------------------------------------- + # Get methods to retrieve list data from the tables + # ----------------------------------------------- + + def clear_tables(self, *args): + return models.clear_tables(*args) + + def get_tag_list(self, obj): + return obj.get_tag_list() + + def get_attribute_list(self, obj): + obj_type = ContentType.objects.get_for_model(obj) + attribute_list = models.Attribute.objects.filter(object_id=obj.id, + object_type=obj_type) + return list(map(self.pack_attribute, attribute_list)) + + def get_primary_name(self, person): + names = person.name_set.filter(preferred=True).order_by("order") + if len(names) > 0: + return Name.create(self.pack_name(names[0])) + else: + return Name() + + def get_alternate_names(self, person): + names = person.name_set.filter(preferred=False).order_by("order") + return [Name.create(self.pack_name(n)) for n in names] + + def get_names(self, person, preferred): + names = person.name_set.filter(preferred=preferred).order_by("order") + if preferred: + if len(names) > 0: + return self.pack_name(names[0]) + else: + return Name().serialize() + else: + return list(map(self.pack_name, names)) + + def get_source_attribute_list(self, source): + return [(map.private, map.key, map.value) for map in source.sourceattribute_set.all().order_by("order")] + + def get_citation_attribute_list(self, citation): + return [(map.private, map.key, map.value) for map in citation.citationattribute_set.all().order_by("order")] + + def get_media_list(self, obj): + obj_type = ContentType.objects.get_for_model(obj) + mediarefs = models.MediaRef.objects.filter(object_id=obj.id, + object_type=obj_type) + return list(map(self.pack_media_ref, mediarefs)) + + def get_note_list(self, obj): + obj_type = ContentType.objects.get_for_model(obj) + noterefs = models.NoteRef.objects.filter(object_id=obj.id, + object_type=obj_type) + return [noteref.ref_object.handle for noteref in noterefs] + + def get_repository_ref_list(self, obj): + obj_type = ContentType.objects.get_for_model(obj) + reporefs = models.RepositoryRef.objects.filter(object_id=obj.id, + object_type=obj_type) + return list(map(self.pack_repository_ref, reporefs)) + + def get_place_ref_list(self, obj): + obj_type = ContentType.objects.get_for_model(obj) + refs = models.PlaceRef.objects.filter(object_id=obj.id, + object_type=obj_type) + return list(map(self.pack_place_ref, refs)) + + def get_url_list(self, obj): + return list(map(self.pack_url, obj.url_set.all().order_by("order"))) + + def get_address_list(self, obj, with_parish): # person or repository + addresses = obj.address_set.all().order_by("order") + return [self.pack_address(address, with_parish) + for address in addresses] + + def get_child_ref_list(self, family): + obj_type = ContentType.objects.get_for_model(family) + childrefs = models.ChildRef.objects.filter(object_id=family.id, + object_type=obj_type).order_by("order") + return list(map(self.pack_child_ref, childrefs)) + + def get_citation_list(self, obj): + obj_type = ContentType.objects.get_for_model(obj) + citationrefs = models.CitationRef.objects.filter(object_id=obj.id, + object_type=obj_type).order_by("order") + return [citationref.citation.handle for citationref in citationrefs] + + def get_event_refs(self, obj, order="order"): + obj_type = ContentType.objects.get_for_model(obj) + eventrefs = models.EventRef.objects.filter(object_id=obj.id, + object_type=obj_type).order_by(order) + return eventrefs + + def get_event_ref_list(self, obj): + obj_type = ContentType.objects.get_for_model(obj) + eventrefs = models.EventRef.objects.filter(object_id=obj.id, + object_type=obj_type).order_by("order") + return list(map(self.pack_event_ref, eventrefs)) + + def get_family_list(self, person): # person has families + return [fam.family.handle for fam in + models.MyFamilies.objects.filter(person=person).order_by("order")] + + def get_parent_family_list(self, person): # person's parents has families + return [fam.family.handle for fam in + models.MyParentFamilies.objects.filter(person=person).order_by("order")] + + def get_person_ref_list(self, person): + obj_type = ContentType.objects.get_for_model(person) + return list(map(self.pack_person_ref, + models.PersonRef.objects.filter(object_id=person.id, + object_type=obj_type))) + + def get_lds_list(self, obj): # person or family + return list(map(self.pack_lds, obj.lds_set.all().order_by("order"))) + + def get_place_handle(self, obj): # obj is event + if obj.place: + return obj.place.handle + return '' + + ## Packers: + + def get_event(self, event): + handle = event.handle + gid = event.gramps_id + the_type = tuple(event.event_type) + description = event.description + change = totime(event.last_changed) + private = event.private + note_list = self.get_note_list(event) + citation_list = self.get_citation_list(event) + media_list = self.get_media_list(event) + attribute_list = self.get_attribute_list(event) + date = self.get_date(event) + place_handle = self.get_place_handle(event) + tag_list = self.get_tag_list(event) + return (str(handle), gid, the_type, date, description, place_handle, + citation_list, note_list, media_list, attribute_list, + change, tag_list, private) + + def get_note_markup(self, note): + retval = [] + markups = models.Markup.objects.filter(note=note).order_by("order") + for markup in markups: + if markup.string and markup.string.isdigit(): + value = int(markup.string) + else: + value = markup.string + start_stop_list = markup.start_stop_list + ss_list = eval(start_stop_list) + retval += [(tuple(markup.styled_text_tag_type), value, ss_list)] + return retval + + def get_tag(self, tag): + changed = totime(tag.last_changed) + return (str(tag.handle), + tag.name, + tag.color, + tag.priority, + changed) + + def get_note(self, note): + styled_text = [note.text, self.get_note_markup(note)] + changed = totime(note.last_changed) + tag_list = self.get_tag_list(note) + return (str(note.handle), + note.gramps_id, + styled_text, + note.preformatted, + tuple(note.note_type), + changed, + tag_list, + note.private) + + def get_family(self, family): + child_ref_list = self.get_child_ref_list(family) + event_ref_list = self.get_event_ref_list(family) + media_list = self.get_media_list(family) + attribute_list = self.get_attribute_list(family) + lds_seal_list = self.get_lds_list(family) + citation_list = self.get_citation_list(family) + note_list = self.get_note_list(family) + tag_list = self.get_tag_list(family) + if family.father: + father_handle = family.father.handle + else: + father_handle = '' + if family.mother: + mother_handle = family.mother.handle + else: + mother_handle = '' + return (str(family.handle), family.gramps_id, + father_handle, mother_handle, + child_ref_list, tuple(family.family_rel_type), + event_ref_list, media_list, + attribute_list, lds_seal_list, + citation_list, note_list, + totime(family.last_changed), + tag_list, + family.private) + + def get_repository(self, repository): + note_list = self.get_note_list(repository) + address_list = self.get_address_list(repository, with_parish=False) + url_list = self.get_url_list(repository) + tag_list = self.get_tag_list(repository) + return (str(repository.handle), + repository.gramps_id, + tuple(repository.repository_type), + repository.name, + note_list, + address_list, + url_list, + totime(repository.last_changed), + tag_list, + repository.private) + + def get_citation(self, citation): + note_list = self.get_note_list(citation) + media_list = self.get_media_list(citation) + attribute_list = self.get_citation_attribute_list(citation) + tag_list = self.get_tag_list(citation) + date = self.get_date(citation) + # I guess citations can have no source + if citation.source: + handle = citation.source.handle + else: + handle = None + return (str(citation.handle), + citation.gramps_id, + date, + citation.page, + citation.confidence, + handle, + note_list, + media_list, + attribute_list, + totime(citation.last_changed), + tag_list, + citation.private) + + def get_source(self, source): + note_list = self.get_note_list(source) + media_list = self.get_media_list(source) + attribute_list = self.get_source_attribute_list(source) + reporef_list = self.get_repository_ref_list(source) + tag_list = self.get_tag_list(source) + return (str(source.handle), + source.gramps_id, + source.title, + source.author, + source.pubinfo, + note_list, + media_list, + source.abbrev, + totime(source.last_changed), + attribute_list, + reporef_list, + tag_list, + source.private) + + def get_media(self, media): + attribute_list = self.get_attribute_list(media) + citation_list = self.get_citation_list(media) + note_list = self.get_note_list(media) + tag_list = self.get_tag_list(media) + date = self.get_date(media) + return (str(media.handle), + media.gramps_id, + conv_to_unicode(media.path, None), + str(media.mime), + str(media.desc), + media.checksum, + attribute_list, + citation_list, + note_list, + totime(media.last_changed), + date, + tag_list, + media.private) + + def get_person(self, person): + primary_name = self.get_names(person, True) # one + alternate_names = self.get_names(person, False) # list + event_ref_list = self.get_event_ref_list(person) + family_list = self.get_family_list(person) + parent_family_list = self.get_parent_family_list(person) + media_list = self.get_media_list(person) + address_list = self.get_address_list(person, with_parish=False) + attribute_list = self.get_attribute_list(person) + url_list = self.get_url_list(person) + lds_ord_list = self.get_lds_list(person) + pcitation_list = self.get_citation_list(person) + pnote_list = self.get_note_list(person) + person_ref_list = self.get_person_ref_list(person) + # This looks up the events for the first EventType given: + death_ref_index = person.death_ref_index + birth_ref_index = person.birth_ref_index + tag_list = self.get_tag_list(person) + + return (str(person.handle), + person.gramps_id, + tuple(person.gender_type)[0], + primary_name, + alternate_names, + death_ref_index, + birth_ref_index, + event_ref_list, + family_list, + parent_family_list, + media_list, + address_list, + attribute_list, + url_list, + lds_ord_list, + pcitation_list, + pnote_list, + totime(person.last_changed), + tag_list, + person.private, + person_ref_list) + + def get_date(self, obj): + if ((obj.calendar == obj.modifier == obj.quality == obj.sortval == obj.newyear == 0) and + obj.text == "" and (not obj.slash1) and (not obj.slash2) and + (obj.day1 == obj.month1 == obj.year1 == 0) and + (obj.day2 == obj.month2 == obj.year2 == 0)): + return None + elif ((not obj.slash1) and (not obj.slash2) and + (obj.day2 == obj.month2 == obj.year2 == 0)): + dateval = (obj.day1, obj.month1, obj.year1, obj.slash1) + else: + dateval = (obj.day1, obj.month1, obj.year1, obj.slash1, + obj.day2, obj.month2, obj.year2, obj.slash2) + return (obj.calendar, obj.modifier, obj.quality, dateval, + obj.text, obj.sortval, obj.newyear) + + def get_place(self, place): + locations = place.location_set.all().order_by("order") + alt_location_list = [self.pack_location(location, True) for location in locations] + url_list = self.get_url_list(place) + media_list = self.get_media_list(place) + citation_list = self.get_citation_list(place) + note_list = self.get_note_list(place) + tag_list = self.get_tag_list(place) + place_ref_list = self.get_place_ref_list(place) + return (str(place.handle), + place.gramps_id, + place.title, + place.long, + place.lat, + place_ref_list, + place.name, + [], ## FIXME: get_alt_names + tuple(place.place_type), + place.code, + alt_location_list, + url_list, + media_list, + citation_list, + note_list, + totime(place.last_changed), + tag_list, + place.private) + + # --------------------------------- + # Packers + # --------------------------------- + + ## The packers build GRAMPS raw unserialized data. + + ## Reference packers + + def pack_child_ref(self, child_ref): + citation_list = self.get_citation_list(child_ref) + note_list = self.get_note_list(child_ref) + return (child_ref.private, citation_list, note_list, child_ref.ref_object.handle, + tuple(child_ref.father_rel_type), tuple(child_ref.mother_rel_type)) + + def pack_person_ref(self, personref): + citation_list = self.get_citation_list(personref) + note_list = self.get_note_list(personref) + return (personref.private, + citation_list, + note_list, + personref.ref_object.handle, + personref.description) + + def pack_media_ref(self, media_ref): + citation_list = self.get_citation_list(media_ref) + note_list = self.get_note_list(media_ref) + attribute_list = self.get_attribute_list(media_ref) + if ((media_ref.x1 == media_ref.y1 == media_ref.x2 == media_ref.y2 == -1) or + (media_ref.x1 == media_ref.y1 == media_ref.x2 == media_ref.y2 == 0)): + role = None + else: + role = (media_ref.x1, media_ref.y1, media_ref.x2, media_ref.y2) + return (media_ref.private, citation_list, note_list, attribute_list, + media_ref.ref_object.handle, role) + + def pack_repository_ref(self, repo_ref): + note_list = self.get_note_list(repo_ref) + return (note_list, + repo_ref.ref_object.handle, + repo_ref.call_number, + tuple(repo_ref.source_media_type), + repo_ref.private) + + def pack_place_ref(self, place_ref): + date = self.get_date(place_ref) + return (place_ref.ref_object.handle, date) + + def pack_media_ref(self, media_ref): + note_list = self.get_note_list(media_ref) + attribute_list = self.get_attribute_list(media_ref) + citation_list = self.get_citation_list(media_ref) + return (media_ref.private, citation_list, note_list, attribute_list, + media_ref.ref_object.handle, (media_ref.x1, + media_ref.y1, + media_ref.x2, + media_ref.y2)) + + def pack_event_ref(self, event_ref): + note_list = self.get_note_list(event_ref) + attribute_list = self.get_attribute_list(event_ref) + return (event_ref.private, note_list, attribute_list, + event_ref.ref_object.handle, tuple(event_ref.role_type)) + + def pack_citation(self, citation): + handle = citation.handle + gid = citation.gramps_id + date = self.get_date(citation) + page = citation.page + confidence = citation.confidence + source_handle = citation.source.handle + note_list = self.get_note_list(citation) + media_list = self.get_media_list(citation) + attribute_list = self.get_citation_attribute_list(citation) + changed = totime(citation.last_changed) + private = citation.private + tag_list = self.get_tag_list(citation) + return (handle, gid, date, page, confidence, source_handle, + note_list, media_list, attribute_list, changed, tag_list, + private) + + def pack_address(self, address, with_parish): + citation_list = self.get_citation_list(address) + date = self.get_date(address) + note_list = self.get_note_list(address) + locations = address.location_set.all().order_by("order") + if len(locations) > 0: + location = self.pack_location(locations[0], with_parish) + else: + if with_parish: + location = (("", "", "", "", "", "", ""), "") + else: + location = ("", "", "", "", "", "", "") + return (address.private, citation_list, note_list, date, location) + + def pack_lds(self, lds): + citation_list = self.get_citation_list(lds) + note_list = self.get_note_list(lds) + date = self.get_date(lds) + if lds.famc: + famc = lds.famc.handle + else: + famc = None + place_handle = self.get_place_handle(lds) + return (citation_list, note_list, date, lds.lds_type[0], place_handle, + famc, lds.temple, lds.status[0], lds.private) + + def pack_source(self, source): + note_list = self.get_note_list(source) + media_list = self.get_media_list(source) + reporef_list = self.get_repository_ref_list(source) + attribute_list = self.get_source_attribute_list(source) + tag_list = self.get_tag_list(source) + return (source.handle, source.gramps_id, source.title, + source.author, source.pubinfo, + note_list, + media_list, + source.abbrev, + totime(last_changed), attribute_list, + reporef_list, + tag_list, + source.private) + + def pack_name(self, name): + citation_list = self.get_citation_list(name) + note_list = self.get_note_list(name) + date = self.get_date(name) + return (name.private, citation_list, note_list, date, + name.first_name, name.make_surname_list(), name.suffix, + name.title, tuple(name.name_type), + name.group_as, name.sort_as.val, + name.display_as.val, name.call, name.nick, + name.famnick) + + def pack_location(self, loc, with_parish): + if with_parish: + return ((loc.street, loc.locality, loc.city, loc.county, loc.state, loc.country, + loc.postal, loc.phone), loc.parish) + else: + return (loc.street, loc.locality, loc.city, loc.county, loc.state, loc.country, + loc.postal, loc.phone) + + def pack_url(self, url): + return (url.private, url.path, url.desc, tuple(url.url_type)) + + def pack_attribute(self, attribute): + citation_list = self.get_citation_list(attribute) + note_list = self.get_note_list(attribute) + return (attribute.private, + citation_list, + note_list, + tuple(attribute.attribute_type), + attribute.value) + + + ## Export lists: + + def add_child_ref_list(self, obj, ref_list): + ## Currently, only Family references children + for child_data in ref_list: + self.add_child_ref(obj, child_data) + + def add_citation_list(self, obj, citation_list): + for citation_handle in citation_list: + self.add_citation_ref(obj, citation_handle) + + def add_event_ref_list(self, obj, event_ref_list): + for event_ref in event_ref_list: + self.add_event_ref(obj, event_ref) + + def add_surname_list(self, name, surname_list): + order = 1 + for data in surname_list: + (surname_text, prefix, primary, origin_type, + connector) = data + surname = models.Surname() + surname.surname = surname_text + surname.prefix = prefix + surname.primary = primary + surname.name_origin_type = models.get_type(models.NameOriginType, + origin_type) + surname.connector = connector + surname.name = name + surname.order = order + surname.save() + order += 1 + + def add_note_list(self, obj, note_list): + for handle in note_list: + # Just the handle + try: + note = models.Note.objects.get(handle=handle) + self.add_note_ref(obj, note) + except: + print(("ERROR: Note does not exist: '%s'" % + str(handle)), file=sys.stderr) + + def add_alternate_name_list(self, person, alternate_names): + for name in alternate_names: + if name: + self.add_name(person, name, False) + + def add_parent_family_list(self, person, parent_family_list): + for parent_family_data in parent_family_list: + self.add_parent_family(person, parent_family_data) + + def add_media_ref_list(self, person, media_list): + for media_data in media_list: + self.add_media_ref(person, media_data) + + def add_attribute_list(self, obj, attribute_list): + for attribute_data in attribute_list: + self.add_attribute(obj, attribute_data) + + def add_tag_list(self, obj, tag_list): + for tag_handle in tag_list: + try: + tag = models.Tag.objects.get(handle=tag_handle) + except: + print(("ERROR: Tag does not exist: '%s'" % + str(tag_handle)), file=sys.stderr) + obj.tags.add(tag) + + def add_url_list(self, field, obj, url_list): + if not url_list: return None + count = 1 + for url_data in url_list: + self.add_url(field, obj, url_data, count) + count += 1 + + def add_person_ref_list(self, obj, person_ref_list): + for person_ref_data in person_ref_list: + self.add_person_ref(obj, person_ref_data) + + def add_address_list(self, field, obj, address_list): + count = 1 + for address_data in address_list: + self.add_address(field, obj, address_data, count) + count += 1 + + def add_lds_list(self, field, obj, lds_ord_list): + count = 1 + for ldsord in lds_ord_list: + lds = self.add_lds(field, obj, ldsord, count) + #obj.lds_list.add(lds) + #obj.save() + count += 1 + + def add_repository_ref_list(self, obj, reporef_list): + for data in reporef_list: + self.add_repository_ref(obj, data) + + def add_place_ref_list(self, obj, placeref_list): + for data in placeref_list: + self.add_place_ref(obj, data) + + def add_family_ref_list(self, person, family_list): + for family_handle in family_list: + self.add_family_ref(person, family_handle) + + def add_alt_name_list(self, place, alt_name_list): + print("FIXME: add alt_name_list!", alt_name_list) + + ## Export reference objects: + + def add_person_ref_default(self, obj, person, private=False, desc=None): + count = person.references.count() + person_ref = models.PersonRef(referenced_by=obj, + ref_object=person, + private=private, + order=count + 1, + description=desc) + person_ref.save() + + def add_person_ref(self, obj, person_ref_data): + (private, + citation_list, + note_list, + handle, + desc) = person_ref_data + try: + person = models.Person.objects.get(handle=handle) + except: + print(("ERROR: Person does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + + count = person.references.count() + person_ref = models.PersonRef(referenced_by=obj, + ref_object=person, + private=private, + order=count + 1, + description=desc) + person_ref.save() + self.add_note_list(person_ref, note_list) + self.add_citation_list(person_ref, citation_list) + + def add_note_ref(self, obj, note): + count = note.references.count() + note_ref = models.NoteRef(referenced_by=obj, + ref_object=note, + private=False, + order=count + 1) + note_ref.save() + + def add_media_ref_default(self, obj, media, private=False, role=None): + count = media.references.count() + if not role: + role = (0,0,0,0) + media_ref = models.MediaRef(referenced_by=obj, + ref_object=media, + x1=role[0], + y1=role[1], + x2=role[2], + y2=role[3], + private=private, + order=count + 1) + media_ref.save() + + def add_media_ref(self, obj, media_ref_data): + (private, citation_list, note_list, attribute_list, + ref, role) = media_ref_data + try: + media = models.Media.objects.get(handle=ref) + except: + print(("ERROR: Media does not exist: '%s'" % + str(ref)), file=sys.stderr) + return + count = media.references.count() + if not role: + role = (0,0,0,0) + media_ref = models.MediaRef(referenced_by=obj, + ref_object=media, + x1=role[0], + y1=role[1], + x2=role[2], + y2=role[3], + private=private, + order=count + 1) + media_ref.save() + self.add_note_list(media_ref, note_list) + self.add_attribute_list(media_ref, attribute_list) + self.add_citation_list(media_ref, citation_list) + + def add_citation_ref_default(self, obj, citation, private=False): + object_type = ContentType.objects.get_for_model(obj) + count = models.CitationRef.objects.filter(object_id=obj.id,object_type=object_type).count() + citation_ref = models.CitationRef(private=private, + referenced_by=obj, + citation=citation, + order=count + 1) + citation_ref.save() + + def add_citation_ref(self, obj, handle): + try: + citation = models.Citation.objects.get(handle=handle) + except: + print(("ERROR: Citation does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + + object_type = ContentType.objects.get_for_model(obj) + count = models.CitationRef.objects.filter(object_id=obj.id,object_type=object_type).count() + citation_ref = models.CitationRef(private=False, + referenced_by=obj, + citation=citation, + order=count + 1) + citation_ref.save() + + def add_citation(self, citation_data): + (handle, gid, date, page, confidence, source_handle, note_list, + media_list, attribute_list, changed, tag_list, private) = citation_data + citation = models.Citation( + handle=handle, + gramps_id=gid, + private=private, + last_changed=todate(changed), + confidence=confidence, + page=page) + citation.save(save_cache=False) + + def add_citation_detail(self, citation_data): + (handle, gid, date, page, confidence, source_handle, note_list, + media_list, attribute_list, change, tag_list, private) = citation_data + try: + citation = models.Citation.objects.get(handle=handle) + except: + print(("ERROR: Citation does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + try: + source = models.Source.objects.get(handle=source_handle) + except: + print(("ERROR: Source does not exist: '%s'" % + str(source_handle)), file=sys.stderr) + return + citation.source = source + self.add_date(citation, date) + citation.save(save_cache=False) + self.add_note_list(citation, note_list) + self.add_media_ref_list(citation, media_list) + self.add_citation_attribute_list(citation, attribute_list) + self.add_tag_list(citation, tag_list) + citation.save_cache() + + def add_child_ref_default(self, obj, child, frel=1, mrel=1, private=False): + object_type = ContentType.objects.get_for_model(obj) # obj is family + count = models.ChildRef.objects.filter(object_id=obj.id,object_type=object_type).count() + child_ref = models.ChildRef(private=private, + referenced_by=obj, + ref_object=child, + order=count + 1, + father_rel_type=models.get_type(models.ChildRefType, frel), # birth + mother_rel_type=models.get_type(models.ChildRefType, mrel)) + child_ref.save() + + def add_child_ref(self, obj, data): + (private, citation_list, note_list, ref, frel, mrel) = data + try: + child = models.Person.objects.get(handle=ref) + except: + print(("ERROR: Person does not exist: '%s'" % + str(ref)), file=sys.stderr) + return + object_type = ContentType.objects.get_for_model(obj) + count = models.ChildRef.objects.filter(object_id=obj.id,object_type=object_type).count() + child_ref = models.ChildRef(private=private, + referenced_by=obj, + ref_object=child, + order=count + 1, + father_rel_type=models.get_type(models.ChildRefType, frel), + mother_rel_type=models.get_type(models.ChildRefType, mrel)) + child_ref.save() + self.add_citation_list(child_ref, citation_list) + self.add_note_list(child_ref, note_list) + + def add_event_ref_default(self, obj, event, private=False, role=models.EventRoleType._DEFAULT): + object_type = ContentType.objects.get_for_model(obj) + count = models.EventRef.objects.filter(object_id=obj.id,object_type=object_type).count() + event_ref = models.EventRef(private=private, + referenced_by=obj, + ref_object=event, + order=count + 1, + role_type = models.get_type(models.EventRoleType, role)) + event_ref.save() + + def add_event_ref(self, obj, event_data): + (private, note_list, attribute_list, ref, role) = event_data + try: + event = models.Event.objects.get(handle=ref) + except: + print(("ERROR: Event does not exist: '%s'" % + str(ref)), file=sys.stderr) + return + object_type = ContentType.objects.get_for_model(obj) + count = models.EventRef.objects.filter(object_id=obj.id,object_type=object_type).count() + event_ref = models.EventRef(private=private, + referenced_by=obj, + ref_object=event, + order=count + 1, + role_type = models.get_type(models.EventRoleType, role)) + event_ref.save() + self.add_note_list(event_ref, note_list) + self.add_attribute_list(event_ref, attribute_list) + + def add_repository_ref_default(self, obj, repository, private=False, call_number="", + source_media_type=models.SourceMediaType._DEFAULT): + object_type = ContentType.objects.get_for_model(obj) + count = models.RepositoryRef.objects.filter(object_id=obj.id,object_type=object_type).count() + repos_ref = models.RepositoryRef(private=private, + referenced_by=obj, + call_number=call_number, + source_media_type=models.get_type(models.SourceMediaType, + source_media_type), + ref_object=repository, + order=count + 1) + repos_ref.save() + + def add_repository_ref(self, obj, reporef_data): + (note_list, + ref, + call_number, + source_media_type, + private) = reporef_data + try: + repository = models.Repository.objects.get(handle=ref) + except: + print(("ERROR: Repository does not exist: '%s'" % + str(ref)), file=sys.stderr) + return + object_type = ContentType.objects.get_for_model(obj) + count = models.RepositoryRef.objects.filter(object_id=obj.id,object_type=object_type).count() + repos_ref = models.RepositoryRef(private=private, + referenced_by=obj, + call_number=call_number, + source_media_type=models.get_type(models.SourceMediaType, + source_media_type), + ref_object=repository, + order=count + 1) + repos_ref.save() + self.add_note_list(repos_ref, note_list) + + def add_family_ref(self, obj, handle): + try: + family = models.Family.objects.get(handle=handle) + except: + print(("ERROR: Family does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + #obj.families.add(family) + pfo = models.MyFamilies(person=obj, family=family, + order=len(models.MyFamilies.objects.filter(person=obj)) + 1) + pfo.save() + obj.save() + + ## Export individual objects: + + def add_source_attribute_list(self, source, attribute_list): + ## FIXME: dict to list + count = 1 + #for key in datamap_dict: + # value = datamap_dict[key] + # datamap = models.SourceDatamap(key=key, value=value, order=count) + # datamap.source = source + # datamap.save() + # count += 1 + + def add_citation_attribute_list(self, citation, attribute_list): + ## FIXME: dict to list + count = 1 + #for key in datamap_dict: + # value = datamap_dict[key] + # datamap = models.CitationDatamap(key=key, value=value, order=count) + # datamap.citation = citation + # datamap.save() + # count += 1 + + def add_lds(self, field, obj, data, order): + (lcitation_list, lnote_list, date, type, place_handle, + famc_handle, temple, status, private) = data + if place_handle: + try: + place = models.Place.objects.get(handle=place_handle) + except: + print(("ERROR: Place does not exist: '%s'" % + str(place_handle)), file=sys.stderr) + place = None + else: + place = None + if famc_handle: + try: + famc = models.Family.objects.get(handle=famc_handle) + except: + print(("ERROR: Family does not exist: '%s'" % + str(famc_handle)), file=sys.stderr) + famc = None + else: + famc = None + lds = models.Lds(lds_type = models.get_type(models.LdsType, type), + temple=temple, + place=place, + famc=famc, + order=order, + status = models.get_type(models.LdsStatus, status), + private=private) + self.add_date(lds, date) + lds.save() + self.add_note_list(lds, lnote_list) + self.add_citation_list(lds, lcitation_list) + if field == "person": + lds.person = obj + elif field == "family": + lds.family = obj + else: + raise AttributeError("invalid field '%s' to attach lds" % + str(field)) + lds.save() + return lds + + def add_address(self, field, obj, address_data, order): + (private, acitation_list, anote_list, date, location) = address_data + address = models.Address(private=private, order=order) + self.add_date(address, date) + address.save() + self.add_location("address", address, location, 1) + self.add_note_list(address, anote_list) + self.add_citation_list(address, acitation_list) + if field == "person": + address.person = obj + elif field == "repository": + address.repository = obj + else: + raise AttributeError("invalid field '%s' to attach address" % + str(field)) + address.save() + #obj.save() + #obj.addresses.add(address) + #obj.save() + + def add_attribute(self, obj, attribute_data): + (private, citation_list, note_list, the_type, value) = attribute_data + attribute_type = models.get_type(models.AttributeType, the_type) + attribute = models.Attribute(private=private, + attribute_of=obj, + attribute_type=attribute_type, + value=value) + attribute.save() + self.add_citation_list(attribute, citation_list) + self.add_note_list(attribute, note_list) + #obj.attributes.add(attribute) + #obj.save() + + def add_url(self, field, obj, url_data, order): + (private, path, desc, type) = url_data + url = models.Url(private=private, + path=path, + desc=desc, + order=order, + url_type=models.get_type(models.UrlType, type)) + if field == "person": + url.person = obj + elif field == "repository": + url.repository = obj + elif field == "place": + url.place = obj + else: + raise AttributeError("invalid field '%s' to attach to url" % + str(field)) + url.save() + #obj.url_list.add(url) + #obj.save() + + def add_place_ref_default(self, obj, place, date=None): + count = place.references.count() + object_type = ContentType.objects.get_for_model(obj) + count = models.PlaceRef.objects.filter(object_id=obj.id, + object_type=object_type).count() + place_ref = models.PlaceRef(referenced_by=obj, + ref_object=place, + order=count + 1) + self.add_date(obj, date) + place_ref.save() + + def add_place_ref(self, obj, data): + place_handle, date = data + if place_handle: + try: + place = models.Place.objects.get(handle=place_handle) + except: + print(("ERROR: Place does not exist: '%s'" % str(place_handle)), file=sys.stderr) + #from gramps.gen.utils.debug import format_exception + #print("".join(format_exception()), file=sys.stderr) + return + object_type = ContentType.objects.get_for_model(obj) + count = models.PlaceRef.objects.filter(object_id=obj.id,object_type=object_type).count() + place_ref = models.PlaceRef(referenced_by=obj, ref_object=place, order=count + 1) + place_ref.save() + self.add_date(place_ref, date) + + def add_parent_family(self, person, parent_family_handle): + try: + family = models.Family.objects.get(handle=parent_family_handle) + except: + print(("ERROR: Family does not exist: '%s'" % + str(parent_family_handle)), file=sys.stderr) + return + #person.parent_families.add(family) + pfo = models.MyParentFamilies( + person=person, + family=family, + order=len(models.MyParentFamilies.objects.filter(person=person)) + 1) + pfo.save() + person.save() + + def add_date(self, obj, date): + if date is None: + (calendar, modifier, quality, text, sortval, newyear) = \ + (0, 0, 0, "", 0, 0) + day1, month1, year1, slash1 = 0, 0, 0, 0 + day2, month2, year2, slash2 = 0, 0, 0, 0 + else: + (calendar, modifier, quality, dateval, text, sortval, newyear) = date + if len(dateval) == 4: + day1, month1, year1, slash1 = dateval + day2, month2, year2, slash2 = 0, 0, 0, 0 + elif len(dateval) == 8: + day1, month1, year1, slash1, day2, month2, year2, slash2 = dateval + else: + raise AttributeError("ERROR: dateval format '%s'" % str(dateval)) + obj.calendar = calendar + obj.modifier = modifier + obj.quality = quality + obj.text = text + obj.sortval = sortval + obj.newyear = newyear + obj.day1 = day1 + obj.month1 = month1 + obj.year1 = year1 + obj.slash1 = slash1 + obj.day2 = day2 + obj.month2 = month2 + obj.year2 = year2 + obj.slash2 = slash2 + + def add_name(self, person, data, preferred): + if data: + (private, citation_list, note_list, date, + first_name, surname_list, suffix, title, + name_type, group_as, sort_as, + display_as, call, nick, famnick) = data + + count = person.name_set.count() + name = models.Name() + name.order = count + 1 + name.preferred = preferred + name.private = private + name.first_name = first_name + name.suffix = suffix + name.title = title + name.name_type = models.get_type(models.NameType, name_type) + name.group_as = group_as + name.sort_as = models.get_type(models.NameFormatType, sort_as) + name.display_as = models.get_type(models.NameFormatType, display_as) + name.call = call + name.nick = nick + name.famnick = famnick + # we know person exists + # needs to have an ID for key + name.person = person + self.add_date(name, date) + name.save() + self.add_surname_list(name, surname_list) + self.add_note_list(name, note_list) + self.add_citation_list(name, citation_list) + #person.save() + + ## Export primary objects: + + def add_person(self, data): + # Unpack from the BSDDB: + (handle, # 0 + gid, # 1 + gender, # 2 + primary_name, # 3 + alternate_names, # 4 + death_ref_index, # 5 + birth_ref_index, # 6 + event_ref_list, # 7 + family_list, # 8 + parent_family_list, # 9 + media_list, # 10 + address_list, # 11 + attribute_list, # 12 + url_list, # 13 + lds_ord_list, # 14 + pcitation_list, # 15 + pnote_list, # 16 + change, # 17 + tag_list, # 18 + private, # 19 + person_ref_list, # 20 + ) = data + + person = models.Person(handle=handle, + gramps_id=gid, + last_changed=todate(change), + private=private, + gender_type=models.get_type(models.GenderType, gender)) + person.save(save_cache=False) + + def add_person_detail(self, data): + # Unpack from the BSDDB: + (handle, # 0 + gid, # 1 + gender, # 2 + primary_name, # 3 + alternate_names, # 4 + death_ref_index, # 5 + birth_ref_index, # 6 + event_ref_list, # 7 + family_list, # 8 + parent_family_list, # 9 + media_list, # 10 + address_list, # 11 + attribute_list, # 12 + url_list, # 13 + lds_ord_list, # 14 + pcitation_list, # 15 + pnote_list, # 16 + change, # 17 + tag_list, # 18 + private, # 19 + person_ref_list, # 20 + ) = data + + try: + person = models.Person.objects.get(handle=handle) + except: + print(("ERROR: Person does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + if primary_name: + self.add_name(person, primary_name, True) + self.add_alternate_name_list(person, alternate_names) + self.add_event_ref_list(person, event_ref_list) + self.add_family_ref_list(person, family_list) + self.add_parent_family_list(person, parent_family_list) + self.add_media_ref_list(person, media_list) + self.add_note_list(person, pnote_list) + self.add_attribute_list(person, attribute_list) + self.add_url_list("person", person, url_list) + self.add_person_ref_list(person, person_ref_list) + self.add_citation_list(person, pcitation_list) + self.add_address_list("person", person, address_list) + self.add_lds_list("person", person, lds_ord_list) + self.add_tag_list(person, tag_list) + # set person.birth and birth.death to correct events: + + obj_type = ContentType.objects.get_for_model(person) + events = models.EventRef.objects.filter( + object_id=person.id, + object_type=obj_type, + ref_object__event_type__val=models.EventType.BIRTH).order_by("order") + + all_events = self.get_event_ref_list(person) + if events: + person.birth = events[0].ref_object + person.birth_ref_index = lookup_role_index(models.EventType.BIRTH, all_events) + + events = models.EventRef.objects.filter( + object_id=person.id, + object_type=obj_type, + ref_object__event_type__val=models.EventType.DEATH).order_by("order") + if events: + person.death = events[0].ref_object + person.death_ref_index = lookup_role_index(models.EventType.DEATH, all_events) + person.save() + return person + + def save_note_markup(self, note, markup_list): + # delete any prexisting markup: + models.Markup.objects.filter(note=note).delete() + count = 1 + for markup in markup_list: + markup_code, value, start_stop_list = markup + m = models.Markup( + note=note, + order=count, + styled_text_tag_type=models.get_type(models.StyledTextTagType, + markup_code, + get_or_create=False), + string=value, + start_stop_list=str(start_stop_list)) + m.save() + + def add_note(self, data): + # Unpack from the BSDDB: + (handle, gid, styled_text, format, note_type, + change, tag_list, private) = data + text, markup_list = styled_text + n = models.Note(handle=handle, + gramps_id=gid, + last_changed=todate(change), + private=private, + preformatted=format, + text=text, + note_type=models.get_type(models.NoteType, note_type)) + n.save(save_cache=False) + self.save_note_markup(n, markup_list) + + def add_note_detail(self, data): + # Unpack from the BSDDB: + (handle, gid, styled_text, format, note_type, + change, tag_list, private) = data + note = models.Note.objects.get(handle=handle) + note.save(save_cache=False) + self.add_tag_list(note, tag_list) + note.save_cache() + + def add_family(self, data): + # Unpack from the BSDDB: + (handle, gid, father_handle, mother_handle, + child_ref_list, the_type, event_ref_list, media_list, + attribute_list, lds_seal_list, citation_list, note_list, + change, tag_list, private) = data + + family = models.Family(handle=handle, gramps_id=gid, + family_rel_type = models.get_type(models.FamilyRelType, the_type), + last_changed=todate(change), + private=private) + family.save(save_cache=False) + + def add_family_detail(self, data): + # Unpack from the BSDDB: + (handle, gid, father_handle, mother_handle, + child_ref_list, the_type, event_ref_list, media_list, + attribute_list, lds_seal_list, citation_list, note_list, + change, tag_list, private) = data + + try: + family = models.Family.objects.get(handle=handle) + except: + print(("ERROR: Family does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + # father_handle and/or mother_handle can be None + if father_handle: + try: + family.father = models.Person.objects.get(handle=father_handle) + except: + print(("ERROR: Father does not exist: '%s'" % + str(father_handle)), file=sys.stderr) + family.father = None + if mother_handle: + try: + family.mother = models.Person.objects.get(handle=mother_handle) + except: + print(("ERROR: Mother does not exist: '%s'" % + str(mother_handle)), file=sys.stderr) + family.mother = None + family.save(save_cache=False) + self.add_child_ref_list(family, child_ref_list) + self.add_note_list(family, note_list) + self.add_attribute_list(family, attribute_list) + self.add_citation_list(family, citation_list) + self.add_media_ref_list(family, media_list) + self.add_event_ref_list(family, event_ref_list) + self.add_lds_list("family", family, lds_seal_list) + self.add_tag_list(family, tag_list) + family.save_cache() + + def add_source(self, data): + (handle, gid, title, + author, pubinfo, + note_list, + media_list, + abbrev, + change, attribute_list, + reporef_list, + tag_list, + private) = data + source = models.Source(handle=handle, gramps_id=gid, title=title, + author=author, pubinfo=pubinfo, abbrev=abbrev, + last_changed=todate(change), private=private) + source.save(save_cache=False) + + def add_source_detail(self, data): + (handle, gid, title, + author, pubinfo, + note_list, + media_list, + abbrev, + change, attribute_list, + reporef_list, + tag_list, + private) = data + try: + source = models.Source.objects.get(handle=handle) + except: + print(("ERROR: Source does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + source.save(save_cache=False) + self.add_note_list(source, note_list) + self.add_media_ref_list(source, media_list) + self.add_source_attribute_list(source, attribute_list) + self.add_repository_ref_list(source, reporef_list) + self.add_tag_list(source, tag_list) + source.save_cache() + + def add_repository(self, data): + (handle, gid, the_type, name, note_list, + address_list, url_list, change, tag_list, private) = data + + repository = models.Repository(handle=handle, + gramps_id=gid, + last_changed=todate(change), + private=private, + repository_type=models.get_type(models.RepositoryType, the_type), + name=name) + repository.save(save_cache=False) + + def add_repository_detail(self, data): + (handle, gid, the_type, name, note_list, + address_list, url_list, change, tag_list, private) = data + try: + repository = models.Repository.objects.get(handle=handle) + except: + print(("ERROR: Repository does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + repository.save(save_cache=False) + self.add_note_list(repository, note_list) + self.add_url_list("repository", repository, url_list) + self.add_address_list("repository", repository, address_list) + self.add_tag_list(repository, tag_list) + repository.save_cache() + + def add_location(self, field, obj, location_data, order): + # location now has 8 items + # street, locality, city, county, state, + # country, postal, phone, parish + + if location_data == None: return + if len(location_data) == 8: + (street, locality, city, county, state, country, postal, phone) = location_data + parish = None + elif len(location_data) == 2: + ((street, locality, city, county, state, country, postal, phone), parish) = location_data + else: + print(("ERROR: unknown location: '%s'" % + str(location_data)), file=sys.stderr) + (street, locality, city, county, state, country, postal, phone, parish) = \ + ("", "", "", "", "", "", "", "", "") + location = models.Location(street = street, + locality = locality, + city = city, + county = county, + state = state, + country = country, + postal = postal, + phone = phone, + parish = parish, + order = order) + if field == "address": + location.address = obj + elif field == "place": + location.place = obj + else: + raise AttributeError("invalid field '%s' to attach to location" % + str(field)) + location.save() + #obj.locations.add(location) + #obj.save() + + def add_place(self, data): + ## ('cef246c95c132bcf6a0255d4d17', 'P0036', 'Santa Clara Co., CA, USA', '', '', [('cef243fb5634559442323368f63', None)], 'Santa Clara Co.', [], (3, ''), '', [], [], [], [], [], 1422124781, [], False) + (handle, gid, title, long, lat, + place_ref_list, + name, + alt_name_list, + place_type, + code, + alt_location_list, + url_list, + media_list, + citation_list, + note_list, + change, + tag_list, + private) = data + place = models.Place( + handle=handle, + gramps_id=gid, + title=title, + long=long, + lat=lat, + name=name, + place_type=models.get_type(models.PlaceType, place_type), + code=code, + last_changed=todate(change), + private=private) + try: + place.save(save_cache=False) + except: + print("FIXME: error in saving place") + + def add_place_detail(self, data): + (handle, gid, title, long, lat, + place_ref_list, + name, + alt_name_list, + place_type, + code, + alt_location_list, + url_list, + media_list, + citation_list, + note_list, + change, + tag_list, + private) = data + try: + place = models.Place.objects.get(handle=handle) + except: + print(("ERROR: Place does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + place.save(save_cache=False) + self.add_url_list("place", place, url_list) + self.add_media_ref_list(place, media_list) + self.add_citation_list(place, citation_list) + self.add_note_list(place, note_list) + self.add_tag_list(place, tag_list) + self.add_place_ref_list(place, place_ref_list) + self.add_alt_name_list(place, alt_name_list) + count = 1 + for loc_data in alt_location_list: + self.add_location("place", place, loc_data, count) + count + 1 + place.save_cache() + + def add_tag(self, data): + (handle, + name, + color, + priority, + change) = data + tag = models.Tag(handle=handle, + gramps_id=create_id(), + name=name, + color=color, + priority=priority, + last_changed=todate(change)) + tag.save(save_cache=False) + + def add_tag_detail(self, data): + (handle, + name, + color, + priority, + change) = data + tag = models.Tag.objects.get(handle=handle) + tag.save() + + def add_media(self, data): + (handle, gid, path, mime, desc, + checksum, + attribute_list, + citation_list, + note_list, + change, + date, + tag_list, + private) = data + media = models.Media(handle=handle, gramps_id=gid, + path=path, mime=mime, checksum=checksum, + desc=desc, last_changed=todate(change), + private=private) + self.add_date(media, date) + media.save(save_cache=False) + + def add_media_detail(self, data): + (handle, gid, path, mime, desc, + checksum, + attribute_list, + citation_list, + note_list, + change, + date, + tag_list, + private) = data + try: + media = models.Media.objects.get(handle=handle) + except: + print(("ERROR: Media does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + media.save(save_cache=False) + self.add_note_list(media, note_list) + self.add_citation_list(media, citation_list) + self.add_attribute_list(media, attribute_list) + self.add_tag_list(media, tag_list) + media.save_cache() + + def add_event(self, data): + (handle, gid, the_type, date, description, place_handle, + citation_list, note_list, media_list, attribute_list, + change, tag_list, private) = data + event = models.Event(handle=handle, + gramps_id=gid, + event_type=models.get_type(models.EventType, the_type), + private=private, + description=description, + last_changed=todate(change)) + self.add_date(event, date) + event.save(save_cache=False) + + def add_event_detail(self, data): + (handle, gid, the_type, date, description, place_handle, + citation_list, note_list, media_list, attribute_list, + change, tag_list, private) = data + try: + event = models.Event.objects.get(handle=handle) + except: + print(("ERROR: Event does not exist: '%s'" % + str(handle)), file=sys.stderr) + return + try: + place = models.Place.objects.get(handle=place_handle) + except: + place = None + print(("ERROR: Place does not exist: '%s'" % + str(place_handle)), file=sys.stderr) + event.place = place + event.save(save_cache=False) + self.add_note_list(event, note_list) + self.add_attribute_list(event, attribute_list) + self.add_media_ref_list(event, media_list) + self.add_citation_list(event, citation_list) + self.add_tag_list(event, tag_list) + event.save_cache() + + def get_raw(self, item): + """ + Build and return the raw, serialized data of an object. + """ + if isinstance(item, models.Person): + raw = self.get_person(item) + elif isinstance(item, models.Family): + raw = self.get_family(item) + elif isinstance(item, models.Place): + raw = self.get_place(item) + elif isinstance(item, models.Media): + raw = self.get_media(item) + elif isinstance(item, models.Source): + raw = self.get_source(item) + elif isinstance(item, models.Citation): + raw = self.get_citation(item) + elif isinstance(item, models.Repository): + raw = self.get_repository(item) + elif isinstance(item, models.Note): + raw = self.get_note(item) + elif isinstance(item, models.Event): + raw = self.get_event(item) + else: + raise Exception("Don't know how to get raw '%s'" % type(item)) + return raw + + def check_caches(self, callback=None): + """ + Call this to check the caches for all primary models. + """ + if not isinstance(callback, collections.Callable): + callback = lambda percent: None # dummy + + callback(0) + count = 0.0 + total = (self.Note.all().count() + + self.Person.all().count() + + self.Event.all().count() + + self.Family.all().count() + + self.Repository.all().count() + + self.Place.all().count() + + self.Media.all().count() + + self.Source.all().count() + + self.Citation.all().count() + + self.Tag.all().count()) + + for item in self.Note.all(): + raw = self.get_note(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Person.all(): + raw = self.get_person(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Family.all(): + raw = self.get_family(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Source.all(): + raw = self.get_source(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Event.all(): + raw = self.get_event(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Repository.all(): + raw = self.get_repository(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Place.all(): + raw = self.get_place(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Media.all(): + raw = self.get_media(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Citation.all(): + raw = self.get_citation(item) + check_diff(item, raw) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Tag.all(): + raw = self.get_tag(item) + check_diff(item, raw) + count += 1 + callback(100) + + def check_families(self): + """ + Check family structures. + """ + for family in self.Family.all(): + if family.mother: + if not family in family.mother.families.all(): + print("Mother not in family", mother, family) + if family.father: + if not family in family.father.families.all(): + print("Father not in family", mother, family) + for child in family.get_children(): + if family not in child.parent_families.all(): + print("Child not in family", child, family) + for person in self.Person.all(): + for family in person.families.all(): + if person not in [family.mother, family.father]: + print("Spouse not in family", person, family) + for family in person.parent_families.all(): + if person not in family.get_children(): + print("Child not in family", person, family) + + def is_public(self, obj, objref): + """ + Returns whether or not an item is "public", and the reason + why/why not. + + @param obj - an instance of any Primary object + @param objref - one of the PrimaryRef.objects + @return - a tuple containing a boolean (public?) and reason. + + There are three reasons why an item might not be public: + 1) The item itself is private. + 2) The item is referenced by a living Person. + 3) The item is referenced by some other private item. + """ + # If it is private, then no: + if obj.private: + return (False, "It is marked private.") + elif hasattr(obj, "probably_alive") and obj.probably_alive: + return (False, "It is marked probaby alive.") + elif hasattr(obj, "mother") and obj.mother: + public, reason = self.is_public(obj.mother, self.PersonRef) + if not public: + return public, reason + elif hasattr(obj, "father") and obj.father: + public, reason = self.is_public(obj.father, self.PersonRef) + if not public: + return public, reason + # FIXME: what about Associations... anything else? Check PrivateProxy + if objref: + if hasattr(objref.model, "ref_object"): + obj_ref_list = objref.filter(ref_object=obj) + elif hasattr(objref.model, "citation"): + obj_ref_list = objref.filter(citation=obj) + else: + raise Exception("objref '%s' needs a ref for '%s'" % (objref.model, obj)) + for reference in obj_ref_list: + ref_from_class = reference.object_type.model_class() + item = None + try: + item = ref_from_class.objects.get(id=reference.object_id) + except: + print("Warning: Corrupt reference: %s" % str(reference)) + continue + # If it is linked to by someone alive? public = False + if hasattr(item, "probably_alive") and item.probably_alive: + return (False, "It is referenced by someone who is probaby alive.") + # If it is linked to by something private? public = False + elif item.private: + return (False, "It is referenced by an item which is marked private.") + return (True, "It is visible to the public.") + + def update_public(self, obj, save=True): + """ + >>> dji.update_public(event) + + Given an Event or other instance, update the event's public + status, or any event referenced to by the instance. + + For example, if a person is found to be alive, then the + referenced events should be marked not public (public = False). + + """ + from gramps.webapp.utils import probably_alive + if obj.__class__.__name__ == "Event": + objref = self.EventRef + elif obj.__class__.__name__ == "Person": + objref = self.PersonRef + elif obj.__class__.__name__ == "Note": + objref = self.NoteRef + elif obj.__class__.__name__ == "Repository": + objref = self.RepositoryRef + elif obj.__class__.__name__ == "Citation": + objref = self.CitationRef + elif obj.__class__.__name__ == "Media": + objref = self.MediaRef + elif obj.__class__.__name__ == "Place": # no need for dependency + objref = None + elif obj.__class__.__name__ == "Source": # no need for dependency + objref = None + elif obj.__class__.__name__ == "Family": + objref = self.ChildRef # correct? + else: + raise Exception("Can't compute public of type '%s'" % str(obj)) + public, reason = self.is_public(obj, objref) # correct? + # Ok, update, if needed: + if obj.public != public: + obj.public = public + if save: + print("Updating public:", obj.__class__.__name__, obj.gramps_id) + obj.save() + #log = self.Log() + #log.referenced_by = obj + #log.object_id = obj.id + #log.object_type = obj_type + #log.log_type = "update public status" + #log.reason = reason + #log.order = 0 + #log.save() + + def update_publics(self, callback=None): + """ + Call this to update probably_alive for all primary models. + """ + if not isinstance(callback, collections.Callable): + callback = lambda percent: None # dummy + + callback(0) + count = 0.0 + total = (self.Note.all().count() + + self.Person.all().count() + + self.Event.all().count() + + self.Family.all().count() + + self.Repository.all().count() + + self.Place.all().count() + + self.Media.all().count() + + self.Source.all().count() + + self.Citation.all().count()) + + for item in self.Note.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Person.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Family.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Source.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Event.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Repository.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Place.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Media.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + for item in self.Citation.all(): + self.update_public(item) + count += 1 + callback(100 * (count/total if total else 0)) + + def update_probably_alive(self, callback=None): + """ + Call this to update primary_alive for people. + """ + from gramps.webapp.utils import probably_alive + if not isinstance(callback, collections.Callable): + callback = lambda percent: None # dummy + callback(0) + count = 0.0 + total = self.Person.all().count() + for item in self.Person.all(): + pa = probably_alive(item.handle) + if pa != item.probably_alive: + print("Updating probably_alive") + item.probably_alive = pa + item.save() + count += 1 + callback(100 * (count/total if total else 0)) diff --git a/gramps/plugins/database/djangodb.gpr.py b/gramps/plugins/database/djangodb.gpr.py new file mode 100644 index 000000000..57b30f2f5 --- /dev/null +++ b/gramps/plugins/database/djangodb.gpr.py @@ -0,0 +1,31 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com> +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +plg = newplugin() +plg.id = 'djangodb' +plg.name = _("Django Database Backend") +plg.name_accell = _("_Django Database Backend") +plg.description = _("Django Object Relational Model Database Backend") +plg.version = '1.0' +plg.gramps_target_version = "4.2" +plg.status = STABLE +plg.fname = 'djangodb.py' +plg.ptype = DATABASE +plg.databaseclass = 'DbDjango' diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py new file mode 100644 index 000000000..adb28dc54 --- /dev/null +++ b/gramps/plugins/database/djangodb.py @@ -0,0 +1,2036 @@ +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2009 Douglas S. Blank <doug.blank@gmail.com> +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +""" Implements a Db interface """ + +#------------------------------------------------------------------------ +# +# Python Modules +# +#------------------------------------------------------------------------ +import sys +import time +import re +import base64 +import pickle +import os + +#------------------------------------------------------------------------ +# +# Gramps Modules +# +#------------------------------------------------------------------------ +import gramps +from gramps.gen.lib import (Person, Family, Event, Place, Repository, + Citation, Source, Note, MediaObject, Tag, + Researcher) +from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn +from gramps.gen.utils.callback import Callback +from gramps.gen.updatecallback import UpdateCallback +from gramps.gen.db import (PERSON_KEY, + FAMILY_KEY, + CITATION_KEY, + SOURCE_KEY, + EVENT_KEY, + MEDIA_KEY, + PLACE_KEY, + REPOSITORY_KEY, + NOTE_KEY) +from gramps.gen.utils.id import create_id +from django.db import transaction + +class Environment(object): + """ + Implements the Environment API. + """ + def __init__(self, db): + self.db = db + + def txn_begin(self): + return DjangoTxn("DbDjango Transaction", self.db) + +class Table(object): + """ + Implements Table interface. + """ + def __init__(self, funcs): + self.funcs = funcs + + def cursor(self): + """ + Returns a Cursor for this Table. + """ + return self.funcs["cursor_func"]() + + def put(key, data, txn=None): + self[key] = data + +class Map(dict): + """ + Implements the map API for person_map, etc. + + Takes a Table() as argument. + """ + def __init__(self, tbl, *args, **kwargs): + super().__init__(*args, **kwargs) + self.db = tbl + +class MetaCursor(object): + def __init__(self): + pass + def __enter__(self): + return self + def __iter__(self): + return self.__next__() + def __next__(self): + yield None + def __exit__(self, *args, **kwargs): + pass + def iter(self): + yield None + def first(self): + self._iter = self.__iter__() + return self.next() + def next(self): + try: + return next(self._iter) + except: + return None + def close(self): + pass + +class Cursor(object): + 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 (bytes(item.handle, "utf-8"), self.func(item.handle)) + def __exit__(self, *args, **kwargs): + pass + def iter(self): + for item in self.model.all(): + yield (bytes(item.handle, "utf-8"), self.func(item.handle)) + yield None + def first(self): + self._iter = self.__iter__() + return self.next() + def next(self): + try: + return next(self._iter) + except: + return None + def close(self): + pass + +class Bookmarks(object): + def __init__(self): + self.handles = [] + def get(self): + return self.handles + def append(self, handle): + self.handles.append(handle) + +class DjangoTxn(DbTxn): + def __init__(self, message, db, table=None): + DbTxn.__init__(self, message, db) + self.table = table + + def get(self, key, default=None, txn=None, **kwargs): + """ + Returns the data object associated with key + """ + try: + return self.table.objects(handle=key) + except: + if txn and key in txn: + return txn[key] + else: + return None + + def put(self, handle, new_data, txn): + """ + """ + txn[handle] = new_data + + def commit(self): + pass + +class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): + """ + A Gramps Database Backend. This replicates the grampsdb functions. + """ + # Set up dictionary for callback signal handler + # --------------------------------------------- + # 1. Signals for primary objects + __signals__ = dict((obj+'-'+op, signal) + for obj in + ['person', 'family', 'event', 'place', + 'source', 'citation', 'media', 'note', 'repository', 'tag'] + for op, signal in zip( + ['add', 'update', 'delete', 'rebuild'], + [(list,), (list,), (list,), None] + ) + ) + + # 2. Signals for long operations + __signals__.update(('long-op-'+op, signal) for op, signal in zip( + ['start', 'heartbeat', 'end'], + [(object,), None, None] + )) + + # 3. Special signal for change in home person + __signals__['home-person-changed'] = None + + # 4. Signal for change in person group name, parameters are + __signals__['person-groupname-rebuild'] = (str, str) + + def __init__(self, directory=None): + DbReadBase.__init__(self) + DbWriteBase.__init__(self) + Callback.__init__(self) + self._tables = { + 'Person': + { + "handle_func": self.get_person_from_handle, + "gramps_id_func": self.get_person_from_gramps_id, + "class_func": gramps.gen.lib.Person, + "cursor_func": self.get_person_cursor, + "handles_func": self.get_person_handles, + "iter_func": self.iter_people, + }, + 'Family': + { + "handle_func": self.get_family_from_handle, + "gramps_id_func": self.get_family_from_gramps_id, + "class_func": gramps.gen.lib.Family, + "cursor_func": self.get_family_cursor, + "handles_func": self.get_family_handles, + "iter_func": self.iter_families, + }, + 'Source': + { + "handle_func": self.get_source_from_handle, + "gramps_id_func": self.get_source_from_gramps_id, + "class_func": gramps.gen.lib.Source, + "cursor_func": self.get_source_cursor, + "handles_func": self.get_source_handles, + "iter_func": self.iter_sources, + }, + 'Citation': + { + "handle_func": self.get_citation_from_handle, + "gramps_id_func": self.get_citation_from_gramps_id, + "class_func": gramps.gen.lib.Citation, + "cursor_func": self.get_citation_cursor, + "handles_func": self.get_citation_handles, + "iter_func": self.iter_citations, + }, + 'Event': + { + "handle_func": self.get_event_from_handle, + "gramps_id_func": self.get_event_from_gramps_id, + "class_func": gramps.gen.lib.Event, + "cursor_func": self.get_event_cursor, + "handles_func": self.get_event_handles, + "iter_func": self.iter_events, + }, + 'Media': + { + "handle_func": self.get_object_from_handle, + "gramps_id_func": self.get_object_from_gramps_id, + "class_func": gramps.gen.lib.MediaObject, + "cursor_func": self.get_media_cursor, + "handles_func": self.get_media_object_handles, + "iter_func": self.iter_media_objects, + }, + 'Place': + { + "handle_func": self.get_place_from_handle, + "gramps_id_func": self.get_place_from_gramps_id, + "class_func": gramps.gen.lib.Place, + "cursor_func": self.get_place_cursor, + "handles_func": self.get_place_handles, + "iter_func": self.iter_places, + }, + 'Repository': + { + "handle_func": self.get_repository_from_handle, + "gramps_id_func": self.get_repository_from_gramps_id, + "class_func": gramps.gen.lib.Repository, + "cursor_func": self.get_repository_cursor, + "handles_func": self.get_repository_handles, + "iter_func": self.iter_repositories, + }, + 'Note': + { + "handle_func": self.get_note_from_handle, + "gramps_id_func": self.get_note_from_gramps_id, + "class_func": gramps.gen.lib.Note, + "cursor_func": self.get_note_cursor, + "handles_func": self.get_note_handles, + "iter_func": self.iter_notes, + }, + 'Tag': + { + "handle_func": self.get_tag_from_handle, + "gramps_id_func": None, + "class_func": gramps.gen.lib.Tag, + "cursor_func": self.get_tag_cursor, + "handles_func": self.get_tag_handles, + "iter_func": self.iter_tags, + }, + } + # 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.undo_callback = None + self.redo_callback = None + self.undo_history_callback = None + self.modified = 0 + self.txn = DjangoTxn("DbDjango Transaction", self) + self.transaction = None + # Import cache for gedcom import, uses transactions, and + # two step adding of objects. + self.import_cache = {} + self.use_import_cache = False + self.use_db_cache = True + self._directory = directory + if directory: + self.load(directory) + + def load(self, directory, pulse_progress=None, mode=None): + self._directory = directory + from django.conf import settings + default_settings = {} + settings_file = os.path.join(directory, "default_settings.py") + with open(settings_file) as f: + code = compile(f.read(), settings_file, 'exec') + exec(code, globals(), default_settings) + + class Module(object): + def __init__(self, dictionary): + self.dictionary = dictionary + def __getattr__(self, item): + return self.dictionary[item] + + try: + settings.configure(Module(default_settings)) + except RuntimeError: + # already configured; ignore + pass + + import django + django.setup() + + from django_support.libdjango import DjangoInterface + self.dji = DjangoInterface() + 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 = DjangoTxn("ID Transaction", self, self.dji.Person) + self.fid_trans = DjangoTxn("FID Transaction", self, self.dji.Family) + self.pid_trans = DjangoTxn("PID Transaction", self, self.dji.Place) + self.cid_trans = DjangoTxn("CID Transaction", self, self.dji.Citation) + self.sid_trans = DjangoTxn("SID Transaction", self, self.dji.Source) + self.oid_trans = DjangoTxn("OID Transaction", self, self.dji.Media) + self.rid_trans = DjangoTxn("RID Transaction", self, self.dji.Repository) + self.nid_trans = DjangoTxn("NID Transaction", self, self.dji.Note) + self.eid_trans = DjangoTxn("EID Transaction", self, self.dji.Event) + 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 = Environment(self) + self.person_map = Map(Table(self._tables["Person"])) + self.family_map = Map(Table(self._tables["Family"])) + self.place_map = Map(Table(self._tables["Place"])) + self.citation_map = Map(Table(self._tables["Citation"])) + self.source_map = Map(Table(self._tables["Source"])) + self.repository_map = Map(Table(self._tables["Repository"])) + self.note_map = Map(Table(self._tables["Note"])) + self.media_map = Map(Table(self._tables["Media"])) + self.event_map = Map(Table(self._tables["Event"])) + self.tag_map = Map(Table(self._tables["Tag"])) + self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) + self.name_group = {} + self.event_names = set() + self.individual_attributes = set() + self.family_attributes = set() + self.source_attributes = set() + self.child_ref_types = set() + self.family_rel_types = set() + self.event_role_names = set() + self.name_types = set() + self.origin_types = set() + self.repository_types = set() + self.note_types = set() + self.source_media_types = set() + self.url_types = set() + self.media_attributes = set() + self.place_types = set() + + def prepare_import(self): + """ + DbDjango does not commit data on gedcom import, but saves them + for later commit. + """ + self.use_import_cache = True + self.import_cache = {} + + @transaction.atomic + def commit_import(self): + """ + Commits the items that were queued up during the last gedcom + import for two step adding. + """ + # First we add the primary objects: + for key in list(self.import_cache.keys()): + obj = self.import_cache[key] + if isinstance(obj, Person): + self.dji.add_person(obj.serialize()) + elif isinstance(obj, Family): + self.dji.add_family(obj.serialize()) + elif isinstance(obj, Event): + self.dji.add_event(obj.serialize()) + elif isinstance(obj, Place): + self.dji.add_place(obj.serialize()) + elif isinstance(obj, Repository): + self.dji.add_repository(obj.serialize()) + elif isinstance(obj, Citation): + self.dji.add_citation(obj.serialize()) + elif isinstance(obj, Source): + self.dji.add_source(obj.serialize()) + elif isinstance(obj, Note): + self.dji.add_note(obj.serialize()) + elif isinstance(obj, MediaObject): + self.dji.add_media(obj.serialize()) + elif isinstance(obj, Tag): + self.dji.add_tag(obj.serialize()) + # Next we add the links: + for key in list(self.import_cache.keys()): + obj = self.import_cache[key] + if isinstance(obj, Person): + self.dji.add_person_detail(obj.serialize()) + elif isinstance(obj, Family): + self.dji.add_family_detail(obj.serialize()) + elif isinstance(obj, Event): + self.dji.add_event_detail(obj.serialize()) + elif isinstance(obj, Place): + self.dji.add_place_detail(obj.serialize()) + elif isinstance(obj, Repository): + self.dji.add_repository_detail(obj.serialize()) + elif isinstance(obj, Citation): + self.dji.add_citation_detail(obj.serialize()) + elif isinstance(obj, Source): + self.dji.add_source_detail(obj.serialize()) + elif isinstance(obj, Note): + self.dji.add_note_detail(obj.serialize()) + elif isinstance(obj, MediaObject): + self.dji.add_media_detail(obj.serialize()) + elif isinstance(obj, Tag): + self.dji.add_tag_detail(obj.serialize()) + self.use_import_cache = False + self.import_cache = {} + self.dji.update_publics() + + def transaction_commit(self, txn): + pass + + def request_rebuild(self): + # caches are ok, but let's compute public's + self.dji.update_publics() + self.emit('person-rebuild') + self.emit('family-rebuild') + self.emit('place-rebuild') + self.emit('source-rebuild') + self.emit('citation-rebuild') + self.emit('media-rebuild') + self.emit('event-rebuild') + self.emit('repository-rebuild') + self.emit('note-rebuild') + self.emit('tag-rebuild') + + def get_undodb(self): + return None + + def transaction_abort(self, txn): + pass + + @staticmethod + def _validated_id_prefix(val, default): + if isinstance(val, str) 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_<object>_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 = Researcher() + return obj + + def get_tag_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Tag.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Tag.all()] + + def get_person_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Person.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Person.all()] + + def get_family_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Family.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Family.all()] + + def get_event_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Event.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Event.all()] + + def get_citation_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Citation.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Citation.all()] + + def get_source_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Source.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Source.all()] + + def get_place_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Place.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Place.all()] + + def get_repository_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Repository.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Repository.all()] + + def get_media_object_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Media.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Media.all()] + + def get_note_handles(self, sort_handles=False): + if sort_handles: + return [item.handle for item in self.dji.Note.all().order_by("handle")] + else: + return [item.handle for item in self.dji.Note.all()] + + def get_media_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + media = self.dji.Media.get(handle=handle) + except: + return None + return self.make_media(media) + + def get_event_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + event = self.dji.Event.get(handle=handle) + except: + return None + return self.make_event(event) + + def get_family_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + family = self.dji.Family.get(handle=handle) + except: + return None + return self.make_family(family) + + def get_repository_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + repository = self.dji.Repository.get(handle=handle) + except: + return None + return self.make_repository(repository) + + def get_person_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + person = self.dji.Person.get(handle=handle) + except: + return None + return self.make_person(person) + + def get_tag_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + tag = self.dji.Tag.get(handle=handle) + except: + return None + return self.make_tag(tag) + + def make_repository(self, repository): + if self.use_db_cache and repository.cache: + data = repository.from_cache() + else: + data = self.dji.get_repository(repository) + return Repository.create(data) + + def make_citation(self, citation): + if self.use_db_cache and citation.cache: + data = citation.from_cache() + else: + data = self.dji.get_citation(citation) + return Citation.create(data) + + def make_source(self, source): + if self.use_db_cache and source.cache: + data = source.from_cache() + else: + data = self.dji.get_source(source) + return Source.create(data) + + def make_family(self, family): + if self.use_db_cache and family.cache: + data = family.from_cache() + else: + data = self.dji.get_family(family) + return Family.create(data) + + def make_person(self, person): + if self.use_db_cache and person.cache: + data = person.from_cache() + else: + data = self.dji.get_person(person) + return Person.create(data) + + def make_event(self, event): + if self.use_db_cache and event.cache: + data = event.from_cache() + else: + data = self.dji.get_event(event) + return Event.create(data) + + def make_note(self, note): + if self.use_db_cache and note.cache: + data = note.from_cache() + else: + data = self.dji.get_note(note) + return Note.create(data) + + def make_tag(self, tag): + data = self.dji.get_tag(tag) + return Tag.create(data) + + def make_place(self, place): + if self.use_db_cache and place.cache: + data = place.from_cache() + else: + data = self.dji.get_place(place) + return Place.create(data) + + def make_media(self, media): + if self.use_db_cache and media.cache: + data = media.from_cache() + else: + data = self.dji.get_media(media) + return MediaObject.create(data) + + def get_place_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + place = self.dji.Place.get(handle=handle) + except: + return None + return self.make_place(place) + + def get_citation_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + citation = self.dji.Citation.get(handle=handle) + except: + return None + return self.make_citation(citation) + + def get_source_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + source = self.dji.Source.get(handle=handle) + except: + return None + return self.make_source(source) + + def get_note_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + note = self.dji.Note.get(handle=handle) + except: + return None + return self.make_note(note) + + def get_object_from_handle(self, handle): + if handle in self.import_cache: + return self.import_cache[handle] + try: + media = self.dji.Media.get(handle=handle) + except: + return None + return self.make_media(media) + + def get_default_person(self): + people = self.dji.Person.all() + if people.count() > 0: + return self.make_person(people[0]) + return None + + def iter_people(self): + return (self.get_person_from_handle(person.handle) + for person in self.dji.Person.all()) + + def iter_person_handles(self): + return (person.handle for person in self.dji.Person.all()) + + def iter_families(self): + return (self.get_family_from_handle(family.handle) + for family in self.dji.Family.all()) + + def iter_family_handles(self): + return (family.handle for family in self.dji.Family.all()) + + def iter_notes(self): + return (self.get_note_from_handle(note.handle) + for note in self.dji.Note.all()) + + def iter_note_handles(self): + return (note.handle for note in self.dji.Note.all()) + + def iter_events(self): + return (self.get_event_from_handle(event.handle) + for event in self.dji.Event.all()) + + def iter_event_handles(self): + return (event.handle for event in self.dji.Event.all()) + + def iter_places(self): + return (self.get_place_from_handle(place.handle) + for place in self.dji.Place.all()) + + def iter_place_handles(self): + return (place.handle for place in self.dji.Place.all()) + + def iter_repositories(self): + return (self.get_repository_from_handle(repository.handle) + for repository in self.dji.Repository.all()) + + def iter_repository_handles(self): + return (repository.handle for repository in self.dji.Repository.all()) + + def iter_sources(self): + return (self.get_source_from_handle(source.handle) + for source in self.dji.Source.all()) + + def iter_source_handles(self): + return (source.handle for source in self.dji.Source.all()) + + def iter_citations(self): + return (self.get_citation_from_handle(citation.handle) + for citation in self.dji.Citation.all()) + + def iter_citation_handles(self): + return (citation.handle for citation in self.dji.Citation.all()) + + def iter_tags(self): + return (self.get_tag_from_handle(tag.handle) + for tag in self.dji.Tag.all()) + + def iter_tag_handles(self): + return (tag.handle for tag in self.dji.Tag.all()) + + def iter_media_objects(self): + return (self.get_media_from_handle(media.handle) + for media in self.dji.Media.all()) + + def get_tag_from_name(self, name): + try: + tag = self.dji.Tag.filter(name=name) + return self.make_tag(tag[0]) + except: + return None + + def get_person_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Person.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_person(match_list[0]) + else: + return None + + def get_family_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + try: + family = self.dji.Family.get(gramps_id=gramps_id) + except: + return None + return self.make_family(family) + + def get_source_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Source.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_source(match_list[0]) + else: + return None + + def get_citation_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Citation.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_citation(match_list[0]) + else: + return None + + def get_event_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Event.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_event(match_list[0]) + else: + return None + + def get_object_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Media.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_media(match_list[0]) + else: + return None + + def get_place_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Place.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_place(match_list[0]) + else: + return None + + def get_repository_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Repsoitory.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_repository(match_list[0]) + else: + return None + + def get_note_from_gramps_id(self, gramps_id): + if self.import_cache: + for handle in self.import_cache: + if self.import_cache[handle].gramps_id == gramps_id: + return self.import_cache[handle] + match_list = self.dji.Note.filter(gramps_id=gramps_id) + if match_list.count() > 0: + return self.make_note(match_list[0]) + else: + return None + + def get_number_of_people(self): + return self.dji.Person.count() + + def get_number_of_events(self): + return self.dji.Event.count() + + def get_number_of_places(self): + return self.dji.Place.count() + + def get_number_of_tags(self): + return self.dji.Tag.count() + + def get_number_of_families(self): + return self.dji.Family.count() + + def get_number_of_notes(self): + return self.dji.Note.count() + + def get_number_of_citations(self): + return self.dji.Citation.count() + + def get_number_of_sources(self): + return self.dji.Source.count() + + def get_number_of_media_objects(self): + return self.dji.Media.count() + + def get_number_of_repositories(self): + return self.dji.Repository.count() + + def get_place_cursor(self): + return Cursor(self.dji.Place, self.get_raw_place_data) + + def get_person_cursor(self): + return Cursor(self.dji.Person, self.get_raw_person_data) + + def get_family_cursor(self): + return Cursor(self.dji.Family, self.get_raw_family_data) + + def get_event_cursor(self): + return Cursor(self.dji.Event, self.get_raw_event_data) + + def get_citation_cursor(self): + return Cursor(self.dji.Citation, self.get_raw_citation_data) + + def get_source_cursor(self): + return Cursor(self.dji.Source, self.get_raw_source_data) + + def get_note_cursor(self): + return Cursor(self.dji.Note, self.get_raw_note_data) + + def get_tag_cursor(self): + return Cursor(self.dji.Tag, self.get_raw_tag_data) + + def get_repository_cursor(self): + return Cursor(self.dji.Repository, self.get_raw_repository_data) + + def get_media_cursor(self): + return Cursor(self.dji.Media, self.get_raw_object_data) + + def has_gramps_id(self, obj_key, gramps_id): + key2table = { + PERSON_KEY: self.dji.Person, + FAMILY_KEY: self.dji.Family, + SOURCE_KEY: self.dji.Source, + CITATION_KEY: self.dji.Citation, + EVENT_KEY: self.dji.Event, + MEDIA_KEY: self.dji.Media, + PLACE_KEY: self.dji.Place, + REPOSITORY_KEY: self.dji.Repository, + NOTE_KEY: self.dji.Note, + } + table = key2table[obj_key] + return table.filter(gramps_id=gramps_id).count() > 0 + + def has_person_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Person.filter(handle=handle).count() == 1 + + def has_family_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Family.filter(handle=handle).count() == 1 + + def has_citation_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Citation.filter(handle=handle).count() == 1 + + def has_source_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Source.filter(handle=handle).count() == 1 + + def has_repository_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Repository.filter(handle=handle).count() == 1 + + def has_note_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Note.filter(handle=handle).count() == 1 + + def has_place_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Place.filter(handle=handle).count() == 1 + + def has_event_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Event.filter(handle=handle).count() == 1 + + def has_tag_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Tag.filter(handle=handle).count() == 1 + + def has_object_handle(self, handle): + if handle in self.import_cache: + return True + return self.dji.Media.filter(handle=handle).count() == 1 + + 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): + try: + return self.dji.get_person(self.dji.Person.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_family_data(self, handle): + try: + return self.dji.get_family(self.dji.Family.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_citation_data(self, handle): + try: + return self.dji.get_citation(self.dji.Citation.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_source_data(self, handle): + try: + return self.dji.get_source(self.dji.Source.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_repository_data(self, handle): + try: + return self.dji.get_repository(self.dji.Repository.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_note_data(self, handle): + try: + return self.dji.get_note(self.dji.Note.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_place_data(self, handle): + try: + return self.dji.get_place(self.dji.Place.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_object_data(self, handle): + try: + return self.dji.get_media(self.dji.Media.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_tag_data(self, handle): + try: + return self.dji.get_tag(self.dji.Tag.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def get_raw_event_data(self, handle): + try: + return self.dji.get_event(self.dji.Event.get(handle=handle)) + except: + if handle in self.import_cache: + return self.import_cache[handle].serialize() + else: + return None + + def add_person(self, person, trans, set_gid=True): + if not person.handle: + person.handle = create_id() + if not person.gramps_id and set_gid: + person.gramps_id = self.find_next_person_gramps_id() + self.commit_person(person, trans) + self.emit("person-add", ([person.handle],)) + return person.handle + + def add_family(self, family, trans, set_gid=True): + if not family.handle: + family.handle = create_id() + if not family.gramps_id and set_gid: + family.gramps_id = self.find_next_family_gramps_id() + self.commit_family(family, trans) + self.emit("family-add", ([family.handle],)) + return family.handle + + def add_citation(self, citation, trans, set_gid=True): + if not citation.handle: + citation.handle = create_id() + if not citation.gramps_id and set_gid: + citation.gramps_id = self.find_next_citation_gramps_id() + self.commit_citation(citation, trans) + self.emit("citation-add", ([citation.handle],)) + return citation.handle + + def add_source(self, source, trans, set_gid=True): + if not source.handle: + source.handle = create_id() + if not source.gramps_id and set_gid: + source.gramps_id = self.find_next_source_gramps_id() + self.commit_source(source, trans) + self.emit("source-add", ([source.handle],)) + return source.handle + + def add_repository(self, repository, trans, set_gid=True): + if not repository.handle: + repository.handle = create_id() + if not repository.gramps_id and set_gid: + repository.gramps_id = self.find_next_repository_gramps_id() + self.commit_repository(repository, trans) + self.emit("repository-add", ([repository.handle],)) + return repository.handle + + def add_note(self, note, trans, set_gid=True): + if not note.handle: + note.handle = create_id() + if not note.gramps_id and set_gid: + note.gramps_id = self.find_next_note_gramps_id() + self.commit_note(note, trans) + self.emit("note-add", ([note.handle],)) + return note.handle + + def add_place(self, place, trans, set_gid=True): + if not place.handle: + place.handle = create_id() + if not place.gramps_id and 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 = create_id() + if not event.gramps_id and 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 = 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 = create_id() + if not obj.gramps_id and 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): + if self.use_import_cache: + self.import_cache[person.handle] = person + else: + raw = person.serialize() + items = self.dji.Person.filter(handle=person.handle) + if items.count() > 0: + # Hack, for the moment: delete and re-add + items[0].delete() + self.dji.add_person(person.serialize()) + self.dji.add_person_detail(person.serialize()) + if items.count() > 0: + self.emit("person-update", ([person.handle],)) + else: + self.emit("person-add", ([person.handle],)) + + def commit_family(self, family, trans, change_time=None): + if self.use_import_cache: + self.import_cache[family.handle] = family + else: + raw = family.serialize() + items = self.dji.Family.filter(handle=family.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_family(family.serialize()) + self.dji.add_family_detail(family.serialize()) + if items.count() > 0: + self.emit("family-update", ([family.handle],)) + else: + self.emit("family-add", ([family.handle],)) + + def commit_citation(self, citation, trans, change_time=None): + if self.use_import_cache: + self.import_cache[citation.handle] = citation + else: + raw = citation.serialize() + items = self.dji.Citation.filter(handle=citation.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_citation(citation.serialize()) + self.dji.add_citation_detail(citation.serialize()) + if items.count() > 0: + self.emit("citation-update", ([citation.handle],)) + else: + self.emit("citation-add", ([citation.handle],)) + + def commit_source(self, source, trans, change_time=None): + if self.use_import_cache: + self.import_cache[source.handle] = source + else: + raw = source.serialize() + items = self.dji.Source.filter(handle=source.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_source(source.serialize()) + self.dji.add_source_detail(source.serialize()) + if items.count() > 0: + self.emit("source-update", ([source.handle],)) + else: + self.emit("source-add", ([source.handle],)) + + def commit_repository(self, repository, trans, change_time=None): + if self.use_import_cache: + self.import_cache[repository.handle] = repository + else: + raw = repository.serialize() + items = self.dji.Repository.filter(handle=repository.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_repository(repository.serialize()) + self.dji.add_repository_detail(repository.serialize()) + if items.count() > 0: + self.emit("repository-update", ([repository.handle],)) + else: + self.emit("repository-add", ([repository.handle],)) + + def commit_note(self, note, trans, change_time=None): + if self.use_import_cache: + self.import_cache[note.handle] = note + else: + raw = note.serialize() + items = self.dji.Note.filter(handle=note.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_note(note.serialize()) + self.dji.add_note_detail(note.serialize()) + if items.count() > 0: + self.emit("note-update", ([note.handle],)) + else: + self.emit("note-add", ([note.handle],)) + + def commit_place(self, place, trans, change_time=None): + if self.use_import_cache: + self.import_cache[place.handle] = place + else: + raw = place.serialize() + items = self.dji.Place.filter(handle=place.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_place(place.serialize()) + self.dji.add_place_detail(place.serialize()) + if items.count() > 0: + self.emit("place-update", ([place.handle],)) + else: + self.emit("place-add", ([place.handle],)) + + def commit_event(self, event, trans, change_time=None): + if self.use_import_cache: + self.import_cache[event.handle] = event + else: + raw = event.serialize() + items = self.dji.Event.filter(handle=event.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_event(event.serialize()) + self.dji.add_event_detail(event.serialize()) + if items.count() > 0: + self.emit("event-update", ([event.handle],)) + else: + self.emit("event-add", ([event.handle],)) + + def commit_tag(self, tag, trans, change_time=None): + if self.use_import_cache: + self.import_cache[tag.handle] = tag + else: + raw = tag.serialize() + items = self.dji.Tag.filter(handle=tag.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_tag(tag.serialize()) + self.dji.add_tag_detail(tag.serialize()) + if items.count() > 0: + self.emit("tag-update", ([tag.handle],)) + else: + self.emit("tag-add", ([tag.handle],)) + + def commit_media_object(self, media, transaction, change_time=None): + """ + Commit the specified MediaObject to the database, storing the changes + as part of the transaction. + """ + if self.use_import_cache: + self.import_cache[obj.handle] = media + else: + raw = media.serialize() + items = self.dji.Media.filter(handle=media.handle) + if items.count() > 0: + items[0].delete() + self.dji.add_media(media.serialize()) + self.dji.add_media_detail(media.serialize()) + if items.count() > 0: + self.emit("media-update", ([media.handle],)) + else: + self.emit("media-add", ([media.handle],)) + + def get_gramps_ids(self, obj_key): + key2table = { + PERSON_KEY: self.id_trans, + FAMILY_KEY: self.fid_trans, + CITATION_KEY: self.cid_trans, + SOURCE_KEY: self.sid_trans, + EVENT_KEY: self.eid_trans, + MEDIA_KEY: self.oid_trans, + PLACE_KEY: self.pid_trans, + REPOSITORY_KEY: self.rid_trans, + NOTE_KEY: self.nid_trans, + } + + table = key2table[obj_key] + return list(table.keys()) + + def transaction_begin(self, transaction): + return + + def set_researcher(self, owner): + pass + + def copy_from_db(self, db): + """ + A (possibily) implementation-specific method to get data from + db into this database. + """ + # First we add the primary objects: + for key in db._tables.keys(): + cursor = db._tables[key]["cursor_func"] + for (handle, data) in cursor(): + if key == "Person": + self.dji.add_person(data) + elif key == "Family": + self.dji.add_family(data) + elif key == "Event": + self.dji.add_event(data) + elif key == "Place": + self.dji.add_place(data) + elif key == "Repository": + self.dji.add_repository(data) + elif key == "Citation": + self.dji.add_citation(data) + elif key == "Source": + self.dji.add_source(data) + elif key == "Note": + self.dji.add_note(data) + elif key == "Media": + self.dji.add_media(data) + elif key == "Tag": + self.dji.add_tag(data) + for key in db._tables.keys(): + cursor = db._tables[key]["cursor_func"] + for (handle, data) in cursor(): + if key == "Person": + self.dji.add_person_detail(data) + elif key == "Family": + self.dji.add_family_detail(data) + elif key == "Event": + self.dji.add_event_detail(data) + elif key == "Place": + self.dji.add_place_detail(data) + elif key == "Repository": + self.dji.add_repository_detail(data) + elif key == "Citation": + self.dji.add_citation_detail(data) + elif key == "Source": + self.dji.add_source_detail(data) + elif key == "Note": + self.dji.add_note_detail(data) + elif key == "Media": + self.dji.add_media_detail(data) + elif key == "Tag": + self.dji.add_tag_detail(data) + # Next we add the links: + self.dji.update_publics() + + def get_from_name_and_handle(self, table_name, handle): + """ + Returns a gen.lib object (or None) given table_name and + handle. + + Examples: + + >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") + >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") + """ + if table_name in self._tables: + return self._tables[table_name]["handle_func"](handle) + return None + + def is_empty(self): + """ + Is the database empty? + """ + return (self.get_number_of_people() == 0 and + self.get_number_of_events() == 0 and + self.get_number_of_places() == 0 and + self.get_number_of_tags() == 0 and + self.get_number_of_families() == 0 and + self.get_number_of_notes() == 0 and + self.get_number_of_citations() == 0 and + self.get_number_of_sources() == 0 and + self.get_number_of_media_objects() == 0 and + self.get_number_of_repositories() == 0) + + __callback_map = {} + + def set_prefixes(self, person, media, family, source, citation, + place, event, repository, note): + self.set_person_id_prefix(person) + self.set_object_id_prefix(media) + self.set_family_id_prefix(family) + self.set_source_id_prefix(source) + self.set_citation_id_prefix(citation) + self.set_place_id_prefix(place) + self.set_event_id_prefix(event) + self.set_repository_id_prefix(repository) + self.set_note_id_prefix(note) + + def has_changed(self): + return False + + def find_backlink_handles(self, handle, include_classes=None): + ## FIXME: figure out how to get objects that refer + ## to this handle + return [] + + def get_note_bookmarks(self): + return self.note_bookmarks + + def get_media_bookmarks(self): + return self.media_bookmarks + + def get_repo_bookmarks(self): + return self.repo_bookmarks + + def get_citation_bookmarks(self): + return self.citation_bookmarks + + def get_source_bookmarks(self): + return self.source_bookmarks + + def get_place_bookmarks(self): + return self.place_bookmarks + + def get_event_bookmarks(self): + return self.event_bookmarks + + def get_bookmarks(self): + return self.bookmarks + + def get_family_bookmarks(self): + return self.family_bookmarks + + def get_save_path(self): + return "/tmp/" + + ## Get types: + def get_event_attribute_types(self): + """ + Return a list of all Attribute types assocated with Event instances + in the database. + """ + return list(self.event_attributes) + + def get_event_types(self): + """ + Return a list of all event types in the database. + """ + return list(self.event_names) + + def get_person_event_types(self): + """ + Deprecated: Use get_event_types + """ + return list(self.event_names) + + def get_person_attribute_types(self): + """ + Return a list of all Attribute types assocated with Person instances + in the database. + """ + return list(self.individual_attributes) + + def get_family_attribute_types(self): + """ + Return a list of all Attribute types assocated with Family instances + in the database. + """ + return list(self.family_attributes) + + def get_family_event_types(self): + """ + Deprecated: Use get_event_types + """ + return list(self.event_names) + + def get_media_attribute_types(self): + """ + Return a list of all Attribute types assocated with Media and MediaRef + instances in the database. + """ + return list(self.media_attributes) + + def get_family_relation_types(self): + """ + Return a list of all relationship types assocated with Family + instances in the database. + """ + return list(self.family_rel_types) + + def get_child_reference_types(self): + """ + Return a list of all child reference types assocated with Family + instances in the database. + """ + return list(self.child_ref_types) + + def get_event_roles(self): + """ + Return a list of all custom event role names assocated with Event + instances in the database. + """ + return list(self.event_role_names) + + def get_name_types(self): + """ + Return a list of all custom names types assocated with Person + instances in the database. + """ + return list(self.name_types) + + def get_origin_types(self): + """ + Return a list of all custom origin types assocated with Person/Surname + instances in the database. + """ + return list(self.origin_types) + + def get_repository_types(self): + """ + Return a list of all custom repository types assocated with Repository + instances in the database. + """ + return list(self.repository_types) + + def get_note_types(self): + """ + Return a list of all custom note types assocated with Note instances + in the database. + """ + return list(self.note_types) + + def get_source_attribute_types(self): + """ + Return a list of all Attribute types assocated with Source/Citation + instances in the database. + """ + return list(self.source_attributes) + + def get_source_media_types(self): + """ + Return a list of all custom source media types assocated with Source + instances in the database. + """ + return list(self.source_media_types) + + def get_url_types(self): + """ + Return a list of all custom names types assocated with Url instances + in the database. + """ + return list(self.url_types) + + def get_place_types(self): + """ + Return a list of all custom place types assocated with Place instances + in the database. + """ + return list(self.place_types) + + def get_default_handle(self): + people = self.dji.Person.all() + if people.count() > 0: + return people[0].handle + return None + + def close(self): + pass + + def get_surname_list(self): + return [] + + def is_open(self): + return True + + def get_table_names(self): + """Return a list of valid table names.""" + return list(self._tables.keys()) + + def find_initial_person(self): + return self.get_default_person() + + # Removals: + def remove_person(self, handle, txn): + self.dji.Person.filter(handle=handle)[0].delete() + self.emit("person-delete", ([handle],)) + + def remove_source(self, handle, transaction): + self.dji.Source.filter(handle=handle)[0].delete() + self.emit("source-delete", ([handle],)) + + def remove_citation(self, handle, transaction): + self.dji.Citation.filter(handle=handle)[0].delete() + self.emit("citation-delete", ([handle],)) + + def remove_event(self, handle, transaction): + self.dji.Event.filter(handle=handle)[0].delete() + self.emit("event-delete", ([handle],)) + + def remove_object(self, handle, transaction): + self.dji.Media.filter(handle=handle)[0].delete() + self.emit("media-delete", ([handle],)) + + def remove_place(self, handle, transaction): + self.dji.Place.filter(handle=handle)[0].delete() + self.emit("place-delete", ([handle],)) + + def remove_family(self, handle, transaction): + self.dji.Family.filter(handle=handle)[0].delete() + self.emit("family-delete", ([handle],)) + + def remove_repository(self, handle, transaction): + self.dji.Repository.filter(handle=handle)[0].delete() + self.emit("repository-delete", ([handle],)) + + def remove_note(self, handle, transaction): + self.dji.Note.filter(handle=handle)[0].delete() + self.emit("note-delete", ([handle],)) + + def remove_tag(self, handle, transaction): + self.dji.Tag.filter(handle=handle)[0].delete() + self.emit("tag-delete", ([handle],)) + + def remove_from_surname_list(self, person): + ## FIXME + pass + + def get_dbname(self): + return "Django Database" + + ## missing + + def find_place_child_handles(self, handle): + pass + + def get_cursor(self, table, txn=None, update=False, commit=False): + pass + + def get_from_name_and_handle(self, table_name, handle): + """ + Returns a gen.lib object (or None) given table_name and + handle. + + Examples: + + >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") + >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") + """ + if table_name in self._tables: + return self._tables[table_name]["handle_func"](handle) + return None + + def get_number_of_records(self, table): + pass + + def get_place_parent_cursor(self): + pass + + def get_place_tree_cursor(self): + pass + + def get_table_metadata(self, table_name): + """Return the metadata for a valid table name.""" + if table_name in self._tables: + return self._tables[table_name] + return None + + def get_transaction_class(self): + pass + + def undo(self, update_history=True): + # FIXME: + return self.undodb.undo(update_history) + + def redo(self, update_history=True): + # FIXME: + return self.undodb.redo(update_history) + + def backup(self): + pass + + def restore(self): + pass From beb8b8e3ab447cc8f28e06eccbd5b1aa8e3fa624 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Wed, 13 May 2015 12:36:17 -0400 Subject: [PATCH 056/105] Loads tree based on id in database.txt --- gramps/cli/grampscli.py | 10 +++++++++- gramps/gui/dbloader.py | 10 +++++++++- gramps/plugins/database/djangodb.py | 19 ++++++++++++++++--- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/gramps/cli/grampscli.py b/gramps/cli/grampscli.py index d1d3cfc07..d7b9afea6 100644 --- a/gramps/cli/grampscli.py +++ b/gramps/cli/grampscli.py @@ -47,6 +47,7 @@ LOG = logging.getLogger(".grampscli") from gramps.gen.display.name import displayer as name_displayer from gramps.gen.config import config from gramps.gen.const import PLUGINS_DIR, USER_PLUGINS +from gramps.gen.db.dbconst import DBBACKEND from gramps.gen.errors import DbError from gramps.gen.dbstate import DbState from gramps.gen.db.exceptions import (DbUpgradeRequiredError, @@ -151,7 +152,14 @@ class CLIDbLoader(object): else: mode = 'w' - db = self.dbstate.make_database("bsddb") + dbid_path = os.path.join(filename, DBBACKEND) + if os.path.isfile(dbid_path): + with open(dbid_path) as fp: + dbid = fp.read().strip() + else: + dbid = "bsddb" + + db = self.dbstate.make_database(dbid) self.dbstate.change_database(db) self.dbstate.db.disable_signals() diff --git a/gramps/gui/dbloader.py b/gramps/gui/dbloader.py index f3d303b15..d95c6bb9a 100644 --- a/gramps/gui/dbloader.py +++ b/gramps/gui/dbloader.py @@ -52,6 +52,7 @@ from gi.repository import GObject # #------------------------------------------------------------------------- from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.db.dbconst import DBBACKEND _ = glocale.translation.gettext from gramps.cli.grampscli import CLIDbLoader from gramps.gen.config import config @@ -304,7 +305,14 @@ class DbLoader(CLIDbLoader): else: mode = 'w' - db = self.dbstate.make_database("bsddb") + dbid_path = os.path.join(filename, DBBACKEND) + if os.path.isfile(dbid_path): + with open(dbid_path) as fp: + dbid = fp.read().strip() + else: + dbid = "bsddb" + + db = self.dbstate.make_database(dbid) db.disable_signals() self.dbstate.no_database() diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index adb28dc54..e0d8c043a 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -55,6 +55,11 @@ from gramps.gen.db import (PERSON_KEY, from gramps.gen.utils.id import create_id from django.db import transaction +## add this directory to sys path, so we can find django_support later: +import sys +import os +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + class Environment(object): """ Implements the Environment API. @@ -322,10 +327,15 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if directory: self.load(directory) - def load(self, directory, pulse_progress=None, mode=None): + def load(self, directory, pulse_progress=None, mode=None, + force_schema_upgrade=False, + force_bsddb_upgrade=False, + force_bsddb_downgrade=False, + force_python_upgrade=False): self._directory = directory from django.conf import settings - default_settings = {} + default_settings = {"__file__": + os.path.join(directory, "default_settings.py")} settings_file = os.path.join(directory, "default_settings.py") with open(settings_file) as f: code = compile(f.read(), settings_file, 'exec') @@ -1785,7 +1795,10 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): return self.family_bookmarks def get_save_path(self): - return "/tmp/" + return self._directory + + def set_save_path(self, directory): + self._directory = directory ## Get types: def get_event_attribute_types(self): From 8a42966c1f209596ae3bdbb59abbe78229a38202 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Wed, 13 May 2015 19:26:14 -0400 Subject: [PATCH 057/105] DictionaryDb: adding missing functions, bringing up to date --- gramps/plugins/database/dictionarydb.py | 454 ++++++++++++++++++++---- gramps/plugins/database/djangodb.py | 6 +- gramps/webapp/utils.py | 6 +- 3 files changed, 395 insertions(+), 71 deletions(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index a3dbd9586..621425e2d 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -29,6 +29,8 @@ import base64 import time import re from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn +from gramps.gen.utils.callback import Callback +from gramps.gen.updatecallback import UpdateCallback from gramps.gen.db import (PERSON_KEY, FAMILY_KEY, CITATION_KEY, @@ -53,33 +55,101 @@ from gramps.gen.lib.repo import Repository from gramps.gen.lib.note import Note from gramps.gen.lib.tag import Tag +class Environment(object): + """ + Implements the Environment API. + """ + def __init__(self, db): + self.db = db + + def txn_begin(self): + return DictionaryTxn("DictionaryDb Transaction", self.db) + +class Table(object): + """ + Implements Table interface. + """ + def __init__(self, funcs): + self.funcs = funcs + + def cursor(self): + """ + Returns a Cursor for this Table. + """ + return self.funcs["cursor_func"]() + + def put(key, data, txn=None): + self[key] = data + +class Map(dict): + """ + Implements the map API for person_map, etc. + + Takes a Table() as argument. + """ + def __init__(self, tbl, *args, **kwargs): + super().__init__(*args, **kwargs) + self.db = tbl + +class MetaCursor(object): + def __init__(self): + pass + def __enter__(self): + return self + def __iter__(self): + return self.__next__() + def __next__(self): + yield None + def __exit__(self, *args, **kwargs): + pass + def iter(self): + yield None + def first(self): + self._iter = self.__iter__() + return self.next() + def next(self): + try: + return next(self._iter) + except: + return None + def close(self): + pass + class Cursor(object): - """ - Iterates through model returning (handle, raw_data)... - """ - def __init__(self, model, func): - self.model = model + def __init__(self, map, func): + self.map = map self.func = func def __enter__(self): return self def __iter__(self): - return self + return self.__next__() def __next__(self): - for handle in self.model.keys(): - return (handle, self.func(handle)) - def next(self): - for handle in self.model.keys(): - return (handle, self.func(handle)) + for item in self.map.keys(): + yield (bytes(item, "utf-8"), self.func(item)) def __exit__(self, *args, **kwargs): pass + def iter(self): + for item in self.map.keys(): + yield (bytes(item, "utf-8"), self.func(item)) + yield None + def first(self): + self._iter = self.__iter__() + return self.next() + def next(self): + try: + return next(self._iter) + except: + return None def close(self): pass -class Bookmarks: +class Bookmarks(object): + def __init__(self): + self.handles = [] def get(self): - return [] # handles + return self.handles def append(self, handle): - pass + self.handles.append(handle) class DictionaryTxn(DbTxn): def __init__(self, message, db, batch=False): @@ -99,13 +169,38 @@ class DictionaryTxn(DbTxn): """ txn[handle] = new_data -class DictionaryDb(DbWriteBase, DbReadBase): +class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): """ A Gramps Database Backend. This replicates the grampsdb functions. """ + __signals__ = dict((obj+'-'+op, signal) + for obj in + ['person', 'family', 'event', 'place', + 'source', 'citation', 'media', 'note', 'repository', 'tag'] + for op, signal in zip( + ['add', 'update', 'delete', 'rebuild'], + [(list,), (list,), (list,), None] + ) + ) + + # 2. Signals for long operations + __signals__.update(('long-op-'+op, signal) for op, signal in zip( + ['start', 'heartbeat', 'end'], + [(object,), None, None] + )) + + # 3. Special signal for change in home person + __signals__['home-person-changed'] = None + + # 4. Signal for change in person group name, parameters are + __signals__['person-groupname-rebuild'] = (str, str) + + __callback_map = {} + def __init__(self, *args, **kwargs): DbReadBase.__init__(self) DbWriteBase.__init__(self) + Callback.__init__(self) self._tables['Person'].update( { "handle_func": self.get_person_from_handle, @@ -115,6 +210,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_person_handles, "add_func": self.add_person, "commit_func": self.commit_person, + "iter_func": self.iter_people, }) self._tables['Family'].update( { @@ -125,6 +221,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_family_handles, "add_func": self.add_family, "commit_func": self.commit_family, + "iter_func": self.iter_families, }) self._tables['Source'].update( { @@ -135,6 +232,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_source_handles, "add_func": self.add_source, "commit_func": self.commit_source, + "iter_func": self.iter_sources, }) self._tables['Citation'].update( { @@ -145,6 +243,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_citation_handles, "add_func": self.add_citation, "commit_func": self.commit_citation, + "iter_func": self.iter_citations, }) self._tables['Event'].update( { @@ -155,6 +254,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_event_handles, "add_func": self.add_event, "commit_func": self.commit_event, + "iter_func": self.iter_events, }) self._tables['Media'].update( { @@ -165,6 +265,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_media_object_handles, "add_func": self.add_object, "commit_func": self.commit_media_object, + "iter_func": self.iter_media_objects, }) self._tables['Place'].update( { @@ -175,6 +276,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_place_handles, "add_func": self.add_place, "commit_func": self.commit_place, + "iter_func": self.iter_places, }) self._tables['Repository'].update( { @@ -185,6 +287,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_repository_handles, "add_func": self.add_repository, "commit_func": self.commit_repository, + "iter_func": self.iter_repositories, }) self._tables['Note'].update( { @@ -195,6 +298,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_note_handles, "add_func": self.add_note, "commit_func": self.commit_note, + "iter_func": self.iter_notes, }) self._tables['Tag'].update( { @@ -205,6 +309,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): "handles_func": self.get_tag_handles, "add_func": self.add_tag, "commit_func": self.commit_tag, + "iter_func": self.iter_tags, }) # skip GEDCOM cross-ref check for now: self.set_feature("skip-check-xref", True) @@ -218,7 +323,7 @@ class DictionaryDb(DbWriteBase, DbReadBase): self.place_bookmarks = Bookmarks() self.citation_bookmarks = Bookmarks() self.source_bookmarks = Bookmarks() - self.repo_bookmarks = Bookmarks() + self.repository_bookmarks = Bookmarks() self.media_bookmarks = Bookmarks() self.note_bookmarks = Bookmarks() self.set_person_id_prefix('I%04d') @@ -249,18 +354,18 @@ class DictionaryDb(DbWriteBase, DbReadBase): 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.tag_map = {} - self.metadata = {} + self.env = Environment(self) + self.person_map = Map(Table(self._tables["Person"])) + self.family_map = Map(Table(self._tables["Family"])) + self.place_map = Map(Table(self._tables["Place"])) + self.citation_map = Map(Table(self._tables["Citation"])) + self.source_map = Map(Table(self._tables["Source"])) + self.repository_map = Map(Table(self._tables["Repository"])) + self.note_map = Map(Table(self._tables["Note"])) + self.media_map = Map(Table(self._tables["Media"])) + self.event_map = Map(Table(self._tables["Event"])) + self.tag_map = Map(Table(self._tables["Tag"])) + self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) self.name_group = {} self.undo_callback = None self.redo_callback = None @@ -286,9 +391,6 @@ class DictionaryDb(DbWriteBase, DbReadBase): def transaction_commit(self, txn): pass - def enable_signals(self): - pass - def get_undodb(self): return None @@ -546,91 +648,107 @@ class DictionaryDb(DbWriteBase, DbReadBase): return obj def get_person_handles(self, sort_handles=False): - if sort_handles: - raise Exception("Implement!") - else: - return self.person_map.keys() + ## Fixme: implement sort + return self.person_map.keys() def get_family_handles(self): return self.family_map.keys() - def get_event_handles(self): + def get_event_handles(self, sort_handles=False): return self.event_map.keys() def get_citation_handles(self, sort_handles=False): - if sort_handles: - raise Exception("Implement!") - else: - return self.citation_map.keys() + ## Fixme: implement sort + return self.citation_map.keys() def get_source_handles(self, sort_handles=False): - if sort_handles: - raise Exception("Implement!") - else: - return self.source_map.keys() + ## Fixme: implement sort + return self.source_map.keys() def get_place_handles(self, sort_handles=False): - if sort_handles: - raise Exception("Implement!") - else: - return self.place_map.keys() + ## Fixme: implement sort + return self.place_map.keys() def get_repository_handles(self): return self.repository_map.keys() def get_media_object_handles(self, sort_handles=False): - if sort_handles: - raise Exception("Implement!") - else: - return self.media_map.keys() + ## Fixme: implement sort + return self.media_map.keys() def get_note_handles(self): return self.note_map.keys() def get_tag_handles(self, sort_handles=False): - if sort_handles: - raise Exception("Implement!") - else: - return self.tag_map.keys() + # FIXME: sort + return self.tag_map.keys() def get_event_from_handle(self, handle): - return self.event_map[handle] + event = None + if handle in self.event_map: + event = self.event_map[handle] + return event def get_family_from_handle(self, handle): - return self.family_map[handle] + family = None + if handle in self.family_map: + family = self.family_map[handle] + return family def get_repository_from_handle(self, handle): - return self.repository_map[handle] + repository = None + if handle in self.repository_map: + repository = self.repository_map[handle] + return repository def get_person_from_handle(self, handle): - return self.person_map[handle] + person = None + if handle in self.person_map: + person = self.person_map[handle] + return person def get_place_from_handle(self, handle): - place = self.place_map[handle] + place = None + if handle in self.place_map: + place = self.place_map[handle] return place def get_citation_from_handle(self, handle): - citation = self.citation_map[handle] + citation = None + if handle in self.citation_map: + citation = self.citation_map[handle] return citation def get_source_from_handle(self, handle): - source = self.source_map[handle] + source = None + if handle in self.source_map: + source = self.source_map[handle] return source def get_note_from_handle(self, handle): - note = self.note_map[handle] + note = None + if handle in self.note_map: + note = self.note_map[handle] return note def get_object_from_handle(self, handle): - media = self.media_map[handle] + media = None + if handle in self.media_map: + media = self.media_map[handle] return media def get_tag_from_handle(self, handle): - tag = self.tag_map[handle] + tag = None + if handle in self.tag_map: + tag = self.tag_map[handle] return tag def get_default_person(self): - return None + handle = self.get_default_handle() + if handle: + return self.get_person_from_handle(handle) + else: + return None def iter_people(self): return (person for person in self.person_map.values()) @@ -1203,3 +1321,205 @@ class DictionaryDb(DbWriteBase, DbReadBase): transaction.add(REFERENCE_KEY, TXNDEL, key, old_data, None) #transaction.reference_del.append(str(key)) self.reference_map.delete(key, txn=txn) + + ## Missing: + + def backup(self): + pass + + def close(self): + pass + + def find_backlink_handles(self, handle, include_classes=None): + return [] + + def find_initial_person(self): + items = self.person_map.keys() + if len(items) > 0: + return self.get_person_from_handle(list(items)[0]) + return None + + def find_place_child_handles(self, handle): + return [] + + def get_bookmarks(self): + return self.bookmarks + + def get_child_reference_types(self): + return [] + + def get_citation_bookmarks(self): + return self.citation_bookmarks + + def get_cursor(self, table, txn=None, update=False, commit=False): + pass + + def get_dbname(self): + return "DictionaryDb" + + def get_default_handle(self): + items = self.person_map.keys() + if len(items) > 0: + return list(items)[0] + return None + + def get_event_attribute_types(self): + return [] + + def get_event_bookmarks(self): + return self.event_bookmarks + + def get_event_roles(self): + return [] + + def get_event_types(self): + return [] + + def get_family_attribute_types(self): + return [] + + def get_family_bookmarks(self): + return self.family_bookmarks + + def get_family_event_types(self): + return [] + + def get_family_relation_types(self): + return [] + + def get_media_attribute_types(self): + return [] + + def get_media_bookmarks(self): + return self.media_bookmarks + + def get_name_types(self): + return [] + + def get_note_bookmarks(self): + return self.note_bookmarks + + def get_note_types(self): + return [] + + def get_number_of_records(self, table): + return 0 + + def get_origin_types(self): + return [] + + def get_person_attribute_types(self): + return [] + + def get_person_event_types(self): + return [] + + def get_place_bookmarks(self): + return self.place_bookmarks + + def get_place_tree_cursor(self): + return [] + + def get_place_types(self): + return [] + + def get_repo_bookmarks(self): + return self.repository_bookmarks + + def get_repository_types(self): + return [] + + def get_save_path(self): + return self._directory + + def get_source_attribute_types(self): + return [] + + def get_source_bookmarks(self): + return self.source_bookmarks + + def get_source_media_types(self): + return [] + + def get_surname_list(self): + return [] + + def get_url_types(self): + return [] + + def has_changed(self): + return True + + def is_open(self): + return True + + def iter_citation_handles(self): + return (key for key in self.citation_map.keys()) + + def iter_citations(self): + return (key for key in self.citation_map.values()) + + def iter_event_handles(self): + return (key for key in self.event_map.keys()) + + def iter_events(self): + return (key for key in self.event_map.values()) + + def iter_media_objects(self): + return (key for key in self.media_map.values()) + + def iter_note_handles(self): + return (key for key in self.note_map.keys()) + + def iter_notes(self): + return (key for key in self.note_map.values()) + + def iter_place_handles(self): + return (key for key in self.place_map.keys()) + + def iter_places(self): + return (key for key in self.place_map.values()) + + def iter_repositories(self): + return (key for key in self.repositories_map.values()) + + def iter_repository_handles(self): + return (key for key in self.repositories_map.keys()) + + def iter_source_handles(self): + return (key for key in self.source_map.keys()) + + def iter_sources(self): + return (key for key in self.source_map.values()) + + def iter_tag_handles(self): + return (key for key in self.tag_map.keys()) + + def iter_tags(self): + return (key for key in self.tag_map.values()) + + def load(self, directory, pulse_progress, mode, + force_schema_upgrade, + force_bsddb_upgrade, + force_bsddb_downgrade, + force_python_upgrade): + self._directory = directory + + def prepare_import(self): + pass + + def redo(self, update_history=True): + pass + + def restore(self): + pass + + def set_prefixes(self, person, media, family, source, citation, + place, event, repository, note): + pass + + def set_save_path(self, directory): + self._directory = directory + + def undo(self, update_history=True): + pass diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index e0d8c043a..ee6d4162d 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -68,7 +68,7 @@ class Environment(object): self.db = db def txn_begin(self): - return DjangoTxn("DbDjango Transaction", self.db) + return DjangoTxn("DjangoDb Transaction", self.db) class Table(object): """ @@ -210,6 +210,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): # 4. Signal for change in person group name, parameters are __signals__['person-groupname-rebuild'] = (str, str) + __callback_map = {} + def __init__(self, directory=None): DbReadBase.__init__(self) DbWriteBase.__init__(self) @@ -1745,8 +1747,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.get_number_of_media_objects() == 0 and self.get_number_of_repositories() == 0) - __callback_map = {} - def set_prefixes(self, person, media, family, source, citation, place, event, repository, note): self.set_person_id_prefix(person) diff --git a/gramps/webapp/utils.py b/gramps/webapp/utils.py index 07eddd341..10e0aaf38 100644 --- a/gramps/webapp/utils.py +++ b/gramps/webapp/utils.py @@ -143,8 +143,12 @@ def get_person_from_handle(db, handle): return None def probably_alive(handle): + ## FIXME: need to call after save? person = db.get_person_from_handle(handle) - return alive(person, db) + if person: + return alive(person, db) + else: + return True def format_number(number, with_grouping=True): if number != "": From 789158aca5a0b3cab5817386e1f0bf32d06d9731 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Wed, 13 May 2015 21:29:07 -0400 Subject: [PATCH 058/105] DictionaryDb: now reads/writes on open/close --- gramps/gen/db/dbconst.py | 58 +++++-- .../plugins/database/bsddb_support/write.py | 33 ---- gramps/plugins/database/dictionarydb.py | 142 +++++++++++++++--- gramps/plugins/database/djangodb.py | 6 - gramps/plugins/importer/importxml.py | 2 +- 5 files changed, 170 insertions(+), 71 deletions(-) diff --git a/gramps/gen/db/dbconst.py b/gramps/gen/db/dbconst.py index e53535248..21a17db80 100644 --- a/gramps/gen/db/dbconst.py +++ b/gramps/gen/db/dbconst.py @@ -28,20 +28,16 @@ Declare constants used by database modules # constants # #------------------------------------------------------------------------- -__all__ = ( - ('DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO', - 'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN', - 'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN', - 'DBBACKEND' - ) + - - ('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY', - 'EVENT_KEY', 'MEDIA_KEY', 'PLACE_KEY', 'REPOSITORY_KEY', - 'NOTE_KEY', 'REFERENCE_KEY', 'TAG_KEY' - ) + - - ('TXNADD', 'TXNUPD', 'TXNDEL') - ) +__all__ = ( 'DBPAGE', 'DBMODE', 'DBCACHE', 'DBLOCKS', 'DBOBJECTS', 'DBUNDO', + 'DBEXT', 'DBMODE_R', 'DBMODE_W', 'DBUNDOFN', 'DBLOCKFN', + 'DBRECOVFN','BDBVERSFN', 'DBLOGNAME', 'SCHVERSFN', 'PCKVERSFN', + 'DBBACKEND', + 'PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'CITATION_KEY', + 'EVENT_KEY', 'MEDIA_KEY', 'PLACE_KEY', 'REPOSITORY_KEY', + 'NOTE_KEY', 'REFERENCE_KEY', 'TAG_KEY', + 'TXNADD', 'TXNUPD', 'TXNDEL', + "CLASS_TO_KEY_MAP", "KEY_TO_CLASS_MAP", "KEY_TO_NAME_MAP" + ) DBEXT = ".db" # File extension to be used for database files DBUNDOFN = "undo.db" # File name of 'undo' database @@ -74,3 +70,37 @@ TAG_KEY = 9 CITATION_KEY = 10 TXNADD, TXNUPD, TXNDEL = 0, 1, 2 + +CLASS_TO_KEY_MAP = {"Person": PERSON_KEY, + "Family": FAMILY_KEY, + "Source": SOURCE_KEY, + "Citation": CITATION_KEY, + "Event": EVENT_KEY, + "MediaObject": MEDIA_KEY, + "Place": PLACE_KEY, + "Repository": REPOSITORY_KEY, + "Note" : NOTE_KEY, + "Tag": TAG_KEY} + +KEY_TO_CLASS_MAP = {PERSON_KEY: "Person", + FAMILY_KEY: "Family", + SOURCE_KEY: "Source", + CITATION_KEY: "Citation", + EVENT_KEY: "Event", + MEDIA_KEY: "MediaObject", + PLACE_KEY: "Place", + REPOSITORY_KEY: "Repository", + NOTE_KEY: "Note", + TAG_KEY: "Tag"} + +KEY_TO_NAME_MAP = {PERSON_KEY: 'person', + FAMILY_KEY: 'family', + EVENT_KEY: 'event', + SOURCE_KEY: 'source', + CITATION_KEY: 'citation', + PLACE_KEY: 'place', + MEDIA_KEY: 'media', + REPOSITORY_KEY: 'repository', + #REFERENCE_KEY: 'reference', + NOTE_KEY: 'note', + TAG_KEY: 'tag'} diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py index 606f9484a..66d4e4a53 100644 --- a/gramps/plugins/database/bsddb_support/write.py +++ b/gramps/plugins/database/bsddb_support/write.py @@ -130,39 +130,6 @@ DBERRS = (db.DBRunRecoveryError, db.DBAccessError, # these maps or modifying the values of the keys will break # existing databases. -CLASS_TO_KEY_MAP = {Person.__name__: PERSON_KEY, - Family.__name__: FAMILY_KEY, - Source.__name__: SOURCE_KEY, - Citation.__name__: CITATION_KEY, - Event.__name__: EVENT_KEY, - MediaObject.__name__: MEDIA_KEY, - Place.__name__: PLACE_KEY, - Repository.__name__:REPOSITORY_KEY, - Note.__name__: NOTE_KEY, - Tag.__name__: TAG_KEY} - -KEY_TO_CLASS_MAP = {PERSON_KEY: Person.__name__, - FAMILY_KEY: Family.__name__, - SOURCE_KEY: Source.__name__, - CITATION_KEY: Citation.__name__, - EVENT_KEY: Event.__name__, - MEDIA_KEY: MediaObject.__name__, - PLACE_KEY: Place.__name__, - REPOSITORY_KEY: Repository.__name__, - NOTE_KEY: Note.__name__, - TAG_KEY: Tag.__name__} - -KEY_TO_NAME_MAP = {PERSON_KEY: 'person', - FAMILY_KEY: 'family', - EVENT_KEY: 'event', - SOURCE_KEY: 'source', - CITATION_KEY: 'citation', - PLACE_KEY: 'place', - MEDIA_KEY: 'media', - REPOSITORY_KEY: 'repository', - #REFERENCE_KEY: 'reference', - NOTE_KEY: 'note', - TAG_KEY: 'tag'} #------------------------------------------------------------------------- # # Helper functions diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 621425e2d..09e2451b6 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -28,6 +28,7 @@ import pickle import base64 import time import re +import os from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback @@ -40,7 +41,8 @@ from gramps.gen.db import (PERSON_KEY, PLACE_KEY, REPOSITORY_KEY, NOTE_KEY, - TAG_KEY) + TAG_KEY, + KEY_TO_NAME_MAP) from gramps.gen.utils.id import create_id from gramps.gen.lib.researcher import Researcher @@ -197,7 +199,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): __callback_map = {} - def __init__(self, *args, **kwargs): + def __init__(self, directory=None): DbReadBase.__init__(self) DbWriteBase.__init__(self) Callback.__init__(self) @@ -323,7 +325,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.place_bookmarks = Bookmarks() self.citation_bookmarks = Bookmarks() self.source_bookmarks = Bookmarks() - self.repository_bookmarks = Bookmarks() + self.repo_bookmarks = Bookmarks() self.media_bookmarks = Bookmarks() self.note_bookmarks = Bookmarks() self.set_person_id_prefix('I%04d') @@ -373,6 +375,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.modified = 0 self.txn = DictionaryTxn("DbDictionary Transaction", self) self.transaction = None + self._directory = directory + if directory: + self.load(directory) def version_supported(self): """Return True when the file has a supported version.""" @@ -768,24 +773,66 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): 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 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_citation_from_gramps_id(self, gramps_id): + for citation in self.citation_map.values(): + if citation.gramps_id == gramps_id: + return citation + return None + + def get_source_from_gramps_id(self, gramps_id): + for source in self.source_map.values(): + if source.gramps_id == gramps_id: + return source + return None + + def get_event_from_gramps_id(self, gramps_id): + for event in self.event_map.values(): + if event.gramps_id == gramps_id: + return event + return None + + def get_media_from_gramps_id(self, gramps_id): + for media in self.media_map.values(): + if media.gramps_id == gramps_id: + return media + return None + def get_place_from_gramps_id(self, gramps_id): for place in self.place_map.values(): if place.gramps_id == gramps_id: return place return None + def get_repository_from_gramps_id(self, gramps_id): + for repository in self.repository_map.values(): + if repository.gramps_id == gramps_id: + return repository + return None + + def get_note_from_gramps_id(self, gramps_id): + for note in self.note_map.values(): + if note.gramps_id == gramps_id: + return note + return None + + def get_tag_from_gramps_id(self, gramps_id): + for tag in self.tag_map.values(): + if tag.gramps_id == gramps_id: + return tag + return None + def get_number_of_people(self): return len(self.person_map) @@ -1038,33 +1085,73 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return obj.handle def commit_person(self, person, trans, change_time=None): + if person.handle in self.person_map: + self.emit("person-update", ([person.handle],)) + else: + self.emit("person-add", ([person.handle],)) self.person_map[person.handle] = person def commit_family(self, family, trans, change_time=None): + if family.handle in self.family_map: + self.emit("family-update", ([family.handle],)) + else: + self.emit("family-add", ([family.handle],)) self.family_map[family.handle] = family def commit_citation(self, citation, trans, change_time=None): + if citation.handle in self.citation_map: + self.emit("citation-update", ([citation.handle],)) + else: + self.emit("citation-add", ([citation.handle],)) self.citation_map[citation.handle] = citation def commit_source(self, source, trans, change_time=None): + if source.handle in self.source_map: + self.emit("source-update", ([source.handle],)) + else: + self.emit("source-add", ([source.handle],)) self.source_map[source.handle] = source def commit_repository(self, repository, trans, change_time=None): + if repository.handle in self.repository_map: + self.emit("repository-update", ([repository.handle],)) + else: + self.emit("repository-add", ([repository.handle],)) self.repository_map[repository.handle] = repository def commit_note(self, note, trans, change_time=None): + if note.handle in self.note_map: + self.emit("note-update", ([note.handle],)) + else: + self.emit("note-add", ([note.handle],)) self.note_map[note.handle] = note def commit_place(self, place, trans, change_time=None): + if place.handle in self.place_map: + self.emit("place-update", ([place.handle],)) + else: + self.emit("place-add", ([place.handle],)) self.place_map[place.handle] = place def commit_event(self, event, trans, change_time=None): + if event.handle in self.event_map: + self.emit("event-update", ([event.handle],)) + else: + self.emit("event-add", ([event.handle],)) self.event_map[event.handle] = event def commit_tag(self, tag, trans, change_time=None): + if tag.handle in self.tag_map: + self.emit("tag-update", ([tag.handle],)) + else: + self.emit("tag-add", ([tag.handle],)) self.tag_map[tag.handle] = tag def commit_media_object(self, obj, transaction, change_time=None): + if commit.handle in self.commit_map: + self.emit("commit-update", ([commit.handle],)) + else: + self.emit("commit-add", ([commit.handle],)) self.media_map[obj.handle] = obj def get_gramps_ids(self, obj_key): @@ -1092,7 +1179,16 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): pass def request_rebuild(self): - pass + self.emit('person-rebuild') + self.emit('family-rebuild') + self.emit('place-rebuild') + self.emit('source-rebuild') + self.emit('citation-rebuild') + self.emit('media-rebuild') + self.emit('event-rebuild') + self.emit('repository-rebuild') + self.emit('note-rebuild') + self.emit('tag-rebuild') def copy_from_db(self, db): """ @@ -1163,6 +1259,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.delete_primary_from_reference_map(handle, transaction, txn=self.txn) self.person_map.delete(handle, txn=self.txn) + self.emit("person-delete", ([handle],)) transaction.add(PERSON_KEY, TXNDEL, handle, person.serialize(), None) def remove_source(self, handle, transaction): @@ -1262,6 +1359,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): txn=self.txn) old_data = data_map.get(handle, txn=self.txn) data_map.delete(handle, txn=self.txn) + self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) transaction.add(key, TXNDEL, handle, old_data, None) def delete_primary_from_reference_map(self, handle, transaction, txn=None): @@ -1328,7 +1426,12 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): pass def close(self): - pass + if self._directory: + from gramps.plugins.export.exportxml import XmlWriter + from gramps.cli.user import User + writer = XmlWriter(self, User(), strip_photos=0, compress=1) + filename = os.path.join(self._directory, "data.gramps") + writer.write(filename) def find_backlink_handles(self, handle, include_classes=None): return [] @@ -1424,7 +1527,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return [] def get_repo_bookmarks(self): - return self.repository_bookmarks + return self.repo_bookmarks def get_repository_types(self): return [] @@ -1498,12 +1601,17 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): def iter_tags(self): return (key for key in self.tag_map.values()) - def load(self, directory, pulse_progress, mode, - force_schema_upgrade, - force_bsddb_upgrade, - force_bsddb_downgrade, - force_python_upgrade): + def load(self, directory, pulse_progress=None, mode=None, + force_schema_upgrade=False, + force_bsddb_upgrade=False, + force_bsddb_downgrade=False, + force_python_upgrade=False): + from gramps.plugins.importer.importxml import importData + from gramps.cli.user import User self._directory = directory + filename = os.path.join(directory, "data.gramps") + if os.path.isfile(filename): + importData(self, filename, User()) def prepare_import(self): pass diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index ee6d4162d..56f40f5ac 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -1402,7 +1402,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not person.gramps_id and set_gid: person.gramps_id = self.find_next_person_gramps_id() self.commit_person(person, trans) - self.emit("person-add", ([person.handle],)) return person.handle def add_family(self, family, trans, set_gid=True): @@ -1411,7 +1410,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not family.gramps_id and set_gid: family.gramps_id = self.find_next_family_gramps_id() self.commit_family(family, trans) - self.emit("family-add", ([family.handle],)) return family.handle def add_citation(self, citation, trans, set_gid=True): @@ -1420,7 +1418,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not citation.gramps_id and set_gid: citation.gramps_id = self.find_next_citation_gramps_id() self.commit_citation(citation, trans) - self.emit("citation-add", ([citation.handle],)) return citation.handle def add_source(self, source, trans, set_gid=True): @@ -1429,7 +1426,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not source.gramps_id and set_gid: source.gramps_id = self.find_next_source_gramps_id() self.commit_source(source, trans) - self.emit("source-add", ([source.handle],)) return source.handle def add_repository(self, repository, trans, set_gid=True): @@ -1438,7 +1434,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not repository.gramps_id and set_gid: repository.gramps_id = self.find_next_repository_gramps_id() self.commit_repository(repository, trans) - self.emit("repository-add", ([repository.handle],)) return repository.handle def add_note(self, note, trans, set_gid=True): @@ -1447,7 +1442,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not note.gramps_id and set_gid: note.gramps_id = self.find_next_note_gramps_id() self.commit_note(note, trans) - self.emit("note-add", ([note.handle],)) return note.handle def add_place(self, place, trans, set_gid=True): diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py index 00aa28420..39a3f04d4 100644 --- a/gramps/plugins/importer/importxml.py +++ b/gramps/plugins/importer/importxml.py @@ -57,7 +57,7 @@ from gramps.gen.lib import (Address, Attribute, AttributeType, ChildRef, SrcAttribute, SrcAttributeType, StyledText, StyledTextTag, StyledTextTagType, Surname, Tag, Url) from gramps.gen.db import DbTxn -from gramps.gen.db.write import CLASS_TO_KEY_MAP +#from gramps.gen.db.write import CLASS_TO_KEY_MAP from gramps.gen.errors import GrampsImportError from gramps.gen.utils.id import create_id from gramps.gen.utils.db import family_name From 3489276fa1a5fa74d9af2945c6f2c5d231b5c619 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Wed, 13 May 2015 22:01:47 -0400 Subject: [PATCH 059/105] Moved key maps to dbconst --- gramps/plugins/database/dictionarydb.py | 15 +++++++-------- gramps/plugins/importer/importxml.py | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 09e2451b6..eab579416 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -29,7 +29,7 @@ import base64 import time import re import os -from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn +from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback from gramps.gen.db import (PERSON_KEY, @@ -41,8 +41,7 @@ from gramps.gen.db import (PERSON_KEY, PLACE_KEY, REPOSITORY_KEY, NOTE_KEY, - TAG_KEY, - KEY_TO_NAME_MAP) + TAG_KEY) from gramps.gen.utils.id import create_id from gramps.gen.lib.researcher import Researcher @@ -1147,12 +1146,12 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit("tag-add", ([tag.handle],)) self.tag_map[tag.handle] = tag - def commit_media_object(self, obj, transaction, change_time=None): - if commit.handle in self.commit_map: - self.emit("commit-update", ([commit.handle],)) + def commit_media_object(self, media, transaction, change_time=None): + if media.handle in self.media_map: + self.emit("media-update", ([media.handle],)) else: - self.emit("commit-add", ([commit.handle],)) - self.media_map[obj.handle] = obj + self.emit("media-add", ([media.handle],)) + self.media_map[media.handle] = media def get_gramps_ids(self, obj_key): key2table = { diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py index 39a3f04d4..3cbea0785 100644 --- a/gramps/plugins/importer/importxml.py +++ b/gramps/plugins/importer/importxml.py @@ -68,7 +68,7 @@ from gramps.gen.display.name import displayer as name_displayer from gramps.gen.db.dbconst import (PERSON_KEY, FAMILY_KEY, SOURCE_KEY, EVENT_KEY, MEDIA_KEY, PLACE_KEY, REPOSITORY_KEY, NOTE_KEY, TAG_KEY, - CITATION_KEY) + CITATION_KEY, CLASS_TO_KEY_MAP) from gramps.gen.updatecallback import UpdateCallback from gramps.version import VERSION from gramps.gen.config import config From 7ded76695ac935f6317c4aa4c6d382cb5e44c41c Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Wed, 13 May 2015 22:55:23 -0400 Subject: [PATCH 060/105] DictionaryDb: implement delete --- gramps/plugins/database/dictionarydb.py | 36 +++++++------------------ 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index eab579416..647f6b0ee 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -999,6 +999,11 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return self.tag_map[handle].serialize() return None + def get_raw_event_data(self, handle): + if handle in self.event_map: + return self.event_map[handle].serialize() + return None + def add_person(self, person, trans, set_gid=True): if not person.handle: person.handle = create_id() @@ -1244,22 +1249,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): if self.readonly or not handle: return - person = self.get_person_from_handle(handle) - #self.genderStats.uncount_person (person) - #self.remove_from_surname_list(person) - if isinstance(handle, str): - handle = handle.encode('utf-8') - if transaction.batch: - with BSDDBTxn(self.env, self.person_map) as txn: - self.delete_primary_from_reference_map(handle, transaction, - txn=txn.txn) - txn.delete(handle) - else: - self.delete_primary_from_reference_map(handle, transaction, - txn=self.txn) - self.person_map.delete(handle, txn=self.txn) + if handle in self.person_map: + del self.person_map[handle] self.emit("person-delete", ([handle],)) - transaction.add(PERSON_KEY, TXNDEL, handle, person.serialize(), None) def remove_source(self, handle, transaction): """ @@ -1345,21 +1337,11 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): def __do_remove(self, handle, transaction, data_map, key): if self.readonly or not handle: return - if isinstance(handle, str): handle = handle.encode('utf-8') - if transaction.batch: - with BSDDBTxn(self.env, data_map) as txn: - self.delete_primary_from_reference_map(handle, transaction, - txn=txn.txn) - txn.delete(handle) - else: - self.delete_primary_from_reference_map(handle, transaction, - txn=self.txn) - old_data = data_map.get(handle, txn=self.txn) - data_map.delete(handle, txn=self.txn) + if handle in data_map: + del data_map[handle] self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) - transaction.add(key, TXNDEL, handle, old_data, None) def delete_primary_from_reference_map(self, handle, transaction, txn=None): """ From 61ec1c1b48a5072a271e10cdfa5b15cd03511993 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 06:31:59 -0400 Subject: [PATCH 061/105] Database backends: bsddb, django, and dictionary --- gramps/cli/clidbman.py | 11 +- gramps/gui/dbman.py | 44 +++-- gramps/plugins/database/dictionarydb.py | 20 ++- .../defaults/default_settings.py | 150 ++++++++++++++++++ .../django_support/defaults/sqlite.db | Bin 0 -> 293888 bytes gramps/plugins/database/djangodb.py | 23 ++- 6 files changed, 230 insertions(+), 18 deletions(-) create mode 100644 gramps/plugins/database/django_support/defaults/default_settings.py create mode 100644 gramps/plugins/database/django_support/defaults/sqlite.db diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py index a4986f615..893a2d84f 100644 --- a/gramps/cli/clidbman.py +++ b/gramps/cli/clidbman.py @@ -225,7 +225,7 @@ class CLIDbManager(object): """ print(_('Import finished...')) - def create_new_db_cli(self, title=None, create_db=True): + def create_new_db_cli(self, title=None, create_db=True, dbid=None): """ Create a new database. """ @@ -244,8 +244,9 @@ class CLIDbManager(object): if create_db: # write the version number into metadata - - newdb = self.dbstate.make_database("bsddb") + if dbid is None: + dbid = "bsddb" + newdb = self.dbstate.make_database(dbid) newdb.write_version(new_path) (tval, last) = time_val(new_path) @@ -254,11 +255,11 @@ class CLIDbManager(object): last, tval, False, "")) return new_path, title - def _create_new_db(self, title=None): + def _create_new_db(self, title=None, dbid=None): """ Create a new database, do extra stuff needed """ - return self.create_new_db_cli(title) + return self.create_new_db_cli(title, dbid=dbid) def import_new_db(self, filename, user): """ diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py index b105ac37b..0891170f4 100644 --- a/gramps/gui/dbman.py +++ b/gramps/gui/dbman.py @@ -72,7 +72,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext from gramps.gen.const import URL_WIKISTRING from .user import User -from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2 +from .dialog import ErrorDialog, QuestionDialog, QuestionDialog2, ICON from .pluginmanager import GuiPluginManager from gramps.cli.clidbman import CLIDbManager, NAME_FILE, time_val from .ddtargets import DdTargets @@ -80,7 +80,6 @@ from gramps.gen.recentfiles import rename_filename, remove_filename from .glade import Glade from gramps.gen.db.exceptions import DbException - _RETURN = Gdk.keyval_from_name("Return") _KP_ENTER = Gdk.keyval_from_name("KP_Enter") @@ -104,6 +103,25 @@ ICON_COL = 6 RCS_BUTTON = { True : _('_Extract'), False : _('_Archive') } +class DatabaseDialog(Gtk.MessageDialog): + def __init__(self, parent=None): + Gtk.MessageDialog.__init__(self, + parent, + flags=Gtk.DialogFlags.MODAL, + type=Gtk.MessageType.QUESTION, + ) + self.set_icon(ICON) + self.set_title('') + + self.set_markup('<span size="larger" weight="bold">%s</span>' % + _('Database Backend')) + self.format_secondary_text( + _("Please select a database backend type")) + + self.add_button("BSDDB Database (standard)", 1) + self.add_button("Dictionary (in-memory)", 2) + self.add_button("Django Database", 3) + class DbManager(CLIDbManager): """ Database Manager. Opens a database manager window that allows users to @@ -761,19 +779,27 @@ class DbManager(CLIDbManager): message. """ self.new.set_sensitive(False) - try: - self._create_new_db() - except (OSError, IOError) as msg: - DbManager.ERROR(_("Could not create Family Tree"), - str(msg)) + # popup window and ask for dbid types, if more than one + ## FIXME: autoload from plugins + dbid = "bsddb" + d = DatabaseDialog(self.top) + database = d.run() + d.destroy() + if database >= 0: + dbid = {1:"bsddb",2:"dictionarydb",3:"djangodb"}[database] + try: + self._create_new_db(dbid=dbid) + except (OSError, IOError) as msg: + DbManager.ERROR(_("Could not create Family Tree"), + str(msg)) self.new.set_sensitive(True) - def _create_new_db(self, title=None, create_db=True): + def _create_new_db(self, title=None, create_db=True, dbid=None): """ Create a new database, append to model """ new_path, title = self.create_new_db_cli(conv_to_unicode(title, 'utf8'), - create_db) + create_db, dbid) path_name = os.path.join(new_path, NAME_FILE) (tval, last) = time_val(new_path) node = self.model.append(None, [title, new_path, path_name, diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 647f6b0ee..936d8f534 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -21,7 +21,7 @@ #------------------------------------------------------------------------ # -# Gramps Modules +# Python Modules # #------------------------------------------------------------------------ import pickle @@ -29,7 +29,15 @@ import base64 import time import re import os +import logging + +#------------------------------------------------------------------------ +# +# Gramps Modules +# +#------------------------------------------------------------------------ from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP +from gramps.gen.db.dbconst import * from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback from gramps.gen.db import (PERSON_KEY, @@ -56,6 +64,8 @@ from gramps.gen.lib.repo import Repository from gramps.gen.lib.note import Note from gramps.gen.lib.tag import Tag +_LOG = logging.getLogger(DBLOGNAME) + class Environment(object): """ Implements the Environment API. @@ -1612,3 +1622,11 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): def undo(self, update_history=True): pass + + def write_version(self, directory): + """Write files for a newly created DB.""" + versionpath = os.path.join(directory, str(DBBACKEND)) + _LOG.debug("Write database backend file to 'dictionarydb'") + with open(versionpath, "w") as version_file: + version_file.write("dictionarydb") + diff --git a/gramps/plugins/database/django_support/defaults/default_settings.py b/gramps/plugins/database/django_support/defaults/default_settings.py new file mode 100644 index 000000000..fae3ffd06 --- /dev/null +++ b/gramps/plugins/database/django_support/defaults/default_settings.py @@ -0,0 +1,150 @@ +import os +from gramps.gen.const import DATA_DIR + +WEB_DIR = os.path.dirname(os.path.realpath(__file__)) + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +INTERNAL_IPS = ('127.0.0.1',) + +ADMINS = ( + ('admin', 'your_email@domain.com'), +) + +MANAGERS = ADMINS +DATABASE_ROUTERS = [] +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(WEB_DIR, 'sqlite.db'), + } +} +DATABASE_ENGINE = 'sqlite3' +DATABASE_NAME = os.path.join(WEB_DIR, 'sqlite.db') +DATABASE_USER = '' +DATABASE_PASSWORD = '' +DATABASE_HOST = '' +DATABASE_PORT = '' +TIME_ZONE = 'America/New_York' +LANGUAGE_CODE = 'en-us' +SITE_ID = 1 +USE_I18N = True +MEDIA_ROOT = '' +MEDIA_URL = '' +ADMIN_MEDIA_PREFIX = '/gramps-media/' +SECRET_KEY = 'zd@%vslj5sqhx94_8)0hsx*rk9tj3^ly$x+^*tq4bggr&uh$ac' + +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', # 1.4 + 'django.template.loaders.app_directories.Loader', # 1.4 + #'django.template.loaders.filesystem.load_template_source', + #'django.template.loaders.app_directories.load_template_source', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', +# 'debug_toolbar.middleware.DebugToolbarMiddleware', +) + +ROOT_URLCONF = 'gramps.webapp.urls' +STATIC_URL = '/static/' # 1.4 + +TEMPLATE_DIRS = ( + # Use absolute paths, not relative paths. + os.path.join(DATA_DIR, "templates"), +) + +TEMPLATE_CONTEXT_PROCESSORS = ( + "django.contrib.auth.context_processors.auth", # 1.4 + "django.contrib.messages.context_processors.messages", # 1.4 +# "django.core.context_processors.auth", +# "django.core.context_processors.debug", + "django.core.context_processors.i18n", + "django.core.context_processors.media", + "gramps.webapp.grampsdb.views.context_processor", + "gramps.webapp.context.messages", +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.staticfiles', + 'django.contrib.messages', # 1.4 + 'django.contrib.sites', + 'django.contrib.admin', + 'gramps.webapp.grampsdb', +# 'django_extensions', +# 'debug_toolbar', +) + +DEBUG_TOOLBAR_PANELS = ( + 'debug_toolbar.panels.version.VersionDebugPanel', + 'debug_toolbar.panels.timer.TimerDebugPanel', + 'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel', + 'debug_toolbar.panels.headers.HeaderDebugPanel', + 'debug_toolbar.panels.request_vars.RequestVarsDebugPanel', + 'debug_toolbar.panels.template.TemplateDebugPanel', + 'debug_toolbar.panels.sql.SQLDebugPanel', + 'debug_toolbar.panels.signals.SignalDebugPanel', + 'debug_toolbar.panels.logger.LoggingPanel', + ) + +def custom_show_toolbar(request): + return True # Always show toolbar, for example purposes only. + +DEBUG_TOOLBAR_CONFIG = { + 'INTERCEPT_REDIRECTS': False, +# 'SHOW_TOOLBAR_CALLBACK': custom_show_toolbar, +# 'EXTRA_SIGNALS': ['myproject.signals.MySignal'], + 'HIDE_DJANGO_SQL': False, + } + +AUTH_PROFILE_MODULE = "grampsdb.Profile" + +# Had to add these to use settings.configure(): +DATABASE_OPTIONS = '' +URL_VALIDATOR_USER_AGENT = '' +DEFAULT_INDEX_TABLESPACE = '' +DEFAULT_TABLESPACE = '' +CACHE_BACKEND = 'locmem://' +TRANSACTIONS_MANAGED = False +LOCALE_PATHS = tuple() + +# Changes for Django 1.3: +USE_L10N = True +FORMAT_MODULE_PATH = "" +## End Changes for Django 1.3 + +# Changes for Django 1.4: +USE_TZ = False +## End Changes for Django 1.4 + +# Changes for Django 1.5: +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + } + } +DEFAULT_CHARSET = "utf-8" +## End Changes for Django 1.5 + +## Changes for Django 1.5.4: +LOGGING_CONFIG = None +AUTH_USER_MODEL = 'auth.User' +## End Changes for Django 1.5.4 + +LOGIN_URL = "/login/" +LOGOUT_URL = "/logout" +LOGIN_REDIRECT_URL = "/" + +## Changes for Django 1.6: +LOGGING = None + +## Changes for Django 1.7.1: +ABSOLUTE_URL_OVERRIDES = {} diff --git a/gramps/plugins/database/django_support/defaults/sqlite.db b/gramps/plugins/database/django_support/defaults/sqlite.db new file mode 100644 index 0000000000000000000000000000000000000000..a9ac911f065f752dfb25be75e2916ff5df508451 GIT binary patch literal 293888 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®`N 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 N%rWbcAX@iQ{{@Gr__6>1 literal 0 HcmV?d00001 diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 56f40f5ac..e380c9601 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -30,6 +30,9 @@ import re import base64 import pickle import os +import logging +import shutil +from django.db import transaction #------------------------------------------------------------------------ # @@ -53,13 +56,13 @@ from gramps.gen.db import (PERSON_KEY, REPOSITORY_KEY, NOTE_KEY) from gramps.gen.utils.id import create_id -from django.db import transaction +from gramps.gen.db.dbconst import * ## add this directory to sys path, so we can find django_support later: -import sys -import os sys.path.append(os.path.dirname(os.path.abspath(__file__))) +_LOG = logging.getLogger(DBLOGNAME) + class Environment(object): """ Implements the Environment API. @@ -2041,3 +2044,17 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def restore(self): pass + + def write_version(self, directory): + """Write files for a newly created DB.""" + versionpath = os.path.join(directory, str(DBBACKEND)) + _LOG.debug("Write database backend file to 'djangodb'") + with open(versionpath, "w") as version_file: + version_file.write("djangodb") + # Write default_settings, sqlite.db + defaults = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "django_support", "defaults") + _LOG.debug("Copy defaults from: " + defaults) + for filename in os.listdir(defaults): + fullpath = os.path.abspath(os.path.join(defaults, filename)) + shutil.copy2(fullpath, directory) From 6da7f78cba91fd8aac69e3db9c6dc2b470e86ab3 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 07:01:47 -0400 Subject: [PATCH 062/105] Hack to reset modules on subsequent uses of Django databases --- gramps/gen/dbstate.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py index 04a8e683d..069934a13 100644 --- a/gramps/gen/dbstate.py +++ b/gramps/gen/dbstate.py @@ -22,6 +22,7 @@ """ Provide the database state class """ +import sys from .db import DbReadBase from .proxy.proxybase import ProxyDbBase @@ -139,6 +140,34 @@ class DbState(Callback): pmgr.reg_plugins(USER_PLUGINS, self, None, load_on_reg=True) pdata = pmgr.get_plugin(id) + ### FIXME: Currently Django needs to reset modules + if pdata.id == "djangodb": + if self.modules_is_set(): + self.reset_modules() + else: + self.save_modules() + mod = pmgr.load_plugin(pdata) database = getattr(mod, pdata.databaseclass) return database() + + ## FIXME: + ## Work-around for databases that need sys refresh (django): + def modules_is_set(self): + if hasattr(self, "_modules"): + return self._modules != None + else: + self._modules = None + return False + + def reset_modules(self): + # First, clear out old modules: + for key in list(sys.modules.keys()): + del(sys.modules[key]) + # Next, restore previous: + for key in self._modules: + sys.modules[key] = self._modules[key] + + def save_modules(self): + self._modules = sys.modules.copy() + From 1118ce449c72ec2ae0b93b6b49be4bcab349e95a Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 09:07:10 -0400 Subject: [PATCH 063/105] Reworked backend Cursors; don't emit changes when changing in batch mode --- gramps/plugins/database/__init__.py | 0 gramps/plugins/database/dbdjango.py | 2036 ----------------------- gramps/plugins/database/dictionarydb.py | 106 +- gramps/plugins/database/djangodb.py | 103 +- 4 files changed, 121 insertions(+), 2124 deletions(-) create mode 100644 gramps/plugins/database/__init__.py delete mode 100644 gramps/plugins/database/dbdjango.py diff --git a/gramps/plugins/database/__init__.py b/gramps/plugins/database/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/gramps/plugins/database/dbdjango.py b/gramps/plugins/database/dbdjango.py deleted file mode 100644 index adb28dc54..000000000 --- a/gramps/plugins/database/dbdjango.py +++ /dev/null @@ -1,2036 +0,0 @@ -# Gramps - a GTK+/GNOME based genealogy program -# -# Copyright (C) 2009 Douglas S. Blank <doug.blank@gmail.com> -# -# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# - -""" Implements a Db interface """ - -#------------------------------------------------------------------------ -# -# Python Modules -# -#------------------------------------------------------------------------ -import sys -import time -import re -import base64 -import pickle -import os - -#------------------------------------------------------------------------ -# -# Gramps Modules -# -#------------------------------------------------------------------------ -import gramps -from gramps.gen.lib import (Person, Family, Event, Place, Repository, - Citation, Source, Note, MediaObject, Tag, - Researcher) -from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn -from gramps.gen.utils.callback import Callback -from gramps.gen.updatecallback import UpdateCallback -from gramps.gen.db import (PERSON_KEY, - FAMILY_KEY, - CITATION_KEY, - SOURCE_KEY, - EVENT_KEY, - MEDIA_KEY, - PLACE_KEY, - REPOSITORY_KEY, - NOTE_KEY) -from gramps.gen.utils.id import create_id -from django.db import transaction - -class Environment(object): - """ - Implements the Environment API. - """ - def __init__(self, db): - self.db = db - - def txn_begin(self): - return DjangoTxn("DbDjango Transaction", self.db) - -class Table(object): - """ - Implements Table interface. - """ - def __init__(self, funcs): - self.funcs = funcs - - def cursor(self): - """ - Returns a Cursor for this Table. - """ - return self.funcs["cursor_func"]() - - def put(key, data, txn=None): - self[key] = data - -class Map(dict): - """ - Implements the map API for person_map, etc. - - Takes a Table() as argument. - """ - def __init__(self, tbl, *args, **kwargs): - super().__init__(*args, **kwargs) - self.db = tbl - -class MetaCursor(object): - def __init__(self): - pass - def __enter__(self): - return self - def __iter__(self): - return self.__next__() - def __next__(self): - yield None - def __exit__(self, *args, **kwargs): - pass - def iter(self): - yield None - def first(self): - self._iter = self.__iter__() - return self.next() - def next(self): - try: - return next(self._iter) - except: - return None - def close(self): - pass - -class Cursor(object): - 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 (bytes(item.handle, "utf-8"), self.func(item.handle)) - def __exit__(self, *args, **kwargs): - pass - def iter(self): - for item in self.model.all(): - yield (bytes(item.handle, "utf-8"), self.func(item.handle)) - yield None - def first(self): - self._iter = self.__iter__() - return self.next() - def next(self): - try: - return next(self._iter) - except: - return None - def close(self): - pass - -class Bookmarks(object): - def __init__(self): - self.handles = [] - def get(self): - return self.handles - def append(self, handle): - self.handles.append(handle) - -class DjangoTxn(DbTxn): - def __init__(self, message, db, table=None): - DbTxn.__init__(self, message, db) - self.table = table - - def get(self, key, default=None, txn=None, **kwargs): - """ - Returns the data object associated with key - """ - try: - return self.table.objects(handle=key) - except: - if txn and key in txn: - return txn[key] - else: - return None - - def put(self, handle, new_data, txn): - """ - """ - txn[handle] = new_data - - def commit(self): - pass - -class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): - """ - A Gramps Database Backend. This replicates the grampsdb functions. - """ - # Set up dictionary for callback signal handler - # --------------------------------------------- - # 1. Signals for primary objects - __signals__ = dict((obj+'-'+op, signal) - for obj in - ['person', 'family', 'event', 'place', - 'source', 'citation', 'media', 'note', 'repository', 'tag'] - for op, signal in zip( - ['add', 'update', 'delete', 'rebuild'], - [(list,), (list,), (list,), None] - ) - ) - - # 2. Signals for long operations - __signals__.update(('long-op-'+op, signal) for op, signal in zip( - ['start', 'heartbeat', 'end'], - [(object,), None, None] - )) - - # 3. Special signal for change in home person - __signals__['home-person-changed'] = None - - # 4. Signal for change in person group name, parameters are - __signals__['person-groupname-rebuild'] = (str, str) - - def __init__(self, directory=None): - DbReadBase.__init__(self) - DbWriteBase.__init__(self) - Callback.__init__(self) - self._tables = { - 'Person': - { - "handle_func": self.get_person_from_handle, - "gramps_id_func": self.get_person_from_gramps_id, - "class_func": gramps.gen.lib.Person, - "cursor_func": self.get_person_cursor, - "handles_func": self.get_person_handles, - "iter_func": self.iter_people, - }, - 'Family': - { - "handle_func": self.get_family_from_handle, - "gramps_id_func": self.get_family_from_gramps_id, - "class_func": gramps.gen.lib.Family, - "cursor_func": self.get_family_cursor, - "handles_func": self.get_family_handles, - "iter_func": self.iter_families, - }, - 'Source': - { - "handle_func": self.get_source_from_handle, - "gramps_id_func": self.get_source_from_gramps_id, - "class_func": gramps.gen.lib.Source, - "cursor_func": self.get_source_cursor, - "handles_func": self.get_source_handles, - "iter_func": self.iter_sources, - }, - 'Citation': - { - "handle_func": self.get_citation_from_handle, - "gramps_id_func": self.get_citation_from_gramps_id, - "class_func": gramps.gen.lib.Citation, - "cursor_func": self.get_citation_cursor, - "handles_func": self.get_citation_handles, - "iter_func": self.iter_citations, - }, - 'Event': - { - "handle_func": self.get_event_from_handle, - "gramps_id_func": self.get_event_from_gramps_id, - "class_func": gramps.gen.lib.Event, - "cursor_func": self.get_event_cursor, - "handles_func": self.get_event_handles, - "iter_func": self.iter_events, - }, - 'Media': - { - "handle_func": self.get_object_from_handle, - "gramps_id_func": self.get_object_from_gramps_id, - "class_func": gramps.gen.lib.MediaObject, - "cursor_func": self.get_media_cursor, - "handles_func": self.get_media_object_handles, - "iter_func": self.iter_media_objects, - }, - 'Place': - { - "handle_func": self.get_place_from_handle, - "gramps_id_func": self.get_place_from_gramps_id, - "class_func": gramps.gen.lib.Place, - "cursor_func": self.get_place_cursor, - "handles_func": self.get_place_handles, - "iter_func": self.iter_places, - }, - 'Repository': - { - "handle_func": self.get_repository_from_handle, - "gramps_id_func": self.get_repository_from_gramps_id, - "class_func": gramps.gen.lib.Repository, - "cursor_func": self.get_repository_cursor, - "handles_func": self.get_repository_handles, - "iter_func": self.iter_repositories, - }, - 'Note': - { - "handle_func": self.get_note_from_handle, - "gramps_id_func": self.get_note_from_gramps_id, - "class_func": gramps.gen.lib.Note, - "cursor_func": self.get_note_cursor, - "handles_func": self.get_note_handles, - "iter_func": self.iter_notes, - }, - 'Tag': - { - "handle_func": self.get_tag_from_handle, - "gramps_id_func": None, - "class_func": gramps.gen.lib.Tag, - "cursor_func": self.get_tag_cursor, - "handles_func": self.get_tag_handles, - "iter_func": self.iter_tags, - }, - } - # 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.undo_callback = None - self.redo_callback = None - self.undo_history_callback = None - self.modified = 0 - self.txn = DjangoTxn("DbDjango Transaction", self) - self.transaction = None - # Import cache for gedcom import, uses transactions, and - # two step adding of objects. - self.import_cache = {} - self.use_import_cache = False - self.use_db_cache = True - self._directory = directory - if directory: - self.load(directory) - - def load(self, directory, pulse_progress=None, mode=None): - self._directory = directory - from django.conf import settings - default_settings = {} - settings_file = os.path.join(directory, "default_settings.py") - with open(settings_file) as f: - code = compile(f.read(), settings_file, 'exec') - exec(code, globals(), default_settings) - - class Module(object): - def __init__(self, dictionary): - self.dictionary = dictionary - def __getattr__(self, item): - return self.dictionary[item] - - try: - settings.configure(Module(default_settings)) - except RuntimeError: - # already configured; ignore - pass - - import django - django.setup() - - from django_support.libdjango import DjangoInterface - self.dji = DjangoInterface() - 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 = DjangoTxn("ID Transaction", self, self.dji.Person) - self.fid_trans = DjangoTxn("FID Transaction", self, self.dji.Family) - self.pid_trans = DjangoTxn("PID Transaction", self, self.dji.Place) - self.cid_trans = DjangoTxn("CID Transaction", self, self.dji.Citation) - self.sid_trans = DjangoTxn("SID Transaction", self, self.dji.Source) - self.oid_trans = DjangoTxn("OID Transaction", self, self.dji.Media) - self.rid_trans = DjangoTxn("RID Transaction", self, self.dji.Repository) - self.nid_trans = DjangoTxn("NID Transaction", self, self.dji.Note) - self.eid_trans = DjangoTxn("EID Transaction", self, self.dji.Event) - 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 = Environment(self) - self.person_map = Map(Table(self._tables["Person"])) - self.family_map = Map(Table(self._tables["Family"])) - self.place_map = Map(Table(self._tables["Place"])) - self.citation_map = Map(Table(self._tables["Citation"])) - self.source_map = Map(Table(self._tables["Source"])) - self.repository_map = Map(Table(self._tables["Repository"])) - self.note_map = Map(Table(self._tables["Note"])) - self.media_map = Map(Table(self._tables["Media"])) - self.event_map = Map(Table(self._tables["Event"])) - self.tag_map = Map(Table(self._tables["Tag"])) - self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) - self.name_group = {} - self.event_names = set() - self.individual_attributes = set() - self.family_attributes = set() - self.source_attributes = set() - self.child_ref_types = set() - self.family_rel_types = set() - self.event_role_names = set() - self.name_types = set() - self.origin_types = set() - self.repository_types = set() - self.note_types = set() - self.source_media_types = set() - self.url_types = set() - self.media_attributes = set() - self.place_types = set() - - def prepare_import(self): - """ - DbDjango does not commit data on gedcom import, but saves them - for later commit. - """ - self.use_import_cache = True - self.import_cache = {} - - @transaction.atomic - def commit_import(self): - """ - Commits the items that were queued up during the last gedcom - import for two step adding. - """ - # First we add the primary objects: - for key in list(self.import_cache.keys()): - obj = self.import_cache[key] - if isinstance(obj, Person): - self.dji.add_person(obj.serialize()) - elif isinstance(obj, Family): - self.dji.add_family(obj.serialize()) - elif isinstance(obj, Event): - self.dji.add_event(obj.serialize()) - elif isinstance(obj, Place): - self.dji.add_place(obj.serialize()) - elif isinstance(obj, Repository): - self.dji.add_repository(obj.serialize()) - elif isinstance(obj, Citation): - self.dji.add_citation(obj.serialize()) - elif isinstance(obj, Source): - self.dji.add_source(obj.serialize()) - elif isinstance(obj, Note): - self.dji.add_note(obj.serialize()) - elif isinstance(obj, MediaObject): - self.dji.add_media(obj.serialize()) - elif isinstance(obj, Tag): - self.dji.add_tag(obj.serialize()) - # Next we add the links: - for key in list(self.import_cache.keys()): - obj = self.import_cache[key] - if isinstance(obj, Person): - self.dji.add_person_detail(obj.serialize()) - elif isinstance(obj, Family): - self.dji.add_family_detail(obj.serialize()) - elif isinstance(obj, Event): - self.dji.add_event_detail(obj.serialize()) - elif isinstance(obj, Place): - self.dji.add_place_detail(obj.serialize()) - elif isinstance(obj, Repository): - self.dji.add_repository_detail(obj.serialize()) - elif isinstance(obj, Citation): - self.dji.add_citation_detail(obj.serialize()) - elif isinstance(obj, Source): - self.dji.add_source_detail(obj.serialize()) - elif isinstance(obj, Note): - self.dji.add_note_detail(obj.serialize()) - elif isinstance(obj, MediaObject): - self.dji.add_media_detail(obj.serialize()) - elif isinstance(obj, Tag): - self.dji.add_tag_detail(obj.serialize()) - self.use_import_cache = False - self.import_cache = {} - self.dji.update_publics() - - def transaction_commit(self, txn): - pass - - def request_rebuild(self): - # caches are ok, but let's compute public's - self.dji.update_publics() - self.emit('person-rebuild') - self.emit('family-rebuild') - self.emit('place-rebuild') - self.emit('source-rebuild') - self.emit('citation-rebuild') - self.emit('media-rebuild') - self.emit('event-rebuild') - self.emit('repository-rebuild') - self.emit('note-rebuild') - self.emit('tag-rebuild') - - def get_undodb(self): - return None - - def transaction_abort(self, txn): - pass - - @staticmethod - def _validated_id_prefix(val, default): - if isinstance(val, str) 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_<object>_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 = Researcher() - return obj - - def get_tag_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Tag.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Tag.all()] - - def get_person_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Person.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Person.all()] - - def get_family_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Family.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Family.all()] - - def get_event_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Event.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Event.all()] - - def get_citation_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Citation.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Citation.all()] - - def get_source_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Source.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Source.all()] - - def get_place_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Place.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Place.all()] - - def get_repository_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Repository.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Repository.all()] - - def get_media_object_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Media.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Media.all()] - - def get_note_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Note.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Note.all()] - - def get_media_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - media = self.dji.Media.get(handle=handle) - except: - return None - return self.make_media(media) - - def get_event_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - event = self.dji.Event.get(handle=handle) - except: - return None - return self.make_event(event) - - def get_family_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - family = self.dji.Family.get(handle=handle) - except: - return None - return self.make_family(family) - - def get_repository_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - repository = self.dji.Repository.get(handle=handle) - except: - return None - return self.make_repository(repository) - - def get_person_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - person = self.dji.Person.get(handle=handle) - except: - return None - return self.make_person(person) - - def get_tag_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - tag = self.dji.Tag.get(handle=handle) - except: - return None - return self.make_tag(tag) - - def make_repository(self, repository): - if self.use_db_cache and repository.cache: - data = repository.from_cache() - else: - data = self.dji.get_repository(repository) - return Repository.create(data) - - def make_citation(self, citation): - if self.use_db_cache and citation.cache: - data = citation.from_cache() - else: - data = self.dji.get_citation(citation) - return Citation.create(data) - - def make_source(self, source): - if self.use_db_cache and source.cache: - data = source.from_cache() - else: - data = self.dji.get_source(source) - return Source.create(data) - - def make_family(self, family): - if self.use_db_cache and family.cache: - data = family.from_cache() - else: - data = self.dji.get_family(family) - return Family.create(data) - - def make_person(self, person): - if self.use_db_cache and person.cache: - data = person.from_cache() - else: - data = self.dji.get_person(person) - return Person.create(data) - - def make_event(self, event): - if self.use_db_cache and event.cache: - data = event.from_cache() - else: - data = self.dji.get_event(event) - return Event.create(data) - - def make_note(self, note): - if self.use_db_cache and note.cache: - data = note.from_cache() - else: - data = self.dji.get_note(note) - return Note.create(data) - - def make_tag(self, tag): - data = self.dji.get_tag(tag) - return Tag.create(data) - - def make_place(self, place): - if self.use_db_cache and place.cache: - data = place.from_cache() - else: - data = self.dji.get_place(place) - return Place.create(data) - - def make_media(self, media): - if self.use_db_cache and media.cache: - data = media.from_cache() - else: - data = self.dji.get_media(media) - return MediaObject.create(data) - - def get_place_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - place = self.dji.Place.get(handle=handle) - except: - return None - return self.make_place(place) - - def get_citation_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - citation = self.dji.Citation.get(handle=handle) - except: - return None - return self.make_citation(citation) - - def get_source_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - source = self.dji.Source.get(handle=handle) - except: - return None - return self.make_source(source) - - def get_note_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - note = self.dji.Note.get(handle=handle) - except: - return None - return self.make_note(note) - - def get_object_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - media = self.dji.Media.get(handle=handle) - except: - return None - return self.make_media(media) - - def get_default_person(self): - people = self.dji.Person.all() - if people.count() > 0: - return self.make_person(people[0]) - return None - - def iter_people(self): - return (self.get_person_from_handle(person.handle) - for person in self.dji.Person.all()) - - def iter_person_handles(self): - return (person.handle for person in self.dji.Person.all()) - - def iter_families(self): - return (self.get_family_from_handle(family.handle) - for family in self.dji.Family.all()) - - def iter_family_handles(self): - return (family.handle for family in self.dji.Family.all()) - - def iter_notes(self): - return (self.get_note_from_handle(note.handle) - for note in self.dji.Note.all()) - - def iter_note_handles(self): - return (note.handle for note in self.dji.Note.all()) - - def iter_events(self): - return (self.get_event_from_handle(event.handle) - for event in self.dji.Event.all()) - - def iter_event_handles(self): - return (event.handle for event in self.dji.Event.all()) - - def iter_places(self): - return (self.get_place_from_handle(place.handle) - for place in self.dji.Place.all()) - - def iter_place_handles(self): - return (place.handle for place in self.dji.Place.all()) - - def iter_repositories(self): - return (self.get_repository_from_handle(repository.handle) - for repository in self.dji.Repository.all()) - - def iter_repository_handles(self): - return (repository.handle for repository in self.dji.Repository.all()) - - def iter_sources(self): - return (self.get_source_from_handle(source.handle) - for source in self.dji.Source.all()) - - def iter_source_handles(self): - return (source.handle for source in self.dji.Source.all()) - - def iter_citations(self): - return (self.get_citation_from_handle(citation.handle) - for citation in self.dji.Citation.all()) - - def iter_citation_handles(self): - return (citation.handle for citation in self.dji.Citation.all()) - - def iter_tags(self): - return (self.get_tag_from_handle(tag.handle) - for tag in self.dji.Tag.all()) - - def iter_tag_handles(self): - return (tag.handle for tag in self.dji.Tag.all()) - - def iter_media_objects(self): - return (self.get_media_from_handle(media.handle) - for media in self.dji.Media.all()) - - def get_tag_from_name(self, name): - try: - tag = self.dji.Tag.filter(name=name) - return self.make_tag(tag[0]) - except: - return None - - def get_person_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Person.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_person(match_list[0]) - else: - return None - - def get_family_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - try: - family = self.dji.Family.get(gramps_id=gramps_id) - except: - return None - return self.make_family(family) - - def get_source_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Source.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_source(match_list[0]) - else: - return None - - def get_citation_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Citation.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_citation(match_list[0]) - else: - return None - - def get_event_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Event.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_event(match_list[0]) - else: - return None - - def get_object_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Media.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_media(match_list[0]) - else: - return None - - def get_place_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Place.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_place(match_list[0]) - else: - return None - - def get_repository_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Repsoitory.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_repository(match_list[0]) - else: - return None - - def get_note_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Note.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_note(match_list[0]) - else: - return None - - def get_number_of_people(self): - return self.dji.Person.count() - - def get_number_of_events(self): - return self.dji.Event.count() - - def get_number_of_places(self): - return self.dji.Place.count() - - def get_number_of_tags(self): - return self.dji.Tag.count() - - def get_number_of_families(self): - return self.dji.Family.count() - - def get_number_of_notes(self): - return self.dji.Note.count() - - def get_number_of_citations(self): - return self.dji.Citation.count() - - def get_number_of_sources(self): - return self.dji.Source.count() - - def get_number_of_media_objects(self): - return self.dji.Media.count() - - def get_number_of_repositories(self): - return self.dji.Repository.count() - - def get_place_cursor(self): - return Cursor(self.dji.Place, self.get_raw_place_data) - - def get_person_cursor(self): - return Cursor(self.dji.Person, self.get_raw_person_data) - - def get_family_cursor(self): - return Cursor(self.dji.Family, self.get_raw_family_data) - - def get_event_cursor(self): - return Cursor(self.dji.Event, self.get_raw_event_data) - - def get_citation_cursor(self): - return Cursor(self.dji.Citation, self.get_raw_citation_data) - - def get_source_cursor(self): - return Cursor(self.dji.Source, self.get_raw_source_data) - - def get_note_cursor(self): - return Cursor(self.dji.Note, self.get_raw_note_data) - - def get_tag_cursor(self): - return Cursor(self.dji.Tag, self.get_raw_tag_data) - - def get_repository_cursor(self): - return Cursor(self.dji.Repository, self.get_raw_repository_data) - - def get_media_cursor(self): - return Cursor(self.dji.Media, self.get_raw_object_data) - - def has_gramps_id(self, obj_key, gramps_id): - key2table = { - PERSON_KEY: self.dji.Person, - FAMILY_KEY: self.dji.Family, - SOURCE_KEY: self.dji.Source, - CITATION_KEY: self.dji.Citation, - EVENT_KEY: self.dji.Event, - MEDIA_KEY: self.dji.Media, - PLACE_KEY: self.dji.Place, - REPOSITORY_KEY: self.dji.Repository, - NOTE_KEY: self.dji.Note, - } - table = key2table[obj_key] - return table.filter(gramps_id=gramps_id).count() > 0 - - def has_person_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Person.filter(handle=handle).count() == 1 - - def has_family_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Family.filter(handle=handle).count() == 1 - - def has_citation_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Citation.filter(handle=handle).count() == 1 - - def has_source_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Source.filter(handle=handle).count() == 1 - - def has_repository_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Repository.filter(handle=handle).count() == 1 - - def has_note_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Note.filter(handle=handle).count() == 1 - - def has_place_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Place.filter(handle=handle).count() == 1 - - def has_event_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Event.filter(handle=handle).count() == 1 - - def has_tag_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Tag.filter(handle=handle).count() == 1 - - def has_object_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Media.filter(handle=handle).count() == 1 - - 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): - try: - return self.dji.get_person(self.dji.Person.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_family_data(self, handle): - try: - return self.dji.get_family(self.dji.Family.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_citation_data(self, handle): - try: - return self.dji.get_citation(self.dji.Citation.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_source_data(self, handle): - try: - return self.dji.get_source(self.dji.Source.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_repository_data(self, handle): - try: - return self.dji.get_repository(self.dji.Repository.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_note_data(self, handle): - try: - return self.dji.get_note(self.dji.Note.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_place_data(self, handle): - try: - return self.dji.get_place(self.dji.Place.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_object_data(self, handle): - try: - return self.dji.get_media(self.dji.Media.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_tag_data(self, handle): - try: - return self.dji.get_tag(self.dji.Tag.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_event_data(self, handle): - try: - return self.dji.get_event(self.dji.Event.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def add_person(self, person, trans, set_gid=True): - if not person.handle: - person.handle = create_id() - if not person.gramps_id and set_gid: - person.gramps_id = self.find_next_person_gramps_id() - self.commit_person(person, trans) - self.emit("person-add", ([person.handle],)) - return person.handle - - def add_family(self, family, trans, set_gid=True): - if not family.handle: - family.handle = create_id() - if not family.gramps_id and set_gid: - family.gramps_id = self.find_next_family_gramps_id() - self.commit_family(family, trans) - self.emit("family-add", ([family.handle],)) - return family.handle - - def add_citation(self, citation, trans, set_gid=True): - if not citation.handle: - citation.handle = create_id() - if not citation.gramps_id and set_gid: - citation.gramps_id = self.find_next_citation_gramps_id() - self.commit_citation(citation, trans) - self.emit("citation-add", ([citation.handle],)) - return citation.handle - - def add_source(self, source, trans, set_gid=True): - if not source.handle: - source.handle = create_id() - if not source.gramps_id and set_gid: - source.gramps_id = self.find_next_source_gramps_id() - self.commit_source(source, trans) - self.emit("source-add", ([source.handle],)) - return source.handle - - def add_repository(self, repository, trans, set_gid=True): - if not repository.handle: - repository.handle = create_id() - if not repository.gramps_id and set_gid: - repository.gramps_id = self.find_next_repository_gramps_id() - self.commit_repository(repository, trans) - self.emit("repository-add", ([repository.handle],)) - return repository.handle - - def add_note(self, note, trans, set_gid=True): - if not note.handle: - note.handle = create_id() - if not note.gramps_id and set_gid: - note.gramps_id = self.find_next_note_gramps_id() - self.commit_note(note, trans) - self.emit("note-add", ([note.handle],)) - return note.handle - - def add_place(self, place, trans, set_gid=True): - if not place.handle: - place.handle = create_id() - if not place.gramps_id and 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 = create_id() - if not event.gramps_id and 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 = 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 = create_id() - if not obj.gramps_id and 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): - if self.use_import_cache: - self.import_cache[person.handle] = person - else: - raw = person.serialize() - items = self.dji.Person.filter(handle=person.handle) - if items.count() > 0: - # Hack, for the moment: delete and re-add - items[0].delete() - self.dji.add_person(person.serialize()) - self.dji.add_person_detail(person.serialize()) - if items.count() > 0: - self.emit("person-update", ([person.handle],)) - else: - self.emit("person-add", ([person.handle],)) - - def commit_family(self, family, trans, change_time=None): - if self.use_import_cache: - self.import_cache[family.handle] = family - else: - raw = family.serialize() - items = self.dji.Family.filter(handle=family.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_family(family.serialize()) - self.dji.add_family_detail(family.serialize()) - if items.count() > 0: - self.emit("family-update", ([family.handle],)) - else: - self.emit("family-add", ([family.handle],)) - - def commit_citation(self, citation, trans, change_time=None): - if self.use_import_cache: - self.import_cache[citation.handle] = citation - else: - raw = citation.serialize() - items = self.dji.Citation.filter(handle=citation.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_citation(citation.serialize()) - self.dji.add_citation_detail(citation.serialize()) - if items.count() > 0: - self.emit("citation-update", ([citation.handle],)) - else: - self.emit("citation-add", ([citation.handle],)) - - def commit_source(self, source, trans, change_time=None): - if self.use_import_cache: - self.import_cache[source.handle] = source - else: - raw = source.serialize() - items = self.dji.Source.filter(handle=source.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_source(source.serialize()) - self.dji.add_source_detail(source.serialize()) - if items.count() > 0: - self.emit("source-update", ([source.handle],)) - else: - self.emit("source-add", ([source.handle],)) - - def commit_repository(self, repository, trans, change_time=None): - if self.use_import_cache: - self.import_cache[repository.handle] = repository - else: - raw = repository.serialize() - items = self.dji.Repository.filter(handle=repository.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_repository(repository.serialize()) - self.dji.add_repository_detail(repository.serialize()) - if items.count() > 0: - self.emit("repository-update", ([repository.handle],)) - else: - self.emit("repository-add", ([repository.handle],)) - - def commit_note(self, note, trans, change_time=None): - if self.use_import_cache: - self.import_cache[note.handle] = note - else: - raw = note.serialize() - items = self.dji.Note.filter(handle=note.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_note(note.serialize()) - self.dji.add_note_detail(note.serialize()) - if items.count() > 0: - self.emit("note-update", ([note.handle],)) - else: - self.emit("note-add", ([note.handle],)) - - def commit_place(self, place, trans, change_time=None): - if self.use_import_cache: - self.import_cache[place.handle] = place - else: - raw = place.serialize() - items = self.dji.Place.filter(handle=place.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_place(place.serialize()) - self.dji.add_place_detail(place.serialize()) - if items.count() > 0: - self.emit("place-update", ([place.handle],)) - else: - self.emit("place-add", ([place.handle],)) - - def commit_event(self, event, trans, change_time=None): - if self.use_import_cache: - self.import_cache[event.handle] = event - else: - raw = event.serialize() - items = self.dji.Event.filter(handle=event.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_event(event.serialize()) - self.dji.add_event_detail(event.serialize()) - if items.count() > 0: - self.emit("event-update", ([event.handle],)) - else: - self.emit("event-add", ([event.handle],)) - - def commit_tag(self, tag, trans, change_time=None): - if self.use_import_cache: - self.import_cache[tag.handle] = tag - else: - raw = tag.serialize() - items = self.dji.Tag.filter(handle=tag.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_tag(tag.serialize()) - self.dji.add_tag_detail(tag.serialize()) - if items.count() > 0: - self.emit("tag-update", ([tag.handle],)) - else: - self.emit("tag-add", ([tag.handle],)) - - def commit_media_object(self, media, transaction, change_time=None): - """ - Commit the specified MediaObject to the database, storing the changes - as part of the transaction. - """ - if self.use_import_cache: - self.import_cache[obj.handle] = media - else: - raw = media.serialize() - items = self.dji.Media.filter(handle=media.handle) - if items.count() > 0: - items[0].delete() - self.dji.add_media(media.serialize()) - self.dji.add_media_detail(media.serialize()) - if items.count() > 0: - self.emit("media-update", ([media.handle],)) - else: - self.emit("media-add", ([media.handle],)) - - def get_gramps_ids(self, obj_key): - key2table = { - PERSON_KEY: self.id_trans, - FAMILY_KEY: self.fid_trans, - CITATION_KEY: self.cid_trans, - SOURCE_KEY: self.sid_trans, - EVENT_KEY: self.eid_trans, - MEDIA_KEY: self.oid_trans, - PLACE_KEY: self.pid_trans, - REPOSITORY_KEY: self.rid_trans, - NOTE_KEY: self.nid_trans, - } - - table = key2table[obj_key] - return list(table.keys()) - - def transaction_begin(self, transaction): - return - - def set_researcher(self, owner): - pass - - def copy_from_db(self, db): - """ - A (possibily) implementation-specific method to get data from - db into this database. - """ - # First we add the primary objects: - for key in db._tables.keys(): - cursor = db._tables[key]["cursor_func"] - for (handle, data) in cursor(): - if key == "Person": - self.dji.add_person(data) - elif key == "Family": - self.dji.add_family(data) - elif key == "Event": - self.dji.add_event(data) - elif key == "Place": - self.dji.add_place(data) - elif key == "Repository": - self.dji.add_repository(data) - elif key == "Citation": - self.dji.add_citation(data) - elif key == "Source": - self.dji.add_source(data) - elif key == "Note": - self.dji.add_note(data) - elif key == "Media": - self.dji.add_media(data) - elif key == "Tag": - self.dji.add_tag(data) - for key in db._tables.keys(): - cursor = db._tables[key]["cursor_func"] - for (handle, data) in cursor(): - if key == "Person": - self.dji.add_person_detail(data) - elif key == "Family": - self.dji.add_family_detail(data) - elif key == "Event": - self.dji.add_event_detail(data) - elif key == "Place": - self.dji.add_place_detail(data) - elif key == "Repository": - self.dji.add_repository_detail(data) - elif key == "Citation": - self.dji.add_citation_detail(data) - elif key == "Source": - self.dji.add_source_detail(data) - elif key == "Note": - self.dji.add_note_detail(data) - elif key == "Media": - self.dji.add_media_detail(data) - elif key == "Tag": - self.dji.add_tag_detail(data) - # Next we add the links: - self.dji.update_publics() - - def get_from_name_and_handle(self, table_name, handle): - """ - Returns a gen.lib object (or None) given table_name and - handle. - - Examples: - - >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") - >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") - """ - if table_name in self._tables: - return self._tables[table_name]["handle_func"](handle) - return None - - def is_empty(self): - """ - Is the database empty? - """ - return (self.get_number_of_people() == 0 and - self.get_number_of_events() == 0 and - self.get_number_of_places() == 0 and - self.get_number_of_tags() == 0 and - self.get_number_of_families() == 0 and - self.get_number_of_notes() == 0 and - self.get_number_of_citations() == 0 and - self.get_number_of_sources() == 0 and - self.get_number_of_media_objects() == 0 and - self.get_number_of_repositories() == 0) - - __callback_map = {} - - def set_prefixes(self, person, media, family, source, citation, - place, event, repository, note): - self.set_person_id_prefix(person) - self.set_object_id_prefix(media) - self.set_family_id_prefix(family) - self.set_source_id_prefix(source) - self.set_citation_id_prefix(citation) - self.set_place_id_prefix(place) - self.set_event_id_prefix(event) - self.set_repository_id_prefix(repository) - self.set_note_id_prefix(note) - - def has_changed(self): - return False - - def find_backlink_handles(self, handle, include_classes=None): - ## FIXME: figure out how to get objects that refer - ## to this handle - return [] - - def get_note_bookmarks(self): - return self.note_bookmarks - - def get_media_bookmarks(self): - return self.media_bookmarks - - def get_repo_bookmarks(self): - return self.repo_bookmarks - - def get_citation_bookmarks(self): - return self.citation_bookmarks - - def get_source_bookmarks(self): - return self.source_bookmarks - - def get_place_bookmarks(self): - return self.place_bookmarks - - def get_event_bookmarks(self): - return self.event_bookmarks - - def get_bookmarks(self): - return self.bookmarks - - def get_family_bookmarks(self): - return self.family_bookmarks - - def get_save_path(self): - return "/tmp/" - - ## Get types: - def get_event_attribute_types(self): - """ - Return a list of all Attribute types assocated with Event instances - in the database. - """ - return list(self.event_attributes) - - def get_event_types(self): - """ - Return a list of all event types in the database. - """ - return list(self.event_names) - - def get_person_event_types(self): - """ - Deprecated: Use get_event_types - """ - return list(self.event_names) - - def get_person_attribute_types(self): - """ - Return a list of all Attribute types assocated with Person instances - in the database. - """ - return list(self.individual_attributes) - - def get_family_attribute_types(self): - """ - Return a list of all Attribute types assocated with Family instances - in the database. - """ - return list(self.family_attributes) - - def get_family_event_types(self): - """ - Deprecated: Use get_event_types - """ - return list(self.event_names) - - def get_media_attribute_types(self): - """ - Return a list of all Attribute types assocated with Media and MediaRef - instances in the database. - """ - return list(self.media_attributes) - - def get_family_relation_types(self): - """ - Return a list of all relationship types assocated with Family - instances in the database. - """ - return list(self.family_rel_types) - - def get_child_reference_types(self): - """ - Return a list of all child reference types assocated with Family - instances in the database. - """ - return list(self.child_ref_types) - - def get_event_roles(self): - """ - Return a list of all custom event role names assocated with Event - instances in the database. - """ - return list(self.event_role_names) - - def get_name_types(self): - """ - Return a list of all custom names types assocated with Person - instances in the database. - """ - return list(self.name_types) - - def get_origin_types(self): - """ - Return a list of all custom origin types assocated with Person/Surname - instances in the database. - """ - return list(self.origin_types) - - def get_repository_types(self): - """ - Return a list of all custom repository types assocated with Repository - instances in the database. - """ - return list(self.repository_types) - - def get_note_types(self): - """ - Return a list of all custom note types assocated with Note instances - in the database. - """ - return list(self.note_types) - - def get_source_attribute_types(self): - """ - Return a list of all Attribute types assocated with Source/Citation - instances in the database. - """ - return list(self.source_attributes) - - def get_source_media_types(self): - """ - Return a list of all custom source media types assocated with Source - instances in the database. - """ - return list(self.source_media_types) - - def get_url_types(self): - """ - Return a list of all custom names types assocated with Url instances - in the database. - """ - return list(self.url_types) - - def get_place_types(self): - """ - Return a list of all custom place types assocated with Place instances - in the database. - """ - return list(self.place_types) - - def get_default_handle(self): - people = self.dji.Person.all() - if people.count() > 0: - return people[0].handle - return None - - def close(self): - pass - - def get_surname_list(self): - return [] - - def is_open(self): - return True - - def get_table_names(self): - """Return a list of valid table names.""" - return list(self._tables.keys()) - - def find_initial_person(self): - return self.get_default_person() - - # Removals: - def remove_person(self, handle, txn): - self.dji.Person.filter(handle=handle)[0].delete() - self.emit("person-delete", ([handle],)) - - def remove_source(self, handle, transaction): - self.dji.Source.filter(handle=handle)[0].delete() - self.emit("source-delete", ([handle],)) - - def remove_citation(self, handle, transaction): - self.dji.Citation.filter(handle=handle)[0].delete() - self.emit("citation-delete", ([handle],)) - - def remove_event(self, handle, transaction): - self.dji.Event.filter(handle=handle)[0].delete() - self.emit("event-delete", ([handle],)) - - def remove_object(self, handle, transaction): - self.dji.Media.filter(handle=handle)[0].delete() - self.emit("media-delete", ([handle],)) - - def remove_place(self, handle, transaction): - self.dji.Place.filter(handle=handle)[0].delete() - self.emit("place-delete", ([handle],)) - - def remove_family(self, handle, transaction): - self.dji.Family.filter(handle=handle)[0].delete() - self.emit("family-delete", ([handle],)) - - def remove_repository(self, handle, transaction): - self.dji.Repository.filter(handle=handle)[0].delete() - self.emit("repository-delete", ([handle],)) - - def remove_note(self, handle, transaction): - self.dji.Note.filter(handle=handle)[0].delete() - self.emit("note-delete", ([handle],)) - - def remove_tag(self, handle, transaction): - self.dji.Tag.filter(handle=handle)[0].delete() - self.emit("tag-delete", ([handle],)) - - def remove_from_surname_list(self, person): - ## FIXME - pass - - def get_dbname(self): - return "Django Database" - - ## missing - - def find_place_child_handles(self, handle): - pass - - def get_cursor(self, table, txn=None, update=False, commit=False): - pass - - def get_from_name_and_handle(self, table_name, handle): - """ - Returns a gen.lib object (or None) given table_name and - handle. - - Examples: - - >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") - >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") - """ - if table_name in self._tables: - return self._tables[table_name]["handle_func"](handle) - return None - - def get_number_of_records(self, table): - pass - - def get_place_parent_cursor(self): - pass - - def get_place_tree_cursor(self): - pass - - def get_table_metadata(self, table_name): - """Return the metadata for a valid table name.""" - if table_name in self._tables: - return self._tables[table_name] - return None - - def get_transaction_class(self): - pass - - def undo(self, update_history=True): - # FIXME: - return self.undodb.undo(update_history) - - def redo(self, update_history=True): - # FIXME: - return self.undodb.redo(update_history) - - def backup(self): - pass - - def restore(self): - pass diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 936d8f534..1542f4c93 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -130,27 +130,33 @@ class Cursor(object): def __init__(self, map, func): self.map = map self.func = func + self._iter = self.__iter__() def __enter__(self): return self def __iter__(self): - return self.__next__() - def __next__(self): for item in self.map.keys(): yield (bytes(item, "utf-8"), self.func(item)) + def __next__(self): + try: + return self._iter.__next__() + except StopIteration: + return None def __exit__(self, *args, **kwargs): pass def iter(self): for item in self.map.keys(): yield (bytes(item, "utf-8"), self.func(item)) - yield None def first(self): self._iter = self.__iter__() - return self.next() + try: + return next(self._iter) + except: + return def next(self): try: return next(self._iter) except: - return None + return def close(self): pass @@ -1099,73 +1105,83 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return obj.handle def commit_person(self, person, trans, change_time=None): - if person.handle in self.person_map: - self.emit("person-update", ([person.handle],)) - else: - self.emit("person-add", ([person.handle],)) + if not trans.batch: + if person.handle in self.person_map: + self.emit("person-update", ([person.handle],)) + else: + self.emit("person-add", ([person.handle],)) self.person_map[person.handle] = person def commit_family(self, family, trans, change_time=None): - if family.handle in self.family_map: - self.emit("family-update", ([family.handle],)) - else: - self.emit("family-add", ([family.handle],)) + if not trans.batch: + if family.handle in self.family_map: + self.emit("family-update", ([family.handle],)) + else: + self.emit("family-add", ([family.handle],)) self.family_map[family.handle] = family def commit_citation(self, citation, trans, change_time=None): - if citation.handle in self.citation_map: - self.emit("citation-update", ([citation.handle],)) - else: - self.emit("citation-add", ([citation.handle],)) + if not trans.batch: + if citation.handle in self.citation_map: + self.emit("citation-update", ([citation.handle],)) + else: + self.emit("citation-add", ([citation.handle],)) self.citation_map[citation.handle] = citation def commit_source(self, source, trans, change_time=None): - if source.handle in self.source_map: - self.emit("source-update", ([source.handle],)) - else: - self.emit("source-add", ([source.handle],)) + if not trans.batch: + if source.handle in self.source_map: + self.emit("source-update", ([source.handle],)) + else: + self.emit("source-add", ([source.handle],)) self.source_map[source.handle] = source def commit_repository(self, repository, trans, change_time=None): - if repository.handle in self.repository_map: - self.emit("repository-update", ([repository.handle],)) - else: - self.emit("repository-add", ([repository.handle],)) + if not trans.batch: + if repository.handle in self.repository_map: + self.emit("repository-update", ([repository.handle],)) + else: + self.emit("repository-add", ([repository.handle],)) self.repository_map[repository.handle] = repository def commit_note(self, note, trans, change_time=None): - if note.handle in self.note_map: - self.emit("note-update", ([note.handle],)) - else: - self.emit("note-add", ([note.handle],)) + if not trans.batch: + if note.handle in self.note_map: + self.emit("note-update", ([note.handle],)) + else: + self.emit("note-add", ([note.handle],)) self.note_map[note.handle] = note def commit_place(self, place, trans, change_time=None): - if place.handle in self.place_map: - self.emit("place-update", ([place.handle],)) - else: - self.emit("place-add", ([place.handle],)) + if not trans.batch: + if place.handle in self.place_map: + self.emit("place-update", ([place.handle],)) + else: + self.emit("place-add", ([place.handle],)) self.place_map[place.handle] = place def commit_event(self, event, trans, change_time=None): - if event.handle in self.event_map: - self.emit("event-update", ([event.handle],)) - else: - self.emit("event-add", ([event.handle],)) + if not trans.batch: + if event.handle in self.event_map: + self.emit("event-update", ([event.handle],)) + else: + self.emit("event-add", ([event.handle],)) self.event_map[event.handle] = event def commit_tag(self, tag, trans, change_time=None): - if tag.handle in self.tag_map: - self.emit("tag-update", ([tag.handle],)) - else: - self.emit("tag-add", ([tag.handle],)) + if not trans.batch: + if tag.handle in self.tag_map: + self.emit("tag-update", ([tag.handle],)) + else: + self.emit("tag-add", ([tag.handle],)) self.tag_map[tag.handle] = tag def commit_media_object(self, media, transaction, change_time=None): - if media.handle in self.media_map: - self.emit("media-update", ([media.handle],)) - else: - self.emit("media-add", ([media.handle],)) + if not trans.batch: + if media.handle in self.media_map: + self.emit("media-update", ([media.handle],)) + else: + self.emit("media-add", ([media.handle],)) self.media_map[media.handle] = media def get_gramps_ids(self, obj_key): diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index e380c9601..852afe779 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -127,13 +127,17 @@ class Cursor(object): def __init__(self, model, func): self.model = model self.func = func + self._iter = self.__iter__() def __enter__(self): return self def __iter__(self): - return self.__next__() - def __next__(self): for item in self.model.all(): yield (bytes(item.handle, "utf-8"), self.func(item.handle)) + def __next__(self): + try: + return self._iter.__next__() + except StopIteration: + return None def __exit__(self, *args, **kwargs): pass def iter(self): @@ -142,7 +146,10 @@ class Cursor(object): yield None def first(self): self._iter = self.__iter__() - return self.next() + try: + return next(self._iter) + except: + return def next(self): try: return next(self._iter) @@ -1494,10 +1501,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_person(person.serialize()) self.dji.add_person_detail(person.serialize()) - if items.count() > 0: - self.emit("person-update", ([person.handle],)) - else: - self.emit("person-add", ([person.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("person-update", ([person.handle],)) + else: + self.emit("person-add", ([person.handle],)) def commit_family(self, family, trans, change_time=None): if self.use_import_cache: @@ -1509,10 +1517,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_family(family.serialize()) self.dji.add_family_detail(family.serialize()) - if items.count() > 0: - self.emit("family-update", ([family.handle],)) - else: - self.emit("family-add", ([family.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("family-update", ([family.handle],)) + else: + self.emit("family-add", ([family.handle],)) def commit_citation(self, citation, trans, change_time=None): if self.use_import_cache: @@ -1524,10 +1533,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_citation(citation.serialize()) self.dji.add_citation_detail(citation.serialize()) - if items.count() > 0: - self.emit("citation-update", ([citation.handle],)) - else: - self.emit("citation-add", ([citation.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("citation-update", ([citation.handle],)) + else: + self.emit("citation-add", ([citation.handle],)) def commit_source(self, source, trans, change_time=None): if self.use_import_cache: @@ -1539,10 +1549,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_source(source.serialize()) self.dji.add_source_detail(source.serialize()) - if items.count() > 0: - self.emit("source-update", ([source.handle],)) - else: - self.emit("source-add", ([source.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("source-update", ([source.handle],)) + else: + self.emit("source-add", ([source.handle],)) def commit_repository(self, repository, trans, change_time=None): if self.use_import_cache: @@ -1554,10 +1565,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_repository(repository.serialize()) self.dji.add_repository_detail(repository.serialize()) - if items.count() > 0: - self.emit("repository-update", ([repository.handle],)) - else: - self.emit("repository-add", ([repository.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("repository-update", ([repository.handle],)) + else: + self.emit("repository-add", ([repository.handle],)) def commit_note(self, note, trans, change_time=None): if self.use_import_cache: @@ -1569,10 +1581,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_note(note.serialize()) self.dji.add_note_detail(note.serialize()) - if items.count() > 0: - self.emit("note-update", ([note.handle],)) - else: - self.emit("note-add", ([note.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("note-update", ([note.handle],)) + else: + self.emit("note-add", ([note.handle],)) def commit_place(self, place, trans, change_time=None): if self.use_import_cache: @@ -1584,10 +1597,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_place(place.serialize()) self.dji.add_place_detail(place.serialize()) - if items.count() > 0: - self.emit("place-update", ([place.handle],)) - else: - self.emit("place-add", ([place.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("place-update", ([place.handle],)) + else: + self.emit("place-add", ([place.handle],)) def commit_event(self, event, trans, change_time=None): if self.use_import_cache: @@ -1599,10 +1613,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_event(event.serialize()) self.dji.add_event_detail(event.serialize()) - if items.count() > 0: - self.emit("event-update", ([event.handle],)) - else: - self.emit("event-add", ([event.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("event-update", ([event.handle],)) + else: + self.emit("event-add", ([event.handle],)) def commit_tag(self, tag, trans, change_time=None): if self.use_import_cache: @@ -1614,10 +1629,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_tag(tag.serialize()) self.dji.add_tag_detail(tag.serialize()) - if items.count() > 0: - self.emit("tag-update", ([tag.handle],)) - else: - self.emit("tag-add", ([tag.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("tag-update", ([tag.handle],)) + else: + self.emit("tag-add", ([tag.handle],)) def commit_media_object(self, media, transaction, change_time=None): """ @@ -1633,10 +1649,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): items[0].delete() self.dji.add_media(media.serialize()) self.dji.add_media_detail(media.serialize()) - if items.count() > 0: - self.emit("media-update", ([media.handle],)) - else: - self.emit("media-add", ([media.handle],)) + if not trans.batch: + if items.count() > 0: + self.emit("media-update", ([media.handle],)) + else: + self.emit("media-add", ([media.handle],)) def get_gramps_ids(self, obj_key): key2table = { From d72ed91f21a86e707bcc8f8fdaecee80d2d1625f Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 11:49:58 -0400 Subject: [PATCH 064/105] DictionaryDb: emit add after actually adding --- gramps/plugins/database/dictionarydb.py | 80 ++++++++++++++++++------- 1 file changed, 60 insertions(+), 20 deletions(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 1542f4c93..15009d584 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -1105,84 +1105,124 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return obj.handle def commit_person(self, person, trans, change_time=None): + emit = None if not trans.batch: if person.handle in self.person_map: - self.emit("person-update", ([person.handle],)) + emit = "person-update" else: - self.emit("person-add", ([person.handle],)) + emit = "person-add" self.person_map[person.handle] = person + # Emit after added: + if emit: + self.emit(emit, ([person.handle],)) def commit_family(self, family, trans, change_time=None): + emit = None if not trans.batch: if family.handle in self.family_map: - self.emit("family-update", ([family.handle],)) + emit = "family-update" else: - self.emit("family-add", ([family.handle],)) + emit = "family-add" self.family_map[family.handle] = family + # Emit after added: + if emit: + self.emit(emit, ([family.handle],)) def commit_citation(self, citation, trans, change_time=None): + emit = None if not trans.batch: if citation.handle in self.citation_map: - self.emit("citation-update", ([citation.handle],)) + emit = "citation-update" else: - self.emit("citation-add", ([citation.handle],)) + emit = "citation-add" self.citation_map[citation.handle] = citation + # Emit after added: + if emit: + self.emit(emit, ([citation.handle],)) def commit_source(self, source, trans, change_time=None): + emit = None if not trans.batch: if source.handle in self.source_map: - self.emit("source-update", ([source.handle],)) + emit = "source-update" else: - self.emit("source-add", ([source.handle],)) + emit = "source-add" self.source_map[source.handle] = source + # Emit after added: + if emit: + self.emit(emit, ([source.handle],)) def commit_repository(self, repository, trans, change_time=None): + emit = None if not trans.batch: if repository.handle in self.repository_map: - self.emit("repository-update", ([repository.handle],)) + emit = "repository-update" else: - self.emit("repository-add", ([repository.handle],)) + emit = "repository-add" self.repository_map[repository.handle] = repository + # Emit after added: + if emit: + self.emit(emit, ([repository.handle],)) def commit_note(self, note, trans, change_time=None): + emit = None if not trans.batch: if note.handle in self.note_map: - self.emit("note-update", ([note.handle],)) + emit "note-update" else: - self.emit("note-add", ([note.handle],)) + emit = "note-add" self.note_map[note.handle] = note + # Emit after added: + if emit: + self.emit(emit, ([note.handle],)) def commit_place(self, place, trans, change_time=None): + emit = None if not trans.batch: if place.handle in self.place_map: - self.emit("place-update", ([place.handle],)) + emit = "place-update" else: - self.emit("place-add", ([place.handle],)) + emit = "place-add" self.place_map[place.handle] = place + # Emit after added: + if emit: + self.emit(emit, ([place.handle],)) def commit_event(self, event, trans, change_time=None): + emit = None if not trans.batch: if event.handle in self.event_map: - self.emit("event-update", ([event.handle],)) + emit = "event-update" else: - self.emit("event-add", ([event.handle],)) + emit = "event-add" self.event_map[event.handle] = event + # Emit after added: + if emit: + self.emit(emit, ([event.handle],)) def commit_tag(self, tag, trans, change_time=None): + emit = None if not trans.batch: if tag.handle in self.tag_map: - self.emit("tag-update", ([tag.handle],)) + emit = "tag-update" else: - self.emit("tag-add", ([tag.handle],)) + emit = "tag-add" self.tag_map[tag.handle] = tag + # Emit after added: + if emit: + self.emit(emit, ([tag.handle],)) def commit_media_object(self, media, transaction, change_time=None): + emit = None if not trans.batch: if media.handle in self.media_map: - self.emit("media-update", ([media.handle],)) + emit = "media-update" else: - self.emit("media-add", ([media.handle],)) + emit = "media-add" self.media_map[media.handle] = media + # Emit after added: + if emit: + self.emit(emit, ([media.handle],)) def get_gramps_ids(self, obj_key): key2table = { From d6d5ecdf5d36cad99a0da616f999769150f29c86 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 11:51:56 -0400 Subject: [PATCH 065/105] DictionaryDb: emit add after actually adding (fixed typo) --- gramps/plugins/database/dictionarydb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 15009d584..5c167fe48 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -1168,7 +1168,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = None if not trans.batch: if note.handle in self.note_map: - emit "note-update" + emit = "note-update" else: emit = "note-add" self.note_map[note.handle] = note From bf12a2bc6762668e10f802e314be0eed451b6cea Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 12:30:30 -0400 Subject: [PATCH 066/105] Basic infrastructure for Undo/Redo --- gramps/gen/db/undoredo.py | 136 ++++++++++++++++++++++++ gramps/plugins/database/dictionarydb.py | 5 +- gramps/plugins/database/djangodb.py | 3 + 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 gramps/gen/db/undoredo.py diff --git a/gramps/gen/db/undoredo.py b/gramps/gen/db/undoredo.py new file mode 100644 index 000000000..b12735093 --- /dev/null +++ b/gramps/gen/db/undoredo.py @@ -0,0 +1,136 @@ +import time +from collections import deque + +class DbUndo(object): + """ + Base class for the Gramps undo/redo manager. Needs to be subclassed + for use with a real backend. + """ + + __slots__ = ('undodb', 'db', 'mapbase', 'undo_history_timestamp', + 'txn', 'undoq', 'redoq') + + def __init__(self, grampsdb): + """ + Class constructor. Set up main instance variables + """ + self.db = grampsdb + self.undoq = deque() + self.redoq = deque() + self.undo_history_timestamp = time.time() + self.txn = None + + def clear(self): + """ + Clear the undo/redo list (but not the backing storage) + """ + self.undoq.clear() + self.redoq.clear() + self.undo_history_timestamp = time.time() + self.txn = None + + def __enter__(self, value): + """ + Context manager method to establish the context + """ + self.open(value) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """ + Context manager method to finish the context + """ + if exc_type is None: + self.close() + return exc_type is None + + def open(self, value): + """ + Open the backing storage. Needs to be overridden in the derived + class. + """ + raise NotImplementedError + + def close(self): + """ + Close the backing storage. Needs to be overridden in the derived + class. + """ + raise NotImplementedError + + def append(self, value): + """ + Add a new entry on the end. Needs to be overridden in the derived + class. + """ + raise NotImplementedError + + def __getitem__(self, index): + """ + Returns an entry by index number. Needs to be overridden in the + derived class. + """ + raise NotImplementedError + + def __setitem__(self, index, value): + """ + Set an entry to a value. Needs to be overridden in the derived class. + """ + raise NotImplementedError + + def __len__(self): + """ + Returns the number of entries. Needs to be overridden in the derived + class. + """ + raise NotImplementedError + + def commit(self, txn, msg): + """ + Commit the transaction to the undo/redo database. "txn" should be + an instance of Gramps transaction class + """ + txn.set_description(msg) + txn.timestamp = time.time() + self.undoq.append(txn) + + def undo(self, update_history=True): + """ + Undo a previously committed transaction + """ + if self.db.readonly or self.undo_count == 0: + return False + return self.__undo(update_history) + + def redo(self, update_history=True): + """ + Redo a previously committed, then undone, transaction + """ + if self.db.readonly or self.redo_count == 0: + return False + return self.__redo(update_history) + + def undoredo(func): + """ + Decorator function to wrap undo and redo operations within a bsddb + transaction. It also catches bsddb errors and raises an exception + as appropriate + """ + pass + + def __redo(self, update_history=True): + """ + Access the last undone transaction, and revert the data to the state + before the transaction was undone. + """ + pass + + def __undo(self, db=None, update_history=True): + """ + Access the last committed transaction, and revert the data to the + state before the transaction was committed. + """ + pass + + undo_count = property(lambda self:len(self.undoq)) + redo_count = property(lambda self:len(self.redoq)) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 5c167fe48..09748295a 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -37,6 +37,7 @@ import logging # #------------------------------------------------------------------------ from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP +from gramps.gen.db.undoredo import DbUndo from gramps.gen.db.dbconst import * from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback @@ -390,6 +391,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.modified = 0 self.txn = DictionaryTxn("DbDictionary Transaction", self) self.transaction = None + self.undodb = DbUndo(self) + self.abort_possible = False self._directory = directory if directory: self.load(directory) @@ -1212,7 +1215,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): if emit: self.emit(emit, ([tag.handle],)) - def commit_media_object(self, media, transaction, change_time=None): + def commit_media_object(self, media, trans, change_time=None): emit = None if not trans.batch: if media.handle in self.media_map: diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 852afe779..0a88733cd 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -44,6 +44,7 @@ from gramps.gen.lib import (Person, Family, Event, Place, Repository, Citation, Source, Note, MediaObject, Tag, Researcher) from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn +from gramps.gen.db.undoredo import DbUndo from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback from gramps.gen.db import (PERSON_KEY, @@ -335,6 +336,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.import_cache = {} self.use_import_cache = False self.use_db_cache = True + self.undodb = DbUndo(self) + self.abort_possible = False self._directory = directory if directory: self.load(directory) From b2ed5d1cbb49e6b7caa83cd70fe8bb6d5db27d1b Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 12:43:24 -0400 Subject: [PATCH 067/105] DjangoDb: send proper object-add signal on new objects --- gramps/plugins/database/djangodb.py | 50 +++++++++++++++++------------ 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 0a88733cd..305cde2a8 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -1499,13 +1499,14 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = person.serialize() items = self.dji.Person.filter(handle=person.handle) - if items.count() > 0: + count = items.count() + if count > 0: # Hack, for the moment: delete and re-add items[0].delete() self.dji.add_person(person.serialize()) self.dji.add_person_detail(person.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("person-update", ([person.handle],)) else: self.emit("person-add", ([person.handle],)) @@ -1516,12 +1517,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = family.serialize() items = self.dji.Family.filter(handle=family.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_family(family.serialize()) self.dji.add_family_detail(family.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("family-update", ([family.handle],)) else: self.emit("family-add", ([family.handle],)) @@ -1532,12 +1534,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = citation.serialize() items = self.dji.Citation.filter(handle=citation.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_citation(citation.serialize()) self.dji.add_citation_detail(citation.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("citation-update", ([citation.handle],)) else: self.emit("citation-add", ([citation.handle],)) @@ -1548,12 +1551,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = source.serialize() items = self.dji.Source.filter(handle=source.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_source(source.serialize()) self.dji.add_source_detail(source.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("source-update", ([source.handle],)) else: self.emit("source-add", ([source.handle],)) @@ -1564,12 +1568,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = repository.serialize() items = self.dji.Repository.filter(handle=repository.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_repository(repository.serialize()) self.dji.add_repository_detail(repository.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("repository-update", ([repository.handle],)) else: self.emit("repository-add", ([repository.handle],)) @@ -1580,12 +1585,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = note.serialize() items = self.dji.Note.filter(handle=note.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_note(note.serialize()) self.dji.add_note_detail(note.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("note-update", ([note.handle],)) else: self.emit("note-add", ([note.handle],)) @@ -1596,12 +1602,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = place.serialize() items = self.dji.Place.filter(handle=place.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_place(place.serialize()) self.dji.add_place_detail(place.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("place-update", ([place.handle],)) else: self.emit("place-add", ([place.handle],)) @@ -1612,12 +1619,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = event.serialize() items = self.dji.Event.filter(handle=event.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_event(event.serialize()) self.dji.add_event_detail(event.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("event-update", ([event.handle],)) else: self.emit("event-add", ([event.handle],)) @@ -1628,12 +1636,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = tag.serialize() items = self.dji.Tag.filter(handle=tag.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_tag(tag.serialize()) self.dji.add_tag_detail(tag.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("tag-update", ([tag.handle],)) else: self.emit("tag-add", ([tag.handle],)) @@ -1648,12 +1657,13 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: raw = media.serialize() items = self.dji.Media.filter(handle=media.handle) - if items.count() > 0: + count = items.count() + if count > 0: items[0].delete() self.dji.add_media(media.serialize()) self.dji.add_media_detail(media.serialize()) if not trans.batch: - if items.count() > 0: + if count > 0: self.emit("media-update", ([media.handle],)) else: self.emit("media-add", ([media.handle],)) From e4d05f301ae4e1d57a6730d22d5f6f52ff0b118d Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 12:48:55 -0400 Subject: [PATCH 068/105] Fixed About dialog to show proper BSDDB version --- gramps/gui/aboutdialog.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/gramps/gui/aboutdialog.py b/gramps/gui/aboutdialog.py index 9886b550f..bf0fc7053 100644 --- a/gramps/gui/aboutdialog.py +++ b/gramps/gui/aboutdialog.py @@ -29,12 +29,6 @@ import os import sys import io -try: - import bsddb3 as bsddb ## ok, in try/except - BSDDB_STR = ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version())) -except: - BSDDB_STR = 'not found' - ##import logging ##_LOG = logging.getLogger(".GrampsAboutDialog") @@ -65,6 +59,20 @@ _ = glocale.translation.gettext from gramps.gen.constfunc import get_env_var from .display import display_url +def ellipses(text): + """ + Ellipsize text on length 40 + """ + if len(text) > 40: + return text[:40] + "..." + return text + +try: + import bsddb3 as bsddb ## ok, in try/except + BSDDB_STR = ellipses(str(bsddb.__version__) + " " + str(bsddb.db.version())) +except: + BSDDB_STR = 'not found' + #------------------------------------------------------------------------- # # GrampsAboutDialog @@ -135,14 +143,6 @@ class GrampsAboutDialog(Gtk.AboutDialog): ellipses(operatingsystem), ellipses(distribution))) -def ellipses(text): - """ - Ellipsize text on length 40 - """ - if len(text) > 40: - return text[:40] + "..." - return text - #------------------------------------------------------------------------- # # AuthorParser From 331a947ea47ce15113d79d223c8cc385a8308221 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 22:35:50 -0400 Subject: [PATCH 069/105] Removed hardcoded database backend types --- gramps/gui/dbman.py | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/gramps/gui/dbman.py b/gramps/gui/dbman.py index 0891170f4..210af49fb 100644 --- a/gramps/gui/dbman.py +++ b/gramps/gui/dbman.py @@ -69,6 +69,7 @@ from gi.repository import Pango # #------------------------------------------------------------------------- from gramps.gen.const import GRAMPS_LOCALE as glocale +from gramps.gen.plug import BasePluginManager _ = glocale.translation.gettext from gramps.gen.const import URL_WIKISTRING from .user import User @@ -104,7 +105,10 @@ ICON_COL = 6 RCS_BUTTON = { True : _('_Extract'), False : _('_Archive') } class DatabaseDialog(Gtk.MessageDialog): - def __init__(self, parent=None): + def __init__(self, parent, options): + """ + options = [(pdata, number), ...] + """ Gtk.MessageDialog.__init__(self, parent, flags=Gtk.DialogFlags.MODAL, @@ -112,15 +116,12 @@ class DatabaseDialog(Gtk.MessageDialog): ) self.set_icon(ICON) self.set_title('') - self.set_markup('<span size="larger" weight="bold">%s</span>' % - _('Database Backend')) + _('Database Backend for New Tree')) self.format_secondary_text( - _("Please select a database backend type")) - - self.add_button("BSDDB Database (standard)", 1) - self.add_button("Dictionary (in-memory)", 2) - self.add_button("Django Database", 3) + _("Please select a database backend type:")) + for option, number in options: + self.add_button(option.name, number) class DbManager(CLIDbManager): """ @@ -779,14 +780,24 @@ class DbManager(CLIDbManager): message. """ self.new.set_sensitive(False) - # popup window and ask for dbid types, if more than one - ## FIXME: autoload from plugins - dbid = "bsddb" - d = DatabaseDialog(self.top) - database = d.run() - d.destroy() - if database >= 0: - dbid = {1:"bsddb",2:"dictionarydb",3:"djangodb"}[database] + dbid = None + pmgr = BasePluginManager.get_instance() + pdata = pmgr.get_reg_databases() + # If just one database backend, just use it: + if len(pdata) == 0: + DbManager.ERROR(_("No available database backends"), + _("Please check your dependencies.")) + elif len(pdata) == 1: + dbid = pdata[0].id + elif len(pdata) > 1: + options = sorted(list(zip(pdata, range(1, len(pdata) + 1))), key=lambda items: items[0].name) + d = DatabaseDialog(self.top, options) + number = d.run() + d.destroy() + if number >= 0: + dbid = [option[0].id for option in options if option[1] == number][0] + ### Now, let's load it up + if dbid: try: self._create_new_db(dbid=dbid) except (OSError, IOError) as msg: From 11ac0f15510204a2fa29d5f3b9064817319ec7d4 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 22:52:57 -0400 Subject: [PATCH 070/105] Database plugin type support reset_system, to reset modules --- gramps/gen/dbstate.py | 20 +++++++++----------- gramps/gen/plug/_pluginreg.py | 13 +++++++++++++ gramps/plugins/database/djangodb.gpr.py | 1 + 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py index 069934a13..0d9c30d4a 100644 --- a/gramps/gen/dbstate.py +++ b/gramps/gen/dbstate.py @@ -140,18 +140,16 @@ class DbState(Callback): pmgr.reg_plugins(USER_PLUGINS, self, None, load_on_reg=True) pdata = pmgr.get_plugin(id) - ### FIXME: Currently Django needs to reset modules - if pdata.id == "djangodb": - if self.modules_is_set(): - self.reset_modules() - else: - self.save_modules() + if pdata: + if pdata.reset_system: + if self.modules_is_set(): + self.reset_modules() + else: + self.save_modules() + mod = pmgr.load_plugin(pdata) + database = getattr(mod, pdata.databaseclass) + return database() - mod = pmgr.load_plugin(pdata) - database = getattr(mod, pdata.databaseclass) - return database() - - ## FIXME: ## Work-around for databases that need sys refresh (django): def modules_is_set(self): if hasattr(self, "_modules"): diff --git a/gramps/gen/plug/_pluginreg.py b/gramps/gen/plug/_pluginreg.py index 5619f18ed..70a93f0f2 100644 --- a/gramps/gen/plug/_pluginreg.py +++ b/gramps/gen/plug/_pluginreg.py @@ -356,6 +356,9 @@ class PluginData(object): .. attribute:: databaseclass The class in the module that is the database class + .. attribute:: reset_system + Boolean to indicate that the system (sys.modules) should + be reset. """ def __init__(self): @@ -430,6 +433,7 @@ class PluginData(object): self._order = END #DATABASE attr self._databaseclass = None + self._reset_system = False #GENERAL attr self._data = [] self._process = None @@ -949,7 +953,16 @@ class PluginData(object): def _get_databaseclass(self): return self._databaseclass + def _set_reset_system(self, reset_system): + if not self._ptype == DATABASE: + raise ValueError('reset_system may only be set for DATABASE plugins') + self._reset_system = reset_system + + def _get_reset_system(self): + return self._reset_system + databaseclass = property(_get_databaseclass, _set_databaseclass) + reset_system = property(_get_reset_system, _set_reset_system) #GENERAL attr def _set_data(self, data): diff --git a/gramps/plugins/database/djangodb.gpr.py b/gramps/plugins/database/djangodb.gpr.py index 57b30f2f5..2d0d0797b 100644 --- a/gramps/plugins/database/djangodb.gpr.py +++ b/gramps/plugins/database/djangodb.gpr.py @@ -29,3 +29,4 @@ plg.status = STABLE plg.fname = 'djangodb.py' plg.ptype = DATABASE plg.databaseclass = 'DbDjango' +plg.reset_system = True From 5171b3748d9277b7bc22a58d7b006e6e9875ba8a Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 14 May 2015 23:15:30 -0400 Subject: [PATCH 071/105] Added missing bookmark count methods to djangodb and dictionarydb --- gramps/plugins/database/dictionarydb.py | 13 +++++++++++++ gramps/plugins/database/djangodb.py | 14 ++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 09748295a..2a15c28b1 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -393,6 +393,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.transaction = None self.undodb = DbUndo(self) self.abort_possible = False + self._bm_changes = 0 self._directory = directory if directory: self.load(directory) @@ -1689,3 +1690,15 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): with open(versionpath, "w") as version_file: version_file.write("dictionarydb") + def report_bm_change(self): + """ + Add 1 to the number of bookmark changes during this session. + """ + self._bm_changes += 1 + + def db_has_bm_changes(self): + """ + Return whethere there were bookmark changes during the session. + """ + return self._bm_changes > 0 + diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 305cde2a8..543243c99 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -338,6 +338,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.use_db_cache = True self.undodb = DbUndo(self) self.abort_possible = False + self._bm_changes = 0 self._directory = directory if directory: self.load(directory) @@ -2088,3 +2089,16 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): for filename in os.listdir(defaults): fullpath = os.path.abspath(os.path.join(defaults, filename)) shutil.copy2(fullpath, directory) + + def report_bm_change(self): + """ + Add 1 to the number of bookmark changes during this session. + """ + self._bm_changes += 1 + + def db_has_bm_changes(self): + """ + Return whethere there were bookmark changes during the session. + """ + return self._bm_changes > 0 + From 3c52f7016b5e9f8c3f23d0eee3064e449f4bd2ed Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Fri, 15 May 2015 06:42:13 -0400 Subject: [PATCH 072/105] Alternative DBs: touch meta_data.db to record last access time --- gramps/plugins/database/dictionarydb.py | 9 +++++++++ gramps/plugins/database/djangodb.py | 11 ++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 2a15c28b1..ff1b8f745 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -67,6 +67,13 @@ from gramps.gen.lib.tag import Tag _LOG = logging.getLogger(DBLOGNAME) +def touch(fname, mode=0o666, dir_fd=None, **kwargs): + ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python + flags = os.O_CREAT | os.O_APPEND + with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f: + os.utime(f.fileno() if os.utime in os.supports_fd else fname, + dir_fd=None if os.supports_fd else dir_fd, **kwargs) + class Environment(object): """ Implements the Environment API. @@ -1483,6 +1490,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): writer = XmlWriter(self, User(), strip_photos=0, compress=1) filename = os.path.join(self._directory, "data.gramps") writer.write(filename) + filename = os.path.join(self._directory, "meta_data.db") + touch(filename) def find_backlink_handles(self, handle, include_classes=None): return [] diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 543243c99..759d366b8 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -64,6 +64,13 @@ sys.path.append(os.path.dirname(os.path.abspath(__file__))) _LOG = logging.getLogger(DBLOGNAME) +def touch(fname, mode=0o666, dir_fd=None, **kwargs): + ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python + flags = os.O_CREAT | os.O_APPEND + with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f: + os.utime(f.fileno() if os.utime in os.supports_fd else fname, + dir_fd=None if os.supports_fd else dir_fd, **kwargs) + class Environment(object): """ Implements the Environment API. @@ -1959,7 +1966,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def close(self): - pass + if self._directory: + filename = os.path.join(self._directory, "meta_data.db") + touch(filename) def get_surname_list(self): return [] From ea996cf6ddca2cad2a88630f5c4063f51ad718e8 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Fri, 15 May 2015 07:32:03 -0400 Subject: [PATCH 073/105] Database API, -L: database reports summary, if possible --- gramps/cli/clidbman.py | 54 ++++++++++++------- gramps/plugins/database/bsddb_support/read.py | 13 +++++ gramps/plugins/database/dictionarydb.py | 29 ++++++++++ gramps/plugins/database/djangodb.py | 32 +++++++++-- 4 files changed, 107 insertions(+), 21 deletions(-) diff --git a/gramps/cli/clidbman.py b/gramps/cli/clidbman.py index 893a2d84f..5bb87e632 100644 --- a/gramps/cli/clidbman.py +++ b/gramps/cli/clidbman.py @@ -133,12 +133,42 @@ class CLIDbManager(object): def get_dbdir_summary(self, dirpath, name): """ - Returns (people_count, bsddb_version, schema_version) of - current DB. - Returns ("Unknown", "Unknown", "Unknown") if invalid DB or other error. + dirpath: full path to database + name: proper name of family tree + + Returns dictionary of summary item. + Should include at least, if possible: + + _("Path") + _("Family Tree") + _("Last accessed") + _("Database backend") + _("Locked?") + + and these details: + + _("Number of people") + _("Version") + _("Schema version") """ - ## Maybe return the txt file contents, for now - return ("Unknown", "Unknown", "Unknown") + dbid = "bsddb" + dbid_path = os.path.join(dirpath, "database.txt") + if os.path.isfile(dbid_path): + dbid = open(dbid_path).read().strip() + try: + database = self.dbstate.make_database(dbid) + database.load(dirpath, None) + retval = database.get_summary() + except Exception as msg: + retval = {"Unavailable": str(msg)[:74] + "..."} + retval.update({ + _("Family Tree"): name, + _("Path"): dirpath, + _("Database backend"): dbid, + _("Last accessed"): time_val(dirpath)[1], + _("Locked?"): self.is_locked(dirpath), + }) + return retval def family_tree_summary(self): """ @@ -149,19 +179,7 @@ class CLIDbManager(object): for item in self.current_names: (name, dirpath, path_name, last, tval, enable, stock_id) = item - count, bsddb_version, schema_version = self.get_dbdir_summary(dirpath, name) - retval = {} - retval[_("Number of people")] = count - if enable: - retval[_("Locked?")] = _("yes") - else: - retval[_("Locked?")] = _("no") - retval[_("Bsddb version")] = bsddb_version - retval[_("Schema version")] = schema_version - retval[_("Family Tree")] = name - retval[_("Path")] = dirpath - retval[_("Last accessed")] = time.strftime('%x %X', - time.localtime(tval)) + retval = self.get_dbdir_summary(dirpath, name) summary_list.append( retval ) return summary_list diff --git a/gramps/plugins/database/bsddb_support/read.py b/gramps/plugins/database/bsddb_support/read.py index d51810581..9280dbc09 100644 --- a/gramps/plugins/database/bsddb_support/read.py +++ b/gramps/plugins/database/bsddb_support/read.py @@ -1976,3 +1976,16 @@ class DbBsddbRead(DbReadBase, Callback): self.__log_error() name = None return name + + def get_summary(self): + """ + Returns dictionary of summary item. + Should include, if possible: + + _("Number of people") + _("Version") + _("Schema version") + """ + return { + _("Number of people"): self.get_number_of_people(), + } diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index ff1b8f745..72a383469 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -36,6 +36,8 @@ import logging # Gramps Modules # #------------------------------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP from gramps.gen.db.undoredo import DbUndo from gramps.gen.db.dbconst import * @@ -1711,3 +1713,30 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): """ return self._bm_changes > 0 + def get_summary(self): + """ + Returns dictionary of summary item. + Should include, if possible: + + _("Number of people") + _("Version") + _("Schema version") + """ + return { + _("Number of people"): self.get_number_of_people(), + } + + def get_dbname(self): + """ + In DictionaryDb, the database is in a text file at the path + """ + filepath = os.path.join(self._directory, "name.txt") + try: + name_file = open(filepath, "r") + name = name_file.readline().strip() + name_file.close() + except (OSError, IOError) as msg: + _LOG.error(str(msg)) + name = None + return name + diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 759d366b8..1c6e3a6a1 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -40,6 +40,8 @@ from django.db import transaction # #------------------------------------------------------------------------ import gramps +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext from gramps.gen.lib import (Person, Family, Event, Place, Repository, Citation, Source, Note, MediaObject, Tag, Researcher) @@ -2028,9 +2030,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): ## FIXME pass - def get_dbname(self): - return "Django Database" - ## missing def find_place_child_handles(self, handle): @@ -2111,3 +2110,30 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): """ return self._bm_changes > 0 + def get_summary(self): + """ + Returns dictionary of summary item. + Should include, if possible: + + _("Number of people") + _("Version") + _("Schema version") + """ + return { + _("Number of people"): self.get_number_of_people(), + } + + def get_dbname(self): + """ + In Django, the database is in a text file at the path + """ + filepath = os.path.join(self._directory, "name.txt") + try: + name_file = open(filepath, "r") + name = name_file.readline().strip() + name_file.close() + except (OSError, IOError) as msg: + _LOG.error(str(msg)) + name = None + return name + From c72d40aa07498856ab864af32fa55770080ed5ea Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Fri, 15 May 2015 09:12:21 -0400 Subject: [PATCH 074/105] bsddb backend: supply version details in get_summary --- .../plugins/database/bsddb_support/write.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py index 66d4e4a53..46430ee85 100644 --- a/gramps/plugins/database/bsddb_support/write.py +++ b/gramps/plugins/database/bsddb_support/write.py @@ -2450,6 +2450,28 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): except (OSError, IOError) as msg: raise DbException(str(msg)) + def get_summary(self): + """ + Returns dictionary of summary item. + Should include, if possible: + + _("Number of people") + _("Version") + _("Schema version") + """ + schema_version = self.metadata.get(b'version', default=None) + bdbversion_file = os.path.join(self.path, BDBVERSFN) + if os.path.isfile(bdbversion_file): + vers_file = open(bdbversion_file) + bsddb_version = vers_file.readline().strip() + else: + bsddb_version = _("Unknown") + return { + _("Number of people"): self.get_number_of_people(), + _("Schema version"): schema_version, + _("Version"): bsddb_version, + } + def mk_backup_name(database, base): """ Return the backup name of the database table From eeb150f1e3290b5df2968d589066f6d3d1c4d8dd Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Fri, 15 May 2015 10:48:14 -0400 Subject: [PATCH 075/105] Database API: expore name, full_name, and brief_name --- gramps/plugins/database/dictionarydb.py | 9 +++++++++ gramps/plugins/database/djangodb.py | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 72a383469..ecdf5a112 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -404,6 +404,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.abort_possible = False self._bm_changes = 0 self._directory = directory + self.full_name = None + self.path = None + self.brief_name = None if directory: self.load(directory) @@ -1671,6 +1674,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): from gramps.plugins.importer.importxml import importData from gramps.cli.user import User self._directory = directory + self.full_name = os.path.abspath(self._directory) + self.path = self.full_name + self.brief_name = os.path.basename(self._directory) filename = os.path.join(directory, "data.gramps") if os.path.isfile(filename): importData(self, filename, User()) @@ -1690,6 +1696,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): def set_save_path(self, directory): self._directory = directory + self.full_name = os.path.abspath(self._directory) + self.path = self.full_name + self.brief_name = os.path.basename(self._directory) def undo(self, update_history=True): pass diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 1c6e3a6a1..182295645 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -349,6 +349,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.abort_possible = False self._bm_changes = 0 self._directory = directory + self.full_name = None + self.path = None + self.brief_name = None if directory: self.load(directory) @@ -358,6 +361,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): force_bsddb_downgrade=False, force_python_upgrade=False): self._directory = directory + self.full_name = os.path.abspath(self._directory) + self.path = self.full_name + self.brief_name = os.path.basename(self._directory) from django.conf import settings default_settings = {"__file__": os.path.join(directory, "default_settings.py")} @@ -1836,6 +1842,9 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def set_save_path(self, directory): self._directory = directory + self.full_name = os.path.abspath(self._directory) + self.path = self.full_name + self.brief_name = os.path.basename(self._directory) ## Get types: def get_event_attribute_types(self): From 014f8e61f1d7b0363b3c14eaa3657fdaba8de370 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Fri, 15 May 2015 22:50:54 -0400 Subject: [PATCH 076/105] DictionaryDb: reworked internal reprs; updated gender stats, researcher --- gramps/plugins/database/dictionarydb.py | 341 ++++++++++++++---------- gramps/plugins/database/djangodb.py | 17 +- 2 files changed, 213 insertions(+), 145 deletions(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index ecdf5a112..38ad398ac 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -38,7 +38,8 @@ import logging #------------------------------------------------------------------------ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP +from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, + KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP) from gramps.gen.db.undoredo import DbUndo from gramps.gen.db.dbconst import * from gramps.gen.utils.callback import Callback @@ -66,6 +67,7 @@ from gramps.gen.lib.place import Place from gramps.gen.lib.repo import Repository from gramps.gen.lib.note import Note from gramps.gen.lib.tag import Tag +from gramps.gen.lib.genderstats import GenderStats _LOG = logging.getLogger(DBLOGNAME) @@ -99,8 +101,8 @@ class Table(object): """ return self.funcs["cursor_func"]() - def put(key, data, txn=None): - self[key] = data + def put(self, key, data, txn=None): + self.funcs["add_func"](data, txn) class Map(dict): """ @@ -137,15 +139,14 @@ class MetaCursor(object): pass class Cursor(object): - def __init__(self, map, func): + def __init__(self, map): self.map = map - self.func = func self._iter = self.__iter__() def __enter__(self): return self def __iter__(self): for item in self.map.keys(): - yield (bytes(item, "utf-8"), self.func(item)) + yield (bytes(item, "utf-8"), self.map[item]) def __next__(self): try: return self._iter.__next__() @@ -155,7 +156,7 @@ class Cursor(object): pass def iter(self): for item in self.map.keys(): - yield (bytes(item, "utf-8"), self.func(item)) + yield (bytes(item, "utf-8"), self.map[item]) def first(self): self._iter = self.__iter__() try: @@ -383,15 +384,25 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.nmap_index = 0 self.env = Environment(self) self.person_map = Map(Table(self._tables["Person"])) + self.person_id_map = {} self.family_map = Map(Table(self._tables["Family"])) + self.family_id_map = {} self.place_map = Map(Table(self._tables["Place"])) + self.place_id_map = {} self.citation_map = Map(Table(self._tables["Citation"])) + self.citation_id_map = {} self.source_map = Map(Table(self._tables["Source"])) + self.source_id_map = {} self.repository_map = Map(Table(self._tables["Repository"])) + self.repository_id_map = {} self.note_map = Map(Table(self._tables["Note"])) + self.note_id_map = {} self.media_map = Map(Table(self._tables["Media"])) + self.media_id_map = {} self.event_map = Map(Table(self._tables["Event"])) + self.event_id_map = {} self.tag_map = Map(Table(self._tables["Tag"])) + self.tag_id_map = {} self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) self.name_group = {} self.undo_callback = None @@ -407,6 +418,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.full_name = None self.path = None self.brief_name = None + self.genderStats = GenderStats() # can pass in loaded stats as dict + self.owner = Researcher() if directory: self.load(directory) @@ -425,12 +438,15 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def transaction_commit(self, txn): + ## FIXME pass def get_undodb(self): + ## FIXME return None def transaction_abort(self, txn): + ## FIXME pass @staticmethod @@ -687,10 +703,12 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): ## Fixme: implement sort return self.person_map.keys() - def get_family_handles(self): + def get_family_handles(self, sort_handles=False): + ## Fixme: implement sort return self.family_map.keys() def get_event_handles(self, sort_handles=False): + ## Fixme: implement sort return self.event_map.keys() def get_citation_handles(self, sort_handles=False): @@ -705,78 +723,80 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): ## Fixme: implement sort return self.place_map.keys() - def get_repository_handles(self): + def get_repository_handles(self, sort_handles=False): + ## Fixme: implement sort return self.repository_map.keys() def get_media_object_handles(self, sort_handles=False): ## Fixme: implement sort return self.media_map.keys() - def get_note_handles(self): + def get_note_handles(self, sort_handles=False): + ## Fixme: implement sort return self.note_map.keys() def get_tag_handles(self, sort_handles=False): - # FIXME: sort + # FIXME: implement sort return self.tag_map.keys() def get_event_from_handle(self, handle): event = None if handle in self.event_map: - event = self.event_map[handle] + event = Event.create(self.event_map[handle]) return event def get_family_from_handle(self, handle): family = None if handle in self.family_map: - family = self.family_map[handle] + family = Family.create(self.family_map[handle]) return family def get_repository_from_handle(self, handle): repository = None if handle in self.repository_map: - repository = self.repository_map[handle] + repository = Repository.create(self.repository_map[handle]) return repository def get_person_from_handle(self, handle): person = None if handle in self.person_map: - person = self.person_map[handle] + person = Person.create(self.person_map[handle]) return person def get_place_from_handle(self, handle): place = None if handle in self.place_map: - place = self.place_map[handle] + place = Place.create(self.place_map[handle]) return place def get_citation_from_handle(self, handle): citation = None if handle in self.citation_map: - citation = self.citation_map[handle] + citation = Citation.create(self.citation_map[handle]) return citation def get_source_from_handle(self, handle): source = None if handle in self.source_map: - source = self.source_map[handle] + source = Source.create(self.source_map[handle]) return source def get_note_from_handle(self, handle): note = None if handle in self.note_map: - note = self.note_map[handle] + note = Note.create(self.note_map[handle]) return note def get_object_from_handle(self, handle): media = None if handle in self.media_map: - media = self.media_map[handle] + media = MediaObject.create(self.media_map[handle]) return media def get_tag_from_handle(self, handle): tag = None if handle in self.tag_map: - tag = self.tag_map[handle] + tag = Tag.create(self.tag_map[handle]) return tag def get_default_person(self): @@ -787,81 +807,73 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def iter_people(self): - return (person for person in self.person_map.values()) + return (Person.create(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()) + return (Family.create(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(): + ## Slow, but typically not too many tags: + for data in self.tag_map.values(): + tag = Tag.create(data) if tag.name == name: return tag 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 + if gramps_id in self.person_id_map: + return Person.create(self.person_id_map[gramps_id]) 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 + if gramps_id in self.family_id_map: + return Family.create(self.family_id_map[gramps_id]) return None def get_citation_from_gramps_id(self, gramps_id): - for citation in self.citation_map.values(): - if citation.gramps_id == gramps_id: - return citation + if gramps_id in self.citation_id_map: + return Citation.create(self.citation_id_map[gramps_id]) return None def get_source_from_gramps_id(self, gramps_id): - for source in self.source_map.values(): - if source.gramps_id == gramps_id: - return source + if gramps_id in self.source_id_map: + return Source.create(self.source_id_map[gramps_id]) return None def get_event_from_gramps_id(self, gramps_id): - for event in self.event_map.values(): - if event.gramps_id == gramps_id: - return event + if gramps_id in self.event_id_map: + return Event.create(self.event_id_map[gramps_id]) return None def get_media_from_gramps_id(self, gramps_id): - for media in self.media_map.values(): - if media.gramps_id == gramps_id: - return media + if gramps_id in self.media_id_map: + return MediaObject.create(self.media_id_map[gramps_id]) return None def get_place_from_gramps_id(self, gramps_id): - for place in self.place_map.values(): - if place.gramps_id == gramps_id: - return place + if gramps_id in self.place_id_map: + return Place.create(self.place_id_map[gramps_id]) return None def get_repository_from_gramps_id(self, gramps_id): - for repository in self.repository_map.values(): - if repository.gramps_id == gramps_id: - return repository + if gramps_id in self.repository_id_map: + return Repository.create(self.repository_id_map[gramps_id]) return None def get_note_from_gramps_id(self, gramps_id): - for note in self.note_map.values(): - if note.gramps_id == gramps_id: - return note + if gramps_id in self.note_id_map: + return Note.create(self.note_id_map[gramps_id]) return None def get_tag_from_gramps_id(self, gramps_id): - for tag in self.tag_map.values(): - if tag.gramps_id == gramps_id: - return tag + if gramps_id in self.tag_id_map: + return Tag.create(self.tag_id_map[gramps_id]) return None def get_number_of_people(self): @@ -874,7 +886,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return len(self.place_map) def get_number_of_tags(self): - return 0 # FIXME + return len(self.tag_map) def get_number_of_families(self): return len(self.family_map) @@ -895,52 +907,48 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return len(self.repository_map) def get_place_cursor(self): - return Cursor(self.place_map, self.get_raw_place_data) + return Cursor(self.place_map) def get_person_cursor(self): - return Cursor(self.person_map, self.get_raw_person_data) + return Cursor(self.person_map) def get_family_cursor(self): - return Cursor(self.family_map, self.get_raw_family_data) + return Cursor(self.family_map) def get_event_cursor(self): - return Cursor(self.event_map, self.get_raw_event_data) + return Cursor(self.event_map) def get_note_cursor(self): - return Cursor(self.note_map, self.get_raw_note_data) + return Cursor(self.note_map) def get_tag_cursor(self): - return Cursor(self.tag_map, self.get_raw_tag_data) + return Cursor(self.tag_map) def get_repository_cursor(self): - return Cursor(self.repository_map, self.get_raw_repository_data) + return Cursor(self.repository_map) def get_media_cursor(self): - return Cursor(self.media_map, self.get_raw_object_data) + return Cursor(self.media_map) def get_citation_cursor(self): - return Cursor(self.citation_map, self.get_raw_citation_data) + return Cursor(self.citation_map) def get_source_cursor(self): - return Cursor(self.source_map, self.get_raw_source_data) + return Cursor(self.source_map) 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, + PERSON_KEY: self.person_id_map, + FAMILY_KEY: self.family_id_map, + SOURCE_KEY: self.source_id_map, + CITATION_KEY: self.citation_id_map, + EVENT_KEY: self.event_id_map, + MEDIA_KEY: self.media_id_map, + PLACE_KEY: self.place_id_map, + REPOSITORY_KEY: self.repository_id_map, + NOTE_KEY: self.note_id_map, } - map = key2table[obj_key] - for item in map.values(): - if item.gramps_id == gramps_id: - return True - return False + return gramps_id in key2table[obj_key] def has_person_handle(self, handle): return handle in self.person_map @@ -981,59 +989,61 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): pass def set_default_person_handle(self, handle): + ## FIXME pass def set_mediapath(self, mediapath): + ## FIXME pass def get_raw_person_data(self, handle): if handle in self.person_map: - return self.person_map[handle].serialize() + return self.person_map[handle] return None def get_raw_family_data(self, handle): if handle in self.family_map: - return self.family_map[handle].serialize() + return self.family_map[handle] return None def get_raw_citation_data(self, handle): if handle in self.citation_map: - return self.citation_map[handle].serialize() + return self.citation_map[handle] return None def get_raw_source_data(self, handle): if handle in self.source_map: - return self.source_map[handle].serialize() + return self.source_map[handle] return None def get_raw_repository_data(self, handle): if handle in self.repository_map: - return self.repository_map[handle].serialize() + return self.repository_map[handle] return None def get_raw_note_data(self, handle): if handle in self.note_map: - return self.note_map[handle].serialize() + return self.note_map[handle] return None def get_raw_place_data(self, handle): if handle in self.place_map: - return self.place_map[handle].serialize() + return self.place_map[handle] return None def get_raw_object_data(self, handle): if handle in self.media_map: - return self.media_map[handle].serialize() + return self.media_map[handle] return None def get_raw_tag_data(self, handle): if handle in self.tag_map: - return self.tag_map[handle].serialize() + return self.tag_map[handle] return None def get_raw_event_data(self, handle): if handle in self.event_map: - return self.event_map[handle].serialize() + return self.event_map[handle] return None def add_person(self, person, trans, set_gid=True): @@ -1127,7 +1137,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "person-update" else: emit = "person-add" - self.person_map[person.handle] = person + self.person_map[person.handle] = person.serialize() + self.person_id_map[person.gramps_id] = self.person_map[person.handle] # Emit after added: if emit: self.emit(emit, ([person.handle],)) @@ -1139,7 +1150,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "family-update" else: emit = "family-add" - self.family_map[family.handle] = family + self.family_map[family.handle] = family.serialize() + self.family_id_map[family.gramps_id] = self.family_map[family.handle] # Emit after added: if emit: self.emit(emit, ([family.handle],)) @@ -1151,7 +1163,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "citation-update" else: emit = "citation-add" - self.citation_map[citation.handle] = citation + self.citation_map[citation.handle] = citation.serialize() + self.citation_id_map[citation.gramps_id] = self.citation_map[citation.handle] # Emit after added: if emit: self.emit(emit, ([citation.handle],)) @@ -1163,7 +1176,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "source-update" else: emit = "source-add" - self.source_map[source.handle] = source + self.source_map[source.handle] = source.serialize() + self.source_id_map[source.gramps_id] = self.source_map[source.handle] # Emit after added: if emit: self.emit(emit, ([source.handle],)) @@ -1175,7 +1189,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "repository-update" else: emit = "repository-add" - self.repository_map[repository.handle] = repository + self.repository_map[repository.handle] = repository.serialize() + self.repository_id_map[repository.gramps_id] = self.repository_map[repository.handle] # Emit after added: if emit: self.emit(emit, ([repository.handle],)) @@ -1187,7 +1202,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "note-update" else: emit = "note-add" - self.note_map[note.handle] = note + self.note_map[note.handle] = note.serialize() + self.note_id_map[note.gramps_id] = self.note_map[note.handle] # Emit after added: if emit: self.emit(emit, ([note.handle],)) @@ -1199,7 +1215,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "place-update" else: emit = "place-add" - self.place_map[place.handle] = place + self.place_map[place.handle] = place.serialize() + self.place_id_map[place.gramps_id] = self.place_map[place.handle] # Emit after added: if emit: self.emit(emit, ([place.handle],)) @@ -1211,7 +1228,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "event-update" else: emit = "event-add" - self.event_map[event.handle] = event + self.event_map[event.handle] = event.serialize() + self.event_id_map[event.gramps_id] = self.event_map[event.handle] # Emit after added: if emit: self.emit(emit, ([event.handle],)) @@ -1223,7 +1241,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "tag-update" else: emit = "tag-add" - self.tag_map[tag.handle] = tag + self.tag_map[tag.handle] = tag.serialize() + self.tag_id_map[tag.gramps_id] = self.tag_map[tag.handle] # Emit after added: if emit: self.emit(emit, ([tag.handle],)) @@ -1235,34 +1254,35 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "media-update" else: emit = "media-add" - self.media_map[media.handle] = media + self.media_map[media.handle] = media.serialize() + self.media_id_map[media.gramps_id] = self.media_map[media.handle] # Emit after added: if emit: self.emit(emit, ([media.handle],)) 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, + PERSON_KEY: self.person_id_map, + FAMILY_KEY: self.family_id_map, + CITATION_KEY: self.citation_id_map, + SOURCE_KEY: self.source_id_map, + EVENT_KEY: self.event_id_map, + MEDIA_KEY: self.media_id_map, + PLACE_KEY: self.place_id_map, + REPOSITORY_KEY: self.repository_id_map, + NOTE_KEY: self.note_id_map, } - table = key2table[obj_key] - return [item.gramps_id for item in table.values()] + return list(key2table[obj_key].keys()) def transaction_begin(self, transaction): + ## FIXME return - def disable_signals(self): - pass - def set_researcher(self, owner): - pass + self.owner.set_from(owner) + + def get_researcher(self): + return self.owner def request_rebuild(self): self.emit('person-rebuild') @@ -1332,7 +1352,9 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): if self.readonly or not handle: return if handle in self.person_map: + person = Person.create(self.person_map[handle]) del self.person_map[handle] + del self.person_id_map[person.gramps_id] self.emit("person-delete", ([handle],)) def remove_source(self, handle, transaction): @@ -1341,7 +1363,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.source_map, - SOURCE_KEY) + self.source_id_map, SOURCE_KEY) def remove_citation(self, handle, transaction): """ @@ -1349,7 +1371,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.citation_map, - CITATION_KEY) + self.citation_id_map, CITATION_KEY) def remove_event(self, handle, transaction): """ @@ -1357,7 +1379,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.event_map, - EVENT_KEY) + self.event_id_map, EVENT_KEY) def remove_object(self, handle, transaction): """ @@ -1365,7 +1387,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.media_map, - MEDIA_KEY) + self.media_id_map, MEDIA_KEY) def remove_place(self, handle, transaction): """ @@ -1373,7 +1395,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.place_map, - PLACE_KEY) + self.place_id_map, PLACE_KEY) def remove_family(self, handle, transaction): """ @@ -1381,7 +1403,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.family_map, - FAMILY_KEY) + self.family_id_map, FAMILY_KEY) def remove_repository(self, handle, transaction): """ @@ -1389,7 +1411,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.repository_map, - REPOSITORY_KEY) + self.repository_id_map, REPOSITORY_KEY) def remove_note(self, handle, transaction): """ @@ -1397,7 +1419,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.note_map, - NOTE_KEY) + self.note_id_map, NOTE_KEY) def remove_tag(self, handle, transaction): """ @@ -1405,7 +1427,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.tag_map, - TAG_KEY) + self.tag_id_map, TAG_KEY) def is_empty(self): """ @@ -1416,13 +1438,13 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return False return True - def __do_remove(self, handle, transaction, data_map, key): + def __do_remove(self, handle, transaction, data_map, data_id_map, key): if self.readonly or not handle: return - if isinstance(handle, str): - handle = handle.encode('utf-8') if handle in data_map: + obj = self._tables[KEY_TO_CLASS_MAP[key]]["class_func"].create(data_map[handle]) del data_map[handle] + del data_id_map[obj.gramps_id] self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) def delete_primary_from_reference_map(self, handle, transaction, txn=None): @@ -1486,6 +1508,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): ## Missing: def backup(self): + ## FIXME pass def close(self): @@ -1499,6 +1522,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): touch(filename) def find_backlink_handles(self, handle, include_classes=None): + ## FIXME return [] def find_initial_person(self): @@ -1508,22 +1532,30 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def find_place_child_handles(self, handle): + ## FIXME return [] def get_bookmarks(self): return self.bookmarks def get_child_reference_types(self): + ## FIXME return [] def get_citation_bookmarks(self): return self.citation_bookmarks def get_cursor(self, table, txn=None, update=False, commit=False): + ## FIXME + ## called from a complete find_back_ref pass - def get_dbname(self): - return "DictionaryDb" + # 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_default_handle(self): items = self.person_map.keys() @@ -1532,124 +1564,141 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def get_event_attribute_types(self): + ## FIXME return [] def get_event_bookmarks(self): return self.event_bookmarks def get_event_roles(self): + ## FIXME return [] def get_event_types(self): + ## FIXME return [] def get_family_attribute_types(self): + ## FIXME return [] def get_family_bookmarks(self): return self.family_bookmarks def get_family_event_types(self): + ## FIXME return [] def get_family_relation_types(self): + ## FIXME return [] def get_media_attribute_types(self): + ## FIXME return [] def get_media_bookmarks(self): return self.media_bookmarks def get_name_types(self): + ## FIXME return [] def get_note_bookmarks(self): return self.note_bookmarks def get_note_types(self): + ## FIXME return [] - def get_number_of_records(self, table): - return 0 - def get_origin_types(self): + ## FIXME return [] def get_person_attribute_types(self): + ## FIXME return [] def get_person_event_types(self): + ## FIXME return [] def get_place_bookmarks(self): return self.place_bookmarks def get_place_tree_cursor(self): + ## FIXME return [] def get_place_types(self): + ## FIXME return [] def get_repo_bookmarks(self): return self.repo_bookmarks def get_repository_types(self): + ## FIXME return [] def get_save_path(self): return self._directory def get_source_attribute_types(self): + ## FIXME return [] def get_source_bookmarks(self): return self.source_bookmarks def get_source_media_types(self): + ## FIXME return [] def get_surname_list(self): + ## FIXME return [] def get_url_types(self): + ## FIXME return [] def has_changed(self): + ## FIXME return True def is_open(self): - return True + return self._directory is not None def iter_citation_handles(self): return (key for key in self.citation_map.keys()) def iter_citations(self): - return (key for key in self.citation_map.values()) + return (Citation.create(key) for key in self.citation_map.values()) def iter_event_handles(self): return (key for key in self.event_map.keys()) def iter_events(self): - return (key for key in self.event_map.values()) + return (Events.create(key) for key in self.event_map.values()) def iter_media_objects(self): - return (key for key in self.media_map.values()) + return (MediaObject.create(key) for key in self.media_map.values()) def iter_note_handles(self): return (key for key in self.note_map.keys()) def iter_notes(self): - return (key for key in self.note_map.values()) + return (Note.create(key) for key in self.note_map.values()) def iter_place_handles(self): return (key for key in self.place_map.keys()) def iter_places(self): - return (key for key in self.place_map.values()) + return (Place.create(key) for key in self.place_map.values()) def iter_repositories(self): - return (key for key in self.repositories_map.values()) + return (Repository.create(key) for key in self.repositories_map.values()) def iter_repository_handles(self): return (key for key in self.repositories_map.keys()) @@ -1658,13 +1707,13 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return (key for key in self.source_map.keys()) def iter_sources(self): - return (key for key in self.source_map.values()) + return (Source.create(key) for key in self.source_map.values()) def iter_tag_handles(self): return (key for key in self.tag_map.keys()) def iter_tags(self): - return (key for key in self.tag_map.values()) + return (Tag.create(key) for key in self.tag_map.values()) def load(self, directory, pulse_progress=None, mode=None, force_schema_upgrade=False, @@ -1682,16 +1731,20 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): importData(self, filename, User()) def prepare_import(self): + ## FIXME pass def redo(self, update_history=True): + ## FIXME pass def restore(self): + ## FIXME pass def set_prefixes(self, person, media, family, source, citation, place, event, repository, note): + ## FIXME pass def set_save_path(self, directory): @@ -1701,6 +1754,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.brief_name = os.path.basename(self._directory) def undo(self, update_history=True): + ## FIXME pass def write_version(self, directory): @@ -1749,3 +1803,10 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): name = None return name + def reindex_reference_map(self): + ## FIXME + pass + + def rebuild_secondary(self, update): + ## FIXME + pass diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 182295645..f0d1faf44 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -44,7 +44,7 @@ from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext from gramps.gen.lib import (Person, Family, Event, Place, Repository, Citation, Source, Note, MediaObject, Tag, - Researcher) + Researcher, GenderStats) from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn from gramps.gen.db.undoredo import DbUndo from gramps.gen.utils.callback import Callback @@ -352,6 +352,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.full_name = None self.path = None self.brief_name = None + self.genderStats = GenderStats() # can pass in loaded stats as dict + self.owner = Researcher() if directory: self.load(directory) @@ -788,8 +790,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def get_researcher(self): - obj = Researcher() - return obj + return self.owner def get_tag_handles(self, sort_handles=False): if sort_handles: @@ -1704,7 +1705,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): return def set_researcher(self, owner): - pass + self.owner.set_from(owner) def copy_from_db(self, db): """ @@ -2037,9 +2038,10 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def remove_from_surname_list(self, person): ## FIXME + ## called by a complete commit_person pass - ## missing + ## was missing def find_place_child_handles(self, handle): pass @@ -2146,3 +2148,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): name = None return name + def reindex_reference_map(self): + pass + + def rebuild_secondary(self, update): + pass From b095c6b6061f0aa1f437606592cad1b8fa52dce3 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Fri, 15 May 2015 23:26:22 -0400 Subject: [PATCH 077/105] DjangoDb: always force a gramps_id; typo fix --- gramps/plugins/database/djangodb.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index f0d1faf44..ca55eb0b1 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -1429,7 +1429,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_person(self, person, trans, set_gid=True): if not person.handle: person.handle = create_id() - if not person.gramps_id and set_gid: + if not person.gramps_id: person.gramps_id = self.find_next_person_gramps_id() self.commit_person(person, trans) return person.handle @@ -1437,7 +1437,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_family(self, family, trans, set_gid=True): if not family.handle: family.handle = create_id() - if not family.gramps_id and set_gid: + if not family.gramps_id: family.gramps_id = self.find_next_family_gramps_id() self.commit_family(family, trans) return family.handle @@ -1445,7 +1445,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_citation(self, citation, trans, set_gid=True): if not citation.handle: citation.handle = create_id() - if not citation.gramps_id and set_gid: + if not citation.gramps_id: citation.gramps_id = self.find_next_citation_gramps_id() self.commit_citation(citation, trans) return citation.handle @@ -1453,7 +1453,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_source(self, source, trans, set_gid=True): if not source.handle: source.handle = create_id() - if not source.gramps_id and set_gid: + if not source.gramps_id: source.gramps_id = self.find_next_source_gramps_id() self.commit_source(source, trans) return source.handle @@ -1461,7 +1461,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_repository(self, repository, trans, set_gid=True): if not repository.handle: repository.handle = create_id() - if not repository.gramps_id and set_gid: + if not repository.gramps_id: repository.gramps_id = self.find_next_repository_gramps_id() self.commit_repository(repository, trans) return repository.handle @@ -1469,7 +1469,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_note(self, note, trans, set_gid=True): if not note.handle: note.handle = create_id() - if not note.gramps_id and set_gid: + if not note.gramps_id: note.gramps_id = self.find_next_note_gramps_id() self.commit_note(note, trans) return note.handle @@ -1477,7 +1477,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_place(self, place, trans, set_gid=True): if not place.handle: place.handle = create_id() - if not place.gramps_id and set_gid: + if not place.gramps_id: place.gramps_id = self.find_next_place_gramps_id() self.commit_place(place, trans) return place.handle @@ -1485,7 +1485,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_event(self, event, trans, set_gid=True): if not event.handle: event.handle = create_id() - if not event.gramps_id and set_gid: + if not event.gramps_id: event.gramps_id = self.find_next_event_gramps_id() self.commit_event(event, trans) return event.handle @@ -1493,7 +1493,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def add_tag(self, tag, trans): if not tag.handle: tag.handle = create_id() - self.commit_event(tag, trans) + self.commit_tag(tag, trans) return tag.handle def add_object(self, obj, transaction, set_gid=True): @@ -1505,7 +1505,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): """ if not obj.handle: obj.handle = create_id() - if not obj.gramps_id and set_gid: + if not obj.gramps_id: obj.gramps_id = self.find_next_object_gramps_id() self.commit_media_object(obj, transaction) return obj.handle @@ -1664,7 +1664,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: self.emit("tag-add", ([tag.handle],)) - def commit_media_object(self, media, transaction, change_time=None): + def commit_media_object(self, media, trans, change_time=None): """ Commit the specified MediaObject to the database, storing the changes as part of the transaction. From 576db27e9de6ed812bf6c4e484a0b858ead6a2bb Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 16 May 2015 00:16:33 -0400 Subject: [PATCH 078/105] Importers: added db.prepare_import/db.commit_import to wrap imports --- gramps/plugins/database/bsddb_support/write.py | 12 ++++++++++++ gramps/plugins/database/dictionarydb.py | 13 +++++++++++++ gramps/plugins/database/djangodb.py | 2 +- gramps/plugins/importer/importcsv.py | 2 ++ gramps/plugins/importer/importgedcom.py | 2 ++ gramps/plugins/importer/importgeneweb.py | 2 ++ gramps/plugins/importer/importgpkg.py | 2 ++ gramps/plugins/importer/importprogen.py | 2 ++ gramps/plugins/importer/importvcard.py | 2 ++ gramps/plugins/importer/importxml.py | 2 ++ gramps/webapp/reports.py | 4 ++-- 11 files changed, 42 insertions(+), 3 deletions(-) diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py index 46430ee85..65bf71980 100644 --- a/gramps/plugins/database/bsddb_support/write.py +++ b/gramps/plugins/database/bsddb_support/write.py @@ -2472,6 +2472,18 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): _("Version"): bsddb_version, } + def prepare_import(self): + """ + Initialization before imports + """ + pass + + def commit_import(self): + """ + Post process after imports + """ + pass + def mk_backup_name(database, base): """ Return the backup name of the database table diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 38ad398ac..44e3721fa 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -1810,3 +1810,16 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): def rebuild_secondary(self, update): ## FIXME pass + + def prepare_import(self): + """ + Initialization before imports + """ + pass + + def commit_import(self): + """ + Post process after imports + """ + pass + diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index ca55eb0b1..f7fe47892 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -518,7 +518,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dji.add_tag_detail(obj.serialize()) self.use_import_cache = False self.import_cache = {} - self.dji.update_publics() + self.request_rebuild() def transaction_commit(self, txn): pass diff --git a/gramps/plugins/importer/importcsv.py b/gramps/plugins/importer/importcsv.py index 4c1e61764..44f7d29ed 100644 --- a/gramps/plugins/importer/importcsv.py +++ b/gramps/plugins/importer/importcsv.py @@ -103,8 +103,10 @@ def importData(dbase, filename, user): parser = CSVParser(dbase, user, (config.get('preferences.tag-on-import-format') if config.get('preferences.tag-on-import') else None)) try: + dbase.prepare_import() with open(filename, 'r') as filehandle: parser.parse(filehandle) + dbase.commit_import() except EnvironmentError as err: user.notify_error(_("%s could not be opened\n") % filename, str(err)) return diff --git a/gramps/plugins/importer/importgedcom.py b/gramps/plugins/importer/importgedcom.py index 5f095674a..34a1d4876 100644 --- a/gramps/plugins/importer/importgedcom.py +++ b/gramps/plugins/importer/importgedcom.py @@ -131,7 +131,9 @@ def importData(database, filename, user): try: read_only = database.readonly database.readonly = False + database.prepare_import() gedparse.parse_gedcom_file(False) + database.commit_import() database.readonly = read_only ifile.close() except IOError as msg: diff --git a/gramps/plugins/importer/importgeneweb.py b/gramps/plugins/importer/importgeneweb.py index 1ad57fc16..5147ebc57 100644 --- a/gramps/plugins/importer/importgeneweb.py +++ b/gramps/plugins/importer/importgeneweb.py @@ -87,7 +87,9 @@ def importData(database, filename, user): return try: + database.prepare_import() status = g.parse_geneweb_file() + database.commit_import() except IOError as msg: errmsg = _("%s could not be opened\n") % filename user.notify_error(errmsg,str(msg)) diff --git a/gramps/plugins/importer/importgpkg.py b/gramps/plugins/importer/importgpkg.py index 3c7f1b4c7..2a164b09d 100644 --- a/gramps/plugins/importer/importgpkg.py +++ b/gramps/plugins/importer/importgpkg.py @@ -93,7 +93,9 @@ def impData(database, name, user): imp_db_name = os.path.join(tmpdir_path, XMLFILE) importer = importData + database.prepare_import() info = importer(database, imp_db_name, user) + database.commit_import() newmediapath = database.get_mediapath() #import of gpkg should not change media path as all media has new paths! diff --git a/gramps/plugins/importer/importprogen.py b/gramps/plugins/importer/importprogen.py index 16b72eeea..3ad119660 100644 --- a/gramps/plugins/importer/importprogen.py +++ b/gramps/plugins/importer/importprogen.py @@ -75,7 +75,9 @@ def _importData(database, filename, user): return try: + database.prepare_import() status = g.parse_progen_file() + database.commit_import() except ProgenError as msg: user.notify_error(_("Pro-Gen data error"), str(msg)) return diff --git a/gramps/plugins/importer/importvcard.py b/gramps/plugins/importer/importvcard.py index 9495afb5f..e9f42d44b 100644 --- a/gramps/plugins/importer/importvcard.py +++ b/gramps/plugins/importer/importvcard.py @@ -63,8 +63,10 @@ def importData(database, filename, user): """Function called by Gramps to import data on persons in VCard format.""" parser = VCardParser(database) try: + database.prepare_import() with OpenFileOrStdin(filename) as filehandle: parser.parse(filehandle) + database.commit_import() except EnvironmentError as msg: user.notify_error(_("%s could not be opened\n") % filename, str(msg)) return diff --git a/gramps/plugins/importer/importxml.py b/gramps/plugins/importer/importxml.py index 3cbea0785..acee74f69 100644 --- a/gramps/plugins/importer/importxml.py +++ b/gramps/plugins/importer/importxml.py @@ -122,6 +122,7 @@ def importData(database, filename, user): line_cnt = 0 person_cnt = 0 + database.prepare_import() with ImportOpenFileContextManager(filename, user) as xml_file: if xml_file is None: return @@ -162,6 +163,7 @@ def importData(database, filename, user): "valid Gramps database.")) return + database.commit_import() database.readonly = read_only return info diff --git a/gramps/webapp/reports.py b/gramps/webapp/reports.py index c0c7f480e..35cc66356 100644 --- a/gramps/webapp/reports.py +++ b/gramps/webapp/reports.py @@ -85,9 +85,9 @@ def import_file(db, filename, user): print("ERROR:", name, exception) return False import_function = getattr(mod, pdata.import_function) - db.prepare_import() + #db.prepare_import() retval = import_function(db, filename, user) - db.commit_import() + #db.commit_import() return retval return False From cfb33ec1a23cfc21c3a69eba50914b6d06c89c79 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 16 May 2015 00:46:22 -0400 Subject: [PATCH 079/105] DjangoDb: typo, added logger --- gramps/gen/dbstate.py | 11 +++++++++++ gramps/plugins/database/djangodb.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py index 0d9c30d4a..85722464d 100644 --- a/gramps/gen/dbstate.py +++ b/gramps/gen/dbstate.py @@ -29,6 +29,14 @@ from .proxy.proxybase import ProxyDbBase from .utils.callback import Callback from .config import config +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +LOG = logging.getLogger(".dbstate") + class DbState(Callback): """ Provide a class to encapsulate the state of the database. @@ -152,6 +160,7 @@ class DbState(Callback): ## Work-around for databases that need sys refresh (django): def modules_is_set(self): + LOG.warn("modules_is_set?") if hasattr(self, "_modules"): return self._modules != None else: @@ -159,6 +168,7 @@ class DbState(Callback): return False def reset_modules(self): + LOG.warn("reset_modules!") # First, clear out old modules: for key in list(sys.modules.keys()): del(sys.modules[key]) @@ -167,5 +177,6 @@ class DbState(Callback): sys.modules[key] = self._modules[key] def save_modules(self): + LOG.warn("save_modules!") self._modules = sys.modules.copy() diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index f7fe47892..c28cc9265 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -1670,7 +1670,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): as part of the transaction. """ if self.use_import_cache: - self.import_cache[obj.handle] = media + self.import_cache[media.handle] = media else: raw = media.serialize() items = self.dji.Media.filter(handle=media.handle) From 4805c3c7f0f5a76640be69d2d070747fc2e99b44 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 16 May 2015 01:40:05 -0400 Subject: [PATCH 080/105] DjangoDb: force load when write_version/create to make work with reset modules --- gramps/gen/dbstate.py | 6 +++--- gramps/plugins/database/djangodb.py | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py index 85722464d..9d1ce8071 100644 --- a/gramps/gen/dbstate.py +++ b/gramps/gen/dbstate.py @@ -160,7 +160,7 @@ class DbState(Callback): ## Work-around for databases that need sys refresh (django): def modules_is_set(self): - LOG.warn("modules_is_set?") + LOG.info("modules_is_set?") if hasattr(self, "_modules"): return self._modules != None else: @@ -168,7 +168,7 @@ class DbState(Callback): return False def reset_modules(self): - LOG.warn("reset_modules!") + LOG.info("reset_modules!") # First, clear out old modules: for key in list(sys.modules.keys()): del(sys.modules[key]) @@ -177,6 +177,6 @@ class DbState(Callback): sys.modules[key] = self._modules[key] def save_modules(self): - LOG.warn("save_modules!") + LOG.info("save_modules!") self._modules = sys.modules.copy() diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index c28cc9265..cb8f70238 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -362,6 +362,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): force_bsddb_upgrade=False, force_bsddb_downgrade=False, force_python_upgrade=False): + _LOG.info("Django loading...") self._directory = directory self.full_name = os.path.abspath(self._directory) self.path = self.full_name @@ -380,9 +381,11 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def __getattr__(self, item): return self.dictionary[item] + _LOG.info("Django loading defaults from: " + directory) try: settings.configure(Module(default_settings)) except RuntimeError: + _LOG.info("Django already configured error! Shouldn't happen!") # already configured; ignore pass @@ -455,6 +458,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.url_types = set() self.media_attributes = set() self.place_types = set() + _LOG.info("Django loading... done!") def prepare_import(self): """ @@ -2108,6 +2112,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): for filename in os.listdir(defaults): fullpath = os.path.abspath(os.path.join(defaults, filename)) shutil.copy2(fullpath, directory) + # force load, to get all modules loaded because of reset issue + self.load(directory) def report_bm_change(self): """ From 8a15aaffb700ac14a1846f6c224aa1b16641cc82 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 16 May 2015 02:15:02 -0400 Subject: [PATCH 081/105] Added missing function; allow failed plugin message to show --- gramps/gen/db/__init__.py | 21 +++++++++++++++++++++ gramps/gui/viewmanager.py | 7 ++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py index 066571e38..10f751303 100644 --- a/gramps/gen/db/__init__.py +++ b/gramps/gen/db/__init__.py @@ -88,3 +88,24 @@ from .base import * from .dbconst import * from .txn import * from .exceptions import * + +def find_surname_name(key, data): + """ + Creating a surname from raw name, to use for sort and index + returns a byte string + """ + return __index_surname(data[5]) + +def __index_surname(surn_list): + """ + All non pa/matronymic surnames are used in indexing. + pa/matronymic not as they change for every generation! + returns a byte string + """ + from gramps.gen.lib import NameOriginType + if surn_list: + surn = " ".join([x[0] for x in surn_list if not (x[3][0] in [ + NameOriginType.PATRONYMIC, NameOriginType.MATRONYMIC]) ]) + else: + surn = "" + return surn diff --git a/gramps/gui/viewmanager.py b/gramps/gui/viewmanager.py index cb2be6f16..11e37a1c9 100644 --- a/gramps/gui/viewmanager.py +++ b/gramps/gui/viewmanager.py @@ -1593,6 +1593,11 @@ def run_plugin(pdata, dbstate, uistate): mod = pmgr.load_plugin(pdata) if not mod: #import of plugin failed + failed = pmgr.get_fail_list() + if failed: + error_msg = failed[-1][1][1] + else: + error_msg = "(no error message)" ErrorDialog( _('Failed Loading Plugin'), _('The plugin %(name)s did not load and reported an error.\n\n' @@ -1607,7 +1612,7 @@ def run_plugin(pdata, dbstate, uistate): 'gramps_bugtracker_url' : URL_BUGHOME, 'firstauthoremail': pdata.authors_email[0] if pdata.authors_email else '...', - 'error_msg': pmgr.get_fail_list()[-1][1][1]}) + 'error_msg': error_msg}) return if pdata.ptype == REPORT: From 97fd387c346b5fba623988ef8a95d53070154d38 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 16 May 2015 09:18:41 -0400 Subject: [PATCH 082/105] Removed duplicate methods --- gramps/plugins/database/dictionarydb.py | 8 -------- gramps/plugins/database/djangodb.py | 21 +++++---------------- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 44e3721fa..878afa880 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -695,10 +695,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): def get_name_group_mapping(self, key): return None - def get_researcher(self): - obj = Researcher() - return obj - def get_person_handles(self, sort_handles=False): ## Fixme: implement sort return self.person_map.keys() @@ -1730,10 +1726,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): if os.path.isfile(filename): importData(self, filename, User()) - def prepare_import(self): - ## FIXME - pass - def redo(self, update_history=True): ## FIXME pass diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index cb8f70238..0091d033a 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -96,8 +96,10 @@ class Table(object): """ return self.funcs["cursor_func"]() - def put(key, data, txn=None): - self[key] = data + def put(self, key, data, txn=None): + ## FIXME: probably needs implementing? + #self[key] = data + pass class Map(dict): """ @@ -196,6 +198,7 @@ class DjangoTxn(DbTxn): def put(self, handle, new_data, txn): """ """ + ## FIXME: probably not correct? txn[handle] = new_data def commit(self): @@ -2053,20 +2056,6 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): def get_cursor(self, table, txn=None, update=False, commit=False): pass - def get_from_name_and_handle(self, table_name, handle): - """ - Returns a gen.lib object (or None) given table_name and - handle. - - Examples: - - >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") - >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") - """ - if table_name in self._tables: - return self._tables[table_name]["handle_func"](handle) - return None - def get_number_of_records(self, table): pass From 8d3657f54aa14a082882cfc3b4bd8226c935d6da Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sun, 17 May 2015 11:38:31 -0400 Subject: [PATCH 083/105] Diff: fixed import of DictionaryDb; removed mistaken tag.gramps_id references in DictionartDb --- gramps/gen/merge/diff.py | 2 +- gramps/plugins/database/dictionarydb.py | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/gramps/gen/merge/diff.py b/gramps/gen/merge/diff.py index 1ba5a45f7..0993638c3 100644 --- a/gramps/gen/merge/diff.py +++ b/gramps/gen/merge/diff.py @@ -28,7 +28,7 @@ from gramps.cli.user import User from ..dbstate import DbState from gramps.cli.grampscli import CLIManager from ..plug import BasePluginManager -from ..db.dictionary import DictionaryDb +from gramps.plugins.database.dictionarydb import DictionaryDb from gramps.gen.lib.handle import HandleClass, Handle from gramps.gen.lib import * from gramps.gen.lib.personref import PersonRef diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 878afa880..549732b34 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -402,7 +402,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.event_map = Map(Table(self._tables["Event"])) self.event_id_map = {} self.tag_map = Map(Table(self._tables["Tag"])) - self.tag_id_map = {} self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) self.name_group = {} self.undo_callback = None @@ -867,11 +866,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return Note.create(self.note_id_map[gramps_id]) return None - def get_tag_from_gramps_id(self, gramps_id): - if gramps_id in self.tag_id_map: - return Tag.create(self.tag_id_map[gramps_id]) - return None - def get_number_of_people(self): return len(self.person_map) @@ -1238,7 +1232,6 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: emit = "tag-add" self.tag_map[tag.handle] = tag.serialize() - self.tag_id_map[tag.gramps_id] = self.tag_map[tag.handle] # Emit after added: if emit: self.emit(emit, ([tag.handle],)) @@ -1423,7 +1416,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): database, preserving the change in the passed transaction. """ self.__do_remove(handle, transaction, self.tag_map, - self.tag_id_map, TAG_KEY) + None, TAG_KEY) def is_empty(self): """ @@ -1440,7 +1433,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): if handle in data_map: obj = self._tables[KEY_TO_CLASS_MAP[key]]["class_func"].create(data_map[handle]) del data_map[handle] - del data_id_map[obj.gramps_id] + if data_id_map: + del data_id_map[obj.gramps_id] self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) def delete_primary_from_reference_map(self, handle, transaction, txn=None): From 8a657250b128a63755ca4797ec55aebeec582571 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sun, 17 May 2015 19:01:21 -0400 Subject: [PATCH 084/105] DictionaryDb: give handle in bytes, handle as str internally in dict --- gramps/plugins/database/dictionarydb.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 549732b34..2eda2460d 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -735,60 +735,80 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return self.tag_map.keys() def get_event_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") event = None if handle in self.event_map: event = Event.create(self.event_map[handle]) return event def get_family_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") family = None if handle in self.family_map: family = Family.create(self.family_map[handle]) return family def get_repository_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") repository = None if handle in self.repository_map: repository = Repository.create(self.repository_map[handle]) return repository def get_person_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") person = None if handle in self.person_map: person = Person.create(self.person_map[handle]) return person def get_place_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") place = None if handle in self.place_map: place = Place.create(self.place_map[handle]) return place def get_citation_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") citation = None if handle in self.citation_map: citation = Citation.create(self.citation_map[handle]) return citation def get_source_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") source = None if handle in self.source_map: source = Source.create(self.source_map[handle]) return source def get_note_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") note = None if handle in self.note_map: note = Note.create(self.note_map[handle]) return note def get_object_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") media = None if handle in self.media_map: media = MediaObject.create(self.media_map[handle]) return media def get_tag_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") tag = None if handle in self.tag_map: tag = Tag.create(self.tag_map[handle]) From 8d92b5d63442ea4f3b200d3ee1d362d5f66664b4 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Thu, 21 May 2015 10:51:36 -0400 Subject: [PATCH 085/105] Added DbState.open_database() for opening without DbManager --- gramps/gen/dbstate.py | 56 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/gramps/gen/dbstate.py b/gramps/gen/dbstate.py index 9d1ce8071..b232d612e 100644 --- a/gramps/gen/dbstate.py +++ b/gramps/gen/dbstate.py @@ -23,6 +23,8 @@ Provide the database state class """ import sys +import os +import io from .db import DbReadBase from .proxy.proxybase import ProxyDbBase @@ -62,9 +64,10 @@ class DbState(Callback): Closes the existing db, and opens a new one. Retained for backward compatibility. """ - self.emit('no-database', ()) - self.db.close() - self.change_database_noclose(database) + if database: + self.emit('no-database', ()) + self.db.close() + self.change_database_noclose(database) def change_database_noclose(self, database): """ @@ -158,6 +161,53 @@ class DbState(Callback): database = getattr(mod, pdata.databaseclass) return database() + def open_database(self, dbname, force_unlock=False, callback=None): + """ + Open a database by name and return the database. + """ + data = self.lookup_family_tree(dbname) + database = None + if data: + dbpath, locked, locked_by, backend = data + if (not locked) or (locked and force_unlock): + database = self.make_database(backend) + database.load(dbpath, callback=callback) + return database + + def lookup_family_tree(self, dbname): + """ + Find a Family Tree given its name, and return properties. + """ + dbdir = os.path.expanduser(config.get('behavior.database-path')) + for dpath in os.listdir(dbdir): + dirpath = os.path.join(dbdir, dpath) + path_name = os.path.join(dirpath, "name.txt") + if os.path.isfile(path_name): + file = io.open(path_name, 'r', encoding='utf8') + name = file.readline().strip() + file.close() + if dbname == name: + locked = False + locked_by = None + backend = None + fname = os.path.join(dirpath, "database.txt") + if os.path.isfile(fname): + ifile = io.open(fname, 'r', encoding='utf8') + backend = ifile.read().strip() + ifile.close() + else: + backend = "bsddb" + try: + fname = os.path.join(dirpath, "lock") + ifile = io.open(fname, 'r', encoding='utf8') + locked_by = ifile.read().strip() + locked = True + ifile.close() + except (OSError, IOError): + pass + return (dirpath, locked, locked_by, backend) + return None + ## Work-around for databases that need sys refresh (django): def modules_is_set(self): LOG.info("modules_is_set?") From 8babc69dc5c30a6db37894a978edb520162ed604 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 01:34:16 -0400 Subject: [PATCH 086/105] WIP: Added DB-API 2.0 interface; needs to load/save details from dbdir; currently using sqlite :memory: database. But could use any DB-API 2.0 compatible layers. --- gramps/plugins/database/dbapi.gpr.py | 31 + gramps/plugins/database/dbapi.py | 2231 +++++++++++++++++++++++ gramps/plugins/database/dictionarydb.py | 2 +- 3 files changed, 2263 insertions(+), 1 deletion(-) create mode 100644 gramps/plugins/database/dbapi.gpr.py create mode 100644 gramps/plugins/database/dbapi.py diff --git a/gramps/plugins/database/dbapi.gpr.py b/gramps/plugins/database/dbapi.gpr.py new file mode 100644 index 000000000..2749e7a50 --- /dev/null +++ b/gramps/plugins/database/dbapi.gpr.py @@ -0,0 +1,31 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com> +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +plg = newplugin() +plg.id = 'dbapi' +plg.name = _("DB-API 2.0") +plg.name_accell = _("DB-_API 2.0") +plg.description = _("DB-API 2.0 Database Backend") +plg.version = '1.0' +plg.gramps_target_version = "4.2" +plg.status = STABLE +plg.fname = 'dbapi.py' +plg.ptype = DATABASE +plg.databaseclass = 'DBAPI' diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py new file mode 100644 index 000000000..b5d61e5dc --- /dev/null +++ b/gramps/plugins/database/dbapi.py @@ -0,0 +1,2231 @@ +#------------------------------------------------------------------------ +# +# Python Modules +# +#------------------------------------------------------------------------ +import pickle +import base64 +import time +import re +import os +import logging + +#------------------------------------------------------------------------ +# +# Gramps Modules +# +#------------------------------------------------------------------------ +import gramps +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext +from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, + KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP) +from gramps.gen.utils.callback import Callback +from gramps.gen.updatecallback import UpdateCallback +from gramps.gen.db.undoredo import DbUndo +from gramps.gen.db.dbconst import * +from gramps.gen.db import (PERSON_KEY, + FAMILY_KEY, + CITATION_KEY, + SOURCE_KEY, + EVENT_KEY, + MEDIA_KEY, + PLACE_KEY, + REPOSITORY_KEY, + NOTE_KEY, + TAG_KEY) + +from gramps.gen.utils.id import create_id +from gramps.gen.lib.researcher import Researcher +from gramps.gen.lib.mediaobj import MediaObject +from gramps.gen.lib.person import Person +from gramps.gen.lib.family import Family +from gramps.gen.lib.src import Source +from gramps.gen.lib.citation import Citation +from gramps.gen.lib.event import Event +from gramps.gen.lib.place import Place +from gramps.gen.lib.repo import Repository +from gramps.gen.lib.note import Note +from gramps.gen.lib.tag import Tag +from gramps.gen.lib.genderstats import GenderStats + +_LOG = logging.getLogger(DBLOGNAME) + +def touch(fname, mode=0o666, dir_fd=None, **kwargs): + ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python + flags = os.O_CREAT | os.O_APPEND + with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f: + os.utime(f.fileno() if os.utime in os.supports_fd else fname, + dir_fd=None if os.supports_fd else dir_fd, **kwargs) + +class Environment(object): + """ + Implements the Environment API. + """ + def __init__(self, db): + self.db = db + + def txn_begin(self): + return DBAPITxn("DBAPIDb Transaction", self.db) + +class Table(object): + """ + Implements Table interface. + """ + def __init__(self, funcs): + self.funcs = funcs + + def cursor(self): + """ + Returns a Cursor for this Table. + """ + return self.funcs["cursor_func"]() + + def put(self, key, data, txn=None): + self.funcs["add_func"](data, txn) + +class Map(object): + """ + Implements the map API for person_map, etc. + + Takes a Table() as argument. + """ + def __init__(self, table, + keys_func="handles_func", + contains_func="has_handle_func", + *args, **kwargs): + super().__init__(*args, **kwargs) + self.table = table + self.keys_func = keys_func + self.contains_func = contains_func + + def keys(self): + return self.table.funcs[self.keys_func]() + + def values(self): + return self.table.funcs["cursor_func"]() + + def __contains__(self, key): + return self.table.funcs[self.contains_func](key) + + def __getitem__(self, key): + if self.table.funcs[self.contains_func](key): + return self.table.funcs["raw_func"](key) + + def __len__(self): + return self.table.funcs["count_func"]() + +class MetaCursor(object): + def __init__(self): + pass + def __enter__(self): + return self + def __iter__(self): + return self.__next__() + def __next__(self): + yield None + def __exit__(self, *args, **kwargs): + pass + def iter(self): + yield None + def first(self): + self._iter = self.__iter__() + return self.next() + def next(self): + try: + return next(self._iter) + except: + return None + def close(self): + pass + +class Cursor(object): + def __init__(self, map): + self.map = map + self._iter = self.__iter__() + def __enter__(self): + return self + def __iter__(self): + for item in self.map.keys(): + yield (bytes(item, "utf-8"), self.map[item]) + def __next__(self): + try: + return self._iter.__next__() + except StopIteration: + return None + def __exit__(self, *args, **kwargs): + pass + def iter(self): + for item in self.map.keys(): + yield (bytes(item, "utf-8"), self.map[item]) + def first(self): + self._iter = self.__iter__() + try: + return next(self._iter) + except: + return + def next(self): + try: + return next(self._iter) + except: + return + def close(self): + pass + +class Bookmarks(object): + def __init__(self): + self.handles = [] + def get(self): + return self.handles + def append(self, handle): + self.handles.append(handle) + +class DBAPITxn(DbTxn): + def __init__(self, message, db, batch=False): + DbTxn.__init__(self, message, db, batch) + + 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 DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): + """ + A Gramps Database Backend. This replicates the grampsdb functions. + """ + __signals__ = dict((obj+'-'+op, signal) + for obj in + ['person', 'family', 'event', 'place', + 'source', 'citation', 'media', 'note', 'repository', 'tag'] + for op, signal in zip( + ['add', 'update', 'delete', 'rebuild'], + [(list,), (list,), (list,), None] + ) + ) + + # 2. Signals for long operations + __signals__.update(('long-op-'+op, signal) for op, signal in zip( + ['start', 'heartbeat', 'end'], + [(object,), None, None] + )) + + # 3. Special signal for change in home person + __signals__['home-person-changed'] = None + + # 4. Signal for change in person group name, parameters are + __signals__['person-groupname-rebuild'] = (str, str) + + __callback_map = {} + + def __init__(self, directory=None): + DbReadBase.__init__(self) + DbWriteBase.__init__(self) + Callback.__init__(self) + self._tables['Person'].update( + { + "handle_func": self.get_person_from_handle, + "gramps_id_func": self.get_person_from_gramps_id, + "class_func": Person, + "cursor_func": self.get_person_cursor, + "handles_func": self.get_person_handles, + "add_func": self.add_person, + "commit_func": self.commit_person, + "iter_func": self.iter_people, + "ids_func": self.get_person_gramps_ids, + "has_handle_func": self.has_handle_for_person, + "has_gramps_id_func": self.has_gramps_id_for_person, + "count": self.get_number_of_people, + "raw_func": self._get_raw_person_data, + }) + self._tables['Family'].update( + { + "handle_func": self.get_family_from_handle, + "gramps_id_func": self.get_family_from_gramps_id, + "class_func": Family, + "cursor_func": self.get_family_cursor, + "handles_func": self.get_family_handles, + "add_func": self.add_family, + "commit_func": self.commit_family, + "iter_func": self.iter_families, + "ids_func": self.get_family_gramps_ids, + "has_handle_func": self.has_handle_for_family, + "has_gramps_id_func": self.has_gramps_id_for_family, + "count": self.get_number_of_families, + "raw_func": self._get_raw_family_data, + }) + self._tables['Source'].update( + { + "handle_func": self.get_source_from_handle, + "gramps_id_func": self.get_source_from_gramps_id, + "class_func": Source, + "cursor_func": self.get_source_cursor, + "handles_func": self.get_source_handles, + "add_func": self.add_source, + "commit_func": self.commit_source, + "iter_func": self.iter_sources, + "ids_func": self.get_source_gramps_ids, + "has_handle_func": self.has_handle_for_source, + "has_gramps_id_func": self.has_gramps_id_for_source, + "count": self.get_number_of_sources, + "raw_func": self._get_raw_source_data, + }) + self._tables['Citation'].update( + { + "handle_func": self.get_citation_from_handle, + "gramps_id_func": self.get_citation_from_gramps_id, + "class_func": Citation, + "cursor_func": self.get_citation_cursor, + "handles_func": self.get_citation_handles, + "add_func": self.add_citation, + "commit_func": self.commit_citation, + "iter_func": self.iter_citations, + "ids_func": self.get_citation_gramps_ids, + "has_handle_func": self.has_handle_for_citation, + "has_gramps_id_func": self.has_gramps_id_for_citation, + "count": self.get_number_of_citations, + "raw_func": self._get_raw_citation_data, + }) + self._tables['Event'].update( + { + "handle_func": self.get_event_from_handle, + "gramps_id_func": self.get_event_from_gramps_id, + "class_func": Event, + "cursor_func": self.get_event_cursor, + "handles_func": self.get_event_handles, + "add_func": self.add_event, + "commit_func": self.commit_event, + "iter_func": self.iter_events, + "ids_func": self.get_event_gramps_ids, + "has_handle_func": self.has_handle_for_event, + "has_gramps_id_func": self.has_gramps_id_for_event, + "count": self.get_number_of_events, + "raw_func": self._get_raw_event_data, + }) + self._tables['Media'].update( + { + "handle_func": self.get_object_from_handle, + "gramps_id_func": self.get_object_from_gramps_id, + "class_func": MediaObject, + "cursor_func": self.get_media_cursor, + "handles_func": self.get_media_object_handles, + "add_func": self.add_object, + "commit_func": self.commit_media_object, + "iter_func": self.iter_media_objects, + "ids_func": self.get_media_gramps_ids, + "has_handle_func": self.has_handle_for_media, + "has_gramps_id_func": self.has_gramps_id_for_media, + "count": self.get_number_of_media_objects, + "raw_func": self._get_raw_media_data, + }) + self._tables['Place'].update( + { + "handle_func": self.get_place_from_handle, + "gramps_id_func": self.get_place_from_gramps_id, + "class_func": Place, + "cursor_func": self.get_place_cursor, + "handles_func": self.get_place_handles, + "add_func": self.add_place, + "commit_func": self.commit_place, + "iter_func": self.iter_places, + "ids_func": self.get_place_gramps_ids, + "has_handle_func": self.has_handle_for_place, + "has_gramps_id_func": self.has_gramps_id_for_place, + "count": self.get_number_of_places, + "raw_func": self._get_raw_place_data, + }) + self._tables['Repository'].update( + { + "handle_func": self.get_repository_from_handle, + "gramps_id_func": self.get_repository_from_gramps_id, + "class_func": Repository, + "cursor_func": self.get_repository_cursor, + "handles_func": self.get_repository_handles, + "add_func": self.add_repository, + "commit_func": self.commit_repository, + "iter_func": self.iter_repositories, + "ids_func": self.get_repository_gramps_ids, + "has_handle_func": self.has_handle_for_repository, + "has_gramps_id_func": self.has_gramps_id_for_repository, + "count": self.get_number_of_repositories, + "raw_func": self._get_raw_repository_data, + }) + self._tables['Note'].update( + { + "handle_func": self.get_note_from_handle, + "gramps_id_func": self.get_note_from_gramps_id, + "class_func": Note, + "cursor_func": self.get_note_cursor, + "handles_func": self.get_note_handles, + "add_func": self.add_note, + "commit_func": self.commit_note, + "iter_func": self.iter_notes, + "ids_func": self.get_note_gramps_ids, + "has_handle_func": self.has_handle_for_note, + "has_gramps_id_func": self.has_gramps_id_for_note, + "count": self.get_number_of_notes, + "raw_func": self._get_raw_note_data, + }) + self._tables['Tag'].update( + { + "handle_func": self.get_tag_from_handle, + "gramps_id_func": None, + "class_func": Tag, + "cursor_func": self.get_tag_cursor, + "handles_func": self.get_tag_handles, + "add_func": self.add_tag, + "commit_func": self.commit_tag, + "iter_func": self.iter_tags, + "count": self.get_number_of_tags, + }) + # skip GEDCOM cross-ref check for now: + self.set_feature("skip-check-xref", True) + self.set_feature("skip-import-additions", 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 = DBAPITxn("ID Transaction", self) + self.fid_trans = DBAPITxn("FID Transaction", self) + self.pid_trans = DBAPITxn("PID Transaction", self) + self.cid_trans = DBAPITxn("CID Transaction", self) + self.sid_trans = DBAPITxn("SID Transaction", self) + self.oid_trans = DBAPITxn("OID Transaction", self) + self.rid_trans = DBAPITxn("RID Transaction", self) + self.nid_trans = DBAPITxn("NID Transaction", self) + self.eid_trans = DBAPITxn("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 = Environment(self) + self.person_map = Map(Table(self._tables["Person"])) + self.person_id_map = Map(Table(self._tables["Person"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.family_map = Map(Table(self._tables["Family"])) + self.family_id_map = Map(Table(self._tables["Family"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.place_map = Map(Table(self._tables["Place"])) + self.place_id_map = Map(Table(self._tables["Place"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.citation_map = Map(Table(self._tables["Citation"])) + self.citation_id_map = Map(Table(self._tables["Citation"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.source_map = Map(Table(self._tables["Source"])) + self.source_id_map = Map(Table(self._tables["Source"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.repository_map = Map(Table(self._tables["Repository"])) + self.repository_id_map = Map(Table(self._tables["Repository"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.note_map = Map(Table(self._tables["Note"])) + self.note_id_map = Map(Table(self._tables["Note"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.media_map = Map(Table(self._tables["Media"])) + self.media_id_map = Map(Table(self._tables["Media"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.event_map = Map(Table(self._tables["Event"])) + self.event_id_map = Map(Table(self._tables["Event"]), + keys_func="ids_func", + contains_func="has_gramps_id_func") + self.tag_map = Map(Table(self._tables["Tag"])) + self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) + self.name_group = {} + self.undo_callback = None + self.redo_callback = None + self.undo_history_callback = None + self.modified = 0 + self.txn = DBAPITxn("DBAPI Transaction", self) + self.transaction = None + self.undodb = DbUndo(self) + self.abort_possible = False + self._bm_changes = 0 + self._directory = directory + self.full_name = None + self.path = None + self.brief_name = None + self.genderStats = GenderStats() # can pass in loaded stats as dict + self.owner = Researcher() + if directory: + self.load(directory) + + def version_supported(self): + """Return True when the file has a supported version.""" + return True + + def get_table_names(self): + """Return a list of valid table names.""" + return list(self._tables.keys()) + + def get_table_metadata(self, table_name): + """Return the metadata for a valid table name.""" + if table_name in self._tables: + return self._tables[table_name] + return None + + def transaction_commit(self, txn): + ## FIXME + pass + + def get_undodb(self): + ## FIXME + return None + + def transaction_abort(self, txn): + ## FIXME + pass + + @staticmethod + def _validated_id_prefix(val, default): + if isinstance(val, str) 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 = int(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_<object>_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_person_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from person;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_family_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from family;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_event_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from event;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_citation_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from citation;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_source_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from source;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_place_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from place;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_repository_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from repository;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_media_object_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from media;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_note_handles(self, sort_handles=False): + ## Fixme: implement sort + cur = self.dbapi.execute("select handle from note;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_tag_handles(self, sort_handles=False): + # FIXME: implement sort + cur = self.dbapi.execute("select handle from tag;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_event_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + event = None + if handle in self.event_map: + event = Event.create(self._get_raw_event_data(handle)) + return event + + def get_family_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + family = None + if handle in self.family_map: + family = Family.create(self._get_raw_family_data(handle)) + return family + + def get_repository_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + repository = None + if handle in self.repository_map: + repository = Repository.create(self._get_raw_repository_data(handle)) + return repository + + def get_person_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + person = None + if handle in self.person_map: + person = Person.create(self._get_raw_person_data(handle)) + return person + + def get_place_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + place = None + if handle in self.place_map: + place = Place.create(self._get_raw_place_data(handle)) + return place + + def get_citation_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + citation = None + if handle in self.citation_map: + citation = Citation.create(self._get_raw_citation_data(handle)) + return citation + + def get_source_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + source = None + if handle in self.source_map: + source = Source.create(self._get_raw_source_data(handle)) + return source + + def get_note_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + note = None + if handle in self.note_map: + note = Note.create(self._get_raw_note_data(handle)) + return note + + def get_object_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + media = None + if handle in self.media_map: + media = MediaObject.create(self._get_raw_media_data(handle)) + return media + + def get_tag_from_handle(self, handle): + if isinstance(handle, bytes): + handle = str(handle, "utf-8") + tag = None + if handle in self.tag_map: + tag = Tag.create(self._get_raw_tag_data(handle)) + return tag + + def get_default_person(self): + handle = self.get_default_handle() + if handle: + return self.get_person_from_handle(handle) + else: + return None + + def iter_people(self): + return (Person.create(data[1]) for data in self.get_person_cursor()) + + def iter_person_handles(self): + return (data[0] for data in self.get_person_cursor()) + + def iter_families(self): + return (Family.create(data[1]) for data in self.get_family_cursor()) + + def iter_family_handles(self): + return (handle for handle in self.family_map.keys()) + + def get_tag_from_name(self, name): + ## Slow, but typically not too many tags: + for data in self.tag_map.values(): + tag = Tag.create(data) + if tag.name == name: + return tag + return None + + def get_person_from_gramps_id(self, gramps_id): + if gramps_id in self.person_id_map: + return Person.create(self.person_id_map[gramps_id]) + return None + + def get_family_from_gramps_id(self, gramps_id): + if gramps_id in self.family_id_map: + return Family.create(self.family_id_map[gramps_id]) + return None + + def get_citation_from_gramps_id(self, gramps_id): + if gramps_id in self.citation_id_map: + return Citation.create(self.citation_id_map[gramps_id]) + return None + + def get_source_from_gramps_id(self, gramps_id): + if gramps_id in self.source_id_map: + return Source.create(self.source_id_map[gramps_id]) + return None + + def get_event_from_gramps_id(self, gramps_id): + if gramps_id in self.event_id_map: + return Event.create(self.event_id_map[gramps_id]) + return None + + def get_media_from_gramps_id(self, gramps_id): + if gramps_id in self.media_id_map: + return MediaObject.create(self.media_id_map[gramps_id]) + return None + + def get_place_from_gramps_id(self, gramps_id): + if gramps_id in self.place_id_map: + return Place.create(self.place_id_map[gramps_id]) + return None + + def get_repository_from_gramps_id(self, gramps_id): + if gramps_id in self.repository_id_map: + return Repository.create(self.repository_id_map[gramps_id]) + return None + + def get_note_from_gramps_id(self, gramps_id): + if gramps_id in self.note_id_map: + return Note.create(self.note_id_map[gramps_id]) + return None + + def get_number_of_people(self): + cur = self.dbapi.execute("select count(handle) from person;") + row = cur.fetchone() + return row[0] + + def get_number_of_events(self): + cur = self.dbapi.execute("select count(handle) from event;") + row = cur.fetchone() + return row[0] + + def get_number_of_places(self): + cur = self.dbapi.execute("select count(handle) from place;") + row = cur.fetchone() + return row[0] + + def get_number_of_tags(self): + cur = self.dbapi.execute("select count(handle) from tag;") + row = cur.fetchone() + return row[0] + + def get_number_of_families(self): + cur = self.dbapi.execute("select count(handle) from family;") + row = cur.fetchone() + return row[0] + + def get_number_of_notes(self): + cur = self.dbapi.execute("select count(handle) from note;") + row = cur.fetchone() + return row[0] + + def get_number_of_citations(self): + cur = self.dbapi.execute("select count(handle) from citation;") + row = cur.fetchone() + return row[0] + + def get_number_of_sources(self): + cur = self.dbapi.execute("select count(handle) from source;") + row = cur.fetchone() + return row[0] + + def get_number_of_media_objects(self): + cur = self.dbapi.execute("select count(handle) from media;") + row = cur.fetchone() + return row[0] + + def get_number_of_repositories(self): + cur = self.dbapi.execute("select count(handle) from repository;") + row = cur.fetchone() + return row[0] + + def get_place_cursor(self): + return Cursor(self.place_map) + + def get_person_cursor(self): + return Cursor(self.person_map) + + def get_family_cursor(self): + return Cursor(self.family_map) + + def get_event_cursor(self): + return Cursor(self.event_map) + + def get_note_cursor(self): + return Cursor(self.note_map) + + def get_tag_cursor(self): + return Cursor(self.tag_map) + + def get_repository_cursor(self): + return Cursor(self.repository_map) + + def get_media_cursor(self): + return Cursor(self.media_map) + + def get_citation_cursor(self): + return Cursor(self.citation_map) + + def get_source_cursor(self): + return Cursor(self.source_map) + + def has_gramps_id(self, obj_key, gramps_id): + key2table = { + PERSON_KEY: self.person_id_map, + FAMILY_KEY: self.family_id_map, + SOURCE_KEY: self.source_id_map, + CITATION_KEY: self.citation_id_map, + EVENT_KEY: self.event_id_map, + MEDIA_KEY: self.media_id_map, + PLACE_KEY: self.place_id_map, + REPOSITORY_KEY: self.repository_id_map, + NOTE_KEY: self.note_id_map, + } + return gramps_id in key2table[obj_key] + + 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): + ## FIXME + pass + + def set_mediapath(self, mediapath): + ## FIXME + pass + + def get_raw_person_data(self, handle): + if handle in self.person_map: + return self.person_map[handle] + return None + + def get_raw_family_data(self, handle): + if handle in self.family_map: + return self.family_map[handle] + return None + + def get_raw_citation_data(self, handle): + if handle in self.citation_map: + return self.citation_map[handle] + return None + + def get_raw_source_data(self, handle): + if handle in self.source_map: + return self.source_map[handle] + return None + + def get_raw_repository_data(self, handle): + if handle in self.repository_map: + return self.repository_map[handle] + return None + + def get_raw_note_data(self, handle): + if handle in self.note_map: + return self.note_map[handle] + return None + + def get_raw_place_data(self, handle): + if handle in self.place_map: + return self.place_map[handle] + return None + + def get_raw_object_data(self, handle): + if handle in self.media_map: + return self.media_map[handle] + return None + + def get_raw_tag_data(self, handle): + if handle in self.tag_map: + return self.tag_map[handle] + return None + + def get_raw_event_data(self, handle): + if handle in self.event_map: + return self.event_map[handle] + return None + + def add_person(self, person, trans, set_gid=True): + if not person.handle: + person.handle = create_id() + if not person.gramps_id and 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 = create_id() + if not family.gramps_id and 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 = create_id() + if not citation.gramps_id and 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 = create_id() + if not source.gramps_id and 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 = create_id() + if not repository.gramps_id and 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 = create_id() + if not note.gramps_id and 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 = create_id() + if not place.gramps_id and 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 = create_id() + if not event.gramps_id and 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 = create_id() + self.commit_tag(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 = create_id() + if not obj.gramps_id and 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): + emit = None + if True or not trans.batch: + if person.handle in self.person_map: + emit = "person-update" + self.dbapi.execute("""UPDATE person SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [person.gramps_id, + pickle.dumps(person.serialize()), + person.handle]) + else: + emit = "person-add" + self.dbapi.execute("""insert into person(handle, gramps_id, blob) + values(?, ?, ?);""", + [person.handle, person.gramps_id, pickle.dumps(person.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([person.handle],)) + + def commit_family(self, family, trans, change_time=None): + emit = None + if True or not trans.batch: + if family.handle in self.family_map: + emit = "family-update" + self.dbapi.execute("""UPDATE family SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [family.gramps_id, + pickle.dumps(family.serialize()), + family.handle]) + else: + emit = "family-add" + self.dbapi.execute("insert into family values(?, ?, ?);", + [family.handle, family.gramps_id, + pickle.dumps(family.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([family.handle],)) + + def commit_citation(self, citation, trans, change_time=None): + emit = None + if True or not trans.batch: + if citation.handle in self.citation_map: + emit = "citation-update" + self.dbapi.execute("""UPDATE citation SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [citation.gramps_id, + pickle.dumps(citation.serialize()), + citation.handle]) + else: + emit = "citation-add" + self.dbapi.execute("insert into citation values(?, ?, ?);", + [citation.handle, citation.gramps_id, pickle.dumps(citation.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([citation.handle],)) + + def commit_source(self, source, trans, change_time=None): + emit = None + if True or not trans.batch: + if source.handle in self.source_map: + emit = "source-update" + self.dbapi.execute("""UPDATE source SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [source.gramps_id, + pickle.dumps(source.serialize()), + source.handle]) + else: + emit = "source-add" + self.dbapi.execute("insert into source values(?, ?, ?);", + [source.handle, source.gramps_id, pickle.dumps(source.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([source.handle],)) + + def commit_repository(self, repository, trans, change_time=None): + emit = None + if True or not trans.batch: + if repository.handle in self.repository_map: + emit = "repository-update" + self.dbapi.execute("""UPDATE repository SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [repository.gramps_id, + pickle.dumps(repository.serialize()), + repository.handle]) + else: + emit = "repository-add" + self.dbapi.execute("insert into repository values(?, ?, ?);", + [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([repository.handle],)) + + def commit_note(self, note, trans, change_time=None): + emit = None + if True or not trans.batch: + if note.handle in self.note_map: + emit = "note-update" + self.dbapi.execute("""UPDATE note SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [note.gramps_id, + pickle.dumps(note.serialize()), + note.handle]) + else: + emit = "note-add" + self.dbapi.execute("insert into note values(?, ?, ?);", + [note.handle, note.gramps_id, pickle.dumps(note.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([note.handle],)) + + def commit_place(self, place, trans, change_time=None): + emit = None + if True or not trans.batch: + if place.handle in self.place_map: + emit = "place-update" + self.dbapi.execute("""UPDATE place SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [place.gramps_id, + pickle.dumps(place.serialize()), + place.handle]) + else: + emit = "place-add" + self.dbapi.execute("insert into place values(?, ?, ?);", + [place.handle, place.gramps_id, pickle.dumps(place.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([place.handle],)) + + def commit_event(self, event, trans, change_time=None): + emit = None + if True or not trans.batch: + if event.handle in self.event_map: + emit = "event-update" + self.dbapi.execute("""UPDATE event SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [event.gramps_id, + pickle.dumps(event.serialize()), + event.handle]) + else: + emit = "event-add" + self.dbapi.execute("insert into event values(?, ?, ?);", + [event.handle, event.gramps_id, pickle.dumps(event.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([event.handle],)) + + def commit_tag(self, tag, trans, change_time=None): + emit = None + if True or not trans.batch: + if tag.handle in self.tag_map: + emit = "tag-update" + self.dbapi.execute("""UPDATE tag SET blob = ? + WHERE handle = ?;""", + [pickle.dumps(tag.serialize()), + tag.handle]) + else: + emit = "tag-add" + self.dbapi.execute("insert into tag values(?, ?);", + [tag.handle, pickle.dumps(tag.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([tag.handle],)) + + def commit_media_object(self, media, trans, change_time=None): + emit = None + if True or not trans.batch: + if media.handle in self.media_map: + emit = "media-update" + self.dbapi.execute("""UPDATE media SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [media.gramps_id, + pickle.dumps(media.serialize()), + media.handle]) + else: + emit = "media-add" + self.dbapi.execute("insert into media values(?, ?, ?);", + [media.handle, media.gramps_id, pickle.dumps(media.serialize())]) + # Emit after added: + if emit: + self.emit(emit, ([media.handle],)) + + def get_gramps_ids(self, obj_key): + key2table = { + PERSON_KEY: self.person_id_map, + FAMILY_KEY: self.family_id_map, + CITATION_KEY: self.citation_id_map, + SOURCE_KEY: self.source_id_map, + EVENT_KEY: self.event_id_map, + MEDIA_KEY: self.media_id_map, + PLACE_KEY: self.place_id_map, + REPOSITORY_KEY: self.repository_id_map, + NOTE_KEY: self.note_id_map, + } + return list(key2table[obj_key].keys()) + + def transaction_begin(self, transaction): + ## FIXME + return + + def set_researcher(self, owner): + self.owner.set_from(owner) + + def get_researcher(self): + return self.owner + + def request_rebuild(self): + self.emit('person-rebuild') + self.emit('family-rebuild') + self.emit('place-rebuild') + self.emit('source-rebuild') + self.emit('citation-rebuild') + self.emit('media-rebuild') + self.emit('event-rebuild') + self.emit('repository-rebuild') + self.emit('note-rebuild') + self.emit('tag-rebuild') + + def copy_from_db(self, db): + """ + A (possibily) implementation-specific method to get data from + db into this database. + """ + for key in db._tables.keys(): + cursor = db._tables[key]["cursor_func"] + class_ = db._tables[key]["class_func"] + for (handle, data) in cursor(): + map = getattr(self, "%s_map" % key.lower()) + map[handle] = class_.create(data) + + def get_transaction_class(self): + """ + Get the transaction class associated with this database backend. + """ + return DBAPITxn + + def get_from_name_and_handle(self, table_name, handle): + """ + Returns a gen.lib object (or None) given table_name and + handle. + + Examples: + + >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") + >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") + """ + if table_name in self._tables: + return self._tables[table_name]["handle_func"](handle) + return None + + def get_from_name_and_gramps_id(self, table_name, gramps_id): + """ + Returns a gen.lib object (or None) given table_name and + Gramps ID. + + Examples: + + >>> self.get_from_name_and_gramps_id("Person", "I00002") + >>> self.get_from_name_and_gramps_id("Family", "F056") + >>> self.get_from_name_and_gramps_id("Media", "M00012") + """ + if table_name in self._tables: + return self._tables[table_name]["gramps_id_func"](gramps_id) + return None + + def remove_person(self, handle, transaction): + """ + Remove the Person specified by the database handle from the database, + preserving the change in the passed transaction. + """ + + if self.readonly or not handle: + return + if handle in self.person_map: + person = Person.create(self.person_map[handle]) + #del self.person_map[handle] + #del self.person_id_map[person.gramps_id] + self.dbapi.execute("DELETE from person WHERE handle = ?;", [handle]) + self.emit("person-delete", ([handle],)) + + def remove_source(self, handle, transaction): + """ + Remove the Source specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.source_map, + self.source_id_map, SOURCE_KEY) + + def remove_citation(self, handle, transaction): + """ + Remove the Citation specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.citation_map, + self.citation_id_map, CITATION_KEY) + + def remove_event(self, handle, transaction): + """ + Remove the Event specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.event_map, + self.event_id_map, EVENT_KEY) + + def remove_object(self, handle, transaction): + """ + Remove the MediaObjectPerson specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.media_map, + self.media_id_map, MEDIA_KEY) + + def remove_place(self, handle, transaction): + """ + Remove the Place specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.place_map, + self.place_id_map, PLACE_KEY) + + def remove_family(self, handle, transaction): + """ + Remove the Family specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.family_map, + self.family_id_map, FAMILY_KEY) + + def remove_repository(self, handle, transaction): + """ + Remove the Repository specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.repository_map, + self.repository_id_map, REPOSITORY_KEY) + + def remove_note(self, handle, transaction): + """ + Remove the Note specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.note_map, + self.note_id_map, NOTE_KEY) + + def remove_tag(self, handle, transaction): + """ + Remove the Tag specified by the database handle from the + database, preserving the change in the passed transaction. + """ + self.__do_remove(handle, transaction, self.tag_map, + None, TAG_KEY) + + def is_empty(self): + """ + Return true if there are no [primary] records in the database + """ + for table in self._tables: + if len(self._tables[table]["handles_func"]()) > 0: + return False + return True + + def __do_remove(self, handle, transaction, data_map, data_id_map, key): + key2table = { + PERSON_KEY: "person", + FAMILY_KEY: "family", + SOURCE_KEY: "source", + CITATION_KEY: "citation", + EVENT_KEY: "event", + MEDIA_KEY: "media", + PLACE_KEY: "place", + REPOSITORY_KEY: "repository", + NOTE_KEY: "note", + } + if self.readonly or not handle: + return + if handle in data_map: + self.dbapi.execute("DELETE from %s WHERE handle = ?;" % key2table[key], + [handle]) + self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) + + def delete_primary_from_reference_map(self, handle, transaction, txn=None): + """ + Remove all references to the primary object from the reference_map. + handle should be utf-8 + """ + 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.decode('utf-8'), 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 __remove_reference(self, key, transaction, txn): + """ + Remove the reference specified by the key, preserving the change in + the passed transaction. + """ + if isinstance(key, tuple): + #create a byte string key, first validity check in python 3! + for val in key: + if isinstance(val, bytes): + raise DbError(_('An attempt is made to save a reference key ' + 'which is partly bytecode, this is not allowed.\n' + 'Key is %s') % str(key)) + key = str(key) + if isinstance(key, str): + key = key.encode('utf-8') + if not self.readonly: + if not transaction.batch: + old_data = self.reference_map.get(key, txn=txn) + transaction.add(REFERENCE_KEY, TXNDEL, key, old_data, None) + #transaction.reference_del.append(str(key)) + self.reference_map.delete(key, txn=txn) + + ## Missing: + + def backup(self): + ## FIXME + pass + + def close(self): + if self._directory: + from gramps.plugins.export.exportxml import XmlWriter + from gramps.cli.user import User + writer = XmlWriter(self, User(), strip_photos=0, compress=1) + filename = os.path.join(self._directory, "data.gramps") + writer.write(filename) + filename = os.path.join(self._directory, "meta_data.db") + touch(filename) + + def find_backlink_handles(self, handle, include_classes=None): + ## FIXME + return [] + + def find_initial_person(self): + items = self.person_map.keys() + if len(items) > 0: + return self.get_person_from_handle(list(items)[0]) + return None + + def find_place_child_handles(self, handle): + ## FIXME + return [] + + def get_bookmarks(self): + return self.bookmarks + + def get_child_reference_types(self): + ## FIXME + return [] + + def get_citation_bookmarks(self): + return self.citation_bookmarks + + def get_cursor(self, table, txn=None, update=False, commit=False): + ## FIXME + ## called from a complete find_back_ref + pass + + # 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_default_handle(self): + items = self.person_map.keys() + if len(items) > 0: + return list(items)[0] + return None + + def get_event_attribute_types(self): + ## FIXME + return [] + + def get_event_bookmarks(self): + return self.event_bookmarks + + def get_event_roles(self): + ## FIXME + return [] + + def get_event_types(self): + ## FIXME + return [] + + def get_family_attribute_types(self): + ## FIXME + return [] + + def get_family_bookmarks(self): + return self.family_bookmarks + + def get_family_event_types(self): + ## FIXME + return [] + + def get_family_relation_types(self): + ## FIXME + return [] + + def get_media_attribute_types(self): + ## FIXME + return [] + + def get_media_bookmarks(self): + return self.media_bookmarks + + def get_name_types(self): + ## FIXME + return [] + + def get_note_bookmarks(self): + return self.note_bookmarks + + def get_note_types(self): + ## FIXME + return [] + + def get_origin_types(self): + ## FIXME + return [] + + def get_person_attribute_types(self): + ## FIXME + return [] + + def get_person_event_types(self): + ## FIXME + return [] + + def get_place_bookmarks(self): + return self.place_bookmarks + + def get_place_tree_cursor(self): + ## FIXME + return [] + + def get_place_types(self): + ## FIXME + return [] + + def get_repo_bookmarks(self): + return self.repo_bookmarks + + def get_repository_types(self): + ## FIXME + return [] + + def get_save_path(self): + return self._directory + + def get_source_attribute_types(self): + ## FIXME + return [] + + def get_source_bookmarks(self): + return self.source_bookmarks + + def get_source_media_types(self): + ## FIXME + return [] + + def get_surname_list(self): + ## FIXME + return [] + + def get_url_types(self): + ## FIXME + return [] + + def has_changed(self): + ## FIXME + return True + + def is_open(self): + return self._directory is not None + + def iter_citation_handles(self): + return (data[0] for data in self.get_citation_cursor()) + + def iter_citations(self): + return (Citation.create(data[1]) for data in self.get_citation_cursor()) + + def iter_event_handles(self): + return (data[0] for data in self.get_event_cursor()) + + def iter_events(self): + return (Event.create(data[1]) for data in self.get_event_cursor()) + + def iter_media_objects(self): + return (MediaObject.create(data[1]) for data in self.get_media_cursor()) + + def iter_note_handles(self): + return (data[0] for data in self.get_note_cursor()) + + def iter_notes(self): + return (Note.create(data[1]) for data in self.get_note_cursor()) + + def iter_place_handles(self): + return (data[0] for data in self.get_place_cursor()) + + def iter_places(self): + return (Place.create(data[1]) for data in self.get_place_cursor()) + + def iter_repositories(self): + return (Repository.create(data[1]) for data in self.get_repository_cursor()) + + def iter_repository_handles(self): + return (data[0] for data in self.get_repository_cursor()) + + def iter_source_handles(self): + return (data[0] for data in self.get_source_cursor()) + + def iter_sources(self): + return (Source.create(data[1]) for data in self.get_source_cursor()) + + def iter_tag_handles(self): + return (data[0] for data in self.get_tag_cursor()) + + def iter_tags(self): + return (Tag.create(data[1]) for data in self.get_tag_cursor()) + + def load(self, directory, pulse_progress=None, mode=None, + force_schema_upgrade=False, + force_bsddb_upgrade=False, + force_bsddb_downgrade=False, + force_python_upgrade=False): + # Run code from directory + import sqlite3 + self.dbapi = sqlite3.connect(':memory:') + self.dbapi.row_factory = sqlite3.Row # allows access by name + # make sure schema is up to date: + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS person ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS family ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS source ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS citation ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS event ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS media ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS place ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS repository ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS note ( + handle TEXT PRIMARY KEY NOT NULL, + gramps_id TEXT , + blob TEXT + );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS tag ( + handle TEXT PRIMARY KEY NOT NULL, + blob TEXT + );""") + + def redo(self, update_history=True): + ## FIXME + pass + + def restore(self): + ## FIXME + pass + + def set_prefixes(self, person, media, family, source, citation, + place, event, repository, note): + ## FIXME + pass + + def set_save_path(self, directory): + self._directory = directory + self.full_name = os.path.abspath(self._directory) + self.path = self.full_name + self.brief_name = os.path.basename(self._directory) + + def undo(self, update_history=True): + ## FIXME + pass + + def write_version(self, directory): + """Write files for a newly created DB.""" + versionpath = os.path.join(directory, str(DBBACKEND)) + _LOG.debug("Write database backend file to 'dbapi'") + with open(versionpath, "w") as version_file: + version_file.write("dbapi") + + def report_bm_change(self): + """ + Add 1 to the number of bookmark changes during this session. + """ + self._bm_changes += 1 + + def db_has_bm_changes(self): + """ + Return whethere there were bookmark changes during the session. + """ + return self._bm_changes > 0 + + def get_summary(self): + """ + Returns dictionary of summary item. + Should include, if possible: + + _("Number of people") + _("Version") + _("Schema version") + """ + return { + _("Number of people"): self.get_number_of_people(), + } + + def get_dbname(self): + """ + In DBAPI, the database is in a text file at the path + """ + filepath = os.path.join(self._directory, "name.txt") + try: + name_file = open(filepath, "r") + name = name_file.readline().strip() + name_file.close() + except (OSError, IOError) as msg: + _LOG.error(str(msg)) + name = None + return name + + def reindex_reference_map(self): + ## FIXME + pass + + def rebuild_secondary(self, update): + ## FIXME + pass + + def prepare_import(self): + """ + DBAPI does not commit data on gedcom import, but saves them + for later commit. + """ + pass + + def commit_import(self): + """ + Commits the items that were queued up during the last gedcom + import for two step adding. + """ + pass + + def has_handle_for_person(self, key): + cur = self.dbapi.execute("select * from person where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_family(self, key): + cur = self.dbapi.execute("select * from family where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_source(self, key): + cur = self.dbapi.execute("select * from source where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_citation(self, key): + cur = self.dbapi.execute("select * from citation where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_event(self, key): + cur = self.dbapi.execute("select * from event where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_media(self, key): + cur = self.dbapi.execute("select * from media where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_place(self, key): + cur = self.dbapi.execute("select * from place where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_repository(self, key): + cur = self.dbapi.execute("select * from repository where handle = ?", [key]) + return cur.fetchone() != None + + def has_handle_for_note(self, key): + cur = self.dbapi.execute("select * from note where handle = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_person(self, key): + cur = self.dbapi.execute("select * from person where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_family(self, key): + cur = self.dbapi.execute("select * from family where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_source(self, key): + cur = self.dbapi.execute("select * from source where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_citation(self, key): + cur = self.dbapi.execute("select * from citation where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_event(self, key): + cur = self.dbapi.execute("select * from event where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_media(self, key): + cur = self.dbapi.execute("select * from media where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_place(self, key): + cur = self.dbapi.execute("select * from place where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_repository(self, key): + cur = self.dbapi.execute("select * from repository where gramps_id = ?", [key]) + return cur.fetchone() != None + + def has_gramps_id_for_note(self, key): + cur = self.dbapi.execute("select * from note where gramps_id = ?", [key]) + return cur.fetchone() != None + + def get_person_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from person;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_family_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from family;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_source_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from source;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_citation_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from citation;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_event_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from event;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_media_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from media;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_place_gramps_ids(self): + cur = self.dbapi.execute("select gramps from place;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_repository_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from repository;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def get_note_gramps_ids(self): + cur = self.dbapi.execute("select gramps_id from note;") + rows = cur.fetchall() + return [row[0] for row in rows] + + def _get_raw_person_data(self, key): + cur = self.dbapi.execute("select blob from person where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_family_data(self, key): + cur = self.dbapi.execute("select blob from family where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_source_data(self, key): + cur = self.dbapi.execute("select blob from source where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_citation_data(self, key): + cur = self.dbapi.execute("select blob from citation where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_event_data(self, key): + cur = self.dbapi.execute("select blob from event where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_media_data(self, key): + cur = self.dbapi.execute("select blob from media where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_place_data(self, key): + cur = self.dbapi.execute("select blob from place where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_repository_data(self, key): + cur = self.dbapi.execute("select blob from repository where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_note_data(self, key): + cur = self.dbapi.execute("select blob from note where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + + def _get_raw_tag_data(self, key): + cur = self.dbapi.execute("select blob from tag where handle = ?", [key]) + row = cur.fetchone() + if row: + return pickle.loads(row[0]) + diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 2eda2460d..8b8d8beb7 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -1690,7 +1690,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): return (key for key in self.event_map.keys()) def iter_events(self): - return (Events.create(key) for key in self.event_map.values()) + return (Event.create(key) for key in self.event_map.values()) def iter_media_objects(self): return (MediaObject.create(key) for key in self.media_map.values()) From 31fe15e7826daa82ec7badfd015e5710e7ff4878 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 02:15:44 -0400 Subject: [PATCH 087/105] DB-API 2.0 can now load/save from file; need to load/save metadata --- gramps/plugins/database/dbapi.py | 31 +++++++++++++++++-- .../defaults/default_settings.py | 7 +++++ 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 gramps/plugins/database/dbapi_support/defaults/default_settings.py diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index b5d61e5dc..c99146b6d 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -9,6 +9,7 @@ import time import re import os import logging +import shutil #------------------------------------------------------------------------ # @@ -1261,6 +1262,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dbapi.execute("""insert into person(handle, gramps_id, blob) values(?, ?, ?);""", [person.handle, person.gramps_id, pickle.dumps(person.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([person.handle],)) @@ -1281,6 +1283,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dbapi.execute("insert into family values(?, ?, ?);", [family.handle, family.gramps_id, pickle.dumps(family.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([family.handle],)) @@ -1300,6 +1303,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "citation-add" self.dbapi.execute("insert into citation values(?, ?, ?);", [citation.handle, citation.gramps_id, pickle.dumps(citation.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([citation.handle],)) @@ -1319,6 +1323,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "source-add" self.dbapi.execute("insert into source values(?, ?, ?);", [source.handle, source.gramps_id, pickle.dumps(source.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([source.handle],)) @@ -1338,6 +1343,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "repository-add" self.dbapi.execute("insert into repository values(?, ?, ?);", [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([repository.handle],)) @@ -1357,6 +1363,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "note-add" self.dbapi.execute("insert into note values(?, ?, ?);", [note.handle, note.gramps_id, pickle.dumps(note.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([note.handle],)) @@ -1376,6 +1383,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "place-add" self.dbapi.execute("insert into place values(?, ?, ?);", [place.handle, place.gramps_id, pickle.dumps(place.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([place.handle],)) @@ -1395,6 +1403,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "event-add" self.dbapi.execute("insert into event values(?, ?, ?);", [event.handle, event.gramps_id, pickle.dumps(event.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([event.handle],)) @@ -1412,6 +1421,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "tag-add" self.dbapi.execute("insert into tag values(?, ?);", [tag.handle, pickle.dumps(tag.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([tag.handle],)) @@ -1431,6 +1441,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = "media-add" self.dbapi.execute("insert into media values(?, ?, ?);", [media.handle, media.gramps_id, pickle.dumps(media.serialize())]) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([media.handle],)) @@ -1706,6 +1717,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): writer.write(filename) filename = os.path.join(self._directory, "meta_data.db") touch(filename) + self.dbapi.close() def find_backlink_handles(self, handle, include_classes=None): ## FIXME @@ -1907,9 +1919,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): force_bsddb_downgrade=False, force_python_upgrade=False): # Run code from directory - import sqlite3 - self.dbapi = sqlite3.connect(':memory:') - self.dbapi.row_factory = sqlite3.Row # allows access by name + default_settings = {"__file__": + os.path.join(directory, "default_settings.py")} + settings_file = os.path.join(directory, "default_settings.py") + with open(settings_file) as f: + code = compile(f.read(), settings_file, 'exec') + exec(code, globals(), default_settings) + + self.dbapi = default_settings["dbapi"] + # make sure schema is up to date: self.dbapi.execute("""CREATE TABLE IF NOT EXISTS person ( handle TEXT PRIMARY KEY NOT NULL, @@ -1990,6 +2008,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): _LOG.debug("Write database backend file to 'dbapi'") with open(versionpath, "w") as version_file: version_file.write("dbapi") + # Write default_settings, sqlite.db + defaults = os.path.join(os.path.dirname(os.path.abspath(__file__)), + "dbapi_support", "defaults") + _LOG.debug("Copy defaults from: " + defaults) + for filename in os.listdir(defaults): + fullpath = os.path.abspath(os.path.join(defaults, filename)) + shutil.copy2(fullpath, directory) def report_bm_change(self): """ diff --git a/gramps/plugins/database/dbapi_support/defaults/default_settings.py b/gramps/plugins/database/dbapi_support/defaults/default_settings.py new file mode 100644 index 000000000..8931c8e89 --- /dev/null +++ b/gramps/plugins/database/dbapi_support/defaults/default_settings.py @@ -0,0 +1,7 @@ +import os +import sqlite3 + +path_to_db = os.path.join(os.path.dirname(os.path.realpath(__file__)), + 'sqlite.db') +dbapi = sqlite3.connect(path_to_db) +dbapi.row_factory = sqlite3.Row # allows access by name From d69f8a6d3c10dda8fe58eacc55896ae39f2d301a Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 09:54:04 -0400 Subject: [PATCH 088/105] Added support for sort_handles --- gramps/plugins/database/dbapi.py | 162 ++++++++++++++++++++++--------- 1 file changed, 114 insertions(+), 48 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index c99146b6d..3dd90ac49 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -38,16 +38,8 @@ from gramps.gen.db import (PERSON_KEY, from gramps.gen.utils.id import create_id from gramps.gen.lib.researcher import Researcher -from gramps.gen.lib.mediaobj import MediaObject -from gramps.gen.lib.person import Person -from gramps.gen.lib.family import Family -from gramps.gen.lib.src import Source -from gramps.gen.lib.citation import Citation -from gramps.gen.lib.event import Event -from gramps.gen.lib.place import Place -from gramps.gen.lib.repo import Repository -from gramps.gen.lib.note import Note -from gramps.gen.lib.tag import Tag +from gramps.gen.lib import (Tag, MediaObject, Person, Family, Source, Citation, Event, + Place, Repository, Note, NameOriginType) from gramps.gen.lib.genderstats import GenderStats _LOG = logging.getLogger(DBLOGNAME) @@ -761,62 +753,70 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def get_person_handles(self, sort_handles=False): - ## Fixme: implement sort - cur = self.dbapi.execute("select handle from person;") + if sort_handles: + cur = self.dbapi.execute("SELECT handle FROM person ORDER BY order_by;") + else: + cur = self.dbapi.execute("SELECT handle FROM person;") rows = cur.fetchall() return [row[0] for row in rows] - def get_family_handles(self, sort_handles=False): - ## Fixme: implement sort + def get_family_handles(self): cur = self.dbapi.execute("select handle from family;") rows = cur.fetchall() return [row[0] for row in rows] - def get_event_handles(self, sort_handles=False): - ## Fixme: implement sort + def get_event_handles(self): cur = self.dbapi.execute("select handle from event;") rows = cur.fetchall() return [row[0] for row in rows] def get_citation_handles(self, sort_handles=False): - ## Fixme: implement sort - cur = self.dbapi.execute("select handle from citation;") + if sort_handles: + cur = self.dbapi.execute("select handle from citation ORDER BY order_by;") + else: + cur = self.dbapi.execute("select handle from citation;") rows = cur.fetchall() return [row[0] for row in rows] def get_source_handles(self, sort_handles=False): - ## Fixme: implement sort - cur = self.dbapi.execute("select handle from source;") + if sort_handles: + cur = self.dbapi.execute("select handle from source ORDER BY order_by;") + else: + cur = self.dbapi.execute("select handle from source;") rows = cur.fetchall() return [row[0] for row in rows] def get_place_handles(self, sort_handles=False): - ## Fixme: implement sort - cur = self.dbapi.execute("select handle from place;") + if sort_handles: + cur = self.dbapi.execute("select handle from place ORDER BY order_by;") + else: + cur = self.dbapi.execute("select handle from place;") rows = cur.fetchall() return [row[0] for row in rows] - def get_repository_handles(self, sort_handles=False): - ## Fixme: implement sort + def get_repository_handles(self): cur = self.dbapi.execute("select handle from repository;") rows = cur.fetchall() return [row[0] for row in rows] def get_media_object_handles(self, sort_handles=False): - ## Fixme: implement sort - cur = self.dbapi.execute("select handle from media;") + if sort_handles: + cur = self.dbapi.execute("select handle from media ORDER BY order_by;") + else: + cur = self.dbapi.execute("select handle from media;") rows = cur.fetchall() return [row[0] for row in rows] - def get_note_handles(self, sort_handles=False): - ## Fixme: implement sort + def get_note_handles(self): cur = self.dbapi.execute("select handle from note;") rows = cur.fetchall() return [row[0] for row in rows] def get_tag_handles(self, sort_handles=False): - # FIXME: implement sort - cur = self.dbapi.execute("select handle from tag;") + if sort_handles: + cur = self.dbapi.execute("select handle from tag ORDER BY order_by;") + else: + cur = self.dbapi.execute("select handle from tag;") rows = cur.fetchall() return [row[0] for row in rows] @@ -1252,16 +1252,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if person.handle in self.person_map: emit = "person-update" self.dbapi.execute("""UPDATE person SET gramps_id = ?, + order_by = ?, blob = ? WHERE handle = ?;""", [person.gramps_id, + self._order_by_person_key(person), pickle.dumps(person.serialize()), person.handle]) else: emit = "person-add" - self.dbapi.execute("""insert into person(handle, gramps_id, blob) - values(?, ?, ?);""", - [person.handle, person.gramps_id, pickle.dumps(person.serialize())]) + self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) + values(?, ?, ?, ?);""", + [person.handle, + self._order_by_person_key(person), + person.gramps_id, + pickle.dumps(person.serialize())]) self.dbapi.commit() # Emit after added: if emit: @@ -1294,15 +1299,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if citation.handle in self.citation_map: emit = "citation-update" self.dbapi.execute("""UPDATE citation SET gramps_id = ?, - blob = ? + order_by = ?, + blob = ? WHERE handle = ?;""", [citation.gramps_id, + self._order_by_citation_key(citation), pickle.dumps(citation.serialize()), citation.handle]) else: emit = "citation-add" - self.dbapi.execute("insert into citation values(?, ?, ?);", - [citation.handle, citation.gramps_id, pickle.dumps(citation.serialize())]) + self.dbapi.execute("insert into citation values(?, ?, ?, ?);", + [citation.handle, + self._order_by_citation_key(citation), + citation.gramps_id, + pickle.dumps(citation.serialize())]) self.dbapi.commit() # Emit after added: if emit: @@ -1314,15 +1324,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if source.handle in self.source_map: emit = "source-update" self.dbapi.execute("""UPDATE source SET gramps_id = ?, + order_by = ?, blob = ? WHERE handle = ?;""", [source.gramps_id, + self._order_by_source_key(source), pickle.dumps(source.serialize()), source.handle]) else: emit = "source-add" - self.dbapi.execute("insert into source values(?, ?, ?);", - [source.handle, source.gramps_id, pickle.dumps(source.serialize())]) + self.dbapi.execute("insert into source values(?, ?, ?, ?);", + [source.handle, + self._order_by_source_key(source), + source.gramps_id, + pickle.dumps(source.serialize())]) self.dbapi.commit() # Emit after added: if emit: @@ -1374,15 +1389,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if place.handle in self.place_map: emit = "place-update" self.dbapi.execute("""UPDATE place SET gramps_id = ?, - blob = ? + order_by = ?, + blob = ? WHERE handle = ?;""", [place.gramps_id, + self._order_by_place_key(place), pickle.dumps(place.serialize()), place.handle]) else: emit = "place-add" - self.dbapi.execute("insert into place values(?, ?, ?);", - [place.handle, place.gramps_id, pickle.dumps(place.serialize())]) + self.dbapi.execute("insert into place values(?, ?, ?, ?);", + [place.handle, + self._order_by_place_key(place), + place.gramps_id, + pickle.dumps(place.serialize())]) self.dbapi.commit() # Emit after added: if emit: @@ -1402,7 +1422,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): else: emit = "event-add" self.dbapi.execute("insert into event values(?, ?, ?);", - [event.handle, event.gramps_id, pickle.dumps(event.serialize())]) + [event.handle, + event.gramps_id, + pickle.dumps(event.serialize())]) self.dbapi.commit() # Emit after added: if emit: @@ -1413,14 +1435,18 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if True or not trans.batch: if tag.handle in self.tag_map: emit = "tag-update" - self.dbapi.execute("""UPDATE tag SET blob = ? + self.dbapi.execute("""UPDATE tag SET blob = ?, + order_by = ? WHERE handle = ?;""", [pickle.dumps(tag.serialize()), + self._order_by_tag_key(tag), tag.handle]) else: emit = "tag-add" - self.dbapi.execute("insert into tag values(?, ?);", - [tag.handle, pickle.dumps(tag.serialize())]) + self.dbapi.execute("insert into tag values(?, ?, ?);", + [tag.handle, + self._order_by_tag_key(tag), + pickle.dumps(tag.serialize())]) self.dbapi.commit() # Emit after added: if emit: @@ -1432,15 +1458,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if media.handle in self.media_map: emit = "media-update" self.dbapi.execute("""UPDATE media SET gramps_id = ?, - blob = ? + order_by = ?, + blob = ? WHERE handle = ?;""", [media.gramps_id, + self._order_by_media_key(media), pickle.dumps(media.serialize()), media.handle]) else: emit = "media-add" - self.dbapi.execute("insert into media values(?, ?, ?);", - [media.handle, media.gramps_id, pickle.dumps(media.serialize())]) + self.dbapi.execute("insert into media values(?, ?, ?, ?);", + [media.handle, + self._order_by_media_key(media), + media.gramps_id, + pickle.dumps(media.serialize())]) self.dbapi.commit() # Emit after added: if emit: @@ -1931,6 +1962,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # make sure schema is up to date: self.dbapi.execute("""CREATE TABLE IF NOT EXISTS person ( handle TEXT PRIMARY KEY NOT NULL, + order_by TEXT , gramps_id TEXT , blob TEXT );""") @@ -1941,11 +1973,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): );""") self.dbapi.execute("""CREATE TABLE IF NOT EXISTS source ( handle TEXT PRIMARY KEY NOT NULL, + order_by TEXT , gramps_id TEXT , blob TEXT );""") self.dbapi.execute("""CREATE TABLE IF NOT EXISTS citation ( handle TEXT PRIMARY KEY NOT NULL, + order_by TEXT , gramps_id TEXT , blob TEXT );""") @@ -1956,11 +1990,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): );""") self.dbapi.execute("""CREATE TABLE IF NOT EXISTS media ( handle TEXT PRIMARY KEY NOT NULL, + order_by TEXT , gramps_id TEXT , blob TEXT );""") self.dbapi.execute("""CREATE TABLE IF NOT EXISTS place ( handle TEXT PRIMARY KEY NOT NULL, + order_by TEXT , gramps_id TEXT , blob TEXT );""") @@ -1976,6 +2012,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): );""") self.dbapi.execute("""CREATE TABLE IF NOT EXISTS tag ( handle TEXT PRIMARY KEY NOT NULL, + order_by TEXT , blob TEXT );""") @@ -2254,3 +2291,32 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if row: return pickle.loads(row[0]) + def _order_by_person_key(self, person): + """ + All non pa/matronymic surnames are used in indexing. + pa/matronymic not as they change for every generation! + returns a byte string + """ + if person.primary_name and person.primary_name.surname_list: + order_by = " ".join([x.surname for x in person.primary_name.surname_list if not + (int(x.origintype) in [NameOriginType.PATRONYMIC, + NameOriginType.MATRONYMIC]) ]) + else: + order_by = "" + return glocale.sort_key(order_by) + + def _order_by_place_key(self, place): + return glocale.sort_key(place.title) + + def _order_by_source_key(self, source): + return glocale.sort_key(source.title) + + def _order_by_citation_key(self, citation): + return glocale.sort_key(citation.page) + + def _order_by_media_key(self, media): + return glocale.sort_key(media.desc) + + def _order_by_tag_key(self, tag): + return glocale.sort_key(tag.get_name()) + From 8be61709b8d74266ce57cbbc36314ccce1096ad8 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 10:07:57 -0400 Subject: [PATCH 089/105] Now using batch transactions --- gramps/plugins/database/dbapi.py | 328 +++++++++++++++---------------- 1 file changed, 163 insertions(+), 165 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 3dd90ac49..774a4bdda 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -495,16 +495,14 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def transaction_commit(self, txn): - ## FIXME - pass + self.dbapi.commit() def get_undodb(self): ## FIXME return None def transaction_abort(self, txn): - ## FIXME - pass + self.dbapi.rollback() @staticmethod def _validated_id_prefix(val, default): @@ -1248,25 +1246,25 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_person(self, person, trans, change_time=None): emit = None - if True or not trans.batch: - if person.handle in self.person_map: - emit = "person-update" - self.dbapi.execute("""UPDATE person SET gramps_id = ?, - order_by = ?, - blob = ? - WHERE handle = ?;""", - [person.gramps_id, - self._order_by_person_key(person), - pickle.dumps(person.serialize()), - person.handle]) - else: - emit = "person-add" - self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) - values(?, ?, ?, ?);""", - [person.handle, - self._order_by_person_key(person), - person.gramps_id, - pickle.dumps(person.serialize())]) + if person.handle in self.person_map: + emit = "person-update" + self.dbapi.execute("""UPDATE person SET gramps_id = ?, + order_by = ?, + blob = ? + WHERE handle = ?;""", + [person.gramps_id, + self._order_by_person_key(person), + pickle.dumps(person.serialize()), + person.handle]) + else: + emit = "person-add" + self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) + values(?, ?, ?, ?);""", + [person.handle, + self._order_by_person_key(person), + person.gramps_id, + pickle.dumps(person.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1274,20 +1272,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_family(self, family, trans, change_time=None): emit = None - if True or not trans.batch: - if family.handle in self.family_map: - emit = "family-update" - self.dbapi.execute("""UPDATE family SET gramps_id = ?, - blob = ? - WHERE handle = ?;""", - [family.gramps_id, - pickle.dumps(family.serialize()), - family.handle]) - else: - emit = "family-add" - self.dbapi.execute("insert into family values(?, ?, ?);", - [family.handle, family.gramps_id, - pickle.dumps(family.serialize())]) + if family.handle in self.family_map: + emit = "family-update" + self.dbapi.execute("""UPDATE family SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [family.gramps_id, + pickle.dumps(family.serialize()), + family.handle]) + else: + emit = "family-add" + self.dbapi.execute("insert into family values(?, ?, ?);", + [family.handle, family.gramps_id, + pickle.dumps(family.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1295,24 +1293,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_citation(self, citation, trans, change_time=None): emit = None - if True or not trans.batch: - if citation.handle in self.citation_map: - emit = "citation-update" - self.dbapi.execute("""UPDATE citation SET gramps_id = ?, - order_by = ?, - blob = ? - WHERE handle = ?;""", - [citation.gramps_id, - self._order_by_citation_key(citation), - pickle.dumps(citation.serialize()), - citation.handle]) - else: - emit = "citation-add" - self.dbapi.execute("insert into citation values(?, ?, ?, ?);", - [citation.handle, - self._order_by_citation_key(citation), - citation.gramps_id, - pickle.dumps(citation.serialize())]) + if citation.handle in self.citation_map: + emit = "citation-update" + self.dbapi.execute("""UPDATE citation SET gramps_id = ?, + order_by = ?, + blob = ? + WHERE handle = ?;""", + [citation.gramps_id, + self._order_by_citation_key(citation), + pickle.dumps(citation.serialize()), + citation.handle]) + else: + emit = "citation-add" + self.dbapi.execute("insert into citation values(?, ?, ?, ?);", + [citation.handle, + self._order_by_citation_key(citation), + citation.gramps_id, + pickle.dumps(citation.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1320,24 +1318,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_source(self, source, trans, change_time=None): emit = None - if True or not trans.batch: - if source.handle in self.source_map: - emit = "source-update" - self.dbapi.execute("""UPDATE source SET gramps_id = ?, - order_by = ?, - blob = ? - WHERE handle = ?;""", - [source.gramps_id, - self._order_by_source_key(source), - pickle.dumps(source.serialize()), - source.handle]) - else: - emit = "source-add" - self.dbapi.execute("insert into source values(?, ?, ?, ?);", - [source.handle, - self._order_by_source_key(source), - source.gramps_id, - pickle.dumps(source.serialize())]) + if source.handle in self.source_map: + emit = "source-update" + self.dbapi.execute("""UPDATE source SET gramps_id = ?, + order_by = ?, + blob = ? + WHERE handle = ?;""", + [source.gramps_id, + self._order_by_source_key(source), + pickle.dumps(source.serialize()), + source.handle]) + else: + emit = "source-add" + self.dbapi.execute("insert into source values(?, ?, ?, ?);", + [source.handle, + self._order_by_source_key(source), + source.gramps_id, + pickle.dumps(source.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1345,19 +1343,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_repository(self, repository, trans, change_time=None): emit = None - if True or not trans.batch: - if repository.handle in self.repository_map: - emit = "repository-update" - self.dbapi.execute("""UPDATE repository SET gramps_id = ?, - blob = ? - WHERE handle = ?;""", - [repository.gramps_id, - pickle.dumps(repository.serialize()), - repository.handle]) - else: - emit = "repository-add" - self.dbapi.execute("insert into repository values(?, ?, ?);", - [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())]) + if repository.handle in self.repository_map: + emit = "repository-update" + self.dbapi.execute("""UPDATE repository SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [repository.gramps_id, + pickle.dumps(repository.serialize()), + repository.handle]) + else: + emit = "repository-add" + self.dbapi.execute("insert into repository values(?, ?, ?);", + [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1365,19 +1363,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_note(self, note, trans, change_time=None): emit = None - if True or not trans.batch: - if note.handle in self.note_map: - emit = "note-update" - self.dbapi.execute("""UPDATE note SET gramps_id = ?, - blob = ? - WHERE handle = ?;""", - [note.gramps_id, - pickle.dumps(note.serialize()), - note.handle]) - else: - emit = "note-add" - self.dbapi.execute("insert into note values(?, ?, ?);", - [note.handle, note.gramps_id, pickle.dumps(note.serialize())]) + if note.handle in self.note_map: + emit = "note-update" + self.dbapi.execute("""UPDATE note SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [note.gramps_id, + pickle.dumps(note.serialize()), + note.handle]) + else: + emit = "note-add" + self.dbapi.execute("insert into note values(?, ?, ?);", + [note.handle, note.gramps_id, pickle.dumps(note.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1385,24 +1383,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_place(self, place, trans, change_time=None): emit = None - if True or not trans.batch: - if place.handle in self.place_map: - emit = "place-update" - self.dbapi.execute("""UPDATE place SET gramps_id = ?, - order_by = ?, - blob = ? - WHERE handle = ?;""", - [place.gramps_id, - self._order_by_place_key(place), - pickle.dumps(place.serialize()), - place.handle]) - else: - emit = "place-add" - self.dbapi.execute("insert into place values(?, ?, ?, ?);", - [place.handle, - self._order_by_place_key(place), - place.gramps_id, - pickle.dumps(place.serialize())]) + if place.handle in self.place_map: + emit = "place-update" + self.dbapi.execute("""UPDATE place SET gramps_id = ?, + order_by = ?, + blob = ? + WHERE handle = ?;""", + [place.gramps_id, + self._order_by_place_key(place), + pickle.dumps(place.serialize()), + place.handle]) + else: + emit = "place-add" + self.dbapi.execute("insert into place values(?, ?, ?, ?);", + [place.handle, + self._order_by_place_key(place), + place.gramps_id, + pickle.dumps(place.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1410,21 +1408,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_event(self, event, trans, change_time=None): emit = None - if True or not trans.batch: - if event.handle in self.event_map: - emit = "event-update" - self.dbapi.execute("""UPDATE event SET gramps_id = ?, - blob = ? - WHERE handle = ?;""", - [event.gramps_id, - pickle.dumps(event.serialize()), - event.handle]) - else: - emit = "event-add" - self.dbapi.execute("insert into event values(?, ?, ?);", - [event.handle, - event.gramps_id, - pickle.dumps(event.serialize())]) + if event.handle in self.event_map: + emit = "event-update" + self.dbapi.execute("""UPDATE event SET gramps_id = ?, + blob = ? + WHERE handle = ?;""", + [event.gramps_id, + pickle.dumps(event.serialize()), + event.handle]) + else: + emit = "event-add" + self.dbapi.execute("insert into event values(?, ?, ?);", + [event.handle, + event.gramps_id, + pickle.dumps(event.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1432,21 +1430,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_tag(self, tag, trans, change_time=None): emit = None - if True or not trans.batch: - if tag.handle in self.tag_map: - emit = "tag-update" - self.dbapi.execute("""UPDATE tag SET blob = ?, - order_by = ? - WHERE handle = ?;""", - [pickle.dumps(tag.serialize()), - self._order_by_tag_key(tag), - tag.handle]) - else: - emit = "tag-add" - self.dbapi.execute("insert into tag values(?, ?, ?);", - [tag.handle, - self._order_by_tag_key(tag), - pickle.dumps(tag.serialize())]) + if tag.handle in self.tag_map: + emit = "tag-update" + self.dbapi.execute("""UPDATE tag SET blob = ?, + order_by = ? + WHERE handle = ?;""", + [pickle.dumps(tag.serialize()), + self._order_by_tag_key(tag), + tag.handle]) + else: + emit = "tag-add" + self.dbapi.execute("insert into tag values(?, ?, ?);", + [tag.handle, + self._order_by_tag_key(tag), + pickle.dumps(tag.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: @@ -1454,24 +1452,24 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def commit_media_object(self, media, trans, change_time=None): emit = None - if True or not trans.batch: - if media.handle in self.media_map: - emit = "media-update" - self.dbapi.execute("""UPDATE media SET gramps_id = ?, - order_by = ?, - blob = ? - WHERE handle = ?;""", - [media.gramps_id, - self._order_by_media_key(media), - pickle.dumps(media.serialize()), - media.handle]) - else: - emit = "media-add" - self.dbapi.execute("insert into media values(?, ?, ?, ?);", - [media.handle, - self._order_by_media_key(media), - media.gramps_id, - pickle.dumps(media.serialize())]) + if media.handle in self.media_map: + emit = "media-update" + self.dbapi.execute("""UPDATE media SET gramps_id = ?, + order_by = ?, + blob = ? + WHERE handle = ?;""", + [media.gramps_id, + self._order_by_media_key(media), + pickle.dumps(media.serialize()), + media.handle]) + else: + emit = "media-add" + self.dbapi.execute("insert into media values(?, ?, ?, ?);", + [media.handle, + self._order_by_media_key(media), + media.gramps_id, + pickle.dumps(media.serialize())]) + if not trans.batch: self.dbapi.commit() # Emit after added: if emit: From a6f7093baeb5360b3568149a837c537e926fe8b3 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 10:27:49 -0400 Subject: [PATCH 090/105] Added indices on order_by fields --- gramps/plugins/database/dbapi.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 774a4bdda..436ba87d5 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -1490,7 +1490,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return list(key2table[obj_key].keys()) def transaction_begin(self, transaction): - ## FIXME + """ + Transactions are handled automatically by the db layer. + """ return def set_researcher(self, owner): @@ -2013,6 +2015,25 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): order_by TEXT , blob TEXT );""") + ## Indices: + self.dbapi.execute("""CREATE INDEX IF NOT EXISTS + order_by ON person (order_by); + """) + self.dbapi.execute("""CREATE INDEX IF NOT EXISTS + order_by ON source (order_by); + """) + self.dbapi.execute("""CREATE INDEX IF NOT EXISTS + order_by ON citation (order_by); + """) + self.dbapi.execute("""CREATE INDEX IF NOT EXISTS + order_by ON media (order_by); + """) + self.dbapi.execute("""CREATE INDEX IF NOT EXISTS + order_by ON place (order_by); + """) + self.dbapi.execute("""CREATE INDEX IF NOT EXISTS + order_by ON tag (order_by); + """) def redo(self, update_history=True): ## FIXME From 1871c6ced4ba896b8be0fff4ec4a31d851f66ee5 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 14:52:43 -0400 Subject: [PATCH 091/105] Basics for back references now work, although doesn't update with edits yet --- gramps/plugins/database/dbapi.py | 145 +++++++++++++++++-------------- 1 file changed, 79 insertions(+), 66 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 436ba87d5..31e50534c 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -20,7 +20,8 @@ import gramps from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, - KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP) + KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP, + CLASS_TO_KEY_MAP) from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback from gramps.gen.db.undoredo import DbUndo @@ -1245,6 +1246,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return obj.handle def commit_person(self, person, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if person.handle in self.person_map: emit = "person-update" @@ -1271,6 +1273,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([person.handle],)) def commit_family(self, family, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if family.handle in self.family_map: emit = "family-update" @@ -1292,6 +1295,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([family.handle],)) def commit_citation(self, citation, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if citation.handle in self.citation_map: emit = "citation-update" @@ -1317,6 +1321,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([citation.handle],)) def commit_source(self, source, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if source.handle in self.source_map: emit = "source-update" @@ -1342,6 +1347,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([source.handle],)) def commit_repository(self, repository, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if repository.handle in self.repository_map: emit = "repository-update" @@ -1362,6 +1368,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([repository.handle],)) def commit_note(self, note, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if note.handle in self.note_map: emit = "note-update" @@ -1382,6 +1389,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([note.handle],)) def commit_place(self, place, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if place.handle in self.place_map: emit = "place-update" @@ -1407,6 +1415,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([place.handle],)) def commit_event(self, event, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if event.handle in self.event_map: emit = "event-update" @@ -1429,6 +1438,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([event.handle],)) def commit_tag(self, tag, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if tag.handle in self.tag_map: emit = "tag-update" @@ -1451,6 +1461,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit(emit, ([tag.handle],)) def commit_media_object(self, media, trans, change_time=None): + ## FIXME: update reference, for back references emit = None if media.handle in self.media_map: emit = "media-update" @@ -1675,64 +1686,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): [handle]) self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) - def delete_primary_from_reference_map(self, handle, transaction, txn=None): - """ - Remove all references to the primary object from the reference_map. - handle should be utf-8 - """ - 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.decode('utf-8'), 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 __remove_reference(self, key, transaction, txn): - """ - Remove the reference specified by the key, preserving the change in - the passed transaction. - """ - if isinstance(key, tuple): - #create a byte string key, first validity check in python 3! - for val in key: - if isinstance(val, bytes): - raise DbError(_('An attempt is made to save a reference key ' - 'which is partly bytecode, this is not allowed.\n' - 'Key is %s') % str(key)) - key = str(key) - if isinstance(key, str): - key = key.encode('utf-8') - if not self.readonly: - if not transaction.batch: - old_data = self.reference_map.get(key, txn=txn) - transaction.add(REFERENCE_KEY, TXNDEL, key, old_data, None) - #transaction.reference_del.append(str(key)) - self.reference_map.delete(key, txn=txn) - ## Missing: def backup(self): @@ -1751,8 +1704,28 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dbapi.close() def find_backlink_handles(self, handle, include_classes=None): - ## FIXME - return [] + """ + 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 = list(find_backlink_handles(handle)) + """ + cur = self.dbapi.execute("SELECT * from reference WHERE ref_handle = ?;", + [handle]) + rows = cur.fetchall() + for row in rows: + if (include_classes is None) or (row["obj_class"] in include_classes): + yield (row["obj_class"], row["obj_handle"]) def find_initial_person(self): items = self.person_map.keys() @@ -2015,6 +1988,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): order_by TEXT , blob TEXT );""") + # Reference + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS reference ( + obj_handle TEXT, + obj_class TEXT, + ref_handle TEXT, + ref_class TEXT + );""") ## Indices: self.dbapi.execute("""CREATE INDEX IF NOT EXISTS order_by ON person (order_by); @@ -2034,6 +2014,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dbapi.execute("""CREATE INDEX IF NOT EXISTS order_by ON tag (order_by); """) + self.dbapi.execute("""CREATE INDEX IF NOT EXISTS + ref_handle ON reference (ref_handle); + """) def redo(self, update_history=True): ## FIXME @@ -2111,13 +2094,43 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): name = None return name - def reindex_reference_map(self): - ## FIXME - pass + def reindex_reference_map(self, callback): + callback(4) + self.dbapi.execute("DELETE FROM reference;") + primary_table = ( + (self.get_person_cursor, Person), + (self.get_family_cursor, Family), + (self.get_event_cursor, Event), + (self.get_place_cursor, Place), + (self.get_source_cursor, Source), + (self.get_citation_cursor, Citation), + (self.get_media_cursor, MediaObject), + (self.get_repository_cursor, Repository), + (self.get_note_cursor, Note), + (self.get_tag_cursor, Tag), + ) + # Now we use the functions and classes defined above + # to loop through each of the primary object tables. + for cursor_func, class_func in primary_table: + logging.info("Rebuilding %s reference map" % + class_func.__name__) + with cursor_func() as cursor: + for found_handle, val in cursor: + obj = class_func.create(val) + references = set(obj.get_referenced_handles_recursively()) + # handle addition of new references + for (ref_class_name, ref_handle) in references: + self.dbapi.execute("INSERT into reference VALUES(?, ?, ?, ?);", + [obj.handle, + obj.__class__.__name__, + ref_handle, + ref_class_name]) + + self.dbapi.commit() + callback(5) def rebuild_secondary(self, update): - ## FIXME - pass + self.reindex_reference_map(update) def prepare_import(self): """ From 9274d74dc134a8e9fa4956a2dd6bd88647322e2a Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 20:25:04 -0400 Subject: [PATCH 092/105] WIP: name_group; clean up of SQL --- gramps/plugins/database/dbapi.py | 89 ++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 33 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 31e50534c..d501dbdd3 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -462,7 +462,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): contains_func="has_gramps_id_func") self.tag_map = Map(Table(self._tables["Tag"])) self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) - self.name_group = {} + ## FIXME: add appropriate methods: + self.name_group = Map(Table({"handles_func": self.get_name_group_keys, # keys + "has_handle_func": self.has_name_group_key, # key in table + "cursor_func": None, # create a cursor, values + "add_func": self.set_name_group_mapping, # add a key, value + "count_func": None})) # how many items? self.undo_callback = None self.redo_callback = None self.undo_history_callback = None @@ -746,10 +751,16 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def get_name_group_keys(self): - return [] + cur = self.dbapi.execute("select name from name_group order by name;") + rows = cur.fetchall() + return [row[0] for row in rows] def get_name_group_mapping(self, key): - return None + cur = self.dbapi.execute("select grouping from name_group where name = ?;", + [key]) + row = cur.fetchone() + if row: + return row[0] def get_person_handles(self, sort_handles=False): if sort_handles: @@ -1096,12 +1107,22 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return handle in self.media_map def has_name_group_key(self, key): - # FIXME: - return False + cur = self.dbapi.execute("select grouping from name_group where name = ?;", + [key]) + row = cur.fetchone() + return True if row else False - def set_name_group_mapping(self, key, value): - # FIXME: - pass + def set_name_group_mapping(self, name, grouping): + sname = name.encode("utf-8") + cur = self.dbapi.execute("SELECT * from name_group where name = ?;", + [sname]) + row = cur.fetchone() + if row: + cur = self.dbapi.execute("DELETE from name_group where name = ?;", + [sname]) + cur = self.dbapi.execute("INSERT into name_group (name, grouping) VALUES(?, ?);", + [sname, grouping]) + self.dbapi.commit() def set_default_person_handle(self, handle): ## FIXME @@ -1260,8 +1281,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): person.handle]) else: emit = "person-add" - self.dbapi.execute("""insert into person(handle, order_by, gramps_id, blob) - values(?, ?, ?, ?);""", + self.dbapi.execute("""insert into person (handle, order_by, gramps_id, blob) + values(?, ?, ?, ?);""", [person.handle, self._order_by_person_key(person), person.gramps_id, @@ -1285,7 +1306,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): family.handle]) else: emit = "family-add" - self.dbapi.execute("insert into family values(?, ?, ?);", + self.dbapi.execute("""insert into family (handle, gramps_id, blob) + values(?, ?, ?);""", [family.handle, family.gramps_id, pickle.dumps(family.serialize())]) if not trans.batch: @@ -1309,7 +1331,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): citation.handle]) else: emit = "citation-add" - self.dbapi.execute("insert into citation values(?, ?, ?, ?);", + self.dbapi.execute("""insert into citation (handle, order_by, gramps_id, blob) + values(?, ?, ?, ?);""", [citation.handle, self._order_by_citation_key(citation), citation.gramps_id, @@ -1335,7 +1358,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): source.handle]) else: emit = "source-add" - self.dbapi.execute("insert into source values(?, ?, ?, ?);", + self.dbapi.execute("""insert into source (handle, order_by, gramps_id, blob) + values(?, ?, ?, ?);""", [source.handle, self._order_by_source_key(source), source.gramps_id, @@ -1359,7 +1383,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): repository.handle]) else: emit = "repository-add" - self.dbapi.execute("insert into repository values(?, ?, ?);", + self.dbapi.execute("""insert into repository (handle, gramps_id, blob) + values(?, ?, ?);""", [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())]) if not trans.batch: self.dbapi.commit() @@ -1380,7 +1405,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): note.handle]) else: emit = "note-add" - self.dbapi.execute("insert into note values(?, ?, ?);", + self.dbapi.execute("""insert into note (handle, gramps_id, blob) + values(?, ?, ?);""", [note.handle, note.gramps_id, pickle.dumps(note.serialize())]) if not trans.batch: self.dbapi.commit() @@ -1403,7 +1429,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): place.handle]) else: emit = "place-add" - self.dbapi.execute("insert into place values(?, ?, ?, ?);", + self.dbapi.execute("""insert into place (handle, order_by, gramps_id, blob) + values(?, ?, ?, ?);""", [place.handle, self._order_by_place_key(place), place.gramps_id, @@ -1427,7 +1454,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): event.handle]) else: emit = "event-add" - self.dbapi.execute("insert into event values(?, ?, ?);", + self.dbapi.execute("""insert into event (handle, gramps_id, blob) + values(?, ?, ?);""", [event.handle, event.gramps_id, pickle.dumps(event.serialize())]) @@ -1450,7 +1478,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): tag.handle]) else: emit = "tag-add" - self.dbapi.execute("insert into tag values(?, ?, ?);", + self.dbapi.execute("""insert into tag (handle, order_by, blob) + values(?, ?, ?);""", [tag.handle, self._order_by_tag_key(tag), pickle.dumps(tag.serialize())]) @@ -1475,7 +1504,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): media.handle]) else: emit = "media-add" - self.dbapi.execute("insert into media values(?, ?, ?, ?);", + self.dbapi.execute("""insert into media (handle, order_by, gramps_id, blob) + values(?, ?, ?, ?);""", [media.handle, self._order_by_media_key(media), media.gramps_id, @@ -1747,18 +1777,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def get_citation_bookmarks(self): return self.citation_bookmarks - def get_cursor(self, table, txn=None, update=False, commit=False): - ## FIXME - ## called from a complete find_back_ref - pass - - # 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_default_handle(self): items = self.person_map.keys() if len(items) > 0: @@ -1988,13 +2006,17 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): order_by TEXT , blob TEXT );""") - # Reference + # Secondary: self.dbapi.execute("""CREATE TABLE IF NOT EXISTS reference ( obj_handle TEXT, obj_class TEXT, ref_handle TEXT, ref_class TEXT );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS name_group ( + name TEXT PRIMARY KEY NOT NULL, + grouping TEXT + );""") ## Indices: self.dbapi.execute("""CREATE INDEX IF NOT EXISTS order_by ON person (order_by); @@ -2120,7 +2142,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): references = set(obj.get_referenced_handles_recursively()) # handle addition of new references for (ref_class_name, ref_handle) in references: - self.dbapi.execute("INSERT into reference VALUES(?, ?, ?, ?);", + self.dbapi.execute("""INSERT into reference (obj_handle, obj_class, ref_handle, ref_class) + VALUES(?, ?, ?, ?);""", [obj.handle, obj.__class__.__name__, ref_handle, From 800e8ebefb6c82231b13239a92fb318686bf91d3 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 21:27:51 -0400 Subject: [PATCH 093/105] Added metadata table and setting/value --- gramps/plugins/database/dbapi.py | 48 ++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index d501dbdd3..503194bf9 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -748,7 +748,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return gid def get_mediapath(self): - return None + cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"]) + row = cur.fetchone() + if row: + return row["value"] + else: + return None def get_name_group_keys(self): cur = self.dbapi.execute("select name from name_group order by name;") @@ -1125,12 +1130,27 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dbapi.commit() def set_default_person_handle(self, handle): - ## FIXME - pass + cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"]) + row = cur.fetchone() + if row: + cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", + [handle, "default-person"]) + else: + cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", + ["default-person", handle]) + self.emit('home-person-changed') + self.dbapi.commit() def set_mediapath(self, mediapath): - ## FIXME - pass + cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"]) + row = cur.fetchone() + if row: + cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", + [mediapath, "media-path"]) + else: + cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", + ["media-path", mediapath]) + self.dbapi.commit() def get_raw_person_data(self, handle): if handle in self.person_map: @@ -1615,6 +1635,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): #del self.person_id_map[person.gramps_id] self.dbapi.execute("DELETE from person WHERE handle = ?;", [handle]) self.emit("person-delete", ([handle],)) + if not transaction.batch: + self.dbapi.commit() def remove_source(self, handle, transaction): """ @@ -1715,6 +1737,8 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dbapi.execute("DELETE from %s WHERE handle = ?;" % key2table[key], [handle]) self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) + if not transaction.batch: + self.dbapi.commit() ## Missing: @@ -1778,10 +1802,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return self.citation_bookmarks def get_default_handle(self): - items = self.person_map.keys() - if len(items) > 0: - return list(items)[0] - return None + cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"]) + row = cur.fetchone() + if row: + return row["value"] + else: + return None def get_event_attribute_types(self): ## FIXME @@ -2017,6 +2043,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): name TEXT PRIMARY KEY NOT NULL, grouping TEXT );""") + self.dbapi.execute("""CREATE TABLE IF NOT EXISTS metadata ( + setting TEXT PRIMARY KEY NOT NULL, + value TEXT + );""") ## Indices: self.dbapi.execute("""CREATE INDEX IF NOT EXISTS order_by ON person (order_by); From dad21d1e2f1dbab9a29a6c7779341812313ac63a Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 23 May 2015 22:46:05 -0400 Subject: [PATCH 094/105] Update backlinks --- gramps/plugins/database/dbapi.py | 55 +++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 503194bf9..d4a2bf09e 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -1287,7 +1287,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return obj.handle def commit_person(self, person, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if person.handle in self.person_map: emit = "person-update" @@ -1309,12 +1308,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): pickle.dumps(person.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(person) # Emit after added: if emit: self.emit(emit, ([person.handle],)) def commit_family(self, family, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if family.handle in self.family_map: emit = "family-update" @@ -1332,12 +1331,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): pickle.dumps(family.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(family) # Emit after added: if emit: self.emit(emit, ([family.handle],)) def commit_citation(self, citation, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if citation.handle in self.citation_map: emit = "citation-update" @@ -1359,12 +1358,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): pickle.dumps(citation.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(citation) # Emit after added: if emit: self.emit(emit, ([citation.handle],)) def commit_source(self, source, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if source.handle in self.source_map: emit = "source-update" @@ -1386,12 +1385,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): pickle.dumps(source.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(source) # Emit after added: if emit: self.emit(emit, ([source.handle],)) def commit_repository(self, repository, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if repository.handle in self.repository_map: emit = "repository-update" @@ -1408,12 +1407,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(repository) # Emit after added: if emit: self.emit(emit, ([repository.handle],)) def commit_note(self, note, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if note.handle in self.note_map: emit = "note-update" @@ -1430,12 +1429,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): [note.handle, note.gramps_id, pickle.dumps(note.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(note) # Emit after added: if emit: self.emit(emit, ([note.handle],)) def commit_place(self, place, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if place.handle in self.place_map: emit = "place-update" @@ -1457,12 +1456,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): pickle.dumps(place.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(place) # Emit after added: if emit: self.emit(emit, ([place.handle],)) def commit_event(self, event, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if event.handle in self.event_map: emit = "event-update" @@ -1479,14 +1478,47 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): [event.handle, event.gramps_id, pickle.dumps(event.serialize())]) + + self.update_event_attributes( + [str(attr.type) for attr in event.attribute_list + if attr.type.is_custom() and str(attr.type)]) + + if event.type.is_custom(): + self.update_event_names(str(event.type)) + if not trans.batch: self.dbapi.commit() + self.update_backlinks(event) + # Emit after added: if emit: self.emit(emit, ([event.handle],)) + def update_backlinks(self, obj): + # First, delete the current references: + self.dbapi.execute("DELETE FROM reference where obj_handle = ?;", + [obj.handle]) + # Now, add the current ones: + references = set(obj.get_referenced_handles_recursively()) + for (ref_class_name, ref_handle) in references: + self.dbapi.execute("""INSERT into reference + (obj_handle, obj_class, ref_handle, ref_class) + VALUES(?, ?, ?, ?);""", + [obj.handle, + obj.__class__.__name__, + ref_handle, + ref_class_name]) + # Will commit later + + def update_event_attributes(self, attr_list): + # FIXME + pass + + def update_event_names(self, event_type): + # FIXME + pass + def commit_tag(self, tag, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if tag.handle in self.tag_map: emit = "tag-update" @@ -1505,12 +1537,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): pickle.dumps(tag.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(tag) # Emit after added: if emit: self.emit(emit, ([tag.handle],)) def commit_media_object(self, media, trans, change_time=None): - ## FIXME: update reference, for back references emit = None if media.handle in self.media_map: emit = "media-update" @@ -1532,6 +1564,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): pickle.dumps(media.serialize())]) if not trans.batch: self.dbapi.commit() + self.update_backlinks(media) # Emit after added: if emit: self.emit(emit, ([media.handle],)) @@ -2197,7 +2230,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): Commits the items that were queued up during the last gedcom import for two step adding. """ - pass + self.reindex_reference_map(lambda n: n) def has_handle_for_person(self, key): cur = self.dbapi.execute("select * from person where handle = ?", [key]) From d5c9c5114a8cfdaa9c95f89dccbec59fa073b9b8 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Mon, 25 May 2015 13:33:04 -0400 Subject: [PATCH 095/105] All metadata functionality now implemented --- .../plugins/database/bsddb_support/write.py | 2 +- gramps/plugins/database/dbapi.py | 491 ++++++++++++------ gramps/plugins/database/dictionarydb.py | 2 +- gramps/plugins/database/djangodb.py | 2 +- 4 files changed, 327 insertions(+), 170 deletions(-) diff --git a/gramps/plugins/database/bsddb_support/write.py b/gramps/plugins/database/bsddb_support/write.py index 65bf71980..2eb665ac5 100644 --- a/gramps/plugins/database/bsddb_support/write.py +++ b/gramps/plugins/database/bsddb_support/write.py @@ -653,7 +653,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): return False @catch_db_error - def load(self, name, callback, mode=DBMODE_W, force_schema_upgrade=False, + def load(self, name, callback=None, mode=DBMODE_W, force_schema_upgrade=False, force_bsddb_upgrade=False, force_bsddb_downgrade=False, force_python_upgrade=False): diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index d4a2bf09e..aad21039e 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -167,13 +167,34 @@ class Cursor(object): pass class Bookmarks(object): - def __init__(self): - self.handles = [] + def __init__(self, default=[]): + self.handles = list(default) + + def set(self, handles): + self.handles = list(handles) + def get(self): return self.handles + def append(self, handle): self.handles.append(handle) + def append_list(self, handles): + self.handles += handles + + def remove(self, handle): + self.handles.remove(handle) + + def pop(self, item): + return self.handles.pop(item) + + def insert(self, pos, item): + self.handles.insert(pos, item) + + def close(self): + del self.handles + + class DBAPITxn(DbTxn): def __init__(self, message, db, batch=False): DbTxn.__init__(self, message, db, batch) @@ -386,6 +407,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.readonly = False self.db_is_open = True self.name_formats = [] + # Bookmarks: self.bookmarks = Bookmarks() self.family_bookmarks = Bookmarks() self.event_bookmarks = Bookmarks() @@ -443,7 +465,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.source_map = Map(Table(self._tables["Source"])) self.source_id_map = Map(Table(self._tables["Source"]), keys_func="ids_func", - contains_func="has_gramps_id_func") + contains_func="has_gramps_id_func") self.repository_map = Map(Table(self._tables["Repository"])) self.repository_id_map = Map(Table(self._tables["Repository"]), keys_func="ids_func", @@ -478,6 +500,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.abort_possible = False self._bm_changes = 0 self._directory = directory + self._has_changed = False self.full_name = None self.path = None self.brief_name = None @@ -503,10 +526,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def transaction_commit(self, txn): self.dbapi.commit() - def get_undodb(self): - ## FIXME - return None - def transaction_abort(self, txn): self.dbapi.rollback() @@ -748,12 +767,42 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return gid def get_mediapath(self): - cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"]) + return self.get_metadata("media-path", "") + + def set_mediapath(self, mediapath): + return self.set_metadata("media-path", mediapath) + + def get_metadata(self, key, default=[]): + """ + Get an item from the database. + """ + cur = self.dbapi.execute("SELECT * FROM metadata WHERE setting = ?;", [key]) row = cur.fetchone() if row: - return row["value"] + return pickle.loads(row["value"]) + elif default == []: + return [] else: - return None + return default + + def set_metadata(self, key, value): + """ + key: string + value: item, will be serialized here + """ + cur = self.dbapi.execute("SELECT * FROM metadata WHERE setting = ?;", [key]) + row = cur.fetchone() + if row: + cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", + [pickle.dumps(value), key]) + else: + cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", + [key, pickle.dumps(value)]) + self.dbapi.commit() + + def set_default_person_handle(self, handle): + self.set_metadata("default-person-handle", handle) + self.emit('home-person-changed') def get_name_group_keys(self): cur = self.dbapi.execute("select name from name_group order by name;") @@ -1129,29 +1178,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): [sname, grouping]) self.dbapi.commit() - def set_default_person_handle(self, handle): - cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"]) - row = cur.fetchone() - if row: - cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", - [handle, "default-person"]) - else: - cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", - ["default-person", handle]) - self.emit('home-person-changed') - self.dbapi.commit() - - def set_mediapath(self, mediapath): - cur = self.dbapi.execute("select * from metadata where setting = ?", ["media-path"]) - row = cur.fetchone() - if row: - cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", - [mediapath, "media-path"]) - else: - cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", - ["media-path", mediapath]) - self.dbapi.commit() - def get_raw_person_data(self, handle): if handle in self.person_map: return self.person_map[handle] @@ -1312,6 +1338,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([person.handle],)) + self._has_changed = True def commit_family(self, family, trans, change_time=None): emit = None @@ -1335,6 +1362,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([family.handle],)) + self._has_changed = True def commit_citation(self, citation, trans, change_time=None): emit = None @@ -1362,6 +1390,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([citation.handle],)) + self._has_changed = True def commit_source(self, source, trans, change_time=None): emit = None @@ -1389,6 +1418,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([source.handle],)) + self._has_changed = True def commit_repository(self, repository, trans, change_time=None): emit = None @@ -1411,6 +1441,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([repository.handle],)) + self._has_changed = True def commit_note(self, note, trans, change_time=None): emit = None @@ -1433,6 +1464,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([note.handle],)) + self._has_changed = True def commit_place(self, place, trans, change_time=None): emit = None @@ -1460,6 +1492,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([place.handle],)) + self._has_changed = True def commit_event(self, event, trans, change_time=None): emit = None @@ -1493,6 +1526,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([event.handle],)) + self._has_changed = True def update_backlinks(self, obj): # First, delete the current references: @@ -1773,12 +1807,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not transaction.batch: self.dbapi.commit() - ## Missing: - - def backup(self): - ## FIXME - pass - def close(self): if self._directory: from gramps.plugins.export.exportxml import XmlWriter @@ -1788,6 +1816,39 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): writer.write(filename) filename = os.path.join(self._directory, "meta_data.db") touch(filename) + # Save metadata + self.set_metadata('bookmarks', self.bookmarks.get()) + self.set_metadata('family_bookmarks', self.family_bookmarks.get()) + self.set_metadata('event_bookmarks', self.event_bookmarks.get()) + self.set_metadata('source_bookmarks', self.source_bookmarks.get()) + self.set_metadata('citation_bookmarks', self.citation_bookmarks.get()) + self.set_metadata('repo_bookmarks', self.repo_bookmarks.get()) + self.set_metadata('media_bookmarks', self.media_bookmarks.get()) + self.set_metadata('place_bookmarks', self.place_bookmarks.get()) + self.set_metadata('note_bookmarks', self.note_bookmarks.get()) + + # Custom type values, sets + self.set_metadata('event_names', self.event_names) + self.set_metadata('fattr_names', self.family_attributes) + self.set_metadata('pattr_names', self.individual_attributes) + self.set_metadata('sattr_names', self.source_attributes) + self.set_metadata('marker_names', self.marker_names) + self.set_metadata('child_refs', self.child_ref_types) + self.set_metadata('family_rels', self.family_rel_types) + self.set_metadata('event_roles', self.event_role_names) + self.set_metadata('name_types', self.name_types) + self.set_metadata('origin_types', self.origin_types) + self.set_metadata('repo_types', self.repository_types) + self.set_metadata('note_types', self.note_types) + self.set_metadata('sm_types', self.source_media_types) + self.set_metadata('url_types', self.url_types) + self.set_metadata('mattr_names', self.media_attributes) + self.set_metadata('eattr_names', self.event_attributes) + self.set_metadata('place_types', self.place_types) + + # surname list + self.set_metadata('surname_list', self.surname_list) + self.dbapi.close() def find_backlink_handles(self, handle, include_classes=None): @@ -1815,136 +1876,185 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): yield (row["obj_class"], row["obj_handle"]) def find_initial_person(self): - items = self.person_map.keys() - if len(items) > 0: - return self.get_person_from_handle(list(items)[0]) - return None - - def find_place_child_handles(self, handle): - ## FIXME - return [] + handle = self.get_default_handle() + person = None + if handle: + person = self.get_person_from_handle(handle) + if person: + return person + cur = self.dbapi.execute("SELECT handle FROM person;") + row = cur.fetchone() + if row: + return row[0] def get_bookmarks(self): return self.bookmarks - def get_child_reference_types(self): - ## FIXME - return [] - def get_citation_bookmarks(self): return self.citation_bookmarks def get_default_handle(self): - cur = self.dbapi.execute("select * from metadata where setting = ?", ["default-person"]) - row = cur.fetchone() - if row: - return row["value"] - else: - return None + return self.get_metadata("default-person-handle", None) - def get_event_attribute_types(self): - ## FIXME - return [] - - def get_event_bookmarks(self): - return self.event_bookmarks - - def get_event_roles(self): - ## FIXME - return [] - - def get_event_types(self): - ## FIXME - return [] - - def get_family_attribute_types(self): - ## FIXME - return [] - - def get_family_bookmarks(self): - return self.family_bookmarks - - def get_family_event_types(self): - ## FIXME - return [] - - def get_family_relation_types(self): - ## FIXME - return [] - - def get_media_attribute_types(self): - ## FIXME - return [] - - def get_media_bookmarks(self): - return self.media_bookmarks - - def get_name_types(self): - ## FIXME - return [] - - def get_note_bookmarks(self): - return self.note_bookmarks - - def get_note_types(self): - ## FIXME - return [] - - def get_origin_types(self): - ## FIXME - return [] - - def get_person_attribute_types(self): - ## FIXME - return [] - - def get_person_event_types(self): - ## FIXME - return [] - - def get_place_bookmarks(self): - return self.place_bookmarks - - def get_place_tree_cursor(self): - ## FIXME - return [] - - def get_place_types(self): - ## FIXME - return [] - - def get_repo_bookmarks(self): - return self.repo_bookmarks - - def get_repository_types(self): - ## FIXME - return [] - - def get_save_path(self): - return self._directory - - def get_source_attribute_types(self): - ## FIXME - return [] - - def get_source_bookmarks(self): - return self.source_bookmarks - - def get_source_media_types(self): + def find_place_child_handles(self, handle): ## FIXME return [] def get_surname_list(self): - ## FIXME - return [] + """ + Return the list of locale-sorted surnames contained in the database. + """ + return self.surname_list + + def get_event_attribute_types(self): + """ + Return a list of all Attribute types assocated with Event instances + in the database. + """ + return list(self.event_attributes) + + def get_event_types(self): + """ + Return a list of all event types in the database. + """ + return list(self.event_names) + + def get_person_event_types(self): + """ + Deprecated: Use get_event_types + """ + return list(self.event_names) + + def get_person_attribute_types(self): + """ + Return a list of all Attribute types assocated with Person instances + in the database. + """ + return list(self.individual_attributes) + + def get_family_attribute_types(self): + """ + Return a list of all Attribute types assocated with Family instances + in the database. + """ + return list(self.family_attributes) + + def get_family_event_types(self): + """ + Deprecated: Use get_event_types + """ + return list(self.event_names) + + def get_media_attribute_types(self): + """ + Return a list of all Attribute types assocated with Media and MediaRef + instances in the database. + """ + return list(self.media_attributes) + + def get_family_relation_types(self): + """ + Return a list of all relationship types assocated with Family + instances in the database. + """ + return list(self.family_rel_types) + + def get_child_reference_types(self): + """ + Return a list of all child reference types assocated with Family + instances in the database. + """ + return list(self.child_ref_types) + + def get_event_roles(self): + """ + Return a list of all custom event role names assocated with Event + instances in the database. + """ + return list(self.event_role_names) + + def get_name_types(self): + """ + Return a list of all custom names types assocated with Person + instances in the database. + """ + return list(self.name_types) + + def get_origin_types(self): + """ + Return a list of all custom origin types assocated with Person/Surname + instances in the database. + """ + return list(self.origin_types) + + def get_repository_types(self): + """ + Return a list of all custom repository types assocated with Repository + instances in the database. + """ + return list(self.repository_types) + + def get_note_types(self): + """ + Return a list of all custom note types assocated with Note instances + in the database. + """ + return list(self.note_types) + + def get_source_attribute_types(self): + """ + Return a list of all Attribute types assocated with Source/Citation + instances in the database. + """ + return list(self.source_attributes) + + def get_source_media_types(self): + """ + Return a list of all custom source media types assocated with Source + instances in the database. + """ + return list(self.source_media_types) def get_url_types(self): - ## FIXME - return [] + """ + Return a list of all custom names types assocated with Url instances + in the database. + """ + return list(self.url_types) + + def get_place_types(self): + """ + Return a list of all custom place types assocated with Place instances + in the database. + """ + return list(self.place_types) + + def get_event_bookmarks(self): + return self.event_bookmarks + + def get_family_bookmarks(self): + return self.family_bookmarks + + def get_media_bookmarks(self): + return self.media_bookmarks + + def get_note_bookmarks(self): + return self.note_bookmarks + + def get_place_bookmarks(self): + return self.place_bookmarks + + def get_repo_bookmarks(self): + return self.repo_bookmarks + + def get_save_path(self): + return self._directory + + def get_source_bookmarks(self): + return self.source_bookmarks def has_changed(self): - ## FIXME - return True + return self._has_changed def is_open(self): return self._directory is not None @@ -1994,7 +2104,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def iter_tags(self): return (Tag.create(data[1]) for data in self.get_tag_cursor()) - def load(self, directory, pulse_progress=None, mode=None, + def load(self, directory, callback=None, mode=None, force_schema_upgrade=False, force_bsddb_upgrade=False, force_bsddb_downgrade=False, @@ -2102,19 +2212,50 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.dbapi.execute("""CREATE INDEX IF NOT EXISTS ref_handle ON reference (ref_handle); """) + # Load metadata + self.bookmarks.set(self.get_metadata('bookmarks')) + self.family_bookmarks.set(self.get_metadata('family_bookmarks')) + self.event_bookmarks.set(self.get_metadata('event_bookmarks')) + self.source_bookmarks.set(self.get_metadata('source_bookmarks')) + self.citation_bookmarks.set(self.get_metadata('citation_bookmarks')) + self.repo_bookmarks.set(self.get_metadata('repo_bookmarks')) + self.media_bookmarks.set(self.get_metadata('media_bookmarks')) + self.place_bookmarks.set(self.get_metadata('place_bookmarks')) + self.note_bookmarks.set(self.get_metadata('note_bookmarks')) - def redo(self, update_history=True): - ## FIXME - pass - - def restore(self): - ## FIXME - pass - + # Custom type values + self.event_names = self.get_metadata('event_names', set()) + self.family_attributes = self.get_metadata('fattr_names', set()) + self.individual_attributes = self.get_metadata('pattr_names', set()) + self.source_attributes = self.get_metadata('sattr_names', set()) + self.marker_names = self.get_metadata('marker_names', set()) + self.child_ref_types = self.get_metadata('child_refs', set()) + self.family_rel_types = self.get_metadata('family_rels', set()) + self.event_role_names = self.get_metadata('event_roles', set()) + self.name_types = self.get_metadata('name_types', set()) + self.origin_types = self.get_metadata('origin_types', set()) + self.repository_types = self.get_metadata('repo_types', set()) + self.note_types = self.get_metadata('note_types', set()) + self.source_media_types = self.get_metadata('sm_types', set()) + self.url_types = self.get_metadata('url_types', set()) + self.media_attributes = self.get_metadata('mattr_names', set()) + self.event_attributes = self.get_metadata('eattr_names', set()) + self.place_types = self.get_metadata('place_types', set()) + + # surname list + self.surname_list = self.get_metadata('surname_list') + def set_prefixes(self, person, media, family, source, citation, place, event, repository, note): - ## FIXME - pass + self.set_person_id_prefix(person) + self.set_object_id_prefix(media) + self.set_family_id_prefix(family) + self.set_source_id_prefix(source) + self.set_citation_id_prefix(citation) + self.set_place_id_prefix(place) + self.set_event_id_prefix(event) + self.set_repository_id_prefix(repository) + self.set_note_id_prefix(note) def set_save_path(self, directory): self._directory = directory @@ -2122,10 +2263,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.path = self.full_name self.brief_name = os.path.basename(self._directory) - def undo(self, update_history=True): - ## FIXME - pass - def write_version(self, directory): """Write files for a newly created DB.""" versionpath = os.path.join(directory, str(DBBACKEND)) @@ -2438,3 +2575,23 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def _order_by_tag_key(self, tag): return glocale.sort_key(tag.get_name()) + def backup(self): + ## FIXME + pass + + def restore(self): + ## FIXME + pass + + def get_undodb(self): + ## FIXME + return None + + def undo(self, update_history=True): + ## FIXME + pass + + def redo(self, update_history=True): + ## FIXME + pass + diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 8b8d8beb7..3fe7e0da5 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -1725,7 +1725,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): def iter_tags(self): return (Tag.create(key) for key in self.tag_map.values()) - def load(self, directory, pulse_progress=None, mode=None, + def load(self, directory, callback=None, mode=None, force_schema_upgrade=False, force_bsddb_upgrade=False, force_bsddb_downgrade=False, diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 0091d033a..a7f64f13e 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -360,7 +360,7 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): if directory: self.load(directory) - def load(self, directory, pulse_progress=None, mode=None, + def load(self, directory, callback=None, mode=None, force_schema_upgrade=False, force_bsddb_upgrade=False, force_bsddb_downgrade=False, From 1b8923932386d9bf4c3d031f626c45591ebbdb1a Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Mon, 25 May 2015 17:53:14 -0400 Subject: [PATCH 096/105] DB-API: added undo-redo infrastructure --- gramps/gen/db/__init__.py | 1 + gramps/gen/db/undoredo.py | 73 +++++++++++++++++----- gramps/plugins/database/dbapi.py | 101 +++++++++++++++++++++++++------ 3 files changed, 141 insertions(+), 34 deletions(-) diff --git a/gramps/gen/db/__init__.py b/gramps/gen/db/__init__.py index 10f751303..d9fcf1ad1 100644 --- a/gramps/gen/db/__init__.py +++ b/gramps/gen/db/__init__.py @@ -88,6 +88,7 @@ from .base import * from .dbconst import * from .txn import * from .exceptions import * +from .undoredo import * def find_surname_name(key, data): """ diff --git a/gramps/gen/db/undoredo.py b/gramps/gen/db/undoredo.py index b12735093..bfcd652d6 100644 --- a/gramps/gen/db/undoredo.py +++ b/gramps/gen/db/undoredo.py @@ -1,4 +1,10 @@ +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- import time +import pickle from collections import deque class DbUndo(object): @@ -19,6 +25,20 @@ class DbUndo(object): self.redoq = deque() self.undo_history_timestamp = time.time() self.txn = None + # N.B. the databases have to be in the same order as the numbers in + # xxx_KEY in gen/db/dbconst.py + self.mapbase = ( + self.db.person_map, + self.db.family_map, + self.db.source_map, + self.db.event_map, + self.db.media_map, + self.db.place_map, + self.db.repository_map, + self.db.note_map, + self.db.tag_map, + self.db.citation_map, + ) def clear(self): """ @@ -85,6 +105,16 @@ class DbUndo(object): """ raise NotImplementedError + def __redo(self, update_history): + """ + """ + raise NotImplementedError + + def __undo(self, update_history): + """ + """ + raise NotImplementedError + def commit(self, txn, msg): """ Commit the transaction to the undo/redo database. "txn" should be @@ -110,27 +140,40 @@ class DbUndo(object): return False return self.__redo(update_history) - def undoredo(func): + def undo_reference(self, data, handle, db_map): """ - Decorator function to wrap undo and redo operations within a bsddb - transaction. It also catches bsddb errors and raises an exception - as appropriate + Helper method to undo a reference map entry """ - pass + try: + if data is None: + db_map.delete(handle, txn=self.txn) + else: + db_map.put(handle, data, txn=self.txn) - def __redo(self, update_history=True): - """ - Access the last undone transaction, and revert the data to the state - before the transaction was undone. - """ - pass + except DBERRS as msg: + self.db._log_error() + raise DbError(msg) - def __undo(self, db=None, update_history=True): + def undo_data(self, data, handle, db_map, emit, signal_root): """ - Access the last committed transaction, and revert the data to the - state before the transaction was committed. + Helper method to undo/redo the changes made """ - pass + try: + if data is None: + emit(signal_root + '-delete', ([handle2internal(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) + emit(signal, ([handle2internal(handle)],)) + + except DBERRS as msg: + self.db._log_error() + raise DbError(msg) undo_count = property(lambda self:len(self.undoq)) redo_count = property(lambda self:len(self.redoq)) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index aad21039e..9df140e32 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -19,12 +19,11 @@ import shutil import gramps from gramps.gen.const import GRAMPS_LOCALE as glocale _ = glocale.translation.gettext -from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, +from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, DbUndo, KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP, CLASS_TO_KEY_MAP) from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback -from gramps.gen.db.undoredo import DbUndo from gramps.gen.db.dbconst import * from gramps.gen.db import (PERSON_KEY, FAMILY_KEY, @@ -52,6 +51,71 @@ def touch(fname, mode=0o666, dir_fd=None, **kwargs): os.utime(f.fileno() if os.utime in os.supports_fd else fname, dir_fd=None if os.supports_fd else dir_fd, **kwargs) +class DBAPIUndo(DbUndo): + def __init__(self, grampsdb, path): + super(DBAPIUndo, self).__init__(grampsdb) + self.undodb = grampsdb + self.path = path + + def open(self, value=None): + """ + Open the backing storage. Needs to be overridden in the derived + class. + """ + pass + # FIXME + + def close(self): + """ + Close the backing storage. Needs to be overridden in the derived + class. + """ + pass + # FIXME + + def append(self, value): + """ + Add a new entry on the end. Needs to be overridden in the derived + class. + """ + pass + # FIXME + + def __getitem__(self, index): + """ + Returns an entry by index number. Needs to be overridden in the + derived class. + """ + return None + # FIXME + + def __setitem__(self, index, value): + """ + Set an entry to a value. Needs to be overridden in the derived class. + """ + pass + # FIXME + + def __len__(self): + """ + Returns the number of entries. Needs to be overridden in the derived + class. + """ + return 0 + # FIXME + + def __redo(self, update_history): + """ + """ + pass + # FIXME + + def __undo(self, update_history): + """ + """ + pass + # FIXME + class Environment(object): """ Implements the Environment API. @@ -427,6 +491,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.set_repository_id_prefix('R%04d') self.set_note_id_prefix('N%04d') # ---------------------------------- + self.undodb = None self.id_trans = DBAPITxn("ID Transaction", self) self.fid_trans = DBAPITxn("FID Transaction", self) self.pid_trans = DBAPITxn("PID Transaction", self) @@ -484,19 +549,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): contains_func="has_gramps_id_func") self.tag_map = Map(Table(self._tables["Tag"])) self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) - ## FIXME: add appropriate methods: - self.name_group = Map(Table({"handles_func": self.get_name_group_keys, # keys - "has_handle_func": self.has_name_group_key, # key in table - "cursor_func": None, # create a cursor, values - "add_func": self.set_name_group_mapping, # add a key, value - "count_func": None})) # how many items? self.undo_callback = None self.redo_callback = None self.undo_history_callback = None self.modified = 0 self.txn = DBAPITxn("DBAPI Transaction", self) self.transaction = None - self.undodb = DbUndo(self) self.abort_possible = False self._bm_changes = 0 self._directory = directory @@ -984,7 +1042,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return (handle for handle in self.family_map.keys()) def get_tag_from_name(self, name): - ## Slow, but typically not too many tags: + ## FIXME: Slow, but typically not too many tags: for data in self.tag_map.values(): tag = Tag.create(data) if tag.name == name: @@ -2244,6 +2302,11 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # surname list self.surname_list = self.get_metadata('surname_list') + + self._directory = directory + self.undolog = os.path.join(self._directory, DBUNDOFN) + self.undodb = DBAPIUndo(self, self.undolog) + self.undodb.open() def set_prefixes(self, person, media, family, source, citation, place, event, repository, note): @@ -2576,22 +2639,22 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return glocale.sort_key(tag.get_name()) def backup(self): - ## FIXME + """ + If you wish to support an optional backup routine, put it here. + """ pass def restore(self): - ## FIXME + """ + If you wish to support an optional restore routine, put it here. + """ pass def get_undodb(self): - ## FIXME - return None + return self.undodb def undo(self, update_history=True): - ## FIXME - pass + return self.undodb.undo(update_history) def redo(self, update_history=True): - ## FIXME - pass - + return self.undodb.redo(update_history) From 1c4f827bf85c93a3c19f8e7c94be588db81b2336 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Mon, 25 May 2015 19:04:27 -0400 Subject: [PATCH 097/105] DB-API: committing objects updates secondary items --- gramps/plugins/database/dbapi.py | 173 ++++++++++++++++++++++++++++--- 1 file changed, 160 insertions(+), 13 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 9df140e32..c8673104f 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -10,6 +10,7 @@ import re import os import logging import shutil +import bisect #------------------------------------------------------------------------ # @@ -1374,6 +1375,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): emit = None if person.handle in self.person_map: emit = "person-update" + old_person = self.get_person_from_handle(person.handle) + # Update gender statistics if necessary + if (old_person.gender != person.gender or + old_person.primary_name.first_name != + person.primary_name.first_name): + + self.genderStats.uncount_person(old_person) + self.genderStats.count_person(person) + # Update surname list if necessary + if (self._order_by_person_key(person) != + self._order_by_person_key(old_person)): + self.remove_from_surname_list(old_person) + self.add_to_surname_list(person, trans.batch) + # update the person: self.dbapi.execute("""UPDATE person SET gramps_id = ?, order_by = ?, blob = ? @@ -1384,6 +1399,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): person.handle]) else: emit = "person-add" + self.genderStats.count_person(person) + self.add_to_surname_list(person, trans.batch) + # Insert the person: self.dbapi.execute("""insert into person (handle, order_by, gramps_id, blob) values(?, ?, ?, ?);""", [person.handle, @@ -1393,11 +1411,68 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(person) + # Other misc update tasks: + self.individual_attributes.update( + [str(attr.type) for attr in person.attribute_list + if attr.type.is_custom() and str(attr.type)]) + + self.event_role_names.update([str(eref.role) + for eref in person.event_ref_list + if eref.role.is_custom()]) + + self.name_types.update([str(name.type) + for name in ([person.primary_name] + + person.alternate_names) + if name.type.is_custom()]) + all_surn = [] # new list we will use for storage + all_surn += person.primary_name.get_surname_list() + for asurname in person.alternate_names: + all_surn += asurname.get_surname_list() + self.origin_types.update([str(surn.origintype) for surn in all_surn + if surn.origintype.is_custom()]) + all_surn = None + self.url_types.update([str(url.type) for url in person.urls + if url.type.is_custom()]) + attr_list = [] + for mref in person.media_list: + attr_list += [str(attr.type) for attr in mref.attribute_list + if attr.type.is_custom() and str(attr.type)] + self.media_attributes.update(attr_list) # Emit after added: if emit: self.emit(emit, ([person.handle],)) self._has_changed = True + def add_to_surname_list(self, person, batch_transaction): + """ + Add surname to surname list + """ + if batch_transaction: + return + name = conv_to_unicode(self._order_by_person_key(person), 'utf-8') + i = bisect.bisect(self.surname_list, name) + if 0 < i <= len(self.surname_list): + if self.surname_list[i-1] != name: + self.surname_list.insert(i, name) + else: + self.surname_list.insert(i, name) + + def remove_from_surname_list(self, person): + """ + Check whether there are persons with the same surname left in + the database. + + If not then we need to remove the name from the list. + The function must be overridden in the derived class. + """ + name = self._order_by_person_key(person) + if isinstance(name, str): + uname = name + name = name.encode('utf-8') + else: + uname = str(name) + # FIXME: check database + def commit_family(self, family, trans, change_time=None): emit = None if family.handle in self.family_map: @@ -1417,6 +1492,31 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(family) + # Misc updates: + self.family_attributes.update( + [str(attr.type) for attr in family.attribute_list + if attr.type.is_custom() and str(attr.type)]) + + rel_list = [] + for ref in family.child_ref_list: + if ref.frel.is_custom(): + rel_list.append(str(ref.frel)) + if ref.mrel.is_custom(): + rel_list.append(str(ref.mrel)) + self.child_ref_types.update(rel_list) + + self.event_role_names.update( + [str(eref.role) for eref in family.event_ref_list + if eref.role.is_custom()]) + + if family.type.is_custom(): + self.family_rel_types.add(str(family.type)) + + attr_list = [] + for mref in family.media_list: + attr_list += [str(attr.type) for attr in mref.attribute_list + if attr.type.is_custom() and str(attr.type)] + self.media_attributes.update(attr_list) # Emit after added: if emit: self.emit(emit, ([family.handle],)) @@ -1445,6 +1545,17 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(citation) + # Misc updates: + attr_list = [] + for mref in citation.media_list: + attr_list += [str(attr.type) for attr in mref.attribute_list + if attr.type.is_custom() and str(attr.type)] + self.media_attributes.update(attr_list) + + self.source_attributes.update( + [str(attr.type) for attr in citation.attribute_list + if attr.type.is_custom() and str(attr.type)]) + # Emit after added: if emit: self.emit(emit, ([citation.handle],)) @@ -1473,6 +1584,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(source) + # Misc updates: + self.source_media_types.update( + [str(ref.media_type) for ref in source.reporef_list + if ref.media_type.is_custom()]) + + attr_list = [] + for mref in source.media_list: + attr_list += [str(attr.type) for attr in mref.attribute_list + if attr.type.is_custom() and str(attr.type)] + self.media_attributes.update(attr_list) + self.source_attributes.update( + [str(attr.type) for attr in source.attribute_list + if attr.type.is_custom() and str(attr.type)]) # Emit after added: if emit: self.emit(emit, ([source.handle],)) @@ -1496,6 +1620,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(repository) + # Misc updates: + if repository.type.is_custom(): + self.repository_types.add(str(repository.type)) + + self.url_types.update([str(url.type) for url in repository.urls + if url.type.is_custom()]) # Emit after added: if emit: self.emit(emit, ([repository.handle],)) @@ -1519,6 +1649,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(note) + # Misc updates: + if note.type.is_custom(): + self.note_types.add(str(note.type)) # Emit after added: if emit: self.emit(emit, ([note.handle],)) @@ -1547,6 +1680,18 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(place) + # Misc updates: + if place.get_type().is_custom(): + self.place_types.add(str(place.get_type())) + + self.url_types.update([str(url.type) for url in place.urls + if url.type.is_custom()]) + + attr_list = [] + for mref in place.media_list: + attr_list += [str(attr.type) for attr in mref.attribute_list + if attr.type.is_custom() and str(attr.type)] + self.media_attributes.update(attr_list) # Emit after added: if emit: self.emit(emit, ([place.handle],)) @@ -1569,18 +1714,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): [event.handle, event.gramps_id, pickle.dumps(event.serialize())]) - - self.update_event_attributes( - [str(attr.type) for attr in event.attribute_list - if attr.type.is_custom() and str(attr.type)]) - - if event.type.is_custom(): - self.update_event_names(str(event.type)) - if not trans.batch: self.dbapi.commit() self.update_backlinks(event) - + # Misc updates: + self.event_attributes.update( + [str(attr.type) for attr in event.attribute_list + if attr.type.is_custom() and str(attr.type)]) + if event.type.is_custom(): + self.event_names.add(str(event.type)) + attr_list = [] + for mref in event.media_list: + attr_list += [str(attr.type) for attr in mref.attribute_list + if attr.type.is_custom() and str(attr.type)] + self.media_attributes.update(attr_list) # Emit after added: if emit: self.emit(emit, ([event.handle],)) @@ -1657,6 +1804,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if not trans.batch: self.dbapi.commit() self.update_backlinks(media) + # Misc updates: + self.media_attributes.update( + [str(attr.type) for attr in media.attribute_list + if attr.type.is_custom() and str(attr.type)]) # Emit after added: if emit: self.emit(emit, ([media.handle],)) @@ -1954,10 +2105,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def get_default_handle(self): return self.get_metadata("default-person-handle", None) - def find_place_child_handles(self, handle): - ## FIXME - return [] - def get_surname_list(self): """ Return the list of locale-sorted surnames contained in the database. From d38785740e7b99ace45367035d07518aeab97ed2 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Mon, 25 May 2015 21:06:44 -0400 Subject: [PATCH 098/105] DB-API: surname_list not working; added tag map support --- gramps/plugins/database/dbapi.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index c8673104f..36e4e6a51 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -10,7 +10,6 @@ import re import os import logging import shutil -import bisect #------------------------------------------------------------------------ # @@ -463,8 +462,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): "handles_func": self.get_tag_handles, "add_func": self.add_tag, "commit_func": self.commit_tag, + "has_handle_func": self.has_handle_for_tag, "iter_func": self.iter_tags, "count": self.get_number_of_tags, + "raw_func": self._get_raw_tag_data, }) # skip GEDCOM cross-ref check for now: self.set_feature("skip-check-xref", True) @@ -1449,13 +1450,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): """ if batch_transaction: return - name = conv_to_unicode(self._order_by_person_key(person), 'utf-8') - i = bisect.bisect(self.surname_list, name) - if 0 < i <= len(self.surname_list): - if self.surname_list[i-1] != name: - self.surname_list.insert(i, name) - else: - self.surname_list.insert(i, name) + #name = self._order_by_person_key(person) + #i = bisect.bisect(self.surname_list, name) + #if 0 < i <= len(self.surname_list): + # if self.surname_list[i-1] != name: + # self.surname_list.insert(i, name) + #else: + # self.surname_list.insert(i, name) def remove_from_surname_list(self, person): """ @@ -1466,11 +1467,11 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): The function must be overridden in the derived class. """ name = self._order_by_person_key(person) - if isinstance(name, str): - uname = name - name = name.encode('utf-8') - else: - uname = str(name) + #if isinstance(name, str): + # uname = name + # name = name.encode('utf-8') + #else: + # uname = str(name) # FIXME: check database def commit_family(self, family, trans, change_time=None): @@ -2615,6 +2616,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): cur = self.dbapi.execute("select * from note where handle = ?", [key]) return cur.fetchone() != None + def has_handle_for_tag(self, key): + cur = self.dbapi.execute("select * from tag where handle = ?", [key]) + return cur.fetchone() != None + def has_gramps_id_for_person(self, key): cur = self.dbapi.execute("select * from person where gramps_id = ?", [key]) return cur.fetchone() != None From 1830e2b9430be674ee9d8a3f6243ddeaf5e422f2 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Mon, 25 May 2015 22:31:36 -0400 Subject: [PATCH 099/105] DB-API: sql clean up; some FIXME's still left --- gramps/plugins/database/dbapi.py | 286 +++++++++++++++++-------------- 1 file changed, 154 insertions(+), 132 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 36e4e6a51..51a409240 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -10,6 +10,7 @@ import re import os import logging import shutil +import bisect #------------------------------------------------------------------------ # @@ -865,12 +866,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.emit('home-person-changed') def get_name_group_keys(self): - cur = self.dbapi.execute("select name from name_group order by name;") + cur = self.dbapi.execute("SELECT name FROM name_group ORDER BY name;") rows = cur.fetchall() return [row[0] for row in rows] def get_name_group_mapping(self, key): - cur = self.dbapi.execute("select grouping from name_group where name = ?;", + cur = self.dbapi.execute("SELECT grouping FROM name_group WHERE name = ?;", [key]) row = cur.fetchone() if row: @@ -885,62 +886,62 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return [row[0] for row in rows] def get_family_handles(self): - cur = self.dbapi.execute("select handle from family;") + cur = self.dbapi.execute("SELECT handle FROM family;") rows = cur.fetchall() return [row[0] for row in rows] def get_event_handles(self): - cur = self.dbapi.execute("select handle from event;") + cur = self.dbapi.execute("SELECT handle FROM event;") rows = cur.fetchall() return [row[0] for row in rows] def get_citation_handles(self, sort_handles=False): if sort_handles: - cur = self.dbapi.execute("select handle from citation ORDER BY order_by;") + cur = self.dbapi.execute("SELECT handle FROM citation ORDER BY order_by;") else: - cur = self.dbapi.execute("select handle from citation;") + cur = self.dbapi.execute("SELECT handle FROM citation;") rows = cur.fetchall() return [row[0] for row in rows] def get_source_handles(self, sort_handles=False): if sort_handles: - cur = self.dbapi.execute("select handle from source ORDER BY order_by;") + cur = self.dbapi.execute("SELECT handle FROM source ORDER BY order_by;") else: - cur = self.dbapi.execute("select handle from source;") + cur = self.dbapi.execute("SELECT handle from source;") rows = cur.fetchall() return [row[0] for row in rows] def get_place_handles(self, sort_handles=False): if sort_handles: - cur = self.dbapi.execute("select handle from place ORDER BY order_by;") + cur = self.dbapi.execute("SELECT handle FROM place ORDER BY order_by;") else: - cur = self.dbapi.execute("select handle from place;") + cur = self.dbapi.execute("SELECT handle FROM place;") rows = cur.fetchall() return [row[0] for row in rows] def get_repository_handles(self): - cur = self.dbapi.execute("select handle from repository;") + cur = self.dbapi.execute("SELECT handle FROM repository;") rows = cur.fetchall() return [row[0] for row in rows] def get_media_object_handles(self, sort_handles=False): if sort_handles: - cur = self.dbapi.execute("select handle from media ORDER BY order_by;") + cur = self.dbapi.execute("SELECT handle FROM media ORDER BY order_by;") else: - cur = self.dbapi.execute("select handle from media;") + cur = self.dbapi.execute("SELECT handle FROM media;") rows = cur.fetchall() return [row[0] for row in rows] def get_note_handles(self): - cur = self.dbapi.execute("select handle from note;") + cur = self.dbapi.execute("SELECT handle FROM note;") rows = cur.fetchall() return [row[0] for row in rows] def get_tag_handles(self, sort_handles=False): if sort_handles: - cur = self.dbapi.execute("select handle from tag ORDER BY order_by;") + cur = self.dbapi.execute("SELECT handle FROM tag ORDER BY order_by;") else: - cur = self.dbapi.execute("select handle from tag;") + cur = self.dbapi.execute("SELECT handle FROM tag;") rows = cur.fetchall() return [row[0] for row in rows] @@ -1097,52 +1098,52 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return None def get_number_of_people(self): - cur = self.dbapi.execute("select count(handle) from person;") + cur = self.dbapi.execute("SELECT count(handle) FROM person;") row = cur.fetchone() return row[0] def get_number_of_events(self): - cur = self.dbapi.execute("select count(handle) from event;") + cur = self.dbapi.execute("SELECT count(handle) FROM event;") row = cur.fetchone() return row[0] def get_number_of_places(self): - cur = self.dbapi.execute("select count(handle) from place;") + cur = self.dbapi.execute("SELECT count(handle) FROM place;") row = cur.fetchone() return row[0] def get_number_of_tags(self): - cur = self.dbapi.execute("select count(handle) from tag;") + cur = self.dbapi.execute("SELECT count(handle) FROM tag;") row = cur.fetchone() return row[0] def get_number_of_families(self): - cur = self.dbapi.execute("select count(handle) from family;") + cur = self.dbapi.execute("SELECT count(handle) FROM family;") row = cur.fetchone() return row[0] def get_number_of_notes(self): - cur = self.dbapi.execute("select count(handle) from note;") + cur = self.dbapi.execute("SELECT count(handle) FROM note;") row = cur.fetchone() return row[0] def get_number_of_citations(self): - cur = self.dbapi.execute("select count(handle) from citation;") + cur = self.dbapi.execute("SELECT count(handle) FROM citation;") row = cur.fetchone() return row[0] def get_number_of_sources(self): - cur = self.dbapi.execute("select count(handle) from source;") + cur = self.dbapi.execute("SELECT count(handle) FROM source;") row = cur.fetchone() return row[0] def get_number_of_media_objects(self): - cur = self.dbapi.execute("select count(handle) from media;") + cur = self.dbapi.execute("SELECT count(handle) FROM media;") row = cur.fetchone() return row[0] def get_number_of_repositories(self): - cur = self.dbapi.execute("select count(handle) from repository;") + cur = self.dbapi.execute("SELECT count(handle) FROM repository;") row = cur.fetchone() return row[0] @@ -1221,20 +1222,20 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return handle in self.media_map def has_name_group_key(self, key): - cur = self.dbapi.execute("select grouping from name_group where name = ?;", + cur = self.dbapi.execute("SELECT grouping FROM name_group WHERE name = ?;", [key]) row = cur.fetchone() return True if row else False def set_name_group_mapping(self, name, grouping): sname = name.encode("utf-8") - cur = self.dbapi.execute("SELECT * from name_group where name = ?;", + cur = self.dbapi.execute("SELECT * FROM name_group WHERE name = ?;", [sname]) row = cur.fetchone() if row: - cur = self.dbapi.execute("DELETE from name_group where name = ?;", + cur = self.dbapi.execute("DELETE FROM name_group WHERE name = ?;", [sname]) - cur = self.dbapi.execute("INSERT into name_group (name, grouping) VALUES(?, ?);", + cur = self.dbapi.execute("INSERT INTO name_group (name, grouping) VALUES(?, ?);", [sname, grouping]) self.dbapi.commit() @@ -1403,15 +1404,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.genderStats.count_person(person) self.add_to_surname_list(person, trans.batch) # Insert the person: - self.dbapi.execute("""insert into person (handle, order_by, gramps_id, blob) - values(?, ?, ?, ?);""", + self.dbapi.execute("""INSERT INTO person (handle, order_by, gramps_id, blob) + VALUES(?, ?, ?, ?);""", [person.handle, self._order_by_person_key(person), person.gramps_id, pickle.dumps(person.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(person) + self.dbapi.commit() # Other misc update tasks: self.individual_attributes.update( [str(attr.type) for attr in person.attribute_list @@ -1450,13 +1451,21 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): """ if batch_transaction: return - #name = self._order_by_person_key(person) - #i = bisect.bisect(self.surname_list, name) - #if 0 < i <= len(self.surname_list): - # if self.surname_list[i-1] != name: - # self.surname_list.insert(i, name) - #else: - # self.surname_list.insert(i, name) + # TODO: check to see if this is correct + name = None + primary_name = person.get_primary_name() + if primary_name: + surname_list = primary_name.get_surname_list() + if len(surname_list) > 0: + name = surname_list[0].surname + if name is None: + return + i = bisect.bisect(self.surname_list, name) + if 0 < i <= len(self.surname_list): + if self.surname_list[i-1] != name: + self.surname_list.insert(i, name) + else: + self.surname_list.insert(i, name) def remove_from_surname_list(self, person): """ @@ -1466,13 +1475,16 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): If not then we need to remove the name from the list. The function must be overridden in the derived class. """ - name = self._order_by_person_key(person) - #if isinstance(name, str): - # uname = name - # name = name.encode('utf-8') - #else: - # uname = str(name) - # FIXME: check database + name = None + primary_name = person.get_primary_name() + if primary_name: + surname_list = primary_name.get_surname_list() + if len(surname_list) > 0: + name = surname_list[0].surname + if name is None: + return + if name in self.surname_list: + self.surname_list.remove(name) def commit_family(self, family, trans, change_time=None): emit = None @@ -1486,13 +1498,13 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): family.handle]) else: emit = "family-add" - self.dbapi.execute("""insert into family (handle, gramps_id, blob) - values(?, ?, ?);""", + self.dbapi.execute("""INSERT INTO family (handle, gramps_id, blob) + VALUES(?, ?, ?);""", [family.handle, family.gramps_id, pickle.dumps(family.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(family) + self.dbapi.commit() # Misc updates: self.family_attributes.update( [str(attr.type) for attr in family.attribute_list @@ -1537,15 +1549,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): citation.handle]) else: emit = "citation-add" - self.dbapi.execute("""insert into citation (handle, order_by, gramps_id, blob) - values(?, ?, ?, ?);""", + self.dbapi.execute("""INSERT INTO citation (handle, order_by, gramps_id, blob) + VALUES(?, ?, ?, ?);""", [citation.handle, self._order_by_citation_key(citation), citation.gramps_id, pickle.dumps(citation.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(citation) + self.dbapi.commit() # Misc updates: attr_list = [] for mref in citation.media_list: @@ -1576,15 +1588,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): source.handle]) else: emit = "source-add" - self.dbapi.execute("""insert into source (handle, order_by, gramps_id, blob) - values(?, ?, ?, ?);""", + self.dbapi.execute("""INSERT INTO source (handle, order_by, gramps_id, blob) + VALUES(?, ?, ?, ?);""", [source.handle, self._order_by_source_key(source), source.gramps_id, pickle.dumps(source.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(source) + self.dbapi.commit() # Misc updates: self.source_media_types.update( [str(ref.media_type) for ref in source.reporef_list @@ -1615,12 +1627,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): repository.handle]) else: emit = "repository-add" - self.dbapi.execute("""insert into repository (handle, gramps_id, blob) - values(?, ?, ?);""", + self.dbapi.execute("""INSERT INTO repository (handle, gramps_id, blob) + VALUES(?, ?, ?);""", [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(repository) + self.dbapi.commit() # Misc updates: if repository.type.is_custom(): self.repository_types.add(str(repository.type)) @@ -1644,12 +1656,12 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): note.handle]) else: emit = "note-add" - self.dbapi.execute("""insert into note (handle, gramps_id, blob) - values(?, ?, ?);""", + self.dbapi.execute("""INSERT INTO note (handle, gramps_id, blob) + VALUES(?, ?, ?);""", [note.handle, note.gramps_id, pickle.dumps(note.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(note) + self.dbapi.commit() # Misc updates: if note.type.is_custom(): self.note_types.add(str(note.type)) @@ -1672,15 +1684,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): place.handle]) else: emit = "place-add" - self.dbapi.execute("""insert into place (handle, order_by, gramps_id, blob) - values(?, ?, ?, ?);""", + self.dbapi.execute("""INSERT INTO place (handle, order_by, gramps_id, blob) + VALUES(?, ?, ?, ?);""", [place.handle, self._order_by_place_key(place), place.gramps_id, pickle.dumps(place.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(place) + self.dbapi.commit() # Misc updates: if place.get_type().is_custom(): self.place_types.add(str(place.get_type())) @@ -1710,14 +1722,14 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): event.handle]) else: emit = "event-add" - self.dbapi.execute("""insert into event (handle, gramps_id, blob) - values(?, ?, ?);""", + self.dbapi.execute("""INSERT INTO event (handle, gramps_id, blob) + VALUES(?, ?, ?);""", [event.handle, event.gramps_id, pickle.dumps(event.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(event) + self.dbapi.commit() # Misc updates: self.event_attributes.update( [str(attr.type) for attr in event.attribute_list @@ -1736,27 +1748,19 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def update_backlinks(self, obj): # First, delete the current references: - self.dbapi.execute("DELETE FROM reference where obj_handle = ?;", + self.dbapi.execute("DELETE FROM reference WHERE obj_handle = ?;", [obj.handle]) # Now, add the current ones: references = set(obj.get_referenced_handles_recursively()) for (ref_class_name, ref_handle) in references: - self.dbapi.execute("""INSERT into reference + self.dbapi.execute("""INSERT INTO reference (obj_handle, obj_class, ref_handle, ref_class) VALUES(?, ?, ?, ?);""", [obj.handle, obj.__class__.__name__, ref_handle, ref_class_name]) - # Will commit later - - def update_event_attributes(self, attr_list): - # FIXME - pass - - def update_event_names(self, event_type): - # FIXME - pass + # This function is followed by a commit. def commit_tag(self, tag, trans, change_time=None): emit = None @@ -1770,14 +1774,14 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): tag.handle]) else: emit = "tag-add" - self.dbapi.execute("""insert into tag (handle, order_by, blob) - values(?, ?, ?);""", + self.dbapi.execute("""INSERT INTO tag (handle, order_by, blob) + VALUES(?, ?, ?);""", [tag.handle, self._order_by_tag_key(tag), pickle.dumps(tag.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(tag) + self.dbapi.commit() # Emit after added: if emit: self.emit(emit, ([tag.handle],)) @@ -1796,15 +1800,15 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): media.handle]) else: emit = "media-add" - self.dbapi.execute("""insert into media (handle, order_by, gramps_id, blob) - values(?, ?, ?, ?);""", + self.dbapi.execute("""INSERT INTO media (handle, order_by, gramps_id, blob) + VALUES(?, ?, ?, ?);""", [media.handle, self._order_by_media_key(media), media.gramps_id, pickle.dumps(media.serialize())]) if not trans.batch: - self.dbapi.commit() self.update_backlinks(media) + self.dbapi.commit() # Misc updates: self.media_attributes.update( [str(attr.type) for attr in media.attribute_list @@ -1910,7 +1914,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): person = Person.create(self.person_map[handle]) #del self.person_map[handle] #del self.person_id_map[person.gramps_id] - self.dbapi.execute("DELETE from person WHERE handle = ?;", [handle]) + self.dbapi.execute("DELETE FROM person WHERE handle = ?;", [handle]) self.emit("person-delete", ([handle],)) if not transaction.batch: self.dbapi.commit() @@ -2011,7 +2015,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): if self.readonly or not handle: return if handle in data_map: - self.dbapi.execute("DELETE from %s WHERE handle = ?;" % key2table[key], + self.dbapi.execute("DELETE FROM %s WHERE handle = ?;" % key2table[key], [handle]) self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) if not transaction.batch: @@ -2078,7 +2082,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): result_list = list(find_backlink_handles(handle)) """ - cur = self.dbapi.execute("SELECT * from reference WHERE ref_handle = ?;", + cur = self.dbapi.execute("SELECT * FROM reference WHERE ref_handle = ?;", [handle]) rows = cur.fetchall() for row in rows: @@ -2553,7 +2557,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): references = set(obj.get_referenced_handles_recursively()) # handle addition of new references for (ref_class_name, ref_handle) in references: - self.dbapi.execute("""INSERT into reference (obj_handle, obj_class, ref_handle, ref_class) + self.dbapi.execute("""INSERT INTO reference (obj_handle, obj_class, ref_handle, ref_class) VALUES(?, ?, ?, ?);""", [obj.handle, obj.__class__.__name__, @@ -2564,199 +2568,217 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): callback(5) def rebuild_secondary(self, update): - self.reindex_reference_map(update) + pass + # FIXME: rebuild the secondary databases/maps: + ## gender stats + ## event_names + ## fattr_names + ## pattr_names + ## sattr_names + ## marker_names + ## child_refs + ## family_rels + ## event_roles + ## name_types + ## origin_types + ## repo_types + ## note_types + ## sm_types + ## url_types + ## mattr_names + ## eattr_names + ## place_types + # surname list def prepare_import(self): """ - DBAPI does not commit data on gedcom import, but saves them - for later commit. + Do anything needed before an import. """ pass def commit_import(self): """ - Commits the items that were queued up during the last gedcom - import for two step adding. + Do anything needed after an import. """ self.reindex_reference_map(lambda n: n) def has_handle_for_person(self, key): - cur = self.dbapi.execute("select * from person where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM person WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_family(self, key): - cur = self.dbapi.execute("select * from family where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM family WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_source(self, key): - cur = self.dbapi.execute("select * from source where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM source WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_citation(self, key): - cur = self.dbapi.execute("select * from citation where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM citation WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_event(self, key): - cur = self.dbapi.execute("select * from event where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM event WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_media(self, key): - cur = self.dbapi.execute("select * from media where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM media WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_place(self, key): - cur = self.dbapi.execute("select * from place where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM place WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_repository(self, key): - cur = self.dbapi.execute("select * from repository where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM repository WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_note(self, key): - cur = self.dbapi.execute("select * from note where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM note WHERE handle = ?", [key]) return cur.fetchone() != None def has_handle_for_tag(self, key): - cur = self.dbapi.execute("select * from tag where handle = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM tag WHERE handle = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_person(self, key): - cur = self.dbapi.execute("select * from person where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM person WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_family(self, key): - cur = self.dbapi.execute("select * from family where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM family WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_source(self, key): - cur = self.dbapi.execute("select * from source where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM source WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_citation(self, key): - cur = self.dbapi.execute("select * from citation where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM citation WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_event(self, key): - cur = self.dbapi.execute("select * from event where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM event WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_media(self, key): - cur = self.dbapi.execute("select * from media where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM media WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_place(self, key): - cur = self.dbapi.execute("select * from place where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM place WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_repository(self, key): - cur = self.dbapi.execute("select * from repository where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM repository WHERE gramps_id = ?", [key]) return cur.fetchone() != None def has_gramps_id_for_note(self, key): - cur = self.dbapi.execute("select * from note where gramps_id = ?", [key]) + cur = self.dbapi.execute("SELECT * FROM note WHERE gramps_id = ?", [key]) return cur.fetchone() != None def get_person_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from person;") + cur = self.dbapi.execute("SELECT gramps_id FROM person;") rows = cur.fetchall() return [row[0] for row in rows] def get_family_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from family;") + cur = self.dbapi.execute("SELECT gramps_id FROM family;") rows = cur.fetchall() return [row[0] for row in rows] def get_source_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from source;") + cur = self.dbapi.execute("SELECT gramps_id FROM source;") rows = cur.fetchall() return [row[0] for row in rows] def get_citation_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from citation;") + cur = self.dbapi.execute("SELECT gramps_id FROM citation;") rows = cur.fetchall() return [row[0] for row in rows] def get_event_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from event;") + cur = self.dbapi.execute("SELECT gramps_id FROM event;") rows = cur.fetchall() return [row[0] for row in rows] def get_media_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from media;") + cur = self.dbapi.execute("SELECT gramps_id FROM media;") rows = cur.fetchall() return [row[0] for row in rows] def get_place_gramps_ids(self): - cur = self.dbapi.execute("select gramps from place;") + cur = self.dbapi.execute("SELECT gramps FROM place;") rows = cur.fetchall() return [row[0] for row in rows] def get_repository_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from repository;") + cur = self.dbapi.execute("SELECT gramps_id FROM repository;") rows = cur.fetchall() return [row[0] for row in rows] def get_note_gramps_ids(self): - cur = self.dbapi.execute("select gramps_id from note;") + cur = self.dbapi.execute("SELECT gramps_id FROM note;") rows = cur.fetchall() return [row[0] for row in rows] def _get_raw_person_data(self, key): - cur = self.dbapi.execute("select blob from person where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM person WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_family_data(self, key): - cur = self.dbapi.execute("select blob from family where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM family WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_source_data(self, key): - cur = self.dbapi.execute("select blob from source where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM source WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_citation_data(self, key): - cur = self.dbapi.execute("select blob from citation where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM citation WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_event_data(self, key): - cur = self.dbapi.execute("select blob from event where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM event WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_media_data(self, key): - cur = self.dbapi.execute("select blob from media where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM media WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_place_data(self, key): - cur = self.dbapi.execute("select blob from place where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM place WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_repository_data(self, key): - cur = self.dbapi.execute("select blob from repository where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM repository WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_note_data(self, key): - cur = self.dbapi.execute("select blob from note where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM note WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) def _get_raw_tag_data(self, key): - cur = self.dbapi.execute("select blob from tag where handle = ?", [key]) + cur = self.dbapi.execute("SELECT blob FROM tag WHERE handle = ?", [key]) row = cur.fetchone() if row: return pickle.loads(row[0]) From 8551a0b071a1ce3d83a4589341b0dedcf416cc3b Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Tue, 26 May 2015 11:35:56 -0400 Subject: [PATCH 100/105] DB-API: fixed error in find_initial_person --- gramps/plugins/database/dbapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 51a409240..54ca72cea 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -2099,7 +2099,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): cur = self.dbapi.execute("SELECT handle FROM person;") row = cur.fetchone() if row: - return row[0] + return self.get_person_from_handle(row[0]) def get_bookmarks(self): return self.bookmarks From a213f92f0d86e85e416b4f9a4968ed81fdf4b71d Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 30 May 2015 07:22:30 -0400 Subject: [PATCH 101/105] DB-API: support dbdid --- gramps/plugins/database/dbapi.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 54ca72cea..17aa99d02 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -468,6 +468,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): "count": self.get_number_of_tags, "raw_func": self._get_raw_tag_data, }) + self.set_save_path(directory) # skip GEDCOM cross-ref check for now: self.set_feature("skip-check-xref", True) self.set_feature("skip-import-additions", True) @@ -560,11 +561,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.transaction = None self.abort_possible = False self._bm_changes = 0 - self._directory = directory self._has_changed = False - self.full_name = None - self.path = None - self.brief_name = None self.genderStats = GenderStats() # can pass in loaded stats as dict self.owner = Researcher() if directory: @@ -2455,7 +2452,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # surname list self.surname_list = self.get_metadata('surname_list') - self._directory = directory + self.set_save_path(directory) self.undolog = os.path.join(self._directory, DBUNDOFN) self.undodb = DBAPIUndo(self, self.undolog) self.undodb.open() @@ -2474,9 +2471,14 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def set_save_path(self, directory): self._directory = directory - self.full_name = os.path.abspath(self._directory) - self.path = self.full_name - self.brief_name = os.path.basename(self._directory) + if directory: + self.full_name = os.path.abspath(self._directory) + self.path = self.full_name + self.brief_name = os.path.basename(self._directory) + else: + self.full_name = "" + self.path = "" + self.brief_name = "" def write_version(self, directory): """Write files for a newly created DB.""" @@ -2832,3 +2834,10 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def redo(self, update_history=True): return self.undodb.redo(update_history) + + def get_dbid(self): + """ + We use the file directory name as the unique ID for + this database on this computer. + """ + return self.brief_name From 5b2bc78108a1b4d3aacdbd60e940609839a0e9ba Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 30 May 2015 07:54:59 -0400 Subject: [PATCH 102/105] DB-API: allow low-level map import --- gramps/plugins/database/dbapi.py | 60 ++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 17aa99d02..5b7d9b032 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -131,8 +131,13 @@ class Table(object): """ Implements Table interface. """ - def __init__(self, funcs): - self.funcs = funcs + def __init__(self, db, table_name, funcs=None): + self.db = db + self.table_name = table_name + if funcs: + self.funcs = funcs + else: + self.funcs = db._tables[table_name] def cursor(self): """ @@ -157,6 +162,7 @@ class Map(object): self.table = table self.keys_func = keys_func self.contains_func = contains_func + self.txn = DBAPITxn("Dummy transaction", db=self.table.db, batch=True) def keys(self): return self.table.funcs[self.keys_func]() @@ -171,6 +177,16 @@ class Map(object): if self.table.funcs[self.contains_func](key): return self.table.funcs["raw_func"](key) + def __setitem__(self, key, value): + """ + This is only done in a low-level raw import. + + value: serialized object + key: bytes key (ignored in this implementation) + """ + obj = self.table.funcs["class_func"].create(value) + self.table.funcs["commit_func"](obj, self.txn) + def __len__(self): return self.table.funcs["count_func"]() @@ -515,44 +531,44 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.rmap_index = 0 self.nmap_index = 0 self.env = Environment(self) - self.person_map = Map(Table(self._tables["Person"])) - self.person_id_map = Map(Table(self._tables["Person"]), + self.person_map = Map(Table(self, "Person")) + self.person_id_map = Map(Table(self, "Person"), keys_func="ids_func", contains_func="has_gramps_id_func") - self.family_map = Map(Table(self._tables["Family"])) - self.family_id_map = Map(Table(self._tables["Family"]), + self.family_map = Map(Table(self, "Family")) + self.family_id_map = Map(Table(self, "Family"), keys_func="ids_func", contains_func="has_gramps_id_func") - self.place_map = Map(Table(self._tables["Place"])) - self.place_id_map = Map(Table(self._tables["Place"]), + self.place_map = Map(Table(self, "Place")) + self.place_id_map = Map(Table(self, "Place"), keys_func="ids_func", contains_func="has_gramps_id_func") - self.citation_map = Map(Table(self._tables["Citation"])) - self.citation_id_map = Map(Table(self._tables["Citation"]), + self.citation_map = Map(Table(self, "Citation")) + self.citation_id_map = Map(Table(self, "Citation"), keys_func="ids_func", contains_func="has_gramps_id_func") - self.source_map = Map(Table(self._tables["Source"])) - self.source_id_map = Map(Table(self._tables["Source"]), + self.source_map = Map(Table(self, "Source")) + self.source_id_map = Map(Table(self, "Source"), keys_func="ids_func", contains_func="has_gramps_id_func") - self.repository_map = Map(Table(self._tables["Repository"])) - self.repository_id_map = Map(Table(self._tables["Repository"]), + self.repository_map = Map(Table(self, "Repository")) + self.repository_id_map = Map(Table(self, "Repository"), keys_func="ids_func", contains_func="has_gramps_id_func") - self.note_map = Map(Table(self._tables["Note"])) - self.note_id_map = Map(Table(self._tables["Note"]), + self.note_map = Map(Table(self, "Note")) + self.note_id_map = Map(Table(self, "Note"), keys_func="ids_func", contains_func="has_gramps_id_func") - self.media_map = Map(Table(self._tables["Media"])) - self.media_id_map = Map(Table(self._tables["Media"]), + self.media_map = Map(Table(self, "Media")) + self.media_id_map = Map(Table(self, "Media"), keys_func="ids_func", contains_func="has_gramps_id_func") - self.event_map = Map(Table(self._tables["Event"])) - self.event_id_map = Map(Table(self._tables["Event"]), + self.event_map = Map(Table(self, "Event")) + self.event_id_map = Map(Table(self, "Event"), keys_func="ids_func", contains_func="has_gramps_id_func") - self.tag_map = Map(Table(self._tables["Tag"])) - self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) + self.tag_map = Map(Table(self, "Tag")) + self.metadata = Map(Table(self, "Metadata", funcs={"cursor_func": lambda: MetaCursor()})) self.undo_callback = None self.redo_callback = None self.undo_history_callback = None From 0b2ad1df3540ba0b8d10057236ae877298e23bde Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Sat, 30 May 2015 08:19:13 -0400 Subject: [PATCH 103/105] DB-API: only backup when changes; fixed has_changed --- gramps/plugins/database/dbapi.py | 38 ++++++++++++++------------------ 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 5b7d9b032..272e713a6 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -577,7 +577,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.transaction = None self.abort_possible = False self._bm_changes = 0 - self._has_changed = False + self.has_changed = False self.genderStats = GenderStats() # can pass in loaded stats as dict self.owner = Researcher() if directory: @@ -1456,7 +1456,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([person.handle],)) - self._has_changed = True + self.has_changed = True def add_to_surname_list(self, person, batch_transaction): """ @@ -1546,7 +1546,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([family.handle],)) - self._has_changed = True + self.has_changed = True def commit_citation(self, citation, trans, change_time=None): emit = None @@ -1585,7 +1585,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([citation.handle],)) - self._has_changed = True + self.has_changed = True def commit_source(self, source, trans, change_time=None): emit = None @@ -1626,7 +1626,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([source.handle],)) - self._has_changed = True + self.has_changed = True def commit_repository(self, repository, trans, change_time=None): emit = None @@ -1655,7 +1655,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([repository.handle],)) - self._has_changed = True + self.has_changed = True def commit_note(self, note, trans, change_time=None): emit = None @@ -1681,7 +1681,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([note.handle],)) - self._has_changed = True + self.has_changed = True def commit_place(self, place, trans, change_time=None): emit = None @@ -1721,7 +1721,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([place.handle],)) - self._has_changed = True + self.has_changed = True def commit_event(self, event, trans, change_time=None): emit = None @@ -1757,7 +1757,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): # Emit after added: if emit: self.emit(emit, ([event.handle],)) - self._has_changed = True + self.has_changed = True def update_backlinks(self, obj): # First, delete the current references: @@ -2036,11 +2036,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def close(self): if self._directory: - from gramps.plugins.export.exportxml import XmlWriter - from gramps.cli.user import User - writer = XmlWriter(self, User(), strip_photos=0, compress=1) - filename = os.path.join(self._directory, "data.gramps") - writer.write(filename) filename = os.path.join(self._directory, "meta_data.db") touch(filename) # Save metadata @@ -2276,9 +2271,6 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): def get_source_bookmarks(self): return self.source_bookmarks - def has_changed(self): - return self._has_changed - def is_open(self): return self._directory is not None @@ -2492,9 +2484,9 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.path = self.full_name self.brief_name = os.path.basename(self._directory) else: - self.full_name = "" - self.path = "" - self.brief_name = "" + self.full_name = None + self.path = None + self.brief_name = None def write_version(self, directory): """Write files for a newly created DB.""" @@ -2834,7 +2826,11 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): """ If you wish to support an optional backup routine, put it here. """ - pass + from gramps.plugins.export.exportxml import XmlWriter + from gramps.cli.user import User + writer = XmlWriter(self, User(), strip_photos=0, compress=1) + filename = os.path.join(self._directory, "data.gramps") + writer.write(filename) def restore(self): """ From 4ee8ac258518077bc9a486b5b70738ca0b07be88 Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Mon, 1 Jun 2015 21:13:38 -0400 Subject: [PATCH 104/105] DB-API: Sped up get_tag_from_name --- gramps/plugins/database/dbapi.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py index 272e713a6..5b1485d50 100644 --- a/gramps/plugins/database/dbapi.py +++ b/gramps/plugins/database/dbapi.py @@ -1058,11 +1058,11 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): return (handle for handle in self.family_map.keys()) def get_tag_from_name(self, name): - ## FIXME: Slow, but typically not too many tags: - for data in self.tag_map.values(): - tag = Tag.create(data) - if tag.name == name: - return tag + cur = self.dbapi.execute("""select handle from tag where order_by = ?;""", + [self._order_by_tag_key(name)]) + row = cur.fetchone() + if row: + self.get_tag_from_handle(row[0]) return None def get_person_from_gramps_id(self, gramps_id): From 7bcb6295471629c5c524dab2bdf0d62257bec6ff Mon Sep 17 00:00:00 2001 From: Doug Blank <dblank@cs.brynmawr.edu> Date: Tue, 2 Jun 2015 10:55:38 -0400 Subject: [PATCH 105/105] Updated backends to 5.0; moved Django and DBAPI to addons --- gramps/plugins/database/bsddb.gpr.py | 2 +- gramps/plugins/database/dbapi.gpr.py | 31 - gramps/plugins/database/dbapi.py | 2855 ----------------- .../defaults/default_settings.py | 7 - gramps/plugins/database/dictionarydb.gpr.py | 2 +- .../defaults/default_settings.py | 150 - .../django_support/defaults/sqlite.db | Bin 293888 -> 0 bytes .../database/django_support/libdjango.py | 2063 ------------ gramps/plugins/database/djangodb.gpr.py | 32 - gramps/plugins/database/djangodb.py | 2150 ------------- 10 files changed, 2 insertions(+), 7290 deletions(-) delete mode 100644 gramps/plugins/database/dbapi.gpr.py delete mode 100644 gramps/plugins/database/dbapi.py delete mode 100644 gramps/plugins/database/dbapi_support/defaults/default_settings.py delete mode 100644 gramps/plugins/database/django_support/defaults/default_settings.py delete mode 100644 gramps/plugins/database/django_support/defaults/sqlite.db delete mode 100644 gramps/plugins/database/django_support/libdjango.py delete mode 100644 gramps/plugins/database/djangodb.gpr.py delete mode 100644 gramps/plugins/database/djangodb.py diff --git a/gramps/plugins/database/bsddb.gpr.py b/gramps/plugins/database/bsddb.gpr.py index 20f2e9fda..82a23573c 100644 --- a/gramps/plugins/database/bsddb.gpr.py +++ b/gramps/plugins/database/bsddb.gpr.py @@ -24,7 +24,7 @@ plg.name = _("BSDDB Database Backend") plg.name_accell = _("_BSDDB Database Backend") plg.description = _("Berkeley Software Distribution Database Backend") plg.version = '1.0' -plg.gramps_target_version = "4.2" +plg.gramps_target_version = "5.0" plg.status = STABLE plg.fname = 'bsddb.py' plg.ptype = DATABASE diff --git a/gramps/plugins/database/dbapi.gpr.py b/gramps/plugins/database/dbapi.gpr.py deleted file mode 100644 index 2749e7a50..000000000 --- a/gramps/plugins/database/dbapi.gpr.py +++ /dev/null @@ -1,31 +0,0 @@ -# -# Gramps - a GTK+/GNOME based genealogy program -# -# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com> -# -# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# - -plg = newplugin() -plg.id = 'dbapi' -plg.name = _("DB-API 2.0") -plg.name_accell = _("DB-_API 2.0") -plg.description = _("DB-API 2.0 Database Backend") -plg.version = '1.0' -plg.gramps_target_version = "4.2" -plg.status = STABLE -plg.fname = 'dbapi.py' -plg.ptype = DATABASE -plg.databaseclass = 'DBAPI' diff --git a/gramps/plugins/database/dbapi.py b/gramps/plugins/database/dbapi.py deleted file mode 100644 index 5b1485d50..000000000 --- a/gramps/plugins/database/dbapi.py +++ /dev/null @@ -1,2855 +0,0 @@ -#------------------------------------------------------------------------ -# -# Python Modules -# -#------------------------------------------------------------------------ -import pickle -import base64 -import time -import re -import os -import logging -import shutil -import bisect - -#------------------------------------------------------------------------ -# -# Gramps Modules -# -#------------------------------------------------------------------------ -import gramps -from gramps.gen.const import GRAMPS_LOCALE as glocale -_ = glocale.translation.gettext -from gramps.gen.db import (DbReadBase, DbWriteBase, DbTxn, DbUndo, - KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP, - CLASS_TO_KEY_MAP) -from gramps.gen.utils.callback import Callback -from gramps.gen.updatecallback import UpdateCallback -from gramps.gen.db.dbconst import * -from gramps.gen.db import (PERSON_KEY, - FAMILY_KEY, - CITATION_KEY, - SOURCE_KEY, - EVENT_KEY, - MEDIA_KEY, - PLACE_KEY, - REPOSITORY_KEY, - NOTE_KEY, - TAG_KEY) - -from gramps.gen.utils.id import create_id -from gramps.gen.lib.researcher import Researcher -from gramps.gen.lib import (Tag, MediaObject, Person, Family, Source, Citation, Event, - Place, Repository, Note, NameOriginType) -from gramps.gen.lib.genderstats import GenderStats - -_LOG = logging.getLogger(DBLOGNAME) - -def touch(fname, mode=0o666, dir_fd=None, **kwargs): - ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python - flags = os.O_CREAT | os.O_APPEND - with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f: - os.utime(f.fileno() if os.utime in os.supports_fd else fname, - dir_fd=None if os.supports_fd else dir_fd, **kwargs) - -class DBAPIUndo(DbUndo): - def __init__(self, grampsdb, path): - super(DBAPIUndo, self).__init__(grampsdb) - self.undodb = grampsdb - self.path = path - - def open(self, value=None): - """ - Open the backing storage. Needs to be overridden in the derived - class. - """ - pass - # FIXME - - def close(self): - """ - Close the backing storage. Needs to be overridden in the derived - class. - """ - pass - # FIXME - - def append(self, value): - """ - Add a new entry on the end. Needs to be overridden in the derived - class. - """ - pass - # FIXME - - def __getitem__(self, index): - """ - Returns an entry by index number. Needs to be overridden in the - derived class. - """ - return None - # FIXME - - def __setitem__(self, index, value): - """ - Set an entry to a value. Needs to be overridden in the derived class. - """ - pass - # FIXME - - def __len__(self): - """ - Returns the number of entries. Needs to be overridden in the derived - class. - """ - return 0 - # FIXME - - def __redo(self, update_history): - """ - """ - pass - # FIXME - - def __undo(self, update_history): - """ - """ - pass - # FIXME - -class Environment(object): - """ - Implements the Environment API. - """ - def __init__(self, db): - self.db = db - - def txn_begin(self): - return DBAPITxn("DBAPIDb Transaction", self.db) - -class Table(object): - """ - Implements Table interface. - """ - def __init__(self, db, table_name, funcs=None): - self.db = db - self.table_name = table_name - if funcs: - self.funcs = funcs - else: - self.funcs = db._tables[table_name] - - def cursor(self): - """ - Returns a Cursor for this Table. - """ - return self.funcs["cursor_func"]() - - def put(self, key, data, txn=None): - self.funcs["add_func"](data, txn) - -class Map(object): - """ - Implements the map API for person_map, etc. - - Takes a Table() as argument. - """ - def __init__(self, table, - keys_func="handles_func", - contains_func="has_handle_func", - *args, **kwargs): - super().__init__(*args, **kwargs) - self.table = table - self.keys_func = keys_func - self.contains_func = contains_func - self.txn = DBAPITxn("Dummy transaction", db=self.table.db, batch=True) - - def keys(self): - return self.table.funcs[self.keys_func]() - - def values(self): - return self.table.funcs["cursor_func"]() - - def __contains__(self, key): - return self.table.funcs[self.contains_func](key) - - def __getitem__(self, key): - if self.table.funcs[self.contains_func](key): - return self.table.funcs["raw_func"](key) - - def __setitem__(self, key, value): - """ - This is only done in a low-level raw import. - - value: serialized object - key: bytes key (ignored in this implementation) - """ - obj = self.table.funcs["class_func"].create(value) - self.table.funcs["commit_func"](obj, self.txn) - - def __len__(self): - return self.table.funcs["count_func"]() - -class MetaCursor(object): - def __init__(self): - pass - def __enter__(self): - return self - def __iter__(self): - return self.__next__() - def __next__(self): - yield None - def __exit__(self, *args, **kwargs): - pass - def iter(self): - yield None - def first(self): - self._iter = self.__iter__() - return self.next() - def next(self): - try: - return next(self._iter) - except: - return None - def close(self): - pass - -class Cursor(object): - def __init__(self, map): - self.map = map - self._iter = self.__iter__() - def __enter__(self): - return self - def __iter__(self): - for item in self.map.keys(): - yield (bytes(item, "utf-8"), self.map[item]) - def __next__(self): - try: - return self._iter.__next__() - except StopIteration: - return None - def __exit__(self, *args, **kwargs): - pass - def iter(self): - for item in self.map.keys(): - yield (bytes(item, "utf-8"), self.map[item]) - def first(self): - self._iter = self.__iter__() - try: - return next(self._iter) - except: - return - def next(self): - try: - return next(self._iter) - except: - return - def close(self): - pass - -class Bookmarks(object): - def __init__(self, default=[]): - self.handles = list(default) - - def set(self, handles): - self.handles = list(handles) - - def get(self): - return self.handles - - def append(self, handle): - self.handles.append(handle) - - def append_list(self, handles): - self.handles += handles - - def remove(self, handle): - self.handles.remove(handle) - - def pop(self, item): - return self.handles.pop(item) - - def insert(self, pos, item): - self.handles.insert(pos, item) - - def close(self): - del self.handles - - -class DBAPITxn(DbTxn): - def __init__(self, message, db, batch=False): - DbTxn.__init__(self, message, db, batch) - - 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 DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback): - """ - A Gramps Database Backend. This replicates the grampsdb functions. - """ - __signals__ = dict((obj+'-'+op, signal) - for obj in - ['person', 'family', 'event', 'place', - 'source', 'citation', 'media', 'note', 'repository', 'tag'] - for op, signal in zip( - ['add', 'update', 'delete', 'rebuild'], - [(list,), (list,), (list,), None] - ) - ) - - # 2. Signals for long operations - __signals__.update(('long-op-'+op, signal) for op, signal in zip( - ['start', 'heartbeat', 'end'], - [(object,), None, None] - )) - - # 3. Special signal for change in home person - __signals__['home-person-changed'] = None - - # 4. Signal for change in person group name, parameters are - __signals__['person-groupname-rebuild'] = (str, str) - - __callback_map = {} - - def __init__(self, directory=None): - DbReadBase.__init__(self) - DbWriteBase.__init__(self) - Callback.__init__(self) - self._tables['Person'].update( - { - "handle_func": self.get_person_from_handle, - "gramps_id_func": self.get_person_from_gramps_id, - "class_func": Person, - "cursor_func": self.get_person_cursor, - "handles_func": self.get_person_handles, - "add_func": self.add_person, - "commit_func": self.commit_person, - "iter_func": self.iter_people, - "ids_func": self.get_person_gramps_ids, - "has_handle_func": self.has_handle_for_person, - "has_gramps_id_func": self.has_gramps_id_for_person, - "count": self.get_number_of_people, - "raw_func": self._get_raw_person_data, - }) - self._tables['Family'].update( - { - "handle_func": self.get_family_from_handle, - "gramps_id_func": self.get_family_from_gramps_id, - "class_func": Family, - "cursor_func": self.get_family_cursor, - "handles_func": self.get_family_handles, - "add_func": self.add_family, - "commit_func": self.commit_family, - "iter_func": self.iter_families, - "ids_func": self.get_family_gramps_ids, - "has_handle_func": self.has_handle_for_family, - "has_gramps_id_func": self.has_gramps_id_for_family, - "count": self.get_number_of_families, - "raw_func": self._get_raw_family_data, - }) - self._tables['Source'].update( - { - "handle_func": self.get_source_from_handle, - "gramps_id_func": self.get_source_from_gramps_id, - "class_func": Source, - "cursor_func": self.get_source_cursor, - "handles_func": self.get_source_handles, - "add_func": self.add_source, - "commit_func": self.commit_source, - "iter_func": self.iter_sources, - "ids_func": self.get_source_gramps_ids, - "has_handle_func": self.has_handle_for_source, - "has_gramps_id_func": self.has_gramps_id_for_source, - "count": self.get_number_of_sources, - "raw_func": self._get_raw_source_data, - }) - self._tables['Citation'].update( - { - "handle_func": self.get_citation_from_handle, - "gramps_id_func": self.get_citation_from_gramps_id, - "class_func": Citation, - "cursor_func": self.get_citation_cursor, - "handles_func": self.get_citation_handles, - "add_func": self.add_citation, - "commit_func": self.commit_citation, - "iter_func": self.iter_citations, - "ids_func": self.get_citation_gramps_ids, - "has_handle_func": self.has_handle_for_citation, - "has_gramps_id_func": self.has_gramps_id_for_citation, - "count": self.get_number_of_citations, - "raw_func": self._get_raw_citation_data, - }) - self._tables['Event'].update( - { - "handle_func": self.get_event_from_handle, - "gramps_id_func": self.get_event_from_gramps_id, - "class_func": Event, - "cursor_func": self.get_event_cursor, - "handles_func": self.get_event_handles, - "add_func": self.add_event, - "commit_func": self.commit_event, - "iter_func": self.iter_events, - "ids_func": self.get_event_gramps_ids, - "has_handle_func": self.has_handle_for_event, - "has_gramps_id_func": self.has_gramps_id_for_event, - "count": self.get_number_of_events, - "raw_func": self._get_raw_event_data, - }) - self._tables['Media'].update( - { - "handle_func": self.get_object_from_handle, - "gramps_id_func": self.get_object_from_gramps_id, - "class_func": MediaObject, - "cursor_func": self.get_media_cursor, - "handles_func": self.get_media_object_handles, - "add_func": self.add_object, - "commit_func": self.commit_media_object, - "iter_func": self.iter_media_objects, - "ids_func": self.get_media_gramps_ids, - "has_handle_func": self.has_handle_for_media, - "has_gramps_id_func": self.has_gramps_id_for_media, - "count": self.get_number_of_media_objects, - "raw_func": self._get_raw_media_data, - }) - self._tables['Place'].update( - { - "handle_func": self.get_place_from_handle, - "gramps_id_func": self.get_place_from_gramps_id, - "class_func": Place, - "cursor_func": self.get_place_cursor, - "handles_func": self.get_place_handles, - "add_func": self.add_place, - "commit_func": self.commit_place, - "iter_func": self.iter_places, - "ids_func": self.get_place_gramps_ids, - "has_handle_func": self.has_handle_for_place, - "has_gramps_id_func": self.has_gramps_id_for_place, - "count": self.get_number_of_places, - "raw_func": self._get_raw_place_data, - }) - self._tables['Repository'].update( - { - "handle_func": self.get_repository_from_handle, - "gramps_id_func": self.get_repository_from_gramps_id, - "class_func": Repository, - "cursor_func": self.get_repository_cursor, - "handles_func": self.get_repository_handles, - "add_func": self.add_repository, - "commit_func": self.commit_repository, - "iter_func": self.iter_repositories, - "ids_func": self.get_repository_gramps_ids, - "has_handle_func": self.has_handle_for_repository, - "has_gramps_id_func": self.has_gramps_id_for_repository, - "count": self.get_number_of_repositories, - "raw_func": self._get_raw_repository_data, - }) - self._tables['Note'].update( - { - "handle_func": self.get_note_from_handle, - "gramps_id_func": self.get_note_from_gramps_id, - "class_func": Note, - "cursor_func": self.get_note_cursor, - "handles_func": self.get_note_handles, - "add_func": self.add_note, - "commit_func": self.commit_note, - "iter_func": self.iter_notes, - "ids_func": self.get_note_gramps_ids, - "has_handle_func": self.has_handle_for_note, - "has_gramps_id_func": self.has_gramps_id_for_note, - "count": self.get_number_of_notes, - "raw_func": self._get_raw_note_data, - }) - self._tables['Tag'].update( - { - "handle_func": self.get_tag_from_handle, - "gramps_id_func": None, - "class_func": Tag, - "cursor_func": self.get_tag_cursor, - "handles_func": self.get_tag_handles, - "add_func": self.add_tag, - "commit_func": self.commit_tag, - "has_handle_func": self.has_handle_for_tag, - "iter_func": self.iter_tags, - "count": self.get_number_of_tags, - "raw_func": self._get_raw_tag_data, - }) - self.set_save_path(directory) - # skip GEDCOM cross-ref check for now: - self.set_feature("skip-check-xref", True) - self.set_feature("skip-import-additions", True) - self.readonly = False - self.db_is_open = True - self.name_formats = [] - # Bookmarks: - 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.undodb = None - self.id_trans = DBAPITxn("ID Transaction", self) - self.fid_trans = DBAPITxn("FID Transaction", self) - self.pid_trans = DBAPITxn("PID Transaction", self) - self.cid_trans = DBAPITxn("CID Transaction", self) - self.sid_trans = DBAPITxn("SID Transaction", self) - self.oid_trans = DBAPITxn("OID Transaction", self) - self.rid_trans = DBAPITxn("RID Transaction", self) - self.nid_trans = DBAPITxn("NID Transaction", self) - self.eid_trans = DBAPITxn("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 = Environment(self) - self.person_map = Map(Table(self, "Person")) - self.person_id_map = Map(Table(self, "Person"), - keys_func="ids_func", - contains_func="has_gramps_id_func") - self.family_map = Map(Table(self, "Family")) - self.family_id_map = Map(Table(self, "Family"), - keys_func="ids_func", - contains_func="has_gramps_id_func") - self.place_map = Map(Table(self, "Place")) - self.place_id_map = Map(Table(self, "Place"), - keys_func="ids_func", - contains_func="has_gramps_id_func") - self.citation_map = Map(Table(self, "Citation")) - self.citation_id_map = Map(Table(self, "Citation"), - keys_func="ids_func", - contains_func="has_gramps_id_func") - self.source_map = Map(Table(self, "Source")) - self.source_id_map = Map(Table(self, "Source"), - keys_func="ids_func", - contains_func="has_gramps_id_func") - self.repository_map = Map(Table(self, "Repository")) - self.repository_id_map = Map(Table(self, "Repository"), - keys_func="ids_func", - contains_func="has_gramps_id_func") - self.note_map = Map(Table(self, "Note")) - self.note_id_map = Map(Table(self, "Note"), - keys_func="ids_func", - contains_func="has_gramps_id_func") - self.media_map = Map(Table(self, "Media")) - self.media_id_map = Map(Table(self, "Media"), - keys_func="ids_func", - contains_func="has_gramps_id_func") - self.event_map = Map(Table(self, "Event")) - self.event_id_map = Map(Table(self, "Event"), - keys_func="ids_func", - contains_func="has_gramps_id_func") - self.tag_map = Map(Table(self, "Tag")) - self.metadata = Map(Table(self, "Metadata", funcs={"cursor_func": lambda: MetaCursor()})) - self.undo_callback = None - self.redo_callback = None - self.undo_history_callback = None - self.modified = 0 - self.txn = DBAPITxn("DBAPI Transaction", self) - self.transaction = None - self.abort_possible = False - self._bm_changes = 0 - self.has_changed = False - self.genderStats = GenderStats() # can pass in loaded stats as dict - self.owner = Researcher() - if directory: - self.load(directory) - - def version_supported(self): - """Return True when the file has a supported version.""" - return True - - def get_table_names(self): - """Return a list of valid table names.""" - return list(self._tables.keys()) - - def get_table_metadata(self, table_name): - """Return the metadata for a valid table name.""" - if table_name in self._tables: - return self._tables[table_name] - return None - - def transaction_commit(self, txn): - self.dbapi.commit() - - def transaction_abort(self, txn): - self.dbapi.rollback() - - @staticmethod - def _validated_id_prefix(val, default): - if isinstance(val, str) 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 = int(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_<object>_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 self.get_metadata("media-path", "") - - def set_mediapath(self, mediapath): - return self.set_metadata("media-path", mediapath) - - def get_metadata(self, key, default=[]): - """ - Get an item from the database. - """ - cur = self.dbapi.execute("SELECT * FROM metadata WHERE setting = ?;", [key]) - row = cur.fetchone() - if row: - return pickle.loads(row["value"]) - elif default == []: - return [] - else: - return default - - def set_metadata(self, key, value): - """ - key: string - value: item, will be serialized here - """ - cur = self.dbapi.execute("SELECT * FROM metadata WHERE setting = ?;", [key]) - row = cur.fetchone() - if row: - cur = self.dbapi.execute("UPDATE metadata SET value = ? WHERE setting = ?;", - [pickle.dumps(value), key]) - else: - cur = self.dbapi.execute("INSERT INTO metadata (setting, value) VALUES (?, ?);", - [key, pickle.dumps(value)]) - self.dbapi.commit() - - def set_default_person_handle(self, handle): - self.set_metadata("default-person-handle", handle) - self.emit('home-person-changed') - - def get_name_group_keys(self): - cur = self.dbapi.execute("SELECT name FROM name_group ORDER BY name;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_name_group_mapping(self, key): - cur = self.dbapi.execute("SELECT grouping FROM name_group WHERE name = ?;", - [key]) - row = cur.fetchone() - if row: - return row[0] - - def get_person_handles(self, sort_handles=False): - if sort_handles: - cur = self.dbapi.execute("SELECT handle FROM person ORDER BY order_by;") - else: - cur = self.dbapi.execute("SELECT handle FROM person;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_family_handles(self): - cur = self.dbapi.execute("SELECT handle FROM family;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_event_handles(self): - cur = self.dbapi.execute("SELECT handle FROM event;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_citation_handles(self, sort_handles=False): - if sort_handles: - cur = self.dbapi.execute("SELECT handle FROM citation ORDER BY order_by;") - else: - cur = self.dbapi.execute("SELECT handle FROM citation;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_source_handles(self, sort_handles=False): - if sort_handles: - cur = self.dbapi.execute("SELECT handle FROM source ORDER BY order_by;") - else: - cur = self.dbapi.execute("SELECT handle from source;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_place_handles(self, sort_handles=False): - if sort_handles: - cur = self.dbapi.execute("SELECT handle FROM place ORDER BY order_by;") - else: - cur = self.dbapi.execute("SELECT handle FROM place;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_repository_handles(self): - cur = self.dbapi.execute("SELECT handle FROM repository;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_media_object_handles(self, sort_handles=False): - if sort_handles: - cur = self.dbapi.execute("SELECT handle FROM media ORDER BY order_by;") - else: - cur = self.dbapi.execute("SELECT handle FROM media;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_note_handles(self): - cur = self.dbapi.execute("SELECT handle FROM note;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_tag_handles(self, sort_handles=False): - if sort_handles: - cur = self.dbapi.execute("SELECT handle FROM tag ORDER BY order_by;") - else: - cur = self.dbapi.execute("SELECT handle FROM tag;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_event_from_handle(self, handle): - if isinstance(handle, bytes): - handle = str(handle, "utf-8") - event = None - if handle in self.event_map: - event = Event.create(self._get_raw_event_data(handle)) - return event - - def get_family_from_handle(self, handle): - if isinstance(handle, bytes): - handle = str(handle, "utf-8") - family = None - if handle in self.family_map: - family = Family.create(self._get_raw_family_data(handle)) - return family - - def get_repository_from_handle(self, handle): - if isinstance(handle, bytes): - handle = str(handle, "utf-8") - repository = None - if handle in self.repository_map: - repository = Repository.create(self._get_raw_repository_data(handle)) - return repository - - def get_person_from_handle(self, handle): - if isinstance(handle, bytes): - handle = str(handle, "utf-8") - person = None - if handle in self.person_map: - person = Person.create(self._get_raw_person_data(handle)) - return person - - def get_place_from_handle(self, handle): - if isinstance(handle, bytes): - handle = str(handle, "utf-8") - place = None - if handle in self.place_map: - place = Place.create(self._get_raw_place_data(handle)) - return place - - def get_citation_from_handle(self, handle): - if isinstance(handle, bytes): - handle = str(handle, "utf-8") - citation = None - if handle in self.citation_map: - citation = Citation.create(self._get_raw_citation_data(handle)) - return citation - - def get_source_from_handle(self, handle): - if isinstance(handle, bytes): - handle = str(handle, "utf-8") - source = None - if handle in self.source_map: - source = Source.create(self._get_raw_source_data(handle)) - return source - - def get_note_from_handle(self, handle): - if isinstance(handle, bytes): - handle = str(handle, "utf-8") - note = None - if handle in self.note_map: - note = Note.create(self._get_raw_note_data(handle)) - return note - - def get_object_from_handle(self, handle): - if isinstance(handle, bytes): - handle = str(handle, "utf-8") - media = None - if handle in self.media_map: - media = MediaObject.create(self._get_raw_media_data(handle)) - return media - - def get_tag_from_handle(self, handle): - if isinstance(handle, bytes): - handle = str(handle, "utf-8") - tag = None - if handle in self.tag_map: - tag = Tag.create(self._get_raw_tag_data(handle)) - return tag - - def get_default_person(self): - handle = self.get_default_handle() - if handle: - return self.get_person_from_handle(handle) - else: - return None - - def iter_people(self): - return (Person.create(data[1]) for data in self.get_person_cursor()) - - def iter_person_handles(self): - return (data[0] for data in self.get_person_cursor()) - - def iter_families(self): - return (Family.create(data[1]) for data in self.get_family_cursor()) - - def iter_family_handles(self): - return (handle for handle in self.family_map.keys()) - - def get_tag_from_name(self, name): - cur = self.dbapi.execute("""select handle from tag where order_by = ?;""", - [self._order_by_tag_key(name)]) - row = cur.fetchone() - if row: - self.get_tag_from_handle(row[0]) - return None - - def get_person_from_gramps_id(self, gramps_id): - if gramps_id in self.person_id_map: - return Person.create(self.person_id_map[gramps_id]) - return None - - def get_family_from_gramps_id(self, gramps_id): - if gramps_id in self.family_id_map: - return Family.create(self.family_id_map[gramps_id]) - return None - - def get_citation_from_gramps_id(self, gramps_id): - if gramps_id in self.citation_id_map: - return Citation.create(self.citation_id_map[gramps_id]) - return None - - def get_source_from_gramps_id(self, gramps_id): - if gramps_id in self.source_id_map: - return Source.create(self.source_id_map[gramps_id]) - return None - - def get_event_from_gramps_id(self, gramps_id): - if gramps_id in self.event_id_map: - return Event.create(self.event_id_map[gramps_id]) - return None - - def get_media_from_gramps_id(self, gramps_id): - if gramps_id in self.media_id_map: - return MediaObject.create(self.media_id_map[gramps_id]) - return None - - def get_place_from_gramps_id(self, gramps_id): - if gramps_id in self.place_id_map: - return Place.create(self.place_id_map[gramps_id]) - return None - - def get_repository_from_gramps_id(self, gramps_id): - if gramps_id in self.repository_id_map: - return Repository.create(self.repository_id_map[gramps_id]) - return None - - def get_note_from_gramps_id(self, gramps_id): - if gramps_id in self.note_id_map: - return Note.create(self.note_id_map[gramps_id]) - return None - - def get_number_of_people(self): - cur = self.dbapi.execute("SELECT count(handle) FROM person;") - row = cur.fetchone() - return row[0] - - def get_number_of_events(self): - cur = self.dbapi.execute("SELECT count(handle) FROM event;") - row = cur.fetchone() - return row[0] - - def get_number_of_places(self): - cur = self.dbapi.execute("SELECT count(handle) FROM place;") - row = cur.fetchone() - return row[0] - - def get_number_of_tags(self): - cur = self.dbapi.execute("SELECT count(handle) FROM tag;") - row = cur.fetchone() - return row[0] - - def get_number_of_families(self): - cur = self.dbapi.execute("SELECT count(handle) FROM family;") - row = cur.fetchone() - return row[0] - - def get_number_of_notes(self): - cur = self.dbapi.execute("SELECT count(handle) FROM note;") - row = cur.fetchone() - return row[0] - - def get_number_of_citations(self): - cur = self.dbapi.execute("SELECT count(handle) FROM citation;") - row = cur.fetchone() - return row[0] - - def get_number_of_sources(self): - cur = self.dbapi.execute("SELECT count(handle) FROM source;") - row = cur.fetchone() - return row[0] - - def get_number_of_media_objects(self): - cur = self.dbapi.execute("SELECT count(handle) FROM media;") - row = cur.fetchone() - return row[0] - - def get_number_of_repositories(self): - cur = self.dbapi.execute("SELECT count(handle) FROM repository;") - row = cur.fetchone() - return row[0] - - def get_place_cursor(self): - return Cursor(self.place_map) - - def get_person_cursor(self): - return Cursor(self.person_map) - - def get_family_cursor(self): - return Cursor(self.family_map) - - def get_event_cursor(self): - return Cursor(self.event_map) - - def get_note_cursor(self): - return Cursor(self.note_map) - - def get_tag_cursor(self): - return Cursor(self.tag_map) - - def get_repository_cursor(self): - return Cursor(self.repository_map) - - def get_media_cursor(self): - return Cursor(self.media_map) - - def get_citation_cursor(self): - return Cursor(self.citation_map) - - def get_source_cursor(self): - return Cursor(self.source_map) - - def has_gramps_id(self, obj_key, gramps_id): - key2table = { - PERSON_KEY: self.person_id_map, - FAMILY_KEY: self.family_id_map, - SOURCE_KEY: self.source_id_map, - CITATION_KEY: self.citation_id_map, - EVENT_KEY: self.event_id_map, - MEDIA_KEY: self.media_id_map, - PLACE_KEY: self.place_id_map, - REPOSITORY_KEY: self.repository_id_map, - NOTE_KEY: self.note_id_map, - } - return gramps_id in key2table[obj_key] - - 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): - cur = self.dbapi.execute("SELECT grouping FROM name_group WHERE name = ?;", - [key]) - row = cur.fetchone() - return True if row else False - - def set_name_group_mapping(self, name, grouping): - sname = name.encode("utf-8") - cur = self.dbapi.execute("SELECT * FROM name_group WHERE name = ?;", - [sname]) - row = cur.fetchone() - if row: - cur = self.dbapi.execute("DELETE FROM name_group WHERE name = ?;", - [sname]) - cur = self.dbapi.execute("INSERT INTO name_group (name, grouping) VALUES(?, ?);", - [sname, grouping]) - self.dbapi.commit() - - def get_raw_person_data(self, handle): - if handle in self.person_map: - return self.person_map[handle] - return None - - def get_raw_family_data(self, handle): - if handle in self.family_map: - return self.family_map[handle] - return None - - def get_raw_citation_data(self, handle): - if handle in self.citation_map: - return self.citation_map[handle] - return None - - def get_raw_source_data(self, handle): - if handle in self.source_map: - return self.source_map[handle] - return None - - def get_raw_repository_data(self, handle): - if handle in self.repository_map: - return self.repository_map[handle] - return None - - def get_raw_note_data(self, handle): - if handle in self.note_map: - return self.note_map[handle] - return None - - def get_raw_place_data(self, handle): - if handle in self.place_map: - return self.place_map[handle] - return None - - def get_raw_object_data(self, handle): - if handle in self.media_map: - return self.media_map[handle] - return None - - def get_raw_tag_data(self, handle): - if handle in self.tag_map: - return self.tag_map[handle] - return None - - def get_raw_event_data(self, handle): - if handle in self.event_map: - return self.event_map[handle] - return None - - def add_person(self, person, trans, set_gid=True): - if not person.handle: - person.handle = create_id() - if not person.gramps_id and 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 = create_id() - if not family.gramps_id and 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 = create_id() - if not citation.gramps_id and 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 = create_id() - if not source.gramps_id and 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 = create_id() - if not repository.gramps_id and 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 = create_id() - if not note.gramps_id and 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 = create_id() - if not place.gramps_id and 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 = create_id() - if not event.gramps_id and 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 = create_id() - self.commit_tag(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 = create_id() - if not obj.gramps_id and 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): - emit = None - if person.handle in self.person_map: - emit = "person-update" - old_person = self.get_person_from_handle(person.handle) - # Update gender statistics if necessary - if (old_person.gender != person.gender or - old_person.primary_name.first_name != - person.primary_name.first_name): - - self.genderStats.uncount_person(old_person) - self.genderStats.count_person(person) - # Update surname list if necessary - if (self._order_by_person_key(person) != - self._order_by_person_key(old_person)): - self.remove_from_surname_list(old_person) - self.add_to_surname_list(person, trans.batch) - # update the person: - self.dbapi.execute("""UPDATE person SET gramps_id = ?, - order_by = ?, - blob = ? - WHERE handle = ?;""", - [person.gramps_id, - self._order_by_person_key(person), - pickle.dumps(person.serialize()), - person.handle]) - else: - emit = "person-add" - self.genderStats.count_person(person) - self.add_to_surname_list(person, trans.batch) - # Insert the person: - self.dbapi.execute("""INSERT INTO person (handle, order_by, gramps_id, blob) - VALUES(?, ?, ?, ?);""", - [person.handle, - self._order_by_person_key(person), - person.gramps_id, - pickle.dumps(person.serialize())]) - if not trans.batch: - self.update_backlinks(person) - self.dbapi.commit() - # Other misc update tasks: - self.individual_attributes.update( - [str(attr.type) for attr in person.attribute_list - if attr.type.is_custom() and str(attr.type)]) - - self.event_role_names.update([str(eref.role) - for eref in person.event_ref_list - if eref.role.is_custom()]) - - self.name_types.update([str(name.type) - for name in ([person.primary_name] - + person.alternate_names) - if name.type.is_custom()]) - all_surn = [] # new list we will use for storage - all_surn += person.primary_name.get_surname_list() - for asurname in person.alternate_names: - all_surn += asurname.get_surname_list() - self.origin_types.update([str(surn.origintype) for surn in all_surn - if surn.origintype.is_custom()]) - all_surn = None - self.url_types.update([str(url.type) for url in person.urls - if url.type.is_custom()]) - attr_list = [] - for mref in person.media_list: - attr_list += [str(attr.type) for attr in mref.attribute_list - if attr.type.is_custom() and str(attr.type)] - self.media_attributes.update(attr_list) - # Emit after added: - if emit: - self.emit(emit, ([person.handle],)) - self.has_changed = True - - def add_to_surname_list(self, person, batch_transaction): - """ - Add surname to surname list - """ - if batch_transaction: - return - # TODO: check to see if this is correct - name = None - primary_name = person.get_primary_name() - if primary_name: - surname_list = primary_name.get_surname_list() - if len(surname_list) > 0: - name = surname_list[0].surname - if name is None: - return - i = bisect.bisect(self.surname_list, name) - if 0 < i <= len(self.surname_list): - if self.surname_list[i-1] != name: - self.surname_list.insert(i, name) - else: - self.surname_list.insert(i, name) - - def remove_from_surname_list(self, person): - """ - Check whether there are persons with the same surname left in - the database. - - If not then we need to remove the name from the list. - The function must be overridden in the derived class. - """ - name = None - primary_name = person.get_primary_name() - if primary_name: - surname_list = primary_name.get_surname_list() - if len(surname_list) > 0: - name = surname_list[0].surname - if name is None: - return - if name in self.surname_list: - self.surname_list.remove(name) - - def commit_family(self, family, trans, change_time=None): - emit = None - if family.handle in self.family_map: - emit = "family-update" - self.dbapi.execute("""UPDATE family SET gramps_id = ?, - blob = ? - WHERE handle = ?;""", - [family.gramps_id, - pickle.dumps(family.serialize()), - family.handle]) - else: - emit = "family-add" - self.dbapi.execute("""INSERT INTO family (handle, gramps_id, blob) - VALUES(?, ?, ?);""", - [family.handle, family.gramps_id, - pickle.dumps(family.serialize())]) - if not trans.batch: - self.update_backlinks(family) - self.dbapi.commit() - # Misc updates: - self.family_attributes.update( - [str(attr.type) for attr in family.attribute_list - if attr.type.is_custom() and str(attr.type)]) - - rel_list = [] - for ref in family.child_ref_list: - if ref.frel.is_custom(): - rel_list.append(str(ref.frel)) - if ref.mrel.is_custom(): - rel_list.append(str(ref.mrel)) - self.child_ref_types.update(rel_list) - - self.event_role_names.update( - [str(eref.role) for eref in family.event_ref_list - if eref.role.is_custom()]) - - if family.type.is_custom(): - self.family_rel_types.add(str(family.type)) - - attr_list = [] - for mref in family.media_list: - attr_list += [str(attr.type) for attr in mref.attribute_list - if attr.type.is_custom() and str(attr.type)] - self.media_attributes.update(attr_list) - # Emit after added: - if emit: - self.emit(emit, ([family.handle],)) - self.has_changed = True - - def commit_citation(self, citation, trans, change_time=None): - emit = None - if citation.handle in self.citation_map: - emit = "citation-update" - self.dbapi.execute("""UPDATE citation SET gramps_id = ?, - order_by = ?, - blob = ? - WHERE handle = ?;""", - [citation.gramps_id, - self._order_by_citation_key(citation), - pickle.dumps(citation.serialize()), - citation.handle]) - else: - emit = "citation-add" - self.dbapi.execute("""INSERT INTO citation (handle, order_by, gramps_id, blob) - VALUES(?, ?, ?, ?);""", - [citation.handle, - self._order_by_citation_key(citation), - citation.gramps_id, - pickle.dumps(citation.serialize())]) - if not trans.batch: - self.update_backlinks(citation) - self.dbapi.commit() - # Misc updates: - attr_list = [] - for mref in citation.media_list: - attr_list += [str(attr.type) for attr in mref.attribute_list - if attr.type.is_custom() and str(attr.type)] - self.media_attributes.update(attr_list) - - self.source_attributes.update( - [str(attr.type) for attr in citation.attribute_list - if attr.type.is_custom() and str(attr.type)]) - - # Emit after added: - if emit: - self.emit(emit, ([citation.handle],)) - self.has_changed = True - - def commit_source(self, source, trans, change_time=None): - emit = None - if source.handle in self.source_map: - emit = "source-update" - self.dbapi.execute("""UPDATE source SET gramps_id = ?, - order_by = ?, - blob = ? - WHERE handle = ?;""", - [source.gramps_id, - self._order_by_source_key(source), - pickle.dumps(source.serialize()), - source.handle]) - else: - emit = "source-add" - self.dbapi.execute("""INSERT INTO source (handle, order_by, gramps_id, blob) - VALUES(?, ?, ?, ?);""", - [source.handle, - self._order_by_source_key(source), - source.gramps_id, - pickle.dumps(source.serialize())]) - if not trans.batch: - self.update_backlinks(source) - self.dbapi.commit() - # Misc updates: - self.source_media_types.update( - [str(ref.media_type) for ref in source.reporef_list - if ref.media_type.is_custom()]) - - attr_list = [] - for mref in source.media_list: - attr_list += [str(attr.type) for attr in mref.attribute_list - if attr.type.is_custom() and str(attr.type)] - self.media_attributes.update(attr_list) - self.source_attributes.update( - [str(attr.type) for attr in source.attribute_list - if attr.type.is_custom() and str(attr.type)]) - # Emit after added: - if emit: - self.emit(emit, ([source.handle],)) - self.has_changed = True - - def commit_repository(self, repository, trans, change_time=None): - emit = None - if repository.handle in self.repository_map: - emit = "repository-update" - self.dbapi.execute("""UPDATE repository SET gramps_id = ?, - blob = ? - WHERE handle = ?;""", - [repository.gramps_id, - pickle.dumps(repository.serialize()), - repository.handle]) - else: - emit = "repository-add" - self.dbapi.execute("""INSERT INTO repository (handle, gramps_id, blob) - VALUES(?, ?, ?);""", - [repository.handle, repository.gramps_id, pickle.dumps(repository.serialize())]) - if not trans.batch: - self.update_backlinks(repository) - self.dbapi.commit() - # Misc updates: - if repository.type.is_custom(): - self.repository_types.add(str(repository.type)) - - self.url_types.update([str(url.type) for url in repository.urls - if url.type.is_custom()]) - # Emit after added: - if emit: - self.emit(emit, ([repository.handle],)) - self.has_changed = True - - def commit_note(self, note, trans, change_time=None): - emit = None - if note.handle in self.note_map: - emit = "note-update" - self.dbapi.execute("""UPDATE note SET gramps_id = ?, - blob = ? - WHERE handle = ?;""", - [note.gramps_id, - pickle.dumps(note.serialize()), - note.handle]) - else: - emit = "note-add" - self.dbapi.execute("""INSERT INTO note (handle, gramps_id, blob) - VALUES(?, ?, ?);""", - [note.handle, note.gramps_id, pickle.dumps(note.serialize())]) - if not trans.batch: - self.update_backlinks(note) - self.dbapi.commit() - # Misc updates: - if note.type.is_custom(): - self.note_types.add(str(note.type)) - # Emit after added: - if emit: - self.emit(emit, ([note.handle],)) - self.has_changed = True - - def commit_place(self, place, trans, change_time=None): - emit = None - if place.handle in self.place_map: - emit = "place-update" - self.dbapi.execute("""UPDATE place SET gramps_id = ?, - order_by = ?, - blob = ? - WHERE handle = ?;""", - [place.gramps_id, - self._order_by_place_key(place), - pickle.dumps(place.serialize()), - place.handle]) - else: - emit = "place-add" - self.dbapi.execute("""INSERT INTO place (handle, order_by, gramps_id, blob) - VALUES(?, ?, ?, ?);""", - [place.handle, - self._order_by_place_key(place), - place.gramps_id, - pickle.dumps(place.serialize())]) - if not trans.batch: - self.update_backlinks(place) - self.dbapi.commit() - # Misc updates: - if place.get_type().is_custom(): - self.place_types.add(str(place.get_type())) - - self.url_types.update([str(url.type) for url in place.urls - if url.type.is_custom()]) - - attr_list = [] - for mref in place.media_list: - attr_list += [str(attr.type) for attr in mref.attribute_list - if attr.type.is_custom() and str(attr.type)] - self.media_attributes.update(attr_list) - # Emit after added: - if emit: - self.emit(emit, ([place.handle],)) - self.has_changed = True - - def commit_event(self, event, trans, change_time=None): - emit = None - if event.handle in self.event_map: - emit = "event-update" - self.dbapi.execute("""UPDATE event SET gramps_id = ?, - blob = ? - WHERE handle = ?;""", - [event.gramps_id, - pickle.dumps(event.serialize()), - event.handle]) - else: - emit = "event-add" - self.dbapi.execute("""INSERT INTO event (handle, gramps_id, blob) - VALUES(?, ?, ?);""", - [event.handle, - event.gramps_id, - pickle.dumps(event.serialize())]) - if not trans.batch: - self.update_backlinks(event) - self.dbapi.commit() - # Misc updates: - self.event_attributes.update( - [str(attr.type) for attr in event.attribute_list - if attr.type.is_custom() and str(attr.type)]) - if event.type.is_custom(): - self.event_names.add(str(event.type)) - attr_list = [] - for mref in event.media_list: - attr_list += [str(attr.type) for attr in mref.attribute_list - if attr.type.is_custom() and str(attr.type)] - self.media_attributes.update(attr_list) - # Emit after added: - if emit: - self.emit(emit, ([event.handle],)) - self.has_changed = True - - def update_backlinks(self, obj): - # First, delete the current references: - self.dbapi.execute("DELETE FROM reference WHERE obj_handle = ?;", - [obj.handle]) - # Now, add the current ones: - references = set(obj.get_referenced_handles_recursively()) - for (ref_class_name, ref_handle) in references: - self.dbapi.execute("""INSERT INTO reference - (obj_handle, obj_class, ref_handle, ref_class) - VALUES(?, ?, ?, ?);""", - [obj.handle, - obj.__class__.__name__, - ref_handle, - ref_class_name]) - # This function is followed by a commit. - - def commit_tag(self, tag, trans, change_time=None): - emit = None - if tag.handle in self.tag_map: - emit = "tag-update" - self.dbapi.execute("""UPDATE tag SET blob = ?, - order_by = ? - WHERE handle = ?;""", - [pickle.dumps(tag.serialize()), - self._order_by_tag_key(tag), - tag.handle]) - else: - emit = "tag-add" - self.dbapi.execute("""INSERT INTO tag (handle, order_by, blob) - VALUES(?, ?, ?);""", - [tag.handle, - self._order_by_tag_key(tag), - pickle.dumps(tag.serialize())]) - if not trans.batch: - self.update_backlinks(tag) - self.dbapi.commit() - # Emit after added: - if emit: - self.emit(emit, ([tag.handle],)) - - def commit_media_object(self, media, trans, change_time=None): - emit = None - if media.handle in self.media_map: - emit = "media-update" - self.dbapi.execute("""UPDATE media SET gramps_id = ?, - order_by = ?, - blob = ? - WHERE handle = ?;""", - [media.gramps_id, - self._order_by_media_key(media), - pickle.dumps(media.serialize()), - media.handle]) - else: - emit = "media-add" - self.dbapi.execute("""INSERT INTO media (handle, order_by, gramps_id, blob) - VALUES(?, ?, ?, ?);""", - [media.handle, - self._order_by_media_key(media), - media.gramps_id, - pickle.dumps(media.serialize())]) - if not trans.batch: - self.update_backlinks(media) - self.dbapi.commit() - # Misc updates: - self.media_attributes.update( - [str(attr.type) for attr in media.attribute_list - if attr.type.is_custom() and str(attr.type)]) - # Emit after added: - if emit: - self.emit(emit, ([media.handle],)) - - def get_gramps_ids(self, obj_key): - key2table = { - PERSON_KEY: self.person_id_map, - FAMILY_KEY: self.family_id_map, - CITATION_KEY: self.citation_id_map, - SOURCE_KEY: self.source_id_map, - EVENT_KEY: self.event_id_map, - MEDIA_KEY: self.media_id_map, - PLACE_KEY: self.place_id_map, - REPOSITORY_KEY: self.repository_id_map, - NOTE_KEY: self.note_id_map, - } - return list(key2table[obj_key].keys()) - - def transaction_begin(self, transaction): - """ - Transactions are handled automatically by the db layer. - """ - return - - def set_researcher(self, owner): - self.owner.set_from(owner) - - def get_researcher(self): - return self.owner - - def request_rebuild(self): - self.emit('person-rebuild') - self.emit('family-rebuild') - self.emit('place-rebuild') - self.emit('source-rebuild') - self.emit('citation-rebuild') - self.emit('media-rebuild') - self.emit('event-rebuild') - self.emit('repository-rebuild') - self.emit('note-rebuild') - self.emit('tag-rebuild') - - def copy_from_db(self, db): - """ - A (possibily) implementation-specific method to get data from - db into this database. - """ - for key in db._tables.keys(): - cursor = db._tables[key]["cursor_func"] - class_ = db._tables[key]["class_func"] - for (handle, data) in cursor(): - map = getattr(self, "%s_map" % key.lower()) - map[handle] = class_.create(data) - - def get_transaction_class(self): - """ - Get the transaction class associated with this database backend. - """ - return DBAPITxn - - def get_from_name_and_handle(self, table_name, handle): - """ - Returns a gen.lib object (or None) given table_name and - handle. - - Examples: - - >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") - >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") - """ - if table_name in self._tables: - return self._tables[table_name]["handle_func"](handle) - return None - - def get_from_name_and_gramps_id(self, table_name, gramps_id): - """ - Returns a gen.lib object (or None) given table_name and - Gramps ID. - - Examples: - - >>> self.get_from_name_and_gramps_id("Person", "I00002") - >>> self.get_from_name_and_gramps_id("Family", "F056") - >>> self.get_from_name_and_gramps_id("Media", "M00012") - """ - if table_name in self._tables: - return self._tables[table_name]["gramps_id_func"](gramps_id) - return None - - def remove_person(self, handle, transaction): - """ - Remove the Person specified by the database handle from the database, - preserving the change in the passed transaction. - """ - - if self.readonly or not handle: - return - if handle in self.person_map: - person = Person.create(self.person_map[handle]) - #del self.person_map[handle] - #del self.person_id_map[person.gramps_id] - self.dbapi.execute("DELETE FROM person WHERE handle = ?;", [handle]) - self.emit("person-delete", ([handle],)) - if not transaction.batch: - self.dbapi.commit() - - def remove_source(self, handle, transaction): - """ - Remove the Source specified by the database handle from the - database, preserving the change in the passed transaction. - """ - self.__do_remove(handle, transaction, self.source_map, - self.source_id_map, SOURCE_KEY) - - def remove_citation(self, handle, transaction): - """ - Remove the Citation specified by the database handle from the - database, preserving the change in the passed transaction. - """ - self.__do_remove(handle, transaction, self.citation_map, - self.citation_id_map, CITATION_KEY) - - def remove_event(self, handle, transaction): - """ - Remove the Event specified by the database handle from the - database, preserving the change in the passed transaction. - """ - self.__do_remove(handle, transaction, self.event_map, - self.event_id_map, EVENT_KEY) - - def remove_object(self, handle, transaction): - """ - Remove the MediaObjectPerson specified by the database handle from the - database, preserving the change in the passed transaction. - """ - self.__do_remove(handle, transaction, self.media_map, - self.media_id_map, MEDIA_KEY) - - def remove_place(self, handle, transaction): - """ - Remove the Place specified by the database handle from the - database, preserving the change in the passed transaction. - """ - self.__do_remove(handle, transaction, self.place_map, - self.place_id_map, PLACE_KEY) - - def remove_family(self, handle, transaction): - """ - Remove the Family specified by the database handle from the - database, preserving the change in the passed transaction. - """ - self.__do_remove(handle, transaction, self.family_map, - self.family_id_map, FAMILY_KEY) - - def remove_repository(self, handle, transaction): - """ - Remove the Repository specified by the database handle from the - database, preserving the change in the passed transaction. - """ - self.__do_remove(handle, transaction, self.repository_map, - self.repository_id_map, REPOSITORY_KEY) - - def remove_note(self, handle, transaction): - """ - Remove the Note specified by the database handle from the - database, preserving the change in the passed transaction. - """ - self.__do_remove(handle, transaction, self.note_map, - self.note_id_map, NOTE_KEY) - - def remove_tag(self, handle, transaction): - """ - Remove the Tag specified by the database handle from the - database, preserving the change in the passed transaction. - """ - self.__do_remove(handle, transaction, self.tag_map, - None, TAG_KEY) - - def is_empty(self): - """ - Return true if there are no [primary] records in the database - """ - for table in self._tables: - if len(self._tables[table]["handles_func"]()) > 0: - return False - return True - - def __do_remove(self, handle, transaction, data_map, data_id_map, key): - key2table = { - PERSON_KEY: "person", - FAMILY_KEY: "family", - SOURCE_KEY: "source", - CITATION_KEY: "citation", - EVENT_KEY: "event", - MEDIA_KEY: "media", - PLACE_KEY: "place", - REPOSITORY_KEY: "repository", - NOTE_KEY: "note", - } - if self.readonly or not handle: - return - if handle in data_map: - self.dbapi.execute("DELETE FROM %s WHERE handle = ?;" % key2table[key], - [handle]) - self.emit(KEY_TO_NAME_MAP[key] + "-delete", ([handle],)) - if not transaction.batch: - self.dbapi.commit() - - def close(self): - if self._directory: - filename = os.path.join(self._directory, "meta_data.db") - touch(filename) - # Save metadata - self.set_metadata('bookmarks', self.bookmarks.get()) - self.set_metadata('family_bookmarks', self.family_bookmarks.get()) - self.set_metadata('event_bookmarks', self.event_bookmarks.get()) - self.set_metadata('source_bookmarks', self.source_bookmarks.get()) - self.set_metadata('citation_bookmarks', self.citation_bookmarks.get()) - self.set_metadata('repo_bookmarks', self.repo_bookmarks.get()) - self.set_metadata('media_bookmarks', self.media_bookmarks.get()) - self.set_metadata('place_bookmarks', self.place_bookmarks.get()) - self.set_metadata('note_bookmarks', self.note_bookmarks.get()) - - # Custom type values, sets - self.set_metadata('event_names', self.event_names) - self.set_metadata('fattr_names', self.family_attributes) - self.set_metadata('pattr_names', self.individual_attributes) - self.set_metadata('sattr_names', self.source_attributes) - self.set_metadata('marker_names', self.marker_names) - self.set_metadata('child_refs', self.child_ref_types) - self.set_metadata('family_rels', self.family_rel_types) - self.set_metadata('event_roles', self.event_role_names) - self.set_metadata('name_types', self.name_types) - self.set_metadata('origin_types', self.origin_types) - self.set_metadata('repo_types', self.repository_types) - self.set_metadata('note_types', self.note_types) - self.set_metadata('sm_types', self.source_media_types) - self.set_metadata('url_types', self.url_types) - self.set_metadata('mattr_names', self.media_attributes) - self.set_metadata('eattr_names', self.event_attributes) - self.set_metadata('place_types', self.place_types) - - # surname list - self.set_metadata('surname_list', self.surname_list) - - self.dbapi.close() - - 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 = list(find_backlink_handles(handle)) - """ - cur = self.dbapi.execute("SELECT * FROM reference WHERE ref_handle = ?;", - [handle]) - rows = cur.fetchall() - for row in rows: - if (include_classes is None) or (row["obj_class"] in include_classes): - yield (row["obj_class"], row["obj_handle"]) - - def find_initial_person(self): - handle = self.get_default_handle() - person = None - if handle: - person = self.get_person_from_handle(handle) - if person: - return person - cur = self.dbapi.execute("SELECT handle FROM person;") - row = cur.fetchone() - if row: - return self.get_person_from_handle(row[0]) - - def get_bookmarks(self): - return self.bookmarks - - def get_citation_bookmarks(self): - return self.citation_bookmarks - - def get_default_handle(self): - return self.get_metadata("default-person-handle", None) - - def get_surname_list(self): - """ - Return the list of locale-sorted surnames contained in the database. - """ - return self.surname_list - - def get_event_attribute_types(self): - """ - Return a list of all Attribute types assocated with Event instances - in the database. - """ - return list(self.event_attributes) - - def get_event_types(self): - """ - Return a list of all event types in the database. - """ - return list(self.event_names) - - def get_person_event_types(self): - """ - Deprecated: Use get_event_types - """ - return list(self.event_names) - - def get_person_attribute_types(self): - """ - Return a list of all Attribute types assocated with Person instances - in the database. - """ - return list(self.individual_attributes) - - def get_family_attribute_types(self): - """ - Return a list of all Attribute types assocated with Family instances - in the database. - """ - return list(self.family_attributes) - - def get_family_event_types(self): - """ - Deprecated: Use get_event_types - """ - return list(self.event_names) - - def get_media_attribute_types(self): - """ - Return a list of all Attribute types assocated with Media and MediaRef - instances in the database. - """ - return list(self.media_attributes) - - def get_family_relation_types(self): - """ - Return a list of all relationship types assocated with Family - instances in the database. - """ - return list(self.family_rel_types) - - def get_child_reference_types(self): - """ - Return a list of all child reference types assocated with Family - instances in the database. - """ - return list(self.child_ref_types) - - def get_event_roles(self): - """ - Return a list of all custom event role names assocated with Event - instances in the database. - """ - return list(self.event_role_names) - - def get_name_types(self): - """ - Return a list of all custom names types assocated with Person - instances in the database. - """ - return list(self.name_types) - - def get_origin_types(self): - """ - Return a list of all custom origin types assocated with Person/Surname - instances in the database. - """ - return list(self.origin_types) - - def get_repository_types(self): - """ - Return a list of all custom repository types assocated with Repository - instances in the database. - """ - return list(self.repository_types) - - def get_note_types(self): - """ - Return a list of all custom note types assocated with Note instances - in the database. - """ - return list(self.note_types) - - def get_source_attribute_types(self): - """ - Return a list of all Attribute types assocated with Source/Citation - instances in the database. - """ - return list(self.source_attributes) - - def get_source_media_types(self): - """ - Return a list of all custom source media types assocated with Source - instances in the database. - """ - return list(self.source_media_types) - - def get_url_types(self): - """ - Return a list of all custom names types assocated with Url instances - in the database. - """ - return list(self.url_types) - - def get_place_types(self): - """ - Return a list of all custom place types assocated with Place instances - in the database. - """ - return list(self.place_types) - - def get_event_bookmarks(self): - return self.event_bookmarks - - def get_family_bookmarks(self): - return self.family_bookmarks - - def get_media_bookmarks(self): - return self.media_bookmarks - - def get_note_bookmarks(self): - return self.note_bookmarks - - def get_place_bookmarks(self): - return self.place_bookmarks - - def get_repo_bookmarks(self): - return self.repo_bookmarks - - def get_save_path(self): - return self._directory - - def get_source_bookmarks(self): - return self.source_bookmarks - - def is_open(self): - return self._directory is not None - - def iter_citation_handles(self): - return (data[0] for data in self.get_citation_cursor()) - - def iter_citations(self): - return (Citation.create(data[1]) for data in self.get_citation_cursor()) - - def iter_event_handles(self): - return (data[0] for data in self.get_event_cursor()) - - def iter_events(self): - return (Event.create(data[1]) for data in self.get_event_cursor()) - - def iter_media_objects(self): - return (MediaObject.create(data[1]) for data in self.get_media_cursor()) - - def iter_note_handles(self): - return (data[0] for data in self.get_note_cursor()) - - def iter_notes(self): - return (Note.create(data[1]) for data in self.get_note_cursor()) - - def iter_place_handles(self): - return (data[0] for data in self.get_place_cursor()) - - def iter_places(self): - return (Place.create(data[1]) for data in self.get_place_cursor()) - - def iter_repositories(self): - return (Repository.create(data[1]) for data in self.get_repository_cursor()) - - def iter_repository_handles(self): - return (data[0] for data in self.get_repository_cursor()) - - def iter_source_handles(self): - return (data[0] for data in self.get_source_cursor()) - - def iter_sources(self): - return (Source.create(data[1]) for data in self.get_source_cursor()) - - def iter_tag_handles(self): - return (data[0] for data in self.get_tag_cursor()) - - def iter_tags(self): - return (Tag.create(data[1]) for data in self.get_tag_cursor()) - - def load(self, directory, callback=None, mode=None, - force_schema_upgrade=False, - force_bsddb_upgrade=False, - force_bsddb_downgrade=False, - force_python_upgrade=False): - # Run code from directory - default_settings = {"__file__": - os.path.join(directory, "default_settings.py")} - settings_file = os.path.join(directory, "default_settings.py") - with open(settings_file) as f: - code = compile(f.read(), settings_file, 'exec') - exec(code, globals(), default_settings) - - self.dbapi = default_settings["dbapi"] - - # make sure schema is up to date: - self.dbapi.execute("""CREATE TABLE IF NOT EXISTS person ( - handle TEXT PRIMARY KEY NOT NULL, - order_by TEXT , - gramps_id TEXT , - blob TEXT - );""") - self.dbapi.execute("""CREATE TABLE IF NOT EXISTS family ( - handle TEXT PRIMARY KEY NOT NULL, - gramps_id TEXT , - blob TEXT - );""") - self.dbapi.execute("""CREATE TABLE IF NOT EXISTS source ( - handle TEXT PRIMARY KEY NOT NULL, - order_by TEXT , - gramps_id TEXT , - blob TEXT - );""") - self.dbapi.execute("""CREATE TABLE IF NOT EXISTS citation ( - handle TEXT PRIMARY KEY NOT NULL, - order_by TEXT , - gramps_id TEXT , - blob TEXT - );""") - self.dbapi.execute("""CREATE TABLE IF NOT EXISTS event ( - handle TEXT PRIMARY KEY NOT NULL, - gramps_id TEXT , - blob TEXT - );""") - self.dbapi.execute("""CREATE TABLE IF NOT EXISTS media ( - handle TEXT PRIMARY KEY NOT NULL, - order_by TEXT , - gramps_id TEXT , - blob TEXT - );""") - self.dbapi.execute("""CREATE TABLE IF NOT EXISTS place ( - handle TEXT PRIMARY KEY NOT NULL, - order_by TEXT , - gramps_id TEXT , - blob TEXT - );""") - self.dbapi.execute("""CREATE TABLE IF NOT EXISTS repository ( - handle TEXT PRIMARY KEY NOT NULL, - gramps_id TEXT , - blob TEXT - );""") - self.dbapi.execute("""CREATE TABLE IF NOT EXISTS note ( - handle TEXT PRIMARY KEY NOT NULL, - gramps_id TEXT , - blob TEXT - );""") - self.dbapi.execute("""CREATE TABLE IF NOT EXISTS tag ( - handle TEXT PRIMARY KEY NOT NULL, - order_by TEXT , - blob TEXT - );""") - # Secondary: - self.dbapi.execute("""CREATE TABLE IF NOT EXISTS reference ( - obj_handle TEXT, - obj_class TEXT, - ref_handle TEXT, - ref_class TEXT - );""") - self.dbapi.execute("""CREATE TABLE IF NOT EXISTS name_group ( - name TEXT PRIMARY KEY NOT NULL, - grouping TEXT - );""") - self.dbapi.execute("""CREATE TABLE IF NOT EXISTS metadata ( - setting TEXT PRIMARY KEY NOT NULL, - value TEXT - );""") - ## Indices: - self.dbapi.execute("""CREATE INDEX IF NOT EXISTS - order_by ON person (order_by); - """) - self.dbapi.execute("""CREATE INDEX IF NOT EXISTS - order_by ON source (order_by); - """) - self.dbapi.execute("""CREATE INDEX IF NOT EXISTS - order_by ON citation (order_by); - """) - self.dbapi.execute("""CREATE INDEX IF NOT EXISTS - order_by ON media (order_by); - """) - self.dbapi.execute("""CREATE INDEX IF NOT EXISTS - order_by ON place (order_by); - """) - self.dbapi.execute("""CREATE INDEX IF NOT EXISTS - order_by ON tag (order_by); - """) - self.dbapi.execute("""CREATE INDEX IF NOT EXISTS - ref_handle ON reference (ref_handle); - """) - # Load metadata - self.bookmarks.set(self.get_metadata('bookmarks')) - self.family_bookmarks.set(self.get_metadata('family_bookmarks')) - self.event_bookmarks.set(self.get_metadata('event_bookmarks')) - self.source_bookmarks.set(self.get_metadata('source_bookmarks')) - self.citation_bookmarks.set(self.get_metadata('citation_bookmarks')) - self.repo_bookmarks.set(self.get_metadata('repo_bookmarks')) - self.media_bookmarks.set(self.get_metadata('media_bookmarks')) - self.place_bookmarks.set(self.get_metadata('place_bookmarks')) - self.note_bookmarks.set(self.get_metadata('note_bookmarks')) - - # Custom type values - self.event_names = self.get_metadata('event_names', set()) - self.family_attributes = self.get_metadata('fattr_names', set()) - self.individual_attributes = self.get_metadata('pattr_names', set()) - self.source_attributes = self.get_metadata('sattr_names', set()) - self.marker_names = self.get_metadata('marker_names', set()) - self.child_ref_types = self.get_metadata('child_refs', set()) - self.family_rel_types = self.get_metadata('family_rels', set()) - self.event_role_names = self.get_metadata('event_roles', set()) - self.name_types = self.get_metadata('name_types', set()) - self.origin_types = self.get_metadata('origin_types', set()) - self.repository_types = self.get_metadata('repo_types', set()) - self.note_types = self.get_metadata('note_types', set()) - self.source_media_types = self.get_metadata('sm_types', set()) - self.url_types = self.get_metadata('url_types', set()) - self.media_attributes = self.get_metadata('mattr_names', set()) - self.event_attributes = self.get_metadata('eattr_names', set()) - self.place_types = self.get_metadata('place_types', set()) - - # surname list - self.surname_list = self.get_metadata('surname_list') - - self.set_save_path(directory) - self.undolog = os.path.join(self._directory, DBUNDOFN) - self.undodb = DBAPIUndo(self, self.undolog) - self.undodb.open() - - def set_prefixes(self, person, media, family, source, citation, - place, event, repository, note): - self.set_person_id_prefix(person) - self.set_object_id_prefix(media) - self.set_family_id_prefix(family) - self.set_source_id_prefix(source) - self.set_citation_id_prefix(citation) - self.set_place_id_prefix(place) - self.set_event_id_prefix(event) - self.set_repository_id_prefix(repository) - self.set_note_id_prefix(note) - - def set_save_path(self, directory): - self._directory = directory - if directory: - self.full_name = os.path.abspath(self._directory) - self.path = self.full_name - self.brief_name = os.path.basename(self._directory) - else: - self.full_name = None - self.path = None - self.brief_name = None - - def write_version(self, directory): - """Write files for a newly created DB.""" - versionpath = os.path.join(directory, str(DBBACKEND)) - _LOG.debug("Write database backend file to 'dbapi'") - with open(versionpath, "w") as version_file: - version_file.write("dbapi") - # Write default_settings, sqlite.db - defaults = os.path.join(os.path.dirname(os.path.abspath(__file__)), - "dbapi_support", "defaults") - _LOG.debug("Copy defaults from: " + defaults) - for filename in os.listdir(defaults): - fullpath = os.path.abspath(os.path.join(defaults, filename)) - shutil.copy2(fullpath, directory) - - def report_bm_change(self): - """ - Add 1 to the number of bookmark changes during this session. - """ - self._bm_changes += 1 - - def db_has_bm_changes(self): - """ - Return whethere there were bookmark changes during the session. - """ - return self._bm_changes > 0 - - def get_summary(self): - """ - Returns dictionary of summary item. - Should include, if possible: - - _("Number of people") - _("Version") - _("Schema version") - """ - return { - _("Number of people"): self.get_number_of_people(), - } - - def get_dbname(self): - """ - In DBAPI, the database is in a text file at the path - """ - filepath = os.path.join(self._directory, "name.txt") - try: - name_file = open(filepath, "r") - name = name_file.readline().strip() - name_file.close() - except (OSError, IOError) as msg: - _LOG.error(str(msg)) - name = None - return name - - def reindex_reference_map(self, callback): - callback(4) - self.dbapi.execute("DELETE FROM reference;") - primary_table = ( - (self.get_person_cursor, Person), - (self.get_family_cursor, Family), - (self.get_event_cursor, Event), - (self.get_place_cursor, Place), - (self.get_source_cursor, Source), - (self.get_citation_cursor, Citation), - (self.get_media_cursor, MediaObject), - (self.get_repository_cursor, Repository), - (self.get_note_cursor, Note), - (self.get_tag_cursor, Tag), - ) - # Now we use the functions and classes defined above - # to loop through each of the primary object tables. - for cursor_func, class_func in primary_table: - logging.info("Rebuilding %s reference map" % - class_func.__name__) - with cursor_func() as cursor: - for found_handle, val in cursor: - obj = class_func.create(val) - references = set(obj.get_referenced_handles_recursively()) - # handle addition of new references - for (ref_class_name, ref_handle) in references: - self.dbapi.execute("""INSERT INTO reference (obj_handle, obj_class, ref_handle, ref_class) - VALUES(?, ?, ?, ?);""", - [obj.handle, - obj.__class__.__name__, - ref_handle, - ref_class_name]) - - self.dbapi.commit() - callback(5) - - def rebuild_secondary(self, update): - pass - # FIXME: rebuild the secondary databases/maps: - ## gender stats - ## event_names - ## fattr_names - ## pattr_names - ## sattr_names - ## marker_names - ## child_refs - ## family_rels - ## event_roles - ## name_types - ## origin_types - ## repo_types - ## note_types - ## sm_types - ## url_types - ## mattr_names - ## eattr_names - ## place_types - # surname list - - def prepare_import(self): - """ - Do anything needed before an import. - """ - pass - - def commit_import(self): - """ - Do anything needed after an import. - """ - self.reindex_reference_map(lambda n: n) - - def has_handle_for_person(self, key): - cur = self.dbapi.execute("SELECT * FROM person WHERE handle = ?", [key]) - return cur.fetchone() != None - - def has_handle_for_family(self, key): - cur = self.dbapi.execute("SELECT * FROM family WHERE handle = ?", [key]) - return cur.fetchone() != None - - def has_handle_for_source(self, key): - cur = self.dbapi.execute("SELECT * FROM source WHERE handle = ?", [key]) - return cur.fetchone() != None - - def has_handle_for_citation(self, key): - cur = self.dbapi.execute("SELECT * FROM citation WHERE handle = ?", [key]) - return cur.fetchone() != None - - def has_handle_for_event(self, key): - cur = self.dbapi.execute("SELECT * FROM event WHERE handle = ?", [key]) - return cur.fetchone() != None - - def has_handle_for_media(self, key): - cur = self.dbapi.execute("SELECT * FROM media WHERE handle = ?", [key]) - return cur.fetchone() != None - - def has_handle_for_place(self, key): - cur = self.dbapi.execute("SELECT * FROM place WHERE handle = ?", [key]) - return cur.fetchone() != None - - def has_handle_for_repository(self, key): - cur = self.dbapi.execute("SELECT * FROM repository WHERE handle = ?", [key]) - return cur.fetchone() != None - - def has_handle_for_note(self, key): - cur = self.dbapi.execute("SELECT * FROM note WHERE handle = ?", [key]) - return cur.fetchone() != None - - def has_handle_for_tag(self, key): - cur = self.dbapi.execute("SELECT * FROM tag WHERE handle = ?", [key]) - return cur.fetchone() != None - - def has_gramps_id_for_person(self, key): - cur = self.dbapi.execute("SELECT * FROM person WHERE gramps_id = ?", [key]) - return cur.fetchone() != None - - def has_gramps_id_for_family(self, key): - cur = self.dbapi.execute("SELECT * FROM family WHERE gramps_id = ?", [key]) - return cur.fetchone() != None - - def has_gramps_id_for_source(self, key): - cur = self.dbapi.execute("SELECT * FROM source WHERE gramps_id = ?", [key]) - return cur.fetchone() != None - - def has_gramps_id_for_citation(self, key): - cur = self.dbapi.execute("SELECT * FROM citation WHERE gramps_id = ?", [key]) - return cur.fetchone() != None - - def has_gramps_id_for_event(self, key): - cur = self.dbapi.execute("SELECT * FROM event WHERE gramps_id = ?", [key]) - return cur.fetchone() != None - - def has_gramps_id_for_media(self, key): - cur = self.dbapi.execute("SELECT * FROM media WHERE gramps_id = ?", [key]) - return cur.fetchone() != None - - def has_gramps_id_for_place(self, key): - cur = self.dbapi.execute("SELECT * FROM place WHERE gramps_id = ?", [key]) - return cur.fetchone() != None - - def has_gramps_id_for_repository(self, key): - cur = self.dbapi.execute("SELECT * FROM repository WHERE gramps_id = ?", [key]) - return cur.fetchone() != None - - def has_gramps_id_for_note(self, key): - cur = self.dbapi.execute("SELECT * FROM note WHERE gramps_id = ?", [key]) - return cur.fetchone() != None - - def get_person_gramps_ids(self): - cur = self.dbapi.execute("SELECT gramps_id FROM person;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_family_gramps_ids(self): - cur = self.dbapi.execute("SELECT gramps_id FROM family;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_source_gramps_ids(self): - cur = self.dbapi.execute("SELECT gramps_id FROM source;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_citation_gramps_ids(self): - cur = self.dbapi.execute("SELECT gramps_id FROM citation;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_event_gramps_ids(self): - cur = self.dbapi.execute("SELECT gramps_id FROM event;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_media_gramps_ids(self): - cur = self.dbapi.execute("SELECT gramps_id FROM media;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_place_gramps_ids(self): - cur = self.dbapi.execute("SELECT gramps FROM place;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_repository_gramps_ids(self): - cur = self.dbapi.execute("SELECT gramps_id FROM repository;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def get_note_gramps_ids(self): - cur = self.dbapi.execute("SELECT gramps_id FROM note;") - rows = cur.fetchall() - return [row[0] for row in rows] - - def _get_raw_person_data(self, key): - cur = self.dbapi.execute("SELECT blob FROM person WHERE handle = ?", [key]) - row = cur.fetchone() - if row: - return pickle.loads(row[0]) - - def _get_raw_family_data(self, key): - cur = self.dbapi.execute("SELECT blob FROM family WHERE handle = ?", [key]) - row = cur.fetchone() - if row: - return pickle.loads(row[0]) - - def _get_raw_source_data(self, key): - cur = self.dbapi.execute("SELECT blob FROM source WHERE handle = ?", [key]) - row = cur.fetchone() - if row: - return pickle.loads(row[0]) - - def _get_raw_citation_data(self, key): - cur = self.dbapi.execute("SELECT blob FROM citation WHERE handle = ?", [key]) - row = cur.fetchone() - if row: - return pickle.loads(row[0]) - - def _get_raw_event_data(self, key): - cur = self.dbapi.execute("SELECT blob FROM event WHERE handle = ?", [key]) - row = cur.fetchone() - if row: - return pickle.loads(row[0]) - - def _get_raw_media_data(self, key): - cur = self.dbapi.execute("SELECT blob FROM media WHERE handle = ?", [key]) - row = cur.fetchone() - if row: - return pickle.loads(row[0]) - - def _get_raw_place_data(self, key): - cur = self.dbapi.execute("SELECT blob FROM place WHERE handle = ?", [key]) - row = cur.fetchone() - if row: - return pickle.loads(row[0]) - - def _get_raw_repository_data(self, key): - cur = self.dbapi.execute("SELECT blob FROM repository WHERE handle = ?", [key]) - row = cur.fetchone() - if row: - return pickle.loads(row[0]) - - def _get_raw_note_data(self, key): - cur = self.dbapi.execute("SELECT blob FROM note WHERE handle = ?", [key]) - row = cur.fetchone() - if row: - return pickle.loads(row[0]) - - def _get_raw_tag_data(self, key): - cur = self.dbapi.execute("SELECT blob FROM tag WHERE handle = ?", [key]) - row = cur.fetchone() - if row: - return pickle.loads(row[0]) - - def _order_by_person_key(self, person): - """ - All non pa/matronymic surnames are used in indexing. - pa/matronymic not as they change for every generation! - returns a byte string - """ - if person.primary_name and person.primary_name.surname_list: - order_by = " ".join([x.surname for x in person.primary_name.surname_list if not - (int(x.origintype) in [NameOriginType.PATRONYMIC, - NameOriginType.MATRONYMIC]) ]) - else: - order_by = "" - return glocale.sort_key(order_by) - - def _order_by_place_key(self, place): - return glocale.sort_key(place.title) - - def _order_by_source_key(self, source): - return glocale.sort_key(source.title) - - def _order_by_citation_key(self, citation): - return glocale.sort_key(citation.page) - - def _order_by_media_key(self, media): - return glocale.sort_key(media.desc) - - def _order_by_tag_key(self, tag): - return glocale.sort_key(tag.get_name()) - - def backup(self): - """ - If you wish to support an optional backup routine, put it here. - """ - from gramps.plugins.export.exportxml import XmlWriter - from gramps.cli.user import User - writer = XmlWriter(self, User(), strip_photos=0, compress=1) - filename = os.path.join(self._directory, "data.gramps") - writer.write(filename) - - def restore(self): - """ - If you wish to support an optional restore routine, put it here. - """ - pass - - def get_undodb(self): - return self.undodb - - def undo(self, update_history=True): - return self.undodb.undo(update_history) - - def redo(self, update_history=True): - return self.undodb.redo(update_history) - - def get_dbid(self): - """ - We use the file directory name as the unique ID for - this database on this computer. - """ - return self.brief_name diff --git a/gramps/plugins/database/dbapi_support/defaults/default_settings.py b/gramps/plugins/database/dbapi_support/defaults/default_settings.py deleted file mode 100644 index 8931c8e89..000000000 --- a/gramps/plugins/database/dbapi_support/defaults/default_settings.py +++ /dev/null @@ -1,7 +0,0 @@ -import os -import sqlite3 - -path_to_db = os.path.join(os.path.dirname(os.path.realpath(__file__)), - 'sqlite.db') -dbapi = sqlite3.connect(path_to_db) -dbapi.row_factory = sqlite3.Row # allows access by name diff --git a/gramps/plugins/database/dictionarydb.gpr.py b/gramps/plugins/database/dictionarydb.gpr.py index 3b0e62eca..d035de58a 100644 --- a/gramps/plugins/database/dictionarydb.gpr.py +++ b/gramps/plugins/database/dictionarydb.gpr.py @@ -24,7 +24,7 @@ plg.name = _("Dictionary Database Backend") plg.name_accell = _("Di_ctionary Database Backend") plg.description = _("Dictionary (in-memory) Database Backend") plg.version = '1.0' -plg.gramps_target_version = "4.2" +plg.gramps_target_version = "5.0" plg.status = STABLE plg.fname = 'dictionarydb.py' plg.ptype = DATABASE diff --git a/gramps/plugins/database/django_support/defaults/default_settings.py b/gramps/plugins/database/django_support/defaults/default_settings.py deleted file mode 100644 index fae3ffd06..000000000 --- a/gramps/plugins/database/django_support/defaults/default_settings.py +++ /dev/null @@ -1,150 +0,0 @@ -import os -from gramps.gen.const import DATA_DIR - -WEB_DIR = os.path.dirname(os.path.realpath(__file__)) - -DEBUG = True -TEMPLATE_DEBUG = DEBUG - -INTERNAL_IPS = ('127.0.0.1',) - -ADMINS = ( - ('admin', 'your_email@domain.com'), -) - -MANAGERS = ADMINS -DATABASE_ROUTERS = [] -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(WEB_DIR, 'sqlite.db'), - } -} -DATABASE_ENGINE = 'sqlite3' -DATABASE_NAME = os.path.join(WEB_DIR, 'sqlite.db') -DATABASE_USER = '' -DATABASE_PASSWORD = '' -DATABASE_HOST = '' -DATABASE_PORT = '' -TIME_ZONE = 'America/New_York' -LANGUAGE_CODE = 'en-us' -SITE_ID = 1 -USE_I18N = True -MEDIA_ROOT = '' -MEDIA_URL = '' -ADMIN_MEDIA_PREFIX = '/gramps-media/' -SECRET_KEY = 'zd@%vslj5sqhx94_8)0hsx*rk9tj3^ly$x+^*tq4bggr&uh$ac' - -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', # 1.4 - 'django.template.loaders.app_directories.Loader', # 1.4 - #'django.template.loaders.filesystem.load_template_source', - #'django.template.loaders.app_directories.load_template_source', -) - -MIDDLEWARE_CLASSES = ( - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', -# 'debug_toolbar.middleware.DebugToolbarMiddleware', -) - -ROOT_URLCONF = 'gramps.webapp.urls' -STATIC_URL = '/static/' # 1.4 - -TEMPLATE_DIRS = ( - # Use absolute paths, not relative paths. - os.path.join(DATA_DIR, "templates"), -) - -TEMPLATE_CONTEXT_PROCESSORS = ( - "django.contrib.auth.context_processors.auth", # 1.4 - "django.contrib.messages.context_processors.messages", # 1.4 -# "django.core.context_processors.auth", -# "django.core.context_processors.debug", - "django.core.context_processors.i18n", - "django.core.context_processors.media", - "gramps.webapp.grampsdb.views.context_processor", - "gramps.webapp.context.messages", -) - -INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.staticfiles', - 'django.contrib.messages', # 1.4 - 'django.contrib.sites', - 'django.contrib.admin', - 'gramps.webapp.grampsdb', -# 'django_extensions', -# 'debug_toolbar', -) - -DEBUG_TOOLBAR_PANELS = ( - 'debug_toolbar.panels.version.VersionDebugPanel', - 'debug_toolbar.panels.timer.TimerDebugPanel', - 'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel', - 'debug_toolbar.panels.headers.HeaderDebugPanel', - 'debug_toolbar.panels.request_vars.RequestVarsDebugPanel', - 'debug_toolbar.panels.template.TemplateDebugPanel', - 'debug_toolbar.panels.sql.SQLDebugPanel', - 'debug_toolbar.panels.signals.SignalDebugPanel', - 'debug_toolbar.panels.logger.LoggingPanel', - ) - -def custom_show_toolbar(request): - return True # Always show toolbar, for example purposes only. - -DEBUG_TOOLBAR_CONFIG = { - 'INTERCEPT_REDIRECTS': False, -# 'SHOW_TOOLBAR_CALLBACK': custom_show_toolbar, -# 'EXTRA_SIGNALS': ['myproject.signals.MySignal'], - 'HIDE_DJANGO_SQL': False, - } - -AUTH_PROFILE_MODULE = "grampsdb.Profile" - -# Had to add these to use settings.configure(): -DATABASE_OPTIONS = '' -URL_VALIDATOR_USER_AGENT = '' -DEFAULT_INDEX_TABLESPACE = '' -DEFAULT_TABLESPACE = '' -CACHE_BACKEND = 'locmem://' -TRANSACTIONS_MANAGED = False -LOCALE_PATHS = tuple() - -# Changes for Django 1.3: -USE_L10N = True -FORMAT_MODULE_PATH = "" -## End Changes for Django 1.3 - -# Changes for Django 1.4: -USE_TZ = False -## End Changes for Django 1.4 - -# Changes for Django 1.5: -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - } - } -DEFAULT_CHARSET = "utf-8" -## End Changes for Django 1.5 - -## Changes for Django 1.5.4: -LOGGING_CONFIG = None -AUTH_USER_MODEL = 'auth.User' -## End Changes for Django 1.5.4 - -LOGIN_URL = "/login/" -LOGOUT_URL = "/logout" -LOGIN_REDIRECT_URL = "/" - -## Changes for Django 1.6: -LOGGING = None - -## Changes for Django 1.7.1: -ABSOLUTE_URL_OVERRIDES = {} diff --git a/gramps/plugins/database/django_support/defaults/sqlite.db b/gramps/plugins/database/django_support/defaults/sqlite.db deleted file mode 100644 index a9ac911f065f752dfb25be75e2916ff5df508451..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 293888 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®`N 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 N%rWbcAX@iQ{{@Gr__6>1 diff --git a/gramps/plugins/database/django_support/libdjango.py b/gramps/plugins/database/django_support/libdjango.py deleted file mode 100644 index fa0a596c8..000000000 --- a/gramps/plugins/database/django_support/libdjango.py +++ /dev/null @@ -1,2063 +0,0 @@ -# Gramps - a GTK+/GNOME based genealogy program -# -# Copyright (C) 2009 Douglas S. Blank <doug.blank@gmail.com> -# -# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# - -""" Interface to Django models """ - -#------------------------------------------------------------------------ -# -# Python Modules -# -#------------------------------------------------------------------------ -import time -import sys -import pickle -import base64 -import collections - -#------------------------------------------------------------------------ -# -# Django Modules -# -#------------------------------------------------------------------------ -from django.contrib.contenttypes.models import ContentType -from django.db import transaction - -#------------------------------------------------------------------------ -# -# Gramps Modules -# -#------------------------------------------------------------------------ -import gramps.webapp.grampsdb.models as models -from gramps.gen.lib import Name -from gramps.gen.utils.id import create_id -from gramps.gen.constfunc import conv_to_unicode - -# To get a django person from a django database: -# djperson = dji.Person.get(handle='djhgsdh324hjg234hj24') -# -# To turn the djperson into a Gramps Person: -# tuple = dji.get_person(djperson) -# gperson = lib.gen.Person(tuple) -# OR -# gperson = dbdjango.DbDjango().get_person_from_handle(handle) - -def check_diff(item, raw): - encoded = str(base64.encodebytes(pickle.dumps(raw)), "utf-8") - if item.cache != encoded: - print("Different:", item.__class__.__name__, item.gramps_id) - print("raw :", raw) - print("cache:", item.from_cache()) - # FIXING, TOO: - item.save_cache() - -#------------------------------------------------------------------------- -# -# Import functions -# -#------------------------------------------------------------------------- -def lookup_role_index(role0, event_ref_list): - """ - Find the handle in a unserialized event_ref_list and return code. - """ - if role0 is None: - return -1 - else: - count = 0 - for event_ref in event_ref_list: - (private, note_list, attribute_list, ref, erole) = event_ref - try: - event = models.Event.objects.get(handle=ref) - except: - return -1 - if event.event_type[0] == role0: - return count - count += 1 - return -1 - -def totime(dtime): - if dtime: - return int(time.mktime(dtime.timetuple())) - else: - return 0 - -#------------------------------------------------------------------------- -# -# Export functions -# -#------------------------------------------------------------------------- -def todate(t): - return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(t)) - -def lookup(index, event_ref_list): - """ - Get the unserialized event_ref in an list of them and return it. - """ - if index < 0: - return None - else: - count = 0 - for event_ref in event_ref_list: - (private, note_list, attribute_list, ref, role) = event_ref - if index == count: - return ref - count += 1 - return None - -def get_datamap(grampsclass): - return [x[0] for x in grampsclass._DATAMAP if x[0] != grampsclass.CUSTOM] - -#------------------------------------------------------------------------- -# -# Django Interface -# -#------------------------------------------------------------------------- -class DjangoInterface(object): - """ - DjangoInterface for interoperating between Gramps and Django. - - This interface comes in a number of parts: - get_ITEMS() - add_ITEMS() - - get_ITEM(ITEM) - - Given an ITEM from a Django table, construct a Gramps Raw Data tuple. - - add_ITEM(data) - - Given a Gramps Raw Data tuple, add the data to the Django tables. - - - """ - def __init__(self): - self.debug = 0 - - def __getattr__(self, name): - """ - Django Objects database interface. - - >>> self.Person.all() - >>> self.Person.get(id=1) - >>> self.Person.get(handle='gh71234dhf3746347734') - """ - if hasattr(models, name): - return getattr(models, name).objects - else: - raise AttributeError("no such model: '%s'" % name) - - def get_next_id(self, obj, prefix): - """ - Get next gramps_id - - >>> dji.get_next_id(Person, "P") - 'P0002' - >>> dji.get_next_id(Media, "M") - 'M2349' - """ - ids = [o["gramps_id"] for o in obj.objects.values("gramps_id")] - count = 1 - while "%s%04d" % (prefix, count) in ids: - count += 1 - return "%s%04d" % (prefix, count) - - def get_model(self, name): - if hasattr(models, name): - return getattr(models, name) - elif hasattr(models, name.title()): - return getattr(models, name.title()) - else: - raise AttributeError("no such model: '%s'" % name) - - # ----------------------------------------------- - # Get methods to retrieve list data from the tables - # ----------------------------------------------- - - def clear_tables(self, *args): - return models.clear_tables(*args) - - def get_tag_list(self, obj): - return obj.get_tag_list() - - def get_attribute_list(self, obj): - obj_type = ContentType.objects.get_for_model(obj) - attribute_list = models.Attribute.objects.filter(object_id=obj.id, - object_type=obj_type) - return list(map(self.pack_attribute, attribute_list)) - - def get_primary_name(self, person): - names = person.name_set.filter(preferred=True).order_by("order") - if len(names) > 0: - return Name.create(self.pack_name(names[0])) - else: - return Name() - - def get_alternate_names(self, person): - names = person.name_set.filter(preferred=False).order_by("order") - return [Name.create(self.pack_name(n)) for n in names] - - def get_names(self, person, preferred): - names = person.name_set.filter(preferred=preferred).order_by("order") - if preferred: - if len(names) > 0: - return self.pack_name(names[0]) - else: - return Name().serialize() - else: - return list(map(self.pack_name, names)) - - def get_source_attribute_list(self, source): - return [(map.private, map.key, map.value) for map in source.sourceattribute_set.all().order_by("order")] - - def get_citation_attribute_list(self, citation): - return [(map.private, map.key, map.value) for map in citation.citationattribute_set.all().order_by("order")] - - def get_media_list(self, obj): - obj_type = ContentType.objects.get_for_model(obj) - mediarefs = models.MediaRef.objects.filter(object_id=obj.id, - object_type=obj_type) - return list(map(self.pack_media_ref, mediarefs)) - - def get_note_list(self, obj): - obj_type = ContentType.objects.get_for_model(obj) - noterefs = models.NoteRef.objects.filter(object_id=obj.id, - object_type=obj_type) - return [noteref.ref_object.handle for noteref in noterefs] - - def get_repository_ref_list(self, obj): - obj_type = ContentType.objects.get_for_model(obj) - reporefs = models.RepositoryRef.objects.filter(object_id=obj.id, - object_type=obj_type) - return list(map(self.pack_repository_ref, reporefs)) - - def get_place_ref_list(self, obj): - obj_type = ContentType.objects.get_for_model(obj) - refs = models.PlaceRef.objects.filter(object_id=obj.id, - object_type=obj_type) - return list(map(self.pack_place_ref, refs)) - - def get_url_list(self, obj): - return list(map(self.pack_url, obj.url_set.all().order_by("order"))) - - def get_address_list(self, obj, with_parish): # person or repository - addresses = obj.address_set.all().order_by("order") - return [self.pack_address(address, with_parish) - for address in addresses] - - def get_child_ref_list(self, family): - obj_type = ContentType.objects.get_for_model(family) - childrefs = models.ChildRef.objects.filter(object_id=family.id, - object_type=obj_type).order_by("order") - return list(map(self.pack_child_ref, childrefs)) - - def get_citation_list(self, obj): - obj_type = ContentType.objects.get_for_model(obj) - citationrefs = models.CitationRef.objects.filter(object_id=obj.id, - object_type=obj_type).order_by("order") - return [citationref.citation.handle for citationref in citationrefs] - - def get_event_refs(self, obj, order="order"): - obj_type = ContentType.objects.get_for_model(obj) - eventrefs = models.EventRef.objects.filter(object_id=obj.id, - object_type=obj_type).order_by(order) - return eventrefs - - def get_event_ref_list(self, obj): - obj_type = ContentType.objects.get_for_model(obj) - eventrefs = models.EventRef.objects.filter(object_id=obj.id, - object_type=obj_type).order_by("order") - return list(map(self.pack_event_ref, eventrefs)) - - def get_family_list(self, person): # person has families - return [fam.family.handle for fam in - models.MyFamilies.objects.filter(person=person).order_by("order")] - - def get_parent_family_list(self, person): # person's parents has families - return [fam.family.handle for fam in - models.MyParentFamilies.objects.filter(person=person).order_by("order")] - - def get_person_ref_list(self, person): - obj_type = ContentType.objects.get_for_model(person) - return list(map(self.pack_person_ref, - models.PersonRef.objects.filter(object_id=person.id, - object_type=obj_type))) - - def get_lds_list(self, obj): # person or family - return list(map(self.pack_lds, obj.lds_set.all().order_by("order"))) - - def get_place_handle(self, obj): # obj is event - if obj.place: - return obj.place.handle - return '' - - ## Packers: - - def get_event(self, event): - handle = event.handle - gid = event.gramps_id - the_type = tuple(event.event_type) - description = event.description - change = totime(event.last_changed) - private = event.private - note_list = self.get_note_list(event) - citation_list = self.get_citation_list(event) - media_list = self.get_media_list(event) - attribute_list = self.get_attribute_list(event) - date = self.get_date(event) - place_handle = self.get_place_handle(event) - tag_list = self.get_tag_list(event) - return (str(handle), gid, the_type, date, description, place_handle, - citation_list, note_list, media_list, attribute_list, - change, tag_list, private) - - def get_note_markup(self, note): - retval = [] - markups = models.Markup.objects.filter(note=note).order_by("order") - for markup in markups: - if markup.string and markup.string.isdigit(): - value = int(markup.string) - else: - value = markup.string - start_stop_list = markup.start_stop_list - ss_list = eval(start_stop_list) - retval += [(tuple(markup.styled_text_tag_type), value, ss_list)] - return retval - - def get_tag(self, tag): - changed = totime(tag.last_changed) - return (str(tag.handle), - tag.name, - tag.color, - tag.priority, - changed) - - def get_note(self, note): - styled_text = [note.text, self.get_note_markup(note)] - changed = totime(note.last_changed) - tag_list = self.get_tag_list(note) - return (str(note.handle), - note.gramps_id, - styled_text, - note.preformatted, - tuple(note.note_type), - changed, - tag_list, - note.private) - - def get_family(self, family): - child_ref_list = self.get_child_ref_list(family) - event_ref_list = self.get_event_ref_list(family) - media_list = self.get_media_list(family) - attribute_list = self.get_attribute_list(family) - lds_seal_list = self.get_lds_list(family) - citation_list = self.get_citation_list(family) - note_list = self.get_note_list(family) - tag_list = self.get_tag_list(family) - if family.father: - father_handle = family.father.handle - else: - father_handle = '' - if family.mother: - mother_handle = family.mother.handle - else: - mother_handle = '' - return (str(family.handle), family.gramps_id, - father_handle, mother_handle, - child_ref_list, tuple(family.family_rel_type), - event_ref_list, media_list, - attribute_list, lds_seal_list, - citation_list, note_list, - totime(family.last_changed), - tag_list, - family.private) - - def get_repository(self, repository): - note_list = self.get_note_list(repository) - address_list = self.get_address_list(repository, with_parish=False) - url_list = self.get_url_list(repository) - tag_list = self.get_tag_list(repository) - return (str(repository.handle), - repository.gramps_id, - tuple(repository.repository_type), - repository.name, - note_list, - address_list, - url_list, - totime(repository.last_changed), - tag_list, - repository.private) - - def get_citation(self, citation): - note_list = self.get_note_list(citation) - media_list = self.get_media_list(citation) - attribute_list = self.get_citation_attribute_list(citation) - tag_list = self.get_tag_list(citation) - date = self.get_date(citation) - # I guess citations can have no source - if citation.source: - handle = citation.source.handle - else: - handle = None - return (str(citation.handle), - citation.gramps_id, - date, - citation.page, - citation.confidence, - handle, - note_list, - media_list, - attribute_list, - totime(citation.last_changed), - tag_list, - citation.private) - - def get_source(self, source): - note_list = self.get_note_list(source) - media_list = self.get_media_list(source) - attribute_list = self.get_source_attribute_list(source) - reporef_list = self.get_repository_ref_list(source) - tag_list = self.get_tag_list(source) - return (str(source.handle), - source.gramps_id, - source.title, - source.author, - source.pubinfo, - note_list, - media_list, - source.abbrev, - totime(source.last_changed), - attribute_list, - reporef_list, - tag_list, - source.private) - - def get_media(self, media): - attribute_list = self.get_attribute_list(media) - citation_list = self.get_citation_list(media) - note_list = self.get_note_list(media) - tag_list = self.get_tag_list(media) - date = self.get_date(media) - return (str(media.handle), - media.gramps_id, - conv_to_unicode(media.path, None), - str(media.mime), - str(media.desc), - media.checksum, - attribute_list, - citation_list, - note_list, - totime(media.last_changed), - date, - tag_list, - media.private) - - def get_person(self, person): - primary_name = self.get_names(person, True) # one - alternate_names = self.get_names(person, False) # list - event_ref_list = self.get_event_ref_list(person) - family_list = self.get_family_list(person) - parent_family_list = self.get_parent_family_list(person) - media_list = self.get_media_list(person) - address_list = self.get_address_list(person, with_parish=False) - attribute_list = self.get_attribute_list(person) - url_list = self.get_url_list(person) - lds_ord_list = self.get_lds_list(person) - pcitation_list = self.get_citation_list(person) - pnote_list = self.get_note_list(person) - person_ref_list = self.get_person_ref_list(person) - # This looks up the events for the first EventType given: - death_ref_index = person.death_ref_index - birth_ref_index = person.birth_ref_index - tag_list = self.get_tag_list(person) - - return (str(person.handle), - person.gramps_id, - tuple(person.gender_type)[0], - primary_name, - alternate_names, - death_ref_index, - birth_ref_index, - event_ref_list, - family_list, - parent_family_list, - media_list, - address_list, - attribute_list, - url_list, - lds_ord_list, - pcitation_list, - pnote_list, - totime(person.last_changed), - tag_list, - person.private, - person_ref_list) - - def get_date(self, obj): - if ((obj.calendar == obj.modifier == obj.quality == obj.sortval == obj.newyear == 0) and - obj.text == "" and (not obj.slash1) and (not obj.slash2) and - (obj.day1 == obj.month1 == obj.year1 == 0) and - (obj.day2 == obj.month2 == obj.year2 == 0)): - return None - elif ((not obj.slash1) and (not obj.slash2) and - (obj.day2 == obj.month2 == obj.year2 == 0)): - dateval = (obj.day1, obj.month1, obj.year1, obj.slash1) - else: - dateval = (obj.day1, obj.month1, obj.year1, obj.slash1, - obj.day2, obj.month2, obj.year2, obj.slash2) - return (obj.calendar, obj.modifier, obj.quality, dateval, - obj.text, obj.sortval, obj.newyear) - - def get_place(self, place): - locations = place.location_set.all().order_by("order") - alt_location_list = [self.pack_location(location, True) for location in locations] - url_list = self.get_url_list(place) - media_list = self.get_media_list(place) - citation_list = self.get_citation_list(place) - note_list = self.get_note_list(place) - tag_list = self.get_tag_list(place) - place_ref_list = self.get_place_ref_list(place) - return (str(place.handle), - place.gramps_id, - place.title, - place.long, - place.lat, - place_ref_list, - place.name, - [], ## FIXME: get_alt_names - tuple(place.place_type), - place.code, - alt_location_list, - url_list, - media_list, - citation_list, - note_list, - totime(place.last_changed), - tag_list, - place.private) - - # --------------------------------- - # Packers - # --------------------------------- - - ## The packers build GRAMPS raw unserialized data. - - ## Reference packers - - def pack_child_ref(self, child_ref): - citation_list = self.get_citation_list(child_ref) - note_list = self.get_note_list(child_ref) - return (child_ref.private, citation_list, note_list, child_ref.ref_object.handle, - tuple(child_ref.father_rel_type), tuple(child_ref.mother_rel_type)) - - def pack_person_ref(self, personref): - citation_list = self.get_citation_list(personref) - note_list = self.get_note_list(personref) - return (personref.private, - citation_list, - note_list, - personref.ref_object.handle, - personref.description) - - def pack_media_ref(self, media_ref): - citation_list = self.get_citation_list(media_ref) - note_list = self.get_note_list(media_ref) - attribute_list = self.get_attribute_list(media_ref) - if ((media_ref.x1 == media_ref.y1 == media_ref.x2 == media_ref.y2 == -1) or - (media_ref.x1 == media_ref.y1 == media_ref.x2 == media_ref.y2 == 0)): - role = None - else: - role = (media_ref.x1, media_ref.y1, media_ref.x2, media_ref.y2) - return (media_ref.private, citation_list, note_list, attribute_list, - media_ref.ref_object.handle, role) - - def pack_repository_ref(self, repo_ref): - note_list = self.get_note_list(repo_ref) - return (note_list, - repo_ref.ref_object.handle, - repo_ref.call_number, - tuple(repo_ref.source_media_type), - repo_ref.private) - - def pack_place_ref(self, place_ref): - date = self.get_date(place_ref) - return (place_ref.ref_object.handle, date) - - def pack_media_ref(self, media_ref): - note_list = self.get_note_list(media_ref) - attribute_list = self.get_attribute_list(media_ref) - citation_list = self.get_citation_list(media_ref) - return (media_ref.private, citation_list, note_list, attribute_list, - media_ref.ref_object.handle, (media_ref.x1, - media_ref.y1, - media_ref.x2, - media_ref.y2)) - - def pack_event_ref(self, event_ref): - note_list = self.get_note_list(event_ref) - attribute_list = self.get_attribute_list(event_ref) - return (event_ref.private, note_list, attribute_list, - event_ref.ref_object.handle, tuple(event_ref.role_type)) - - def pack_citation(self, citation): - handle = citation.handle - gid = citation.gramps_id - date = self.get_date(citation) - page = citation.page - confidence = citation.confidence - source_handle = citation.source.handle - note_list = self.get_note_list(citation) - media_list = self.get_media_list(citation) - attribute_list = self.get_citation_attribute_list(citation) - changed = totime(citation.last_changed) - private = citation.private - tag_list = self.get_tag_list(citation) - return (handle, gid, date, page, confidence, source_handle, - note_list, media_list, attribute_list, changed, tag_list, - private) - - def pack_address(self, address, with_parish): - citation_list = self.get_citation_list(address) - date = self.get_date(address) - note_list = self.get_note_list(address) - locations = address.location_set.all().order_by("order") - if len(locations) > 0: - location = self.pack_location(locations[0], with_parish) - else: - if with_parish: - location = (("", "", "", "", "", "", ""), "") - else: - location = ("", "", "", "", "", "", "") - return (address.private, citation_list, note_list, date, location) - - def pack_lds(self, lds): - citation_list = self.get_citation_list(lds) - note_list = self.get_note_list(lds) - date = self.get_date(lds) - if lds.famc: - famc = lds.famc.handle - else: - famc = None - place_handle = self.get_place_handle(lds) - return (citation_list, note_list, date, lds.lds_type[0], place_handle, - famc, lds.temple, lds.status[0], lds.private) - - def pack_source(self, source): - note_list = self.get_note_list(source) - media_list = self.get_media_list(source) - reporef_list = self.get_repository_ref_list(source) - attribute_list = self.get_source_attribute_list(source) - tag_list = self.get_tag_list(source) - return (source.handle, source.gramps_id, source.title, - source.author, source.pubinfo, - note_list, - media_list, - source.abbrev, - totime(last_changed), attribute_list, - reporef_list, - tag_list, - source.private) - - def pack_name(self, name): - citation_list = self.get_citation_list(name) - note_list = self.get_note_list(name) - date = self.get_date(name) - return (name.private, citation_list, note_list, date, - name.first_name, name.make_surname_list(), name.suffix, - name.title, tuple(name.name_type), - name.group_as, name.sort_as.val, - name.display_as.val, name.call, name.nick, - name.famnick) - - def pack_location(self, loc, with_parish): - if with_parish: - return ((loc.street, loc.locality, loc.city, loc.county, loc.state, loc.country, - loc.postal, loc.phone), loc.parish) - else: - return (loc.street, loc.locality, loc.city, loc.county, loc.state, loc.country, - loc.postal, loc.phone) - - def pack_url(self, url): - return (url.private, url.path, url.desc, tuple(url.url_type)) - - def pack_attribute(self, attribute): - citation_list = self.get_citation_list(attribute) - note_list = self.get_note_list(attribute) - return (attribute.private, - citation_list, - note_list, - tuple(attribute.attribute_type), - attribute.value) - - - ## Export lists: - - def add_child_ref_list(self, obj, ref_list): - ## Currently, only Family references children - for child_data in ref_list: - self.add_child_ref(obj, child_data) - - def add_citation_list(self, obj, citation_list): - for citation_handle in citation_list: - self.add_citation_ref(obj, citation_handle) - - def add_event_ref_list(self, obj, event_ref_list): - for event_ref in event_ref_list: - self.add_event_ref(obj, event_ref) - - def add_surname_list(self, name, surname_list): - order = 1 - for data in surname_list: - (surname_text, prefix, primary, origin_type, - connector) = data - surname = models.Surname() - surname.surname = surname_text - surname.prefix = prefix - surname.primary = primary - surname.name_origin_type = models.get_type(models.NameOriginType, - origin_type) - surname.connector = connector - surname.name = name - surname.order = order - surname.save() - order += 1 - - def add_note_list(self, obj, note_list): - for handle in note_list: - # Just the handle - try: - note = models.Note.objects.get(handle=handle) - self.add_note_ref(obj, note) - except: - print(("ERROR: Note does not exist: '%s'" % - str(handle)), file=sys.stderr) - - def add_alternate_name_list(self, person, alternate_names): - for name in alternate_names: - if name: - self.add_name(person, name, False) - - def add_parent_family_list(self, person, parent_family_list): - for parent_family_data in parent_family_list: - self.add_parent_family(person, parent_family_data) - - def add_media_ref_list(self, person, media_list): - for media_data in media_list: - self.add_media_ref(person, media_data) - - def add_attribute_list(self, obj, attribute_list): - for attribute_data in attribute_list: - self.add_attribute(obj, attribute_data) - - def add_tag_list(self, obj, tag_list): - for tag_handle in tag_list: - try: - tag = models.Tag.objects.get(handle=tag_handle) - except: - print(("ERROR: Tag does not exist: '%s'" % - str(tag_handle)), file=sys.stderr) - obj.tags.add(tag) - - def add_url_list(self, field, obj, url_list): - if not url_list: return None - count = 1 - for url_data in url_list: - self.add_url(field, obj, url_data, count) - count += 1 - - def add_person_ref_list(self, obj, person_ref_list): - for person_ref_data in person_ref_list: - self.add_person_ref(obj, person_ref_data) - - def add_address_list(self, field, obj, address_list): - count = 1 - for address_data in address_list: - self.add_address(field, obj, address_data, count) - count += 1 - - def add_lds_list(self, field, obj, lds_ord_list): - count = 1 - for ldsord in lds_ord_list: - lds = self.add_lds(field, obj, ldsord, count) - #obj.lds_list.add(lds) - #obj.save() - count += 1 - - def add_repository_ref_list(self, obj, reporef_list): - for data in reporef_list: - self.add_repository_ref(obj, data) - - def add_place_ref_list(self, obj, placeref_list): - for data in placeref_list: - self.add_place_ref(obj, data) - - def add_family_ref_list(self, person, family_list): - for family_handle in family_list: - self.add_family_ref(person, family_handle) - - def add_alt_name_list(self, place, alt_name_list): - print("FIXME: add alt_name_list!", alt_name_list) - - ## Export reference objects: - - def add_person_ref_default(self, obj, person, private=False, desc=None): - count = person.references.count() - person_ref = models.PersonRef(referenced_by=obj, - ref_object=person, - private=private, - order=count + 1, - description=desc) - person_ref.save() - - def add_person_ref(self, obj, person_ref_data): - (private, - citation_list, - note_list, - handle, - desc) = person_ref_data - try: - person = models.Person.objects.get(handle=handle) - except: - print(("ERROR: Person does not exist: '%s'" % - str(handle)), file=sys.stderr) - return - - count = person.references.count() - person_ref = models.PersonRef(referenced_by=obj, - ref_object=person, - private=private, - order=count + 1, - description=desc) - person_ref.save() - self.add_note_list(person_ref, note_list) - self.add_citation_list(person_ref, citation_list) - - def add_note_ref(self, obj, note): - count = note.references.count() - note_ref = models.NoteRef(referenced_by=obj, - ref_object=note, - private=False, - order=count + 1) - note_ref.save() - - def add_media_ref_default(self, obj, media, private=False, role=None): - count = media.references.count() - if not role: - role = (0,0,0,0) - media_ref = models.MediaRef(referenced_by=obj, - ref_object=media, - x1=role[0], - y1=role[1], - x2=role[2], - y2=role[3], - private=private, - order=count + 1) - media_ref.save() - - def add_media_ref(self, obj, media_ref_data): - (private, citation_list, note_list, attribute_list, - ref, role) = media_ref_data - try: - media = models.Media.objects.get(handle=ref) - except: - print(("ERROR: Media does not exist: '%s'" % - str(ref)), file=sys.stderr) - return - count = media.references.count() - if not role: - role = (0,0,0,0) - media_ref = models.MediaRef(referenced_by=obj, - ref_object=media, - x1=role[0], - y1=role[1], - x2=role[2], - y2=role[3], - private=private, - order=count + 1) - media_ref.save() - self.add_note_list(media_ref, note_list) - self.add_attribute_list(media_ref, attribute_list) - self.add_citation_list(media_ref, citation_list) - - def add_citation_ref_default(self, obj, citation, private=False): - object_type = ContentType.objects.get_for_model(obj) - count = models.CitationRef.objects.filter(object_id=obj.id,object_type=object_type).count() - citation_ref = models.CitationRef(private=private, - referenced_by=obj, - citation=citation, - order=count + 1) - citation_ref.save() - - def add_citation_ref(self, obj, handle): - try: - citation = models.Citation.objects.get(handle=handle) - except: - print(("ERROR: Citation does not exist: '%s'" % - str(handle)), file=sys.stderr) - return - - object_type = ContentType.objects.get_for_model(obj) - count = models.CitationRef.objects.filter(object_id=obj.id,object_type=object_type).count() - citation_ref = models.CitationRef(private=False, - referenced_by=obj, - citation=citation, - order=count + 1) - citation_ref.save() - - def add_citation(self, citation_data): - (handle, gid, date, page, confidence, source_handle, note_list, - media_list, attribute_list, changed, tag_list, private) = citation_data - citation = models.Citation( - handle=handle, - gramps_id=gid, - private=private, - last_changed=todate(changed), - confidence=confidence, - page=page) - citation.save(save_cache=False) - - def add_citation_detail(self, citation_data): - (handle, gid, date, page, confidence, source_handle, note_list, - media_list, attribute_list, change, tag_list, private) = citation_data - try: - citation = models.Citation.objects.get(handle=handle) - except: - print(("ERROR: Citation does not exist: '%s'" % - str(handle)), file=sys.stderr) - return - try: - source = models.Source.objects.get(handle=source_handle) - except: - print(("ERROR: Source does not exist: '%s'" % - str(source_handle)), file=sys.stderr) - return - citation.source = source - self.add_date(citation, date) - citation.save(save_cache=False) - self.add_note_list(citation, note_list) - self.add_media_ref_list(citation, media_list) - self.add_citation_attribute_list(citation, attribute_list) - self.add_tag_list(citation, tag_list) - citation.save_cache() - - def add_child_ref_default(self, obj, child, frel=1, mrel=1, private=False): - object_type = ContentType.objects.get_for_model(obj) # obj is family - count = models.ChildRef.objects.filter(object_id=obj.id,object_type=object_type).count() - child_ref = models.ChildRef(private=private, - referenced_by=obj, - ref_object=child, - order=count + 1, - father_rel_type=models.get_type(models.ChildRefType, frel), # birth - mother_rel_type=models.get_type(models.ChildRefType, mrel)) - child_ref.save() - - def add_child_ref(self, obj, data): - (private, citation_list, note_list, ref, frel, mrel) = data - try: - child = models.Person.objects.get(handle=ref) - except: - print(("ERROR: Person does not exist: '%s'" % - str(ref)), file=sys.stderr) - return - object_type = ContentType.objects.get_for_model(obj) - count = models.ChildRef.objects.filter(object_id=obj.id,object_type=object_type).count() - child_ref = models.ChildRef(private=private, - referenced_by=obj, - ref_object=child, - order=count + 1, - father_rel_type=models.get_type(models.ChildRefType, frel), - mother_rel_type=models.get_type(models.ChildRefType, mrel)) - child_ref.save() - self.add_citation_list(child_ref, citation_list) - self.add_note_list(child_ref, note_list) - - def add_event_ref_default(self, obj, event, private=False, role=models.EventRoleType._DEFAULT): - object_type = ContentType.objects.get_for_model(obj) - count = models.EventRef.objects.filter(object_id=obj.id,object_type=object_type).count() - event_ref = models.EventRef(private=private, - referenced_by=obj, - ref_object=event, - order=count + 1, - role_type = models.get_type(models.EventRoleType, role)) - event_ref.save() - - def add_event_ref(self, obj, event_data): - (private, note_list, attribute_list, ref, role) = event_data - try: - event = models.Event.objects.get(handle=ref) - except: - print(("ERROR: Event does not exist: '%s'" % - str(ref)), file=sys.stderr) - return - object_type = ContentType.objects.get_for_model(obj) - count = models.EventRef.objects.filter(object_id=obj.id,object_type=object_type).count() - event_ref = models.EventRef(private=private, - referenced_by=obj, - ref_object=event, - order=count + 1, - role_type = models.get_type(models.EventRoleType, role)) - event_ref.save() - self.add_note_list(event_ref, note_list) - self.add_attribute_list(event_ref, attribute_list) - - def add_repository_ref_default(self, obj, repository, private=False, call_number="", - source_media_type=models.SourceMediaType._DEFAULT): - object_type = ContentType.objects.get_for_model(obj) - count = models.RepositoryRef.objects.filter(object_id=obj.id,object_type=object_type).count() - repos_ref = models.RepositoryRef(private=private, - referenced_by=obj, - call_number=call_number, - source_media_type=models.get_type(models.SourceMediaType, - source_media_type), - ref_object=repository, - order=count + 1) - repos_ref.save() - - def add_repository_ref(self, obj, reporef_data): - (note_list, - ref, - call_number, - source_media_type, - private) = reporef_data - try: - repository = models.Repository.objects.get(handle=ref) - except: - print(("ERROR: Repository does not exist: '%s'" % - str(ref)), file=sys.stderr) - return - object_type = ContentType.objects.get_for_model(obj) - count = models.RepositoryRef.objects.filter(object_id=obj.id,object_type=object_type).count() - repos_ref = models.RepositoryRef(private=private, - referenced_by=obj, - call_number=call_number, - source_media_type=models.get_type(models.SourceMediaType, - source_media_type), - ref_object=repository, - order=count + 1) - repos_ref.save() - self.add_note_list(repos_ref, note_list) - - def add_family_ref(self, obj, handle): - try: - family = models.Family.objects.get(handle=handle) - except: - print(("ERROR: Family does not exist: '%s'" % - str(handle)), file=sys.stderr) - return - #obj.families.add(family) - pfo = models.MyFamilies(person=obj, family=family, - order=len(models.MyFamilies.objects.filter(person=obj)) + 1) - pfo.save() - obj.save() - - ## Export individual objects: - - def add_source_attribute_list(self, source, attribute_list): - ## FIXME: dict to list - count = 1 - #for key in datamap_dict: - # value = datamap_dict[key] - # datamap = models.SourceDatamap(key=key, value=value, order=count) - # datamap.source = source - # datamap.save() - # count += 1 - - def add_citation_attribute_list(self, citation, attribute_list): - ## FIXME: dict to list - count = 1 - #for key in datamap_dict: - # value = datamap_dict[key] - # datamap = models.CitationDatamap(key=key, value=value, order=count) - # datamap.citation = citation - # datamap.save() - # count += 1 - - def add_lds(self, field, obj, data, order): - (lcitation_list, lnote_list, date, type, place_handle, - famc_handle, temple, status, private) = data - if place_handle: - try: - place = models.Place.objects.get(handle=place_handle) - except: - print(("ERROR: Place does not exist: '%s'" % - str(place_handle)), file=sys.stderr) - place = None - else: - place = None - if famc_handle: - try: - famc = models.Family.objects.get(handle=famc_handle) - except: - print(("ERROR: Family does not exist: '%s'" % - str(famc_handle)), file=sys.stderr) - famc = None - else: - famc = None - lds = models.Lds(lds_type = models.get_type(models.LdsType, type), - temple=temple, - place=place, - famc=famc, - order=order, - status = models.get_type(models.LdsStatus, status), - private=private) - self.add_date(lds, date) - lds.save() - self.add_note_list(lds, lnote_list) - self.add_citation_list(lds, lcitation_list) - if field == "person": - lds.person = obj - elif field == "family": - lds.family = obj - else: - raise AttributeError("invalid field '%s' to attach lds" % - str(field)) - lds.save() - return lds - - def add_address(self, field, obj, address_data, order): - (private, acitation_list, anote_list, date, location) = address_data - address = models.Address(private=private, order=order) - self.add_date(address, date) - address.save() - self.add_location("address", address, location, 1) - self.add_note_list(address, anote_list) - self.add_citation_list(address, acitation_list) - if field == "person": - address.person = obj - elif field == "repository": - address.repository = obj - else: - raise AttributeError("invalid field '%s' to attach address" % - str(field)) - address.save() - #obj.save() - #obj.addresses.add(address) - #obj.save() - - def add_attribute(self, obj, attribute_data): - (private, citation_list, note_list, the_type, value) = attribute_data - attribute_type = models.get_type(models.AttributeType, the_type) - attribute = models.Attribute(private=private, - attribute_of=obj, - attribute_type=attribute_type, - value=value) - attribute.save() - self.add_citation_list(attribute, citation_list) - self.add_note_list(attribute, note_list) - #obj.attributes.add(attribute) - #obj.save() - - def add_url(self, field, obj, url_data, order): - (private, path, desc, type) = url_data - url = models.Url(private=private, - path=path, - desc=desc, - order=order, - url_type=models.get_type(models.UrlType, type)) - if field == "person": - url.person = obj - elif field == "repository": - url.repository = obj - elif field == "place": - url.place = obj - else: - raise AttributeError("invalid field '%s' to attach to url" % - str(field)) - url.save() - #obj.url_list.add(url) - #obj.save() - - def add_place_ref_default(self, obj, place, date=None): - count = place.references.count() - object_type = ContentType.objects.get_for_model(obj) - count = models.PlaceRef.objects.filter(object_id=obj.id, - object_type=object_type).count() - place_ref = models.PlaceRef(referenced_by=obj, - ref_object=place, - order=count + 1) - self.add_date(obj, date) - place_ref.save() - - def add_place_ref(self, obj, data): - place_handle, date = data - if place_handle: - try: - place = models.Place.objects.get(handle=place_handle) - except: - print(("ERROR: Place does not exist: '%s'" % str(place_handle)), file=sys.stderr) - #from gramps.gen.utils.debug import format_exception - #print("".join(format_exception()), file=sys.stderr) - return - object_type = ContentType.objects.get_for_model(obj) - count = models.PlaceRef.objects.filter(object_id=obj.id,object_type=object_type).count() - place_ref = models.PlaceRef(referenced_by=obj, ref_object=place, order=count + 1) - place_ref.save() - self.add_date(place_ref, date) - - def add_parent_family(self, person, parent_family_handle): - try: - family = models.Family.objects.get(handle=parent_family_handle) - except: - print(("ERROR: Family does not exist: '%s'" % - str(parent_family_handle)), file=sys.stderr) - return - #person.parent_families.add(family) - pfo = models.MyParentFamilies( - person=person, - family=family, - order=len(models.MyParentFamilies.objects.filter(person=person)) + 1) - pfo.save() - person.save() - - def add_date(self, obj, date): - if date is None: - (calendar, modifier, quality, text, sortval, newyear) = \ - (0, 0, 0, "", 0, 0) - day1, month1, year1, slash1 = 0, 0, 0, 0 - day2, month2, year2, slash2 = 0, 0, 0, 0 - else: - (calendar, modifier, quality, dateval, text, sortval, newyear) = date - if len(dateval) == 4: - day1, month1, year1, slash1 = dateval - day2, month2, year2, slash2 = 0, 0, 0, 0 - elif len(dateval) == 8: - day1, month1, year1, slash1, day2, month2, year2, slash2 = dateval - else: - raise AttributeError("ERROR: dateval format '%s'" % str(dateval)) - obj.calendar = calendar - obj.modifier = modifier - obj.quality = quality - obj.text = text - obj.sortval = sortval - obj.newyear = newyear - obj.day1 = day1 - obj.month1 = month1 - obj.year1 = year1 - obj.slash1 = slash1 - obj.day2 = day2 - obj.month2 = month2 - obj.year2 = year2 - obj.slash2 = slash2 - - def add_name(self, person, data, preferred): - if data: - (private, citation_list, note_list, date, - first_name, surname_list, suffix, title, - name_type, group_as, sort_as, - display_as, call, nick, famnick) = data - - count = person.name_set.count() - name = models.Name() - name.order = count + 1 - name.preferred = preferred - name.private = private - name.first_name = first_name - name.suffix = suffix - name.title = title - name.name_type = models.get_type(models.NameType, name_type) - name.group_as = group_as - name.sort_as = models.get_type(models.NameFormatType, sort_as) - name.display_as = models.get_type(models.NameFormatType, display_as) - name.call = call - name.nick = nick - name.famnick = famnick - # we know person exists - # needs to have an ID for key - name.person = person - self.add_date(name, date) - name.save() - self.add_surname_list(name, surname_list) - self.add_note_list(name, note_list) - self.add_citation_list(name, citation_list) - #person.save() - - ## Export primary objects: - - def add_person(self, data): - # Unpack from the BSDDB: - (handle, # 0 - gid, # 1 - gender, # 2 - primary_name, # 3 - alternate_names, # 4 - death_ref_index, # 5 - birth_ref_index, # 6 - event_ref_list, # 7 - family_list, # 8 - parent_family_list, # 9 - media_list, # 10 - address_list, # 11 - attribute_list, # 12 - url_list, # 13 - lds_ord_list, # 14 - pcitation_list, # 15 - pnote_list, # 16 - change, # 17 - tag_list, # 18 - private, # 19 - person_ref_list, # 20 - ) = data - - person = models.Person(handle=handle, - gramps_id=gid, - last_changed=todate(change), - private=private, - gender_type=models.get_type(models.GenderType, gender)) - person.save(save_cache=False) - - def add_person_detail(self, data): - # Unpack from the BSDDB: - (handle, # 0 - gid, # 1 - gender, # 2 - primary_name, # 3 - alternate_names, # 4 - death_ref_index, # 5 - birth_ref_index, # 6 - event_ref_list, # 7 - family_list, # 8 - parent_family_list, # 9 - media_list, # 10 - address_list, # 11 - attribute_list, # 12 - url_list, # 13 - lds_ord_list, # 14 - pcitation_list, # 15 - pnote_list, # 16 - change, # 17 - tag_list, # 18 - private, # 19 - person_ref_list, # 20 - ) = data - - try: - person = models.Person.objects.get(handle=handle) - except: - print(("ERROR: Person does not exist: '%s'" % - str(handle)), file=sys.stderr) - return - if primary_name: - self.add_name(person, primary_name, True) - self.add_alternate_name_list(person, alternate_names) - self.add_event_ref_list(person, event_ref_list) - self.add_family_ref_list(person, family_list) - self.add_parent_family_list(person, parent_family_list) - self.add_media_ref_list(person, media_list) - self.add_note_list(person, pnote_list) - self.add_attribute_list(person, attribute_list) - self.add_url_list("person", person, url_list) - self.add_person_ref_list(person, person_ref_list) - self.add_citation_list(person, pcitation_list) - self.add_address_list("person", person, address_list) - self.add_lds_list("person", person, lds_ord_list) - self.add_tag_list(person, tag_list) - # set person.birth and birth.death to correct events: - - obj_type = ContentType.objects.get_for_model(person) - events = models.EventRef.objects.filter( - object_id=person.id, - object_type=obj_type, - ref_object__event_type__val=models.EventType.BIRTH).order_by("order") - - all_events = self.get_event_ref_list(person) - if events: - person.birth = events[0].ref_object - person.birth_ref_index = lookup_role_index(models.EventType.BIRTH, all_events) - - events = models.EventRef.objects.filter( - object_id=person.id, - object_type=obj_type, - ref_object__event_type__val=models.EventType.DEATH).order_by("order") - if events: - person.death = events[0].ref_object - person.death_ref_index = lookup_role_index(models.EventType.DEATH, all_events) - person.save() - return person - - def save_note_markup(self, note, markup_list): - # delete any prexisting markup: - models.Markup.objects.filter(note=note).delete() - count = 1 - for markup in markup_list: - markup_code, value, start_stop_list = markup - m = models.Markup( - note=note, - order=count, - styled_text_tag_type=models.get_type(models.StyledTextTagType, - markup_code, - get_or_create=False), - string=value, - start_stop_list=str(start_stop_list)) - m.save() - - def add_note(self, data): - # Unpack from the BSDDB: - (handle, gid, styled_text, format, note_type, - change, tag_list, private) = data - text, markup_list = styled_text - n = models.Note(handle=handle, - gramps_id=gid, - last_changed=todate(change), - private=private, - preformatted=format, - text=text, - note_type=models.get_type(models.NoteType, note_type)) - n.save(save_cache=False) - self.save_note_markup(n, markup_list) - - def add_note_detail(self, data): - # Unpack from the BSDDB: - (handle, gid, styled_text, format, note_type, - change, tag_list, private) = data - note = models.Note.objects.get(handle=handle) - note.save(save_cache=False) - self.add_tag_list(note, tag_list) - note.save_cache() - - def add_family(self, data): - # Unpack from the BSDDB: - (handle, gid, father_handle, mother_handle, - child_ref_list, the_type, event_ref_list, media_list, - attribute_list, lds_seal_list, citation_list, note_list, - change, tag_list, private) = data - - family = models.Family(handle=handle, gramps_id=gid, - family_rel_type = models.get_type(models.FamilyRelType, the_type), - last_changed=todate(change), - private=private) - family.save(save_cache=False) - - def add_family_detail(self, data): - # Unpack from the BSDDB: - (handle, gid, father_handle, mother_handle, - child_ref_list, the_type, event_ref_list, media_list, - attribute_list, lds_seal_list, citation_list, note_list, - change, tag_list, private) = data - - try: - family = models.Family.objects.get(handle=handle) - except: - print(("ERROR: Family does not exist: '%s'" % - str(handle)), file=sys.stderr) - return - # father_handle and/or mother_handle can be None - if father_handle: - try: - family.father = models.Person.objects.get(handle=father_handle) - except: - print(("ERROR: Father does not exist: '%s'" % - str(father_handle)), file=sys.stderr) - family.father = None - if mother_handle: - try: - family.mother = models.Person.objects.get(handle=mother_handle) - except: - print(("ERROR: Mother does not exist: '%s'" % - str(mother_handle)), file=sys.stderr) - family.mother = None - family.save(save_cache=False) - self.add_child_ref_list(family, child_ref_list) - self.add_note_list(family, note_list) - self.add_attribute_list(family, attribute_list) - self.add_citation_list(family, citation_list) - self.add_media_ref_list(family, media_list) - self.add_event_ref_list(family, event_ref_list) - self.add_lds_list("family", family, lds_seal_list) - self.add_tag_list(family, tag_list) - family.save_cache() - - def add_source(self, data): - (handle, gid, title, - author, pubinfo, - note_list, - media_list, - abbrev, - change, attribute_list, - reporef_list, - tag_list, - private) = data - source = models.Source(handle=handle, gramps_id=gid, title=title, - author=author, pubinfo=pubinfo, abbrev=abbrev, - last_changed=todate(change), private=private) - source.save(save_cache=False) - - def add_source_detail(self, data): - (handle, gid, title, - author, pubinfo, - note_list, - media_list, - abbrev, - change, attribute_list, - reporef_list, - tag_list, - private) = data - try: - source = models.Source.objects.get(handle=handle) - except: - print(("ERROR: Source does not exist: '%s'" % - str(handle)), file=sys.stderr) - return - source.save(save_cache=False) - self.add_note_list(source, note_list) - self.add_media_ref_list(source, media_list) - self.add_source_attribute_list(source, attribute_list) - self.add_repository_ref_list(source, reporef_list) - self.add_tag_list(source, tag_list) - source.save_cache() - - def add_repository(self, data): - (handle, gid, the_type, name, note_list, - address_list, url_list, change, tag_list, private) = data - - repository = models.Repository(handle=handle, - gramps_id=gid, - last_changed=todate(change), - private=private, - repository_type=models.get_type(models.RepositoryType, the_type), - name=name) - repository.save(save_cache=False) - - def add_repository_detail(self, data): - (handle, gid, the_type, name, note_list, - address_list, url_list, change, tag_list, private) = data - try: - repository = models.Repository.objects.get(handle=handle) - except: - print(("ERROR: Repository does not exist: '%s'" % - str(handle)), file=sys.stderr) - return - repository.save(save_cache=False) - self.add_note_list(repository, note_list) - self.add_url_list("repository", repository, url_list) - self.add_address_list("repository", repository, address_list) - self.add_tag_list(repository, tag_list) - repository.save_cache() - - def add_location(self, field, obj, location_data, order): - # location now has 8 items - # street, locality, city, county, state, - # country, postal, phone, parish - - if location_data == None: return - if len(location_data) == 8: - (street, locality, city, county, state, country, postal, phone) = location_data - parish = None - elif len(location_data) == 2: - ((street, locality, city, county, state, country, postal, phone), parish) = location_data - else: - print(("ERROR: unknown location: '%s'" % - str(location_data)), file=sys.stderr) - (street, locality, city, county, state, country, postal, phone, parish) = \ - ("", "", "", "", "", "", "", "", "") - location = models.Location(street = street, - locality = locality, - city = city, - county = county, - state = state, - country = country, - postal = postal, - phone = phone, - parish = parish, - order = order) - if field == "address": - location.address = obj - elif field == "place": - location.place = obj - else: - raise AttributeError("invalid field '%s' to attach to location" % - str(field)) - location.save() - #obj.locations.add(location) - #obj.save() - - def add_place(self, data): - ## ('cef246c95c132bcf6a0255d4d17', 'P0036', 'Santa Clara Co., CA, USA', '', '', [('cef243fb5634559442323368f63', None)], 'Santa Clara Co.', [], (3, ''), '', [], [], [], [], [], 1422124781, [], False) - (handle, gid, title, long, lat, - place_ref_list, - name, - alt_name_list, - place_type, - code, - alt_location_list, - url_list, - media_list, - citation_list, - note_list, - change, - tag_list, - private) = data - place = models.Place( - handle=handle, - gramps_id=gid, - title=title, - long=long, - lat=lat, - name=name, - place_type=models.get_type(models.PlaceType, place_type), - code=code, - last_changed=todate(change), - private=private) - try: - place.save(save_cache=False) - except: - print("FIXME: error in saving place") - - def add_place_detail(self, data): - (handle, gid, title, long, lat, - place_ref_list, - name, - alt_name_list, - place_type, - code, - alt_location_list, - url_list, - media_list, - citation_list, - note_list, - change, - tag_list, - private) = data - try: - place = models.Place.objects.get(handle=handle) - except: - print(("ERROR: Place does not exist: '%s'" % - str(handle)), file=sys.stderr) - return - place.save(save_cache=False) - self.add_url_list("place", place, url_list) - self.add_media_ref_list(place, media_list) - self.add_citation_list(place, citation_list) - self.add_note_list(place, note_list) - self.add_tag_list(place, tag_list) - self.add_place_ref_list(place, place_ref_list) - self.add_alt_name_list(place, alt_name_list) - count = 1 - for loc_data in alt_location_list: - self.add_location("place", place, loc_data, count) - count + 1 - place.save_cache() - - def add_tag(self, data): - (handle, - name, - color, - priority, - change) = data - tag = models.Tag(handle=handle, - gramps_id=create_id(), - name=name, - color=color, - priority=priority, - last_changed=todate(change)) - tag.save(save_cache=False) - - def add_tag_detail(self, data): - (handle, - name, - color, - priority, - change) = data - tag = models.Tag.objects.get(handle=handle) - tag.save() - - def add_media(self, data): - (handle, gid, path, mime, desc, - checksum, - attribute_list, - citation_list, - note_list, - change, - date, - tag_list, - private) = data - media = models.Media(handle=handle, gramps_id=gid, - path=path, mime=mime, checksum=checksum, - desc=desc, last_changed=todate(change), - private=private) - self.add_date(media, date) - media.save(save_cache=False) - - def add_media_detail(self, data): - (handle, gid, path, mime, desc, - checksum, - attribute_list, - citation_list, - note_list, - change, - date, - tag_list, - private) = data - try: - media = models.Media.objects.get(handle=handle) - except: - print(("ERROR: Media does not exist: '%s'" % - str(handle)), file=sys.stderr) - return - media.save(save_cache=False) - self.add_note_list(media, note_list) - self.add_citation_list(media, citation_list) - self.add_attribute_list(media, attribute_list) - self.add_tag_list(media, tag_list) - media.save_cache() - - def add_event(self, data): - (handle, gid, the_type, date, description, place_handle, - citation_list, note_list, media_list, attribute_list, - change, tag_list, private) = data - event = models.Event(handle=handle, - gramps_id=gid, - event_type=models.get_type(models.EventType, the_type), - private=private, - description=description, - last_changed=todate(change)) - self.add_date(event, date) - event.save(save_cache=False) - - def add_event_detail(self, data): - (handle, gid, the_type, date, description, place_handle, - citation_list, note_list, media_list, attribute_list, - change, tag_list, private) = data - try: - event = models.Event.objects.get(handle=handle) - except: - print(("ERROR: Event does not exist: '%s'" % - str(handle)), file=sys.stderr) - return - try: - place = models.Place.objects.get(handle=place_handle) - except: - place = None - print(("ERROR: Place does not exist: '%s'" % - str(place_handle)), file=sys.stderr) - event.place = place - event.save(save_cache=False) - self.add_note_list(event, note_list) - self.add_attribute_list(event, attribute_list) - self.add_media_ref_list(event, media_list) - self.add_citation_list(event, citation_list) - self.add_tag_list(event, tag_list) - event.save_cache() - - def get_raw(self, item): - """ - Build and return the raw, serialized data of an object. - """ - if isinstance(item, models.Person): - raw = self.get_person(item) - elif isinstance(item, models.Family): - raw = self.get_family(item) - elif isinstance(item, models.Place): - raw = self.get_place(item) - elif isinstance(item, models.Media): - raw = self.get_media(item) - elif isinstance(item, models.Source): - raw = self.get_source(item) - elif isinstance(item, models.Citation): - raw = self.get_citation(item) - elif isinstance(item, models.Repository): - raw = self.get_repository(item) - elif isinstance(item, models.Note): - raw = self.get_note(item) - elif isinstance(item, models.Event): - raw = self.get_event(item) - else: - raise Exception("Don't know how to get raw '%s'" % type(item)) - return raw - - def check_caches(self, callback=None): - """ - Call this to check the caches for all primary models. - """ - if not isinstance(callback, collections.Callable): - callback = lambda percent: None # dummy - - callback(0) - count = 0.0 - total = (self.Note.all().count() + - self.Person.all().count() + - self.Event.all().count() + - self.Family.all().count() + - self.Repository.all().count() + - self.Place.all().count() + - self.Media.all().count() + - self.Source.all().count() + - self.Citation.all().count() + - self.Tag.all().count()) - - for item in self.Note.all(): - raw = self.get_note(item) - check_diff(item, raw) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Person.all(): - raw = self.get_person(item) - check_diff(item, raw) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Family.all(): - raw = self.get_family(item) - check_diff(item, raw) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Source.all(): - raw = self.get_source(item) - check_diff(item, raw) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Event.all(): - raw = self.get_event(item) - check_diff(item, raw) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Repository.all(): - raw = self.get_repository(item) - check_diff(item, raw) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Place.all(): - raw = self.get_place(item) - check_diff(item, raw) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Media.all(): - raw = self.get_media(item) - check_diff(item, raw) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Citation.all(): - raw = self.get_citation(item) - check_diff(item, raw) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Tag.all(): - raw = self.get_tag(item) - check_diff(item, raw) - count += 1 - callback(100) - - def check_families(self): - """ - Check family structures. - """ - for family in self.Family.all(): - if family.mother: - if not family in family.mother.families.all(): - print("Mother not in family", mother, family) - if family.father: - if not family in family.father.families.all(): - print("Father not in family", mother, family) - for child in family.get_children(): - if family not in child.parent_families.all(): - print("Child not in family", child, family) - for person in self.Person.all(): - for family in person.families.all(): - if person not in [family.mother, family.father]: - print("Spouse not in family", person, family) - for family in person.parent_families.all(): - if person not in family.get_children(): - print("Child not in family", person, family) - - def is_public(self, obj, objref): - """ - Returns whether or not an item is "public", and the reason - why/why not. - - @param obj - an instance of any Primary object - @param objref - one of the PrimaryRef.objects - @return - a tuple containing a boolean (public?) and reason. - - There are three reasons why an item might not be public: - 1) The item itself is private. - 2) The item is referenced by a living Person. - 3) The item is referenced by some other private item. - """ - # If it is private, then no: - if obj.private: - return (False, "It is marked private.") - elif hasattr(obj, "probably_alive") and obj.probably_alive: - return (False, "It is marked probaby alive.") - elif hasattr(obj, "mother") and obj.mother: - public, reason = self.is_public(obj.mother, self.PersonRef) - if not public: - return public, reason - elif hasattr(obj, "father") and obj.father: - public, reason = self.is_public(obj.father, self.PersonRef) - if not public: - return public, reason - # FIXME: what about Associations... anything else? Check PrivateProxy - if objref: - if hasattr(objref.model, "ref_object"): - obj_ref_list = objref.filter(ref_object=obj) - elif hasattr(objref.model, "citation"): - obj_ref_list = objref.filter(citation=obj) - else: - raise Exception("objref '%s' needs a ref for '%s'" % (objref.model, obj)) - for reference in obj_ref_list: - ref_from_class = reference.object_type.model_class() - item = None - try: - item = ref_from_class.objects.get(id=reference.object_id) - except: - print("Warning: Corrupt reference: %s" % str(reference)) - continue - # If it is linked to by someone alive? public = False - if hasattr(item, "probably_alive") and item.probably_alive: - return (False, "It is referenced by someone who is probaby alive.") - # If it is linked to by something private? public = False - elif item.private: - return (False, "It is referenced by an item which is marked private.") - return (True, "It is visible to the public.") - - def update_public(self, obj, save=True): - """ - >>> dji.update_public(event) - - Given an Event or other instance, update the event's public - status, or any event referenced to by the instance. - - For example, if a person is found to be alive, then the - referenced events should be marked not public (public = False). - - """ - from gramps.webapp.utils import probably_alive - if obj.__class__.__name__ == "Event": - objref = self.EventRef - elif obj.__class__.__name__ == "Person": - objref = self.PersonRef - elif obj.__class__.__name__ == "Note": - objref = self.NoteRef - elif obj.__class__.__name__ == "Repository": - objref = self.RepositoryRef - elif obj.__class__.__name__ == "Citation": - objref = self.CitationRef - elif obj.__class__.__name__ == "Media": - objref = self.MediaRef - elif obj.__class__.__name__ == "Place": # no need for dependency - objref = None - elif obj.__class__.__name__ == "Source": # no need for dependency - objref = None - elif obj.__class__.__name__ == "Family": - objref = self.ChildRef # correct? - else: - raise Exception("Can't compute public of type '%s'" % str(obj)) - public, reason = self.is_public(obj, objref) # correct? - # Ok, update, if needed: - if obj.public != public: - obj.public = public - if save: - print("Updating public:", obj.__class__.__name__, obj.gramps_id) - obj.save() - #log = self.Log() - #log.referenced_by = obj - #log.object_id = obj.id - #log.object_type = obj_type - #log.log_type = "update public status" - #log.reason = reason - #log.order = 0 - #log.save() - - def update_publics(self, callback=None): - """ - Call this to update probably_alive for all primary models. - """ - if not isinstance(callback, collections.Callable): - callback = lambda percent: None # dummy - - callback(0) - count = 0.0 - total = (self.Note.all().count() + - self.Person.all().count() + - self.Event.all().count() + - self.Family.all().count() + - self.Repository.all().count() + - self.Place.all().count() + - self.Media.all().count() + - self.Source.all().count() + - self.Citation.all().count()) - - for item in self.Note.all(): - self.update_public(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Person.all(): - self.update_public(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Family.all(): - self.update_public(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Source.all(): - self.update_public(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Event.all(): - self.update_public(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Repository.all(): - self.update_public(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Place.all(): - self.update_public(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Media.all(): - self.update_public(item) - count += 1 - callback(100 * (count/total if total else 0)) - - for item in self.Citation.all(): - self.update_public(item) - count += 1 - callback(100 * (count/total if total else 0)) - - def update_probably_alive(self, callback=None): - """ - Call this to update primary_alive for people. - """ - from gramps.webapp.utils import probably_alive - if not isinstance(callback, collections.Callable): - callback = lambda percent: None # dummy - callback(0) - count = 0.0 - total = self.Person.all().count() - for item in self.Person.all(): - pa = probably_alive(item.handle) - if pa != item.probably_alive: - print("Updating probably_alive") - item.probably_alive = pa - item.save() - count += 1 - callback(100 * (count/total if total else 0)) diff --git a/gramps/plugins/database/djangodb.gpr.py b/gramps/plugins/database/djangodb.gpr.py deleted file mode 100644 index 2d0d0797b..000000000 --- a/gramps/plugins/database/djangodb.gpr.py +++ /dev/null @@ -1,32 +0,0 @@ -# -# Gramps - a GTK+/GNOME based genealogy program -# -# Copyright (C) 2015 Douglas Blank <doug.blank@gmail.com> -# -# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# - -plg = newplugin() -plg.id = 'djangodb' -plg.name = _("Django Database Backend") -plg.name_accell = _("_Django Database Backend") -plg.description = _("Django Object Relational Model Database Backend") -plg.version = '1.0' -plg.gramps_target_version = "4.2" -plg.status = STABLE -plg.fname = 'djangodb.py' -plg.ptype = DATABASE -plg.databaseclass = 'DbDjango' -plg.reset_system = True diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py deleted file mode 100644 index a7f64f13e..000000000 --- a/gramps/plugins/database/djangodb.py +++ /dev/null @@ -1,2150 +0,0 @@ -# Gramps - a GTK+/GNOME based genealogy program -# -# Copyright (C) 2009 Douglas S. Blank <doug.blank@gmail.com> -# -# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# - -""" Implements a Db interface """ - -#------------------------------------------------------------------------ -# -# Python Modules -# -#------------------------------------------------------------------------ -import sys -import time -import re -import base64 -import pickle -import os -import logging -import shutil -from django.db import transaction - -#------------------------------------------------------------------------ -# -# Gramps Modules -# -#------------------------------------------------------------------------ -import gramps -from gramps.gen.const import GRAMPS_LOCALE as glocale -_ = glocale.translation.gettext -from gramps.gen.lib import (Person, Family, Event, Place, Repository, - Citation, Source, Note, MediaObject, Tag, - Researcher, GenderStats) -from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn -from gramps.gen.db.undoredo import DbUndo -from gramps.gen.utils.callback import Callback -from gramps.gen.updatecallback import UpdateCallback -from gramps.gen.db import (PERSON_KEY, - FAMILY_KEY, - CITATION_KEY, - SOURCE_KEY, - EVENT_KEY, - MEDIA_KEY, - PLACE_KEY, - REPOSITORY_KEY, - NOTE_KEY) -from gramps.gen.utils.id import create_id -from gramps.gen.db.dbconst import * - -## add this directory to sys path, so we can find django_support later: -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -_LOG = logging.getLogger(DBLOGNAME) - -def touch(fname, mode=0o666, dir_fd=None, **kwargs): - ## After http://stackoverflow.com/questions/1158076/implement-touch-using-python - flags = os.O_CREAT | os.O_APPEND - with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f: - os.utime(f.fileno() if os.utime in os.supports_fd else fname, - dir_fd=None if os.supports_fd else dir_fd, **kwargs) - -class Environment(object): - """ - Implements the Environment API. - """ - def __init__(self, db): - self.db = db - - def txn_begin(self): - return DjangoTxn("DjangoDb Transaction", self.db) - -class Table(object): - """ - Implements Table interface. - """ - def __init__(self, funcs): - self.funcs = funcs - - def cursor(self): - """ - Returns a Cursor for this Table. - """ - return self.funcs["cursor_func"]() - - def put(self, key, data, txn=None): - ## FIXME: probably needs implementing? - #self[key] = data - pass - -class Map(dict): - """ - Implements the map API for person_map, etc. - - Takes a Table() as argument. - """ - def __init__(self, tbl, *args, **kwargs): - super().__init__(*args, **kwargs) - self.db = tbl - -class MetaCursor(object): - def __init__(self): - pass - def __enter__(self): - return self - def __iter__(self): - return self.__next__() - def __next__(self): - yield None - def __exit__(self, *args, **kwargs): - pass - def iter(self): - yield None - def first(self): - self._iter = self.__iter__() - return self.next() - def next(self): - try: - return next(self._iter) - except: - return None - def close(self): - pass - -class Cursor(object): - def __init__(self, model, func): - self.model = model - self.func = func - self._iter = self.__iter__() - def __enter__(self): - return self - def __iter__(self): - for item in self.model.all(): - yield (bytes(item.handle, "utf-8"), self.func(item.handle)) - def __next__(self): - try: - return self._iter.__next__() - except StopIteration: - return None - def __exit__(self, *args, **kwargs): - pass - def iter(self): - for item in self.model.all(): - yield (bytes(item.handle, "utf-8"), self.func(item.handle)) - yield None - def first(self): - self._iter = self.__iter__() - try: - return next(self._iter) - except: - return - def next(self): - try: - return next(self._iter) - except: - return None - def close(self): - pass - -class Bookmarks(object): - def __init__(self): - self.handles = [] - def get(self): - return self.handles - def append(self, handle): - self.handles.append(handle) - -class DjangoTxn(DbTxn): - def __init__(self, message, db, table=None): - DbTxn.__init__(self, message, db) - self.table = table - - def get(self, key, default=None, txn=None, **kwargs): - """ - Returns the data object associated with key - """ - try: - return self.table.objects(handle=key) - except: - if txn and key in txn: - return txn[key] - else: - return None - - def put(self, handle, new_data, txn): - """ - """ - ## FIXME: probably not correct? - txn[handle] = new_data - - def commit(self): - pass - -class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): - """ - A Gramps Database Backend. This replicates the grampsdb functions. - """ - # Set up dictionary for callback signal handler - # --------------------------------------------- - # 1. Signals for primary objects - __signals__ = dict((obj+'-'+op, signal) - for obj in - ['person', 'family', 'event', 'place', - 'source', 'citation', 'media', 'note', 'repository', 'tag'] - for op, signal in zip( - ['add', 'update', 'delete', 'rebuild'], - [(list,), (list,), (list,), None] - ) - ) - - # 2. Signals for long operations - __signals__.update(('long-op-'+op, signal) for op, signal in zip( - ['start', 'heartbeat', 'end'], - [(object,), None, None] - )) - - # 3. Special signal for change in home person - __signals__['home-person-changed'] = None - - # 4. Signal for change in person group name, parameters are - __signals__['person-groupname-rebuild'] = (str, str) - - __callback_map = {} - - def __init__(self, directory=None): - DbReadBase.__init__(self) - DbWriteBase.__init__(self) - Callback.__init__(self) - self._tables = { - 'Person': - { - "handle_func": self.get_person_from_handle, - "gramps_id_func": self.get_person_from_gramps_id, - "class_func": gramps.gen.lib.Person, - "cursor_func": self.get_person_cursor, - "handles_func": self.get_person_handles, - "iter_func": self.iter_people, - }, - 'Family': - { - "handle_func": self.get_family_from_handle, - "gramps_id_func": self.get_family_from_gramps_id, - "class_func": gramps.gen.lib.Family, - "cursor_func": self.get_family_cursor, - "handles_func": self.get_family_handles, - "iter_func": self.iter_families, - }, - 'Source': - { - "handle_func": self.get_source_from_handle, - "gramps_id_func": self.get_source_from_gramps_id, - "class_func": gramps.gen.lib.Source, - "cursor_func": self.get_source_cursor, - "handles_func": self.get_source_handles, - "iter_func": self.iter_sources, - }, - 'Citation': - { - "handle_func": self.get_citation_from_handle, - "gramps_id_func": self.get_citation_from_gramps_id, - "class_func": gramps.gen.lib.Citation, - "cursor_func": self.get_citation_cursor, - "handles_func": self.get_citation_handles, - "iter_func": self.iter_citations, - }, - 'Event': - { - "handle_func": self.get_event_from_handle, - "gramps_id_func": self.get_event_from_gramps_id, - "class_func": gramps.gen.lib.Event, - "cursor_func": self.get_event_cursor, - "handles_func": self.get_event_handles, - "iter_func": self.iter_events, - }, - 'Media': - { - "handle_func": self.get_object_from_handle, - "gramps_id_func": self.get_object_from_gramps_id, - "class_func": gramps.gen.lib.MediaObject, - "cursor_func": self.get_media_cursor, - "handles_func": self.get_media_object_handles, - "iter_func": self.iter_media_objects, - }, - 'Place': - { - "handle_func": self.get_place_from_handle, - "gramps_id_func": self.get_place_from_gramps_id, - "class_func": gramps.gen.lib.Place, - "cursor_func": self.get_place_cursor, - "handles_func": self.get_place_handles, - "iter_func": self.iter_places, - }, - 'Repository': - { - "handle_func": self.get_repository_from_handle, - "gramps_id_func": self.get_repository_from_gramps_id, - "class_func": gramps.gen.lib.Repository, - "cursor_func": self.get_repository_cursor, - "handles_func": self.get_repository_handles, - "iter_func": self.iter_repositories, - }, - 'Note': - { - "handle_func": self.get_note_from_handle, - "gramps_id_func": self.get_note_from_gramps_id, - "class_func": gramps.gen.lib.Note, - "cursor_func": self.get_note_cursor, - "handles_func": self.get_note_handles, - "iter_func": self.iter_notes, - }, - 'Tag': - { - "handle_func": self.get_tag_from_handle, - "gramps_id_func": None, - "class_func": gramps.gen.lib.Tag, - "cursor_func": self.get_tag_cursor, - "handles_func": self.get_tag_handles, - "iter_func": self.iter_tags, - }, - } - # 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.undo_callback = None - self.redo_callback = None - self.undo_history_callback = None - self.modified = 0 - self.txn = DjangoTxn("DbDjango Transaction", self) - self.transaction = None - # Import cache for gedcom import, uses transactions, and - # two step adding of objects. - self.import_cache = {} - self.use_import_cache = False - self.use_db_cache = True - self.undodb = DbUndo(self) - self.abort_possible = False - self._bm_changes = 0 - self._directory = directory - self.full_name = None - self.path = None - self.brief_name = None - self.genderStats = GenderStats() # can pass in loaded stats as dict - self.owner = Researcher() - if directory: - self.load(directory) - - def load(self, directory, callback=None, mode=None, - force_schema_upgrade=False, - force_bsddb_upgrade=False, - force_bsddb_downgrade=False, - force_python_upgrade=False): - _LOG.info("Django loading...") - self._directory = directory - self.full_name = os.path.abspath(self._directory) - self.path = self.full_name - self.brief_name = os.path.basename(self._directory) - from django.conf import settings - default_settings = {"__file__": - os.path.join(directory, "default_settings.py")} - settings_file = os.path.join(directory, "default_settings.py") - with open(settings_file) as f: - code = compile(f.read(), settings_file, 'exec') - exec(code, globals(), default_settings) - - class Module(object): - def __init__(self, dictionary): - self.dictionary = dictionary - def __getattr__(self, item): - return self.dictionary[item] - - _LOG.info("Django loading defaults from: " + directory) - try: - settings.configure(Module(default_settings)) - except RuntimeError: - _LOG.info("Django already configured error! Shouldn't happen!") - # already configured; ignore - pass - - import django - django.setup() - - from django_support.libdjango import DjangoInterface - self.dji = DjangoInterface() - 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 = DjangoTxn("ID Transaction", self, self.dji.Person) - self.fid_trans = DjangoTxn("FID Transaction", self, self.dji.Family) - self.pid_trans = DjangoTxn("PID Transaction", self, self.dji.Place) - self.cid_trans = DjangoTxn("CID Transaction", self, self.dji.Citation) - self.sid_trans = DjangoTxn("SID Transaction", self, self.dji.Source) - self.oid_trans = DjangoTxn("OID Transaction", self, self.dji.Media) - self.rid_trans = DjangoTxn("RID Transaction", self, self.dji.Repository) - self.nid_trans = DjangoTxn("NID Transaction", self, self.dji.Note) - self.eid_trans = DjangoTxn("EID Transaction", self, self.dji.Event) - 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 = Environment(self) - self.person_map = Map(Table(self._tables["Person"])) - self.family_map = Map(Table(self._tables["Family"])) - self.place_map = Map(Table(self._tables["Place"])) - self.citation_map = Map(Table(self._tables["Citation"])) - self.source_map = Map(Table(self._tables["Source"])) - self.repository_map = Map(Table(self._tables["Repository"])) - self.note_map = Map(Table(self._tables["Note"])) - self.media_map = Map(Table(self._tables["Media"])) - self.event_map = Map(Table(self._tables["Event"])) - self.tag_map = Map(Table(self._tables["Tag"])) - self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) - self.name_group = {} - self.event_names = set() - self.individual_attributes = set() - self.family_attributes = set() - self.source_attributes = set() - self.child_ref_types = set() - self.family_rel_types = set() - self.event_role_names = set() - self.name_types = set() - self.origin_types = set() - self.repository_types = set() - self.note_types = set() - self.source_media_types = set() - self.url_types = set() - self.media_attributes = set() - self.place_types = set() - _LOG.info("Django loading... done!") - - def prepare_import(self): - """ - DbDjango does not commit data on gedcom import, but saves them - for later commit. - """ - self.use_import_cache = True - self.import_cache = {} - - @transaction.atomic - def commit_import(self): - """ - Commits the items that were queued up during the last gedcom - import for two step adding. - """ - # First we add the primary objects: - for key in list(self.import_cache.keys()): - obj = self.import_cache[key] - if isinstance(obj, Person): - self.dji.add_person(obj.serialize()) - elif isinstance(obj, Family): - self.dji.add_family(obj.serialize()) - elif isinstance(obj, Event): - self.dji.add_event(obj.serialize()) - elif isinstance(obj, Place): - self.dji.add_place(obj.serialize()) - elif isinstance(obj, Repository): - self.dji.add_repository(obj.serialize()) - elif isinstance(obj, Citation): - self.dji.add_citation(obj.serialize()) - elif isinstance(obj, Source): - self.dji.add_source(obj.serialize()) - elif isinstance(obj, Note): - self.dji.add_note(obj.serialize()) - elif isinstance(obj, MediaObject): - self.dji.add_media(obj.serialize()) - elif isinstance(obj, Tag): - self.dji.add_tag(obj.serialize()) - # Next we add the links: - for key in list(self.import_cache.keys()): - obj = self.import_cache[key] - if isinstance(obj, Person): - self.dji.add_person_detail(obj.serialize()) - elif isinstance(obj, Family): - self.dji.add_family_detail(obj.serialize()) - elif isinstance(obj, Event): - self.dji.add_event_detail(obj.serialize()) - elif isinstance(obj, Place): - self.dji.add_place_detail(obj.serialize()) - elif isinstance(obj, Repository): - self.dji.add_repository_detail(obj.serialize()) - elif isinstance(obj, Citation): - self.dji.add_citation_detail(obj.serialize()) - elif isinstance(obj, Source): - self.dji.add_source_detail(obj.serialize()) - elif isinstance(obj, Note): - self.dji.add_note_detail(obj.serialize()) - elif isinstance(obj, MediaObject): - self.dji.add_media_detail(obj.serialize()) - elif isinstance(obj, Tag): - self.dji.add_tag_detail(obj.serialize()) - self.use_import_cache = False - self.import_cache = {} - self.request_rebuild() - - def transaction_commit(self, txn): - pass - - def request_rebuild(self): - # caches are ok, but let's compute public's - self.dji.update_publics() - self.emit('person-rebuild') - self.emit('family-rebuild') - self.emit('place-rebuild') - self.emit('source-rebuild') - self.emit('citation-rebuild') - self.emit('media-rebuild') - self.emit('event-rebuild') - self.emit('repository-rebuild') - self.emit('note-rebuild') - self.emit('tag-rebuild') - - def get_undodb(self): - return None - - def transaction_abort(self, txn): - pass - - @staticmethod - def _validated_id_prefix(val, default): - if isinstance(val, str) 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_<object>_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): - return self.owner - - def get_tag_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Tag.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Tag.all()] - - def get_person_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Person.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Person.all()] - - def get_family_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Family.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Family.all()] - - def get_event_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Event.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Event.all()] - - def get_citation_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Citation.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Citation.all()] - - def get_source_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Source.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Source.all()] - - def get_place_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Place.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Place.all()] - - def get_repository_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Repository.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Repository.all()] - - def get_media_object_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Media.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Media.all()] - - def get_note_handles(self, sort_handles=False): - if sort_handles: - return [item.handle for item in self.dji.Note.all().order_by("handle")] - else: - return [item.handle for item in self.dji.Note.all()] - - def get_media_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - media = self.dji.Media.get(handle=handle) - except: - return None - return self.make_media(media) - - def get_event_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - event = self.dji.Event.get(handle=handle) - except: - return None - return self.make_event(event) - - def get_family_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - family = self.dji.Family.get(handle=handle) - except: - return None - return self.make_family(family) - - def get_repository_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - repository = self.dji.Repository.get(handle=handle) - except: - return None - return self.make_repository(repository) - - def get_person_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - person = self.dji.Person.get(handle=handle) - except: - return None - return self.make_person(person) - - def get_tag_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - tag = self.dji.Tag.get(handle=handle) - except: - return None - return self.make_tag(tag) - - def make_repository(self, repository): - if self.use_db_cache and repository.cache: - data = repository.from_cache() - else: - data = self.dji.get_repository(repository) - return Repository.create(data) - - def make_citation(self, citation): - if self.use_db_cache and citation.cache: - data = citation.from_cache() - else: - data = self.dji.get_citation(citation) - return Citation.create(data) - - def make_source(self, source): - if self.use_db_cache and source.cache: - data = source.from_cache() - else: - data = self.dji.get_source(source) - return Source.create(data) - - def make_family(self, family): - if self.use_db_cache and family.cache: - data = family.from_cache() - else: - data = self.dji.get_family(family) - return Family.create(data) - - def make_person(self, person): - if self.use_db_cache and person.cache: - data = person.from_cache() - else: - data = self.dji.get_person(person) - return Person.create(data) - - def make_event(self, event): - if self.use_db_cache and event.cache: - data = event.from_cache() - else: - data = self.dji.get_event(event) - return Event.create(data) - - def make_note(self, note): - if self.use_db_cache and note.cache: - data = note.from_cache() - else: - data = self.dji.get_note(note) - return Note.create(data) - - def make_tag(self, tag): - data = self.dji.get_tag(tag) - return Tag.create(data) - - def make_place(self, place): - if self.use_db_cache and place.cache: - data = place.from_cache() - else: - data = self.dji.get_place(place) - return Place.create(data) - - def make_media(self, media): - if self.use_db_cache and media.cache: - data = media.from_cache() - else: - data = self.dji.get_media(media) - return MediaObject.create(data) - - def get_place_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - place = self.dji.Place.get(handle=handle) - except: - return None - return self.make_place(place) - - def get_citation_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - citation = self.dji.Citation.get(handle=handle) - except: - return None - return self.make_citation(citation) - - def get_source_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - source = self.dji.Source.get(handle=handle) - except: - return None - return self.make_source(source) - - def get_note_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - note = self.dji.Note.get(handle=handle) - except: - return None - return self.make_note(note) - - def get_object_from_handle(self, handle): - if handle in self.import_cache: - return self.import_cache[handle] - try: - media = self.dji.Media.get(handle=handle) - except: - return None - return self.make_media(media) - - def get_default_person(self): - people = self.dji.Person.all() - if people.count() > 0: - return self.make_person(people[0]) - return None - - def iter_people(self): - return (self.get_person_from_handle(person.handle) - for person in self.dji.Person.all()) - - def iter_person_handles(self): - return (person.handle for person in self.dji.Person.all()) - - def iter_families(self): - return (self.get_family_from_handle(family.handle) - for family in self.dji.Family.all()) - - def iter_family_handles(self): - return (family.handle for family in self.dji.Family.all()) - - def iter_notes(self): - return (self.get_note_from_handle(note.handle) - for note in self.dji.Note.all()) - - def iter_note_handles(self): - return (note.handle for note in self.dji.Note.all()) - - def iter_events(self): - return (self.get_event_from_handle(event.handle) - for event in self.dji.Event.all()) - - def iter_event_handles(self): - return (event.handle for event in self.dji.Event.all()) - - def iter_places(self): - return (self.get_place_from_handle(place.handle) - for place in self.dji.Place.all()) - - def iter_place_handles(self): - return (place.handle for place in self.dji.Place.all()) - - def iter_repositories(self): - return (self.get_repository_from_handle(repository.handle) - for repository in self.dji.Repository.all()) - - def iter_repository_handles(self): - return (repository.handle for repository in self.dji.Repository.all()) - - def iter_sources(self): - return (self.get_source_from_handle(source.handle) - for source in self.dji.Source.all()) - - def iter_source_handles(self): - return (source.handle for source in self.dji.Source.all()) - - def iter_citations(self): - return (self.get_citation_from_handle(citation.handle) - for citation in self.dji.Citation.all()) - - def iter_citation_handles(self): - return (citation.handle for citation in self.dji.Citation.all()) - - def iter_tags(self): - return (self.get_tag_from_handle(tag.handle) - for tag in self.dji.Tag.all()) - - def iter_tag_handles(self): - return (tag.handle for tag in self.dji.Tag.all()) - - def iter_media_objects(self): - return (self.get_media_from_handle(media.handle) - for media in self.dji.Media.all()) - - def get_tag_from_name(self, name): - try: - tag = self.dji.Tag.filter(name=name) - return self.make_tag(tag[0]) - except: - return None - - def get_person_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Person.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_person(match_list[0]) - else: - return None - - def get_family_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - try: - family = self.dji.Family.get(gramps_id=gramps_id) - except: - return None - return self.make_family(family) - - def get_source_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Source.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_source(match_list[0]) - else: - return None - - def get_citation_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Citation.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_citation(match_list[0]) - else: - return None - - def get_event_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Event.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_event(match_list[0]) - else: - return None - - def get_object_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Media.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_media(match_list[0]) - else: - return None - - def get_place_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Place.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_place(match_list[0]) - else: - return None - - def get_repository_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Repsoitory.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_repository(match_list[0]) - else: - return None - - def get_note_from_gramps_id(self, gramps_id): - if self.import_cache: - for handle in self.import_cache: - if self.import_cache[handle].gramps_id == gramps_id: - return self.import_cache[handle] - match_list = self.dji.Note.filter(gramps_id=gramps_id) - if match_list.count() > 0: - return self.make_note(match_list[0]) - else: - return None - - def get_number_of_people(self): - return self.dji.Person.count() - - def get_number_of_events(self): - return self.dji.Event.count() - - def get_number_of_places(self): - return self.dji.Place.count() - - def get_number_of_tags(self): - return self.dji.Tag.count() - - def get_number_of_families(self): - return self.dji.Family.count() - - def get_number_of_notes(self): - return self.dji.Note.count() - - def get_number_of_citations(self): - return self.dji.Citation.count() - - def get_number_of_sources(self): - return self.dji.Source.count() - - def get_number_of_media_objects(self): - return self.dji.Media.count() - - def get_number_of_repositories(self): - return self.dji.Repository.count() - - def get_place_cursor(self): - return Cursor(self.dji.Place, self.get_raw_place_data) - - def get_person_cursor(self): - return Cursor(self.dji.Person, self.get_raw_person_data) - - def get_family_cursor(self): - return Cursor(self.dji.Family, self.get_raw_family_data) - - def get_event_cursor(self): - return Cursor(self.dji.Event, self.get_raw_event_data) - - def get_citation_cursor(self): - return Cursor(self.dji.Citation, self.get_raw_citation_data) - - def get_source_cursor(self): - return Cursor(self.dji.Source, self.get_raw_source_data) - - def get_note_cursor(self): - return Cursor(self.dji.Note, self.get_raw_note_data) - - def get_tag_cursor(self): - return Cursor(self.dji.Tag, self.get_raw_tag_data) - - def get_repository_cursor(self): - return Cursor(self.dji.Repository, self.get_raw_repository_data) - - def get_media_cursor(self): - return Cursor(self.dji.Media, self.get_raw_object_data) - - def has_gramps_id(self, obj_key, gramps_id): - key2table = { - PERSON_KEY: self.dji.Person, - FAMILY_KEY: self.dji.Family, - SOURCE_KEY: self.dji.Source, - CITATION_KEY: self.dji.Citation, - EVENT_KEY: self.dji.Event, - MEDIA_KEY: self.dji.Media, - PLACE_KEY: self.dji.Place, - REPOSITORY_KEY: self.dji.Repository, - NOTE_KEY: self.dji.Note, - } - table = key2table[obj_key] - return table.filter(gramps_id=gramps_id).count() > 0 - - def has_person_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Person.filter(handle=handle).count() == 1 - - def has_family_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Family.filter(handle=handle).count() == 1 - - def has_citation_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Citation.filter(handle=handle).count() == 1 - - def has_source_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Source.filter(handle=handle).count() == 1 - - def has_repository_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Repository.filter(handle=handle).count() == 1 - - def has_note_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Note.filter(handle=handle).count() == 1 - - def has_place_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Place.filter(handle=handle).count() == 1 - - def has_event_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Event.filter(handle=handle).count() == 1 - - def has_tag_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Tag.filter(handle=handle).count() == 1 - - def has_object_handle(self, handle): - if handle in self.import_cache: - return True - return self.dji.Media.filter(handle=handle).count() == 1 - - 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): - try: - return self.dji.get_person(self.dji.Person.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_family_data(self, handle): - try: - return self.dji.get_family(self.dji.Family.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_citation_data(self, handle): - try: - return self.dji.get_citation(self.dji.Citation.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_source_data(self, handle): - try: - return self.dji.get_source(self.dji.Source.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_repository_data(self, handle): - try: - return self.dji.get_repository(self.dji.Repository.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_note_data(self, handle): - try: - return self.dji.get_note(self.dji.Note.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_place_data(self, handle): - try: - return self.dji.get_place(self.dji.Place.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_object_data(self, handle): - try: - return self.dji.get_media(self.dji.Media.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_tag_data(self, handle): - try: - return self.dji.get_tag(self.dji.Tag.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def get_raw_event_data(self, handle): - try: - return self.dji.get_event(self.dji.Event.get(handle=handle)) - except: - if handle in self.import_cache: - return self.import_cache[handle].serialize() - else: - return None - - def add_person(self, person, trans, set_gid=True): - if not person.handle: - person.handle = create_id() - if not person.gramps_id: - 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 = create_id() - if not family.gramps_id: - 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 = create_id() - if not citation.gramps_id: - 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 = create_id() - if not source.gramps_id: - 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 = create_id() - if not repository.gramps_id: - 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 = create_id() - if not note.gramps_id: - 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 = create_id() - if not place.gramps_id: - 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 = create_id() - if not event.gramps_id: - 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 = create_id() - self.commit_tag(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 = create_id() - if not obj.gramps_id: - 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): - if self.use_import_cache: - self.import_cache[person.handle] = person - else: - raw = person.serialize() - items = self.dji.Person.filter(handle=person.handle) - count = items.count() - if count > 0: - # Hack, for the moment: delete and re-add - items[0].delete() - self.dji.add_person(person.serialize()) - self.dji.add_person_detail(person.serialize()) - if not trans.batch: - if count > 0: - self.emit("person-update", ([person.handle],)) - else: - self.emit("person-add", ([person.handle],)) - - def commit_family(self, family, trans, change_time=None): - if self.use_import_cache: - self.import_cache[family.handle] = family - else: - raw = family.serialize() - items = self.dji.Family.filter(handle=family.handle) - count = items.count() - if count > 0: - items[0].delete() - self.dji.add_family(family.serialize()) - self.dji.add_family_detail(family.serialize()) - if not trans.batch: - if count > 0: - self.emit("family-update", ([family.handle],)) - else: - self.emit("family-add", ([family.handle],)) - - def commit_citation(self, citation, trans, change_time=None): - if self.use_import_cache: - self.import_cache[citation.handle] = citation - else: - raw = citation.serialize() - items = self.dji.Citation.filter(handle=citation.handle) - count = items.count() - if count > 0: - items[0].delete() - self.dji.add_citation(citation.serialize()) - self.dji.add_citation_detail(citation.serialize()) - if not trans.batch: - if count > 0: - self.emit("citation-update", ([citation.handle],)) - else: - self.emit("citation-add", ([citation.handle],)) - - def commit_source(self, source, trans, change_time=None): - if self.use_import_cache: - self.import_cache[source.handle] = source - else: - raw = source.serialize() - items = self.dji.Source.filter(handle=source.handle) - count = items.count() - if count > 0: - items[0].delete() - self.dji.add_source(source.serialize()) - self.dji.add_source_detail(source.serialize()) - if not trans.batch: - if count > 0: - self.emit("source-update", ([source.handle],)) - else: - self.emit("source-add", ([source.handle],)) - - def commit_repository(self, repository, trans, change_time=None): - if self.use_import_cache: - self.import_cache[repository.handle] = repository - else: - raw = repository.serialize() - items = self.dji.Repository.filter(handle=repository.handle) - count = items.count() - if count > 0: - items[0].delete() - self.dji.add_repository(repository.serialize()) - self.dji.add_repository_detail(repository.serialize()) - if not trans.batch: - if count > 0: - self.emit("repository-update", ([repository.handle],)) - else: - self.emit("repository-add", ([repository.handle],)) - - def commit_note(self, note, trans, change_time=None): - if self.use_import_cache: - self.import_cache[note.handle] = note - else: - raw = note.serialize() - items = self.dji.Note.filter(handle=note.handle) - count = items.count() - if count > 0: - items[0].delete() - self.dji.add_note(note.serialize()) - self.dji.add_note_detail(note.serialize()) - if not trans.batch: - if count > 0: - self.emit("note-update", ([note.handle],)) - else: - self.emit("note-add", ([note.handle],)) - - def commit_place(self, place, trans, change_time=None): - if self.use_import_cache: - self.import_cache[place.handle] = place - else: - raw = place.serialize() - items = self.dji.Place.filter(handle=place.handle) - count = items.count() - if count > 0: - items[0].delete() - self.dji.add_place(place.serialize()) - self.dji.add_place_detail(place.serialize()) - if not trans.batch: - if count > 0: - self.emit("place-update", ([place.handle],)) - else: - self.emit("place-add", ([place.handle],)) - - def commit_event(self, event, trans, change_time=None): - if self.use_import_cache: - self.import_cache[event.handle] = event - else: - raw = event.serialize() - items = self.dji.Event.filter(handle=event.handle) - count = items.count() - if count > 0: - items[0].delete() - self.dji.add_event(event.serialize()) - self.dji.add_event_detail(event.serialize()) - if not trans.batch: - if count > 0: - self.emit("event-update", ([event.handle],)) - else: - self.emit("event-add", ([event.handle],)) - - def commit_tag(self, tag, trans, change_time=None): - if self.use_import_cache: - self.import_cache[tag.handle] = tag - else: - raw = tag.serialize() - items = self.dji.Tag.filter(handle=tag.handle) - count = items.count() - if count > 0: - items[0].delete() - self.dji.add_tag(tag.serialize()) - self.dji.add_tag_detail(tag.serialize()) - if not trans.batch: - if count > 0: - self.emit("tag-update", ([tag.handle],)) - else: - self.emit("tag-add", ([tag.handle],)) - - def commit_media_object(self, media, trans, change_time=None): - """ - Commit the specified MediaObject to the database, storing the changes - as part of the transaction. - """ - if self.use_import_cache: - self.import_cache[media.handle] = media - else: - raw = media.serialize() - items = self.dji.Media.filter(handle=media.handle) - count = items.count() - if count > 0: - items[0].delete() - self.dji.add_media(media.serialize()) - self.dji.add_media_detail(media.serialize()) - if not trans.batch: - if count > 0: - self.emit("media-update", ([media.handle],)) - else: - self.emit("media-add", ([media.handle],)) - - def get_gramps_ids(self, obj_key): - key2table = { - PERSON_KEY: self.id_trans, - FAMILY_KEY: self.fid_trans, - CITATION_KEY: self.cid_trans, - SOURCE_KEY: self.sid_trans, - EVENT_KEY: self.eid_trans, - MEDIA_KEY: self.oid_trans, - PLACE_KEY: self.pid_trans, - REPOSITORY_KEY: self.rid_trans, - NOTE_KEY: self.nid_trans, - } - - table = key2table[obj_key] - return list(table.keys()) - - def transaction_begin(self, transaction): - return - - def set_researcher(self, owner): - self.owner.set_from(owner) - - def copy_from_db(self, db): - """ - A (possibily) implementation-specific method to get data from - db into this database. - """ - # First we add the primary objects: - for key in db._tables.keys(): - cursor = db._tables[key]["cursor_func"] - for (handle, data) in cursor(): - if key == "Person": - self.dji.add_person(data) - elif key == "Family": - self.dji.add_family(data) - elif key == "Event": - self.dji.add_event(data) - elif key == "Place": - self.dji.add_place(data) - elif key == "Repository": - self.dji.add_repository(data) - elif key == "Citation": - self.dji.add_citation(data) - elif key == "Source": - self.dji.add_source(data) - elif key == "Note": - self.dji.add_note(data) - elif key == "Media": - self.dji.add_media(data) - elif key == "Tag": - self.dji.add_tag(data) - for key in db._tables.keys(): - cursor = db._tables[key]["cursor_func"] - for (handle, data) in cursor(): - if key == "Person": - self.dji.add_person_detail(data) - elif key == "Family": - self.dji.add_family_detail(data) - elif key == "Event": - self.dji.add_event_detail(data) - elif key == "Place": - self.dji.add_place_detail(data) - elif key == "Repository": - self.dji.add_repository_detail(data) - elif key == "Citation": - self.dji.add_citation_detail(data) - elif key == "Source": - self.dji.add_source_detail(data) - elif key == "Note": - self.dji.add_note_detail(data) - elif key == "Media": - self.dji.add_media_detail(data) - elif key == "Tag": - self.dji.add_tag_detail(data) - # Next we add the links: - self.dji.update_publics() - - def get_from_name_and_handle(self, table_name, handle): - """ - Returns a gen.lib object (or None) given table_name and - handle. - - Examples: - - >>> self.get_from_name_and_handle("Person", "a7ad62365bc652387008") - >>> self.get_from_name_and_handle("Media", "c3434653675bcd736f23") - """ - if table_name in self._tables: - return self._tables[table_name]["handle_func"](handle) - return None - - def is_empty(self): - """ - Is the database empty? - """ - return (self.get_number_of_people() == 0 and - self.get_number_of_events() == 0 and - self.get_number_of_places() == 0 and - self.get_number_of_tags() == 0 and - self.get_number_of_families() == 0 and - self.get_number_of_notes() == 0 and - self.get_number_of_citations() == 0 and - self.get_number_of_sources() == 0 and - self.get_number_of_media_objects() == 0 and - self.get_number_of_repositories() == 0) - - def set_prefixes(self, person, media, family, source, citation, - place, event, repository, note): - self.set_person_id_prefix(person) - self.set_object_id_prefix(media) - self.set_family_id_prefix(family) - self.set_source_id_prefix(source) - self.set_citation_id_prefix(citation) - self.set_place_id_prefix(place) - self.set_event_id_prefix(event) - self.set_repository_id_prefix(repository) - self.set_note_id_prefix(note) - - def has_changed(self): - return False - - def find_backlink_handles(self, handle, include_classes=None): - ## FIXME: figure out how to get objects that refer - ## to this handle - return [] - - def get_note_bookmarks(self): - return self.note_bookmarks - - def get_media_bookmarks(self): - return self.media_bookmarks - - def get_repo_bookmarks(self): - return self.repo_bookmarks - - def get_citation_bookmarks(self): - return self.citation_bookmarks - - def get_source_bookmarks(self): - return self.source_bookmarks - - def get_place_bookmarks(self): - return self.place_bookmarks - - def get_event_bookmarks(self): - return self.event_bookmarks - - def get_bookmarks(self): - return self.bookmarks - - def get_family_bookmarks(self): - return self.family_bookmarks - - def get_save_path(self): - return self._directory - - def set_save_path(self, directory): - self._directory = directory - self.full_name = os.path.abspath(self._directory) - self.path = self.full_name - self.brief_name = os.path.basename(self._directory) - - ## Get types: - def get_event_attribute_types(self): - """ - Return a list of all Attribute types assocated with Event instances - in the database. - """ - return list(self.event_attributes) - - def get_event_types(self): - """ - Return a list of all event types in the database. - """ - return list(self.event_names) - - def get_person_event_types(self): - """ - Deprecated: Use get_event_types - """ - return list(self.event_names) - - def get_person_attribute_types(self): - """ - Return a list of all Attribute types assocated with Person instances - in the database. - """ - return list(self.individual_attributes) - - def get_family_attribute_types(self): - """ - Return a list of all Attribute types assocated with Family instances - in the database. - """ - return list(self.family_attributes) - - def get_family_event_types(self): - """ - Deprecated: Use get_event_types - """ - return list(self.event_names) - - def get_media_attribute_types(self): - """ - Return a list of all Attribute types assocated with Media and MediaRef - instances in the database. - """ - return list(self.media_attributes) - - def get_family_relation_types(self): - """ - Return a list of all relationship types assocated with Family - instances in the database. - """ - return list(self.family_rel_types) - - def get_child_reference_types(self): - """ - Return a list of all child reference types assocated with Family - instances in the database. - """ - return list(self.child_ref_types) - - def get_event_roles(self): - """ - Return a list of all custom event role names assocated with Event - instances in the database. - """ - return list(self.event_role_names) - - def get_name_types(self): - """ - Return a list of all custom names types assocated with Person - instances in the database. - """ - return list(self.name_types) - - def get_origin_types(self): - """ - Return a list of all custom origin types assocated with Person/Surname - instances in the database. - """ - return list(self.origin_types) - - def get_repository_types(self): - """ - Return a list of all custom repository types assocated with Repository - instances in the database. - """ - return list(self.repository_types) - - def get_note_types(self): - """ - Return a list of all custom note types assocated with Note instances - in the database. - """ - return list(self.note_types) - - def get_source_attribute_types(self): - """ - Return a list of all Attribute types assocated with Source/Citation - instances in the database. - """ - return list(self.source_attributes) - - def get_source_media_types(self): - """ - Return a list of all custom source media types assocated with Source - instances in the database. - """ - return list(self.source_media_types) - - def get_url_types(self): - """ - Return a list of all custom names types assocated with Url instances - in the database. - """ - return list(self.url_types) - - def get_place_types(self): - """ - Return a list of all custom place types assocated with Place instances - in the database. - """ - return list(self.place_types) - - def get_default_handle(self): - people = self.dji.Person.all() - if people.count() > 0: - return people[0].handle - return None - - def close(self): - if self._directory: - filename = os.path.join(self._directory, "meta_data.db") - touch(filename) - - def get_surname_list(self): - return [] - - def is_open(self): - return True - - def get_table_names(self): - """Return a list of valid table names.""" - return list(self._tables.keys()) - - def find_initial_person(self): - return self.get_default_person() - - # Removals: - def remove_person(self, handle, txn): - self.dji.Person.filter(handle=handle)[0].delete() - self.emit("person-delete", ([handle],)) - - def remove_source(self, handle, transaction): - self.dji.Source.filter(handle=handle)[0].delete() - self.emit("source-delete", ([handle],)) - - def remove_citation(self, handle, transaction): - self.dji.Citation.filter(handle=handle)[0].delete() - self.emit("citation-delete", ([handle],)) - - def remove_event(self, handle, transaction): - self.dji.Event.filter(handle=handle)[0].delete() - self.emit("event-delete", ([handle],)) - - def remove_object(self, handle, transaction): - self.dji.Media.filter(handle=handle)[0].delete() - self.emit("media-delete", ([handle],)) - - def remove_place(self, handle, transaction): - self.dji.Place.filter(handle=handle)[0].delete() - self.emit("place-delete", ([handle],)) - - def remove_family(self, handle, transaction): - self.dji.Family.filter(handle=handle)[0].delete() - self.emit("family-delete", ([handle],)) - - def remove_repository(self, handle, transaction): - self.dji.Repository.filter(handle=handle)[0].delete() - self.emit("repository-delete", ([handle],)) - - def remove_note(self, handle, transaction): - self.dji.Note.filter(handle=handle)[0].delete() - self.emit("note-delete", ([handle],)) - - def remove_tag(self, handle, transaction): - self.dji.Tag.filter(handle=handle)[0].delete() - self.emit("tag-delete", ([handle],)) - - def remove_from_surname_list(self, person): - ## FIXME - ## called by a complete commit_person - pass - - ## was missing - - def find_place_child_handles(self, handle): - pass - - def get_cursor(self, table, txn=None, update=False, commit=False): - pass - - def get_number_of_records(self, table): - pass - - def get_place_parent_cursor(self): - pass - - def get_place_tree_cursor(self): - pass - - def get_table_metadata(self, table_name): - """Return the metadata for a valid table name.""" - if table_name in self._tables: - return self._tables[table_name] - return None - - def get_transaction_class(self): - pass - - def undo(self, update_history=True): - # FIXME: - return self.undodb.undo(update_history) - - def redo(self, update_history=True): - # FIXME: - return self.undodb.redo(update_history) - - def backup(self): - pass - - def restore(self): - pass - - def write_version(self, directory): - """Write files for a newly created DB.""" - versionpath = os.path.join(directory, str(DBBACKEND)) - _LOG.debug("Write database backend file to 'djangodb'") - with open(versionpath, "w") as version_file: - version_file.write("djangodb") - # Write default_settings, sqlite.db - defaults = os.path.join(os.path.dirname(os.path.abspath(__file__)), - "django_support", "defaults") - _LOG.debug("Copy defaults from: " + defaults) - for filename in os.listdir(defaults): - fullpath = os.path.abspath(os.path.join(defaults, filename)) - shutil.copy2(fullpath, directory) - # force load, to get all modules loaded because of reset issue - self.load(directory) - - def report_bm_change(self): - """ - Add 1 to the number of bookmark changes during this session. - """ - self._bm_changes += 1 - - def db_has_bm_changes(self): - """ - Return whethere there were bookmark changes during the session. - """ - return self._bm_changes > 0 - - def get_summary(self): - """ - Returns dictionary of summary item. - Should include, if possible: - - _("Number of people") - _("Version") - _("Schema version") - """ - return { - _("Number of people"): self.get_number_of_people(), - } - - def get_dbname(self): - """ - In Django, the database is in a text file at the path - """ - filepath = os.path.join(self._directory, "name.txt") - try: - name_file = open(filepath, "r") - name = name_file.readline().strip() - name_file.close() - except (OSError, IOError) as msg: - _LOG.error(str(msg)) - name = None - return name - - def reindex_reference_map(self): - pass - - def rebuild_secondary(self, update): - pass