Add new modules to support restructured database objects

bsddbtxn.py -- Wrapper for BSDDB DBTxn to support context manager protocol
    txn.py      -- GrampsDbTxn class to manage atomic transactions
    write.py    -- GrampsDbWrite class for read/write databases
    read.py     -- GrampsDbRead class for read-only databases
    undoredo.py -- GrampsDbUndo class to manage the undo database
    upgrade.py  -- Helper module for upgrading a database at open time


svn: r13078
This commit is contained in:
Gerald Britton 2009-08-19 17:05:39 +00:00
parent fa05e52a04
commit c8f7bf0c69
6 changed files with 4782 additions and 0 deletions

210
src/gen/db/bsddbtxn.py Normal file
View File

@ -0,0 +1,210 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2009 Gerald W. Britton
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id: bsddbtxn.py 12786 2009-07-11 15:32:37Z gbritton $
"""
BSDDBTxn class: Wrapper for BSDDB transaction-oriented methods
"""
#-------------------------------------------------------------------------
#
# BSDDBTxn
#
#-------------------------------------------------------------------------
class BSDDBTxn(object):
"""
Wrapper for BSDDB methods that set up and manage transactions. Implements
context management functionality allowing constructs like:
with BSDDBTxn(env) as txn:
DB.get(txn=txn)
DB.put(txn=txn)
DB.delete(txn=txn)
and other transaction-oriented DB access methods, where "env" is a
BSDDB DBEnv object and "DB" is a BSDDB database object.
Transactions are automatically begun when the "with" statement is executed
and automatically committed when control flows off the end of the "with"
statement context, either implicitly by reaching the end of the indentation
level or explicity if a "return" statement is encountered or an exception
is raised.
"""
__slots__ = ['env', 'db', 'txn', 'parent']
def __init__(self, env, db=None):
"""
Initialize transaction instance
"""
self.env = env
self.db = db
self.txn = None
# Context manager methods
def __enter__(self, parent=None, **kwargs):
"""
Context manager entry method
Begin the transaction
"""
self.txn = self.begin(parent, **kwargs)
self.parent = parent
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""
Context manager exit function
Commit the transaction if no exception occurred
"""
if exc_type is not None:
return False
if self.txn:
self.commit()
return True
# Methods implementing txn_ methods in DBEnv
def begin(self, *args, **kwargs):
"""
Create and begin a new transaction. A DBTxn object is returned
"""
self.txn = self.env.txn_begin(*args, **kwargs)
return self.txn
def checkpoint(self, *args, **kwargs):
"""
Flush the underlying memory pool, write a checkpoint record to the
log and then flush the log
"""
if self.env:
self.env.txn_checkpoint(*args, **kwargs)
def stat(self):
"""
Return a dictionary of transaction statistics
"""
if self.env:
return self.env.txn_stat()
def recover(self):
"""
Returns a list of tuples (GID, TXN) of transactions prepared but
still unresolved
"""
if self.env:
return self.env.txn_recover()
# Methods implementing DBTxn methods
def abort(self):
"""
Abort the transaction
"""
if self.txn:
self.txn.abort()
self.txn = None
def commit(self, flags=0):
"""
End the transaction, committing any changes to the databases
"""
if self.txn:
self.txn.commit(flags)
self.txn = None
def id(self):
"""
Return the unique transaction id associated with the specified
transaction
"""
if self.txn:
return self.txn.id()
def prepare(self, gid):
"""
Initiate the beginning of a two-phase commit
"""
if self.txn:
self.txn.prepare(gid)
def discard(self):
"""
Release all the per-process resources associated with the specified
transaction, neither committing nor aborting the transaction
"""
if self.txn:
self.txn.discard()
self.txn = None
# Methods implementing DB methods within the transaction context
def get(self, key, default=None, txn=None, **kwargs):
"""
Returns the data object associated with key
"""
if txn == None: txn = self.txn
return self.db.get(key, default, txn, **kwargs)
def pget(self, key, default=None, txn=None, **kwargs):
"""
Returns the primary key, given the secondary one, and associated data
"""
if txn == None: txn = self.txn
return self.db.pget(key, default, txn, **kwargs)
def put(self, key, data, txn=None, **kwargs):
"""
Stores the key/data pair in the database
"""
if txn == None: txn = self.txn
return self.db.put(key, data, txn, **kwargs)
def delete(self, key, txn=None, **kwargs):
"""
Removes a key/data pair from the database
"""
if txn == None: txn = self.txn
self.db.delete(key, txn, **kwargs)
# test code
if __name__ == "__main__":
print "1"
from bsddb import db, dbshelve
print "2"
x = db.DBEnv()
print "3"
x.open('/tmp', db.DB_CREATE | db.DB_PRIVATE |\
db.DB_INIT_MPOOL | db.DB_INIT_LOCK |\
db.DB_INIT_LOG | db.DB_INIT_TXN | db.DB_THREAD)
print "4"
d = dbshelve.DBShelf(x)
print "5"
#from tran import BSDDBTxn as T
print "6"
T = BSDDBTxn
with T(x) as tx:
print "stat", tx.stat()
print "id", tx.id()
tx.checkpoint()

1566
src/gen/db/read.py Normal file

File diff suppressed because it is too large Load Diff

335
src/gen/db/txn.py Normal file
View File

@ -0,0 +1,335 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2004-2006 Donald N. Allingham
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id: txn.py 12672 2009-06-16 15:49:17Z gbritton $
"""
Exports the GrampsDbTxn class for managing Gramps transactions and the undo
database.
"""
#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
from __future__ import with_statement
import cPickle as pickle
from bsddb import dbshelve, db
import logging
#-------------------------------------------------------------------------
#
# Gramps modules
#
#-------------------------------------------------------------------------
from gen.db.dbconst import *
from gen.db import BSDDBTxn
import Errors
_LOG = logging.getLogger(DBLOGNAME)
#-------------------------------------------------------------------------
#
# Gramps transaction class
#
#-------------------------------------------------------------------------
class GrampsDbTxn(dict):
"""
Define a group of database commits that define a single logical operation.
This class should not be used directly, but subclassed to reference a real
database
"""
__slots__ = ('msg', 'commitdb', 'db', 'first',
'last', 'timestamp', 'db_maps')
def get_db_txn(self, value):
"""
Return a transaction object from the database
"""
raise NotImplementedError
def __enter__(self):
"""
Context manager entry method
"""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""
Context manager exit method
"""
if exc_type is None:
self.commit()
return exc_type is None
def __init__(self, msg, commitdb, grampsdb):
"""
Create a new transaction.
A Transaction instance should not be created directly, but by the
GrampsDbBase class or classes derived from GrampsDbBase. The commitdb
parameter is a list-like interface that stores the commit data. This
could be a simple list, or a RECNO-style database object. The grampsdb
parameter is a reference to the GrampsDbWrite object to which this
transaction will be applied.
The data structure used to handle the transactions is a Python
dictionary where:
key = (object type, transaction type) where:
object type = the numeric type of an object. These are
defined as PERSON_KEY = 0, FAMILY_KEY = 1, etc.
as imported from dbconst.
transaction type = a numeric representation of the type of
transaction: TXNADD = 0, TXNUPD = 1, TXNDEL = 2
data = Python list where:
list element = (handle, data) where:
handle = handle (database key) of the object in the transaction
data = pickled representation of the object
"""
super(GrampsDbTxn, self).__init__({})
self.msg = msg
self.commitdb = commitdb
self.db = grampsdb
self.first = None
self.last = None
self.timestamp = 0
# Dictionary to enable table-driven logic in the class
self.db_maps = {
PERSON_KEY: (self.db.person_map, 'person'),
FAMILY_KEY: (self.db.family_map, 'family'),
EVENT_KEY: (self.db.event_map, 'event'),
SOURCE_KEY: (self.db.source_map, 'source'),
PLACE_KEY: (self.db.place_map, 'place'),
MEDIA_KEY: (self.db.media_map, 'media'),
REPOSITORY_KEY: (self.db.repository_map, 'repository'),
#REFERENCE_KEY: (self.db.reference_map, 'reference'),
NOTE_KEY: (self.db.note_map, 'note'),
}
def get_description(self):
"""
Return the text string that describes the logical operation performed
by the Transaction.
"""
return self.msg
def set_description(self, msg):
"""
Set the text string that describes the logical operation performed by
the Transaction.
"""
self.msg = msg
def add(self, obj_type, trans_type, handle, old_data, new_data):
"""
Add a commit operation to the Transaction.
The obj_type is a constant that indicates what type of PrimaryObject
is being added. The handle is the object's database handle, and the
data is the tuple returned by the object's serialize method.
"""
self.last = self.commitdb.append(
pickle.dumps((obj_type, trans_type, handle, old_data, new_data), 1))
if self.last is None:
self.last = len(self.commitdb) -1
if self.first is None:
self.first = self.last
if (obj_type, trans_type) in self:
self[(obj_type, trans_type)] += [(handle, new_data)]
else:
self[(obj_type, trans_type)] = [(handle, new_data)]
def get_recnos(self, reverse=False):
"""
Return a list of record numbers associated with the transaction.
While the list is an arbitrary index of integers, it can be used
to indicate record numbers for a database.
"""
if not reverse:
return xrange(self.first, self.last+1)
else:
return xrange(self.last, self.first-1, -1)
def get_record(self, recno):
"""
Return a tuple representing the PrimaryObject type, database handle
for the PrimaryObject, and a tuple representing the data created by
the object's serialize method.
"""
return pickle.loads(self.commitdb[recno])
def __len__(self):
"""
Return the number of commits associated with the Transaction.
"""
if self.first is None or self.last is None:
return 0
return self.last - self.first + 1
def commit(self, msg=None):
"""
Commit the transaction to the assocated commit database.
"""
if msg is not None:
self.msg = msg
if not len(self) or self.db.readonly:
return
# Begin new database transaction
txn = self.get_db_txn(self.db.env)
self.db.txn = txn.begin()
# Commit all add transactions to the database
db_map = lambda key: self.db_maps[key][0]
for (obj_type, trans_type), data in self.iteritems():
if trans_type == TXNADD and obj_type in self.db_maps:
for handle, new_data in data:
assert handle == str(handle)
db_map(obj_type).put(handle, new_data, txn=txn.txn)
# Commit all update transactions to the database
for (obj_type, trans_type), data in self.iteritems():
if trans_type == TXNUPD and obj_type in self.db_maps:
for handle, new_data in data:
assert handle == str(handle)
db_map(obj_type).put(handle, new_data, txn=txn.txn)
# Before we commit delete transactions, emit signals as required
# Loop through the data maps, emitting signals as required
emit = self.__emit
for obj_type, (m_, obj_name) in self.db_maps.iteritems():
# Do an emit for each object and transaction type as required
emit(obj_type, TXNADD, obj_name, '-add')
emit(obj_type, TXNUPD, obj_name, '-update')
emit(obj_type, TXNDEL, obj_name, '-delete')
# Commit all delete transactions to the database
for (obj_type, trans_type), data in self.iteritems():
if trans_type == TXNDEL and obj_type in self.db_maps:
for handle, n_ in data:
assert handle == str(handle)
db_map(obj_type).delete(handle, txn=txn.txn)
# Add new reference keys as required
db_map = self.db.reference_map
if (REFERENCE_KEY, TXNADD) in self:
for handle, new_data in self[(REFERENCE_KEY, TXNADD)]:
assert handle == str(handle)
db_map.put(handle, new_data, txn=txn.txn)
# Delete old reference keys as required
if (REFERENCE_KEY, TXNDEL) in self:
for handle, none_ in self[(REFERENCE_KEY, TXNDEL)]:
assert handle == str(handle)
db_map.delete(handle, txn=txn.txn)
# Commit database transaction
txn.commit()
self.db.txn = None
self.clear()
return
# Define helper function to do the actual emits
def __emit(self,obj_type, trans_type, obj, suffix):
if (obj_type, trans_type) in self:
handles = [handle for handle, data in
self[(obj_type, trans_type)]]
if handles:
self.db.emit(obj + suffix, (handles, ))
# Test functions
def testtxn():
"""
Test suite
"""
class M(dict):
"""Fake database map with just two methods"""
def put(self, key, data, txn=None):
super(M, self).__setitem__(key, data)
def delete(self, key, txn=None):
super(M, self).__delitem__(key)
class D:
"""Fake gramps database"""
def __init__(self):
self.person_map = M()
self.family_map = M()
self.source_map = M()
self.event_map = M()
self.media_map = M()
self.place_map = M()
self.note_map = M()
self.repository_map = M()
self.reference_map = M()
self.readonly = False
self.env = None
def emit(self, obj, value):
pass
class C(list):
""" Fake commit database"""
pass
class G(GrampsDbTxn):
"""Derived transacton class"""
def get_db_txn(self, env):
return T()
class T():
"""Fake DBMS transaction class"""
def __init__(self):
self.txn = None
def begin(self):
return self
def commit(self):
pass
commitdb = C()
grampsdb = D()
trans = G("Test Transaction", commitdb, grampsdb)
trans.add(0, TXNADD, '1', None, "data1")
trans.add(0, TXNADD, '2', None, "data2")
trans.add(0, TXNUPD, '2', None, "data3")
trans.add(0, TXNDEL, '1', None, None)
print trans
print trans.get_description()
print trans.set_description("new text")
print trans.get_description()
for i in trans.get_recnos():
print trans.get_record(i)
print list(trans.get_recnos())
print list(trans.get_recnos(reverse=True))
trans.commit("test")
print grampsdb.person_map
if __name__ == '__main__':
testtxn()

509
src/gen/db/undoredo.py Normal file
View File

@ -0,0 +1,509 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2004-2006 Donald N. Allingham
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id: undoredo.py 12672 2009-06-16 15:49:17Z gbritton $
"""
Exports the GrampsDbUndo class for managing Gramps transactions
undos and redos.
"""
#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
import time, os
import cPickle as pickle
from bsddb import db
from gettext import gettext as _
#-------------------------------------------------------------------------
#
# Gramps modules
#
#-------------------------------------------------------------------------
from gen.db.dbconst import *
from gen.db import BSDDBTxn
import Errors
#-------------------------------------------------------------------------
#
# Local Constants
#
#-------------------------------------------------------------------------
DBERRS = (db.DBRunRecoveryError, db.DBAccessError,
db.DBPageNotFoundError, db.DBInvalidArgError)
_SIGBASE = ('person', 'family', 'source', 'event', 'media',
'place', 'repository', 'reference', 'note')
#-------------------------------------------------------------------------
#
# GrampsDbUndo class
#
#-------------------------------------------------------------------------
class GrampsDbUndo(object):
"""
Base class for the gramps undo/redo manager. Needs to be subclassed
for use with a real backend.
"""
__slots__ = ['undodb', 'db', 'mapbase', 'translist', 'undoindex',
'undo_history_timestamp', 'txn']
def __init__(self, grampsdb):
"""
Class constructor. Set up main instance variables
"""
self.db = grampsdb
self.clear()
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.reference_map,
self.db.note_map,
)
def clear(self):
"""
Clear the undo/redo list (but not the backing storage)
"""
self.translist = []
self.undoindex = -1
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 gramps transaction class
"""
txn.set_description(msg)
txn.timestamp = time.time()
# If we're within our undo limit, add this transaction
self.undoindex += 1
if self.undoindex < DBUNDO:
if self.undoindex >= len(self.translist):
self.translist.append(txn)
else:
self.translist[self.undoindex] = txn
del self.translist[self.undoindex+1:]
# Otherwise, we've exceeded our undo limit
else:
self.db.abort_possible = False
self.undo_history_timestamp = time.time()
self.translist[-1] = txn
def undo_available(self):
"""
Return boolean of whether or not there's a possibility of undo.
"""
if 0 <= self.undoindex < len(self.translist):
return True
return False
def redo_available(self):
"""
Return boolean of whether or not there's a possibility of redo.
"""
if 0 <= self.undoindex+1 < len(self.translist):
return True
return False
def undo(self, update_history=True):
"""
Undo a previously committed transaction
"""
if self.db.readonly or not self.undo_available():
return False
return self.__undoredo(update_history, self.__undo)
def redo(self, update_history=True):
"""
Redo a previously committed, then undone, transaction
"""
if self.db.readonly or not self.redo_available():
return False
return self.__undoredo(update_history, self.__redo)
def __undoredo(self, update_history, func):
"""
Helper method used by both undo and redo methods.
"""
try:
with BSDDBTxn(self.db.env) as txn:
self.txn = self.db.txn = txn.txn
status = func(update_history)
if not status:
txn.abort()
self.db.txn = None
return status
except DBERRS, msg:
self.db._log_error()
raise Errors.DbError(msg)
def __undo(self, update_history=True):
"""
Access the last committed transaction, and revert the data to the
state before the transaction was committed.
"""
transaction = self.translist[self.undoindex]
db = self.db
self.undoindex -= 1
subitems = transaction.get_recnos(reverse=True)
# Process all records in the transaction
for record_id in subitems:
(key, trans_type, handle, old_data, new_data) = \
pickle.loads(self.undodb[record_id])
if key == REFERENCE_KEY:
self.undo_reference(old_data, handle, self.mapbase[key])
else:
self.undo_data(old_data, handle, self.mapbase[key],
db.emit, _SIGBASE[key])
# Notify listeners
if db.undo_callback:
if self.undo_available():
db.undo_callback(_("_Undo %s")
% transaction.get_description())
else:
db.undo_callback(None)
if db.redo_callback:
db.redo_callback(_("_Redo %s")
% transaction.get_description())
if update_history and db.undo_history_callback:
db.undo_history_callback()
return True
def __redo(self, db=None, update_history=True):
"""
Accesse the last undone transaction, and revert the data to the state
before the transaction was undone.
"""
self.undoindex += 1
transaction = self.translist[self.undoindex]
db = self.db
subitems = transaction.get_recnos()
# Process all records in the transaction
for record_id in subitems:
(key, trans_type, handle, old_data, new_data) = \
pickle.loads(self.undodb[record_id])
if key == REFERENCE_KEY:
self.undo_reference(new_data, handle, self.mapbase[key])
else:
self.undo_data(new_data, handle, self.mapbase[key],
db.emit, _SIGBASE[key])
# Notify listeners
if db.undo_callback:
db.undo_callback(_("_Undo %s")
% transaction.get_description())
if db.redo_callback:
if self.redo_available():
new_transaction = self.translist[self.undoindex+1]
db.redo_callback(_("_Redo %s")
% new_transaction.get_description())
else:
db.redo_callback(None)
if update_history and db.undo_history_callback:
db.undo_history_callback()
return True
def undo_reference(self, data, handle, db_map):
"""
Helper method to undo a reference map entry
"""
try:
if data is None:
db_map.delete(handle, txn=self.txn)
else:
db_map.put(handle, data, txn=self.txn)
except DBERRS, msg:
self.db._log_error()
raise Errors.DbError(msg)
def undo_data(self, data, handle, db_map, emit, signal_root):
"""
Helper method to undo/redo the changes made
"""
try:
if data is None:
emit(signal_root + '-delete', ([handle],))
db_map.delete(handle, txn=self.txn)
else:
ex_data = db_map.get(handle, txn=self.txn)
if ex_data:
signal = signal_root + '-update'
else:
signal = signal_root + '-add'
db_map.put(handle, data, txn=self.txn)
emit(signal, ([handle],))
except DBERRS, msg:
self.db._log_error()
raise Errors.DbError(msg)
class GrampsDbUndoList(GrampsDbUndo):
"""
Implementation of the gramps undo database using a Python list
"""
def __init__(self, grampsdb):
"""
Class constructor
"""
super(GrampsDbUndoList, self).__init__(grampsdb)
self.undodb = []
def open(self):
"""
A list does not need to be opened
"""
pass
def close(self):
"""
Close the list by resetting it to empty
"""
self.undodb = []
self.clear()
def append(self, value):
"""
Add an entry on the end of the list
"""
self.undodb.append(value)
return len(self.undodb)-1
def __getitem__(self, index):
"""
Return an item at the specified index
"""
return self.undodb[index]
def __setitem__(self, index, value):
"""
Set an item at the speficied index to the given value
"""
self.undodb[index] = value
def __iter__(self):
"""
Iterator
"""
for item in self.undodb:
yield item
def __len__(self):
"""
Return number of entries in the list
"""
return len(self.undodb)
class GrampsDbUndoBSDDB(GrampsDbUndo):
"""
Class constructor for gramps undo/redo database using a bsddb recno
database as the backing store.
"""
def __init__(self, grampsdb, path):
"""
Class constructor
"""
super(GrampsDbUndoBSDDB, self).__init__(grampsdb)
self.undodb = db.DB()
self.path = path
def open(self):
"""
Open the undo/redo database
"""
self.undodb.open(self.path, db.DB_RECNO, db.DB_CREATE)
def close(self):
"""
Close the undo/redo database
"""
self.undodb.close()
try:
os.remove(self.path)
except OSError:
pass
self.clear()
def append(self, value):
"""
Add an entry on the end of the database
"""
return self.undodb.append(value)
def __len__(self):
"""
Returns the number of entries in the database
"""
x = self.undodb.stat()['nkeys']
y = len(self.undodb)
assert x == y
return x
def __getitem__(self, index):
"""
Returns the entry stored at the specified index
"""
return self.undodb.get(index)
def __setitem__(self, index, value):
"""
Sets the entry stored at the specified index to the value given.
"""
self.undodb.put(index, value)
def __iter__(self):
"""
Iterator
"""
cursor = self.undodb.cursor()
data = cursor.first()
while data:
yield data
data = cursor.next()
def testundo():
class T:
def __init__(self):
self.msg = ''
self.timetstamp = 0
def set_description(self, msg):
self.msg = msg
class D:
def __init__(self):
self.person_map = {}
self.family_map = {}
self.source_map = {}
self.event_map = {}
self.media_map = {}
self.place_map = {}
self.note_map = {}
self.repository_map = {}
self.reference_map = {}
print "list tests"
undo = GrampsDbUndoList(D())
print undo.append('foo')
print undo.append('bar')
print undo[0]
undo[0] = 'foobar'
print undo[0]
print "len", len(undo)
print "iter"
for data in undo:
print data
print
print "bsddb tests"
undo = GrampsDbUndoBSDDB(D(), '/tmp/testundo')
undo.open()
print undo.append('foo')
print undo.append('fo2')
print undo.append('fo3')
print undo[1]
undo[1] = 'bar'
print undo[1]
for data in undo:
print data
print "len", len(undo)
print "test commit"
undo.commit(T(), msg="test commit")
undo.close()
if __name__ == '__main__':
testundo()

305
src/gen/db/upgrade.py Normal file
View File

@ -0,0 +1,305 @@
from gen.db import BSDDBTxn
def gramps_upgrade_14(self):
"""Upgrade database from version 13 to 14."""
# This upgrade modifies notes and dates
length = (len(self.note_map) + len(self.person_map) +
len(self.event_map) + len(self.family_map) +
len(self.repository_map) + len(self.media_map) +
len(self.place_map) + len(self.source_map))
self.set_total(length)
# ---------------------------------
# Modify Notes
# ---------------------------------
# replace clear text with StyledText in Notes
for handle in self.note_map.keys():
note = self.note_map[handle]
(junk_handle, gramps_id, text, format, note_type,
change, marker, private) = note
styled_text = (text, [])
new_note = (handle, gramps_id, styled_text, format, note_type,
change, marker, private)
with BSDDBTxn(self.env, self.note_map) as txn:
txn.put(str(handle), new_note)
self.update()
# ---------------------------------
# Modify Event
# ---------------------------------
# update dates with newyear
for handle in self.event_map.keys():
event = self.event_map[handle]
(junk_handle, gramps_id, the_type, date, description, place,
source_list, note_list, media_list, attribute_list,
change, marker, private) = event
new_date = convert_date_14(date)
new_source_list = new_source_list_14(source_list)
new_media_list = new_media_list_14(media_list)
new_attribute_list = new_attribute_list_14(attribute_list)
new_event = (junk_handle, gramps_id, the_type, new_date,
description, place, new_source_list, note_list,
new_media_list, new_attribute_list, change,marker,private)
with BSDDBTxn(self.env, self.event_map) as txn:
txn.put(str(handle), new_event)
self.update()
# ---------------------------------
# Modify Person
# ---------------------------------
# update dates with newyear
for handle in self.person_map.keys():
person = self.person_map[handle]
(junk_handle, # 0
gramps_id, # 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
urls, # 13
lds_ord_list, # 14
psource_list, # 15
pnote_list, # 16
change, # 17
marker, # 18
pprivate, # 19
person_ref_list, # 20
) = person
new_address_list = []
for address in address_list:
(privacy, asource_list, anote_list, date, location) = address
new_date = convert_date_14(date)
new_asource_list = new_source_list_14(asource_list)
new_address_list.append((privacy, new_asource_list, anote_list,
new_date, location))
new_ord_list = []
for ldsord in lds_ord_list:
(lsource_list, lnote_list, date, type, place,
famc, temple, status, lprivate) = ldsord
new_date = convert_date_14(date)
new_lsource_list = new_source_list_14(lsource_list)
new_ord_list.append( (new_lsource_list, lnote_list, new_date, type,
place, famc, temple, status, lprivate))
new_primary_name = convert_name_14(primary_name)
new_alternate_names = [convert_name_14(name) for name
in alternate_names]
new_media_list = new_media_list_14(media_list)
new_psource_list = new_source_list_14(psource_list)
new_attribute_list = new_attribute_list_14(attribute_list)
new_person_ref_list = new_person_ref_list_14(person_ref_list)
new_person = (junk_handle, # 0
gramps_id, # 1
gender, # 2
new_primary_name, # 3
new_alternate_names, # 4
death_ref_index, # 5
birth_ref_index, # 6
event_ref_list, # 7
family_list, # 8
parent_family_list, # 9
new_media_list, # 10
new_address_list, # 11
new_attribute_list, # 12
urls, # 13
new_ord_list, # 14
new_psource_list, # 15
pnote_list, # 16
change, # 17
marker, # 18
pprivate, # 19
new_person_ref_list, # 20
)
with BSDDBTxn(self.env, self.person_map) as txn:
txn.put(str(handle), new_person)
self.update()
# ---------------------------------
# Modify Family
# ---------------------------------
# update dates with newyear
for handle in self.family_map.keys():
family = self.family_map[handle]
(junk_handle, gramps_id, father_handle, mother_handle,
child_ref_list, the_type, event_ref_list, media_list,
attribute_list, lds_seal_list, source_list, note_list,
change, marker, private) = family
new_child_ref_list = new_child_ref_list_14(child_ref_list)
new_media_list = new_media_list_14(media_list)
new_source_list = new_source_list_14(source_list)
new_attribute_list = new_attribute_list_14(attribute_list)
new_seal_list = []
for ldsord in lds_seal_list:
(lsource_list, lnote_list, date, type, place,
famc, temple, status, lprivate) = ldsord
new_date = convert_date_14(date)
new_lsource_list = new_source_list_14(lsource_list)
new_seal_list.append( (new_lsource_list, lnote_list, new_date, type,
place, famc, temple, status, lprivate))
new_family = (junk_handle, gramps_id, father_handle, mother_handle,
new_child_ref_list, the_type, event_ref_list, new_media_list,
new_attribute_list, new_seal_list, new_source_list, note_list,
change, marker, private)
with BSDDBTxn(self.env, self.family_map) as txn:
txn.put(str(handle), new_family)
self.update()
# ---------------------------------
# Modify Repository
# ---------------------------------
# update dates with newyear
for handle in self.repository_map.keys():
repository = self.repository_map[handle]
# address
(junk_handle, gramps_id, the_type, name, note_list,
address_list, urls, change, marker, private) = repository
new_address_list = []
for address in address_list:
(privacy, asource_list, anote_list, date, location) = address
new_date = convert_date_14(date)
new_asource_list = new_source_list_14(asource_list)
new_address_list.append((privacy, new_asource_list, anote_list,
new_date, location))
new_repository = (junk_handle, gramps_id, the_type, name, note_list,
new_address_list, urls, change, marker, private)
with BSDDBTxn(self.env, self.repository_map) as txn:
txn.put(str(handle), new_repository)
self.update()
# ---------------------------------
# Modify Media
# ---------------------------------
for media_handle in self.media_map.keys():
media = self.media_map[media_handle]
(handle, gramps_id, path, mime, desc,
attribute_list, source_list, note_list, change,
date, marker, private) = media
new_source_list = new_source_list_14(source_list)
new_date = convert_date_14(date)
new_media = (handle, gramps_id, path, mime, desc,
attribute_list, new_source_list, note_list, change,
new_date, marker, private)
with BSDDBTxn(self.env, self.media_map) as txn:
txn.put(str(handle), new_media)
self.update()
# ---------------------------------
# Modify Place
# ---------------------------------
for place_handle in self.place_map.keys():
place = self.place_map[place_handle]
(handle, gramps_id, title, long, lat,
main_loc, alt_loc, urls, media_list, source_list, note_list,
change, marker, private) = place
new_media_list = new_media_list_14(media_list)
new_source_list = new_source_list_14(source_list)
new_place = (handle, gramps_id, title, long, lat,
main_loc, alt_loc, urls, new_media_list,
new_source_list, note_list, change, marker, private)
with BSDDBTxn(self.env, self.place_map) as txn:
txn.put(str(handle), new_place)
self.update()
# ---------------------------------
# Modify Source
# ---------------------------------
for source_handle in self.source_map.keys():
source = self.source_map[source_handle]
(handle, gramps_id, title, author,
pubinfo, note_list, media_list,
abbrev, change, datamap, reporef_list,
marker, private) = source
new_media_list = new_media_list_14(media_list)
new_source = (handle, gramps_id, title, author,
pubinfo, note_list, new_media_list,
abbrev, change, datamap, reporef_list,
marker, private)
with BSDDBTxn(self.env, self.source_map) as txn:
txn.put(str(handle), new_source)
self.update()
# Bump up database version. Separate transaction to save metadata.
with BSDDBTxn(self.env, self.metadata) as txn:
txn.put('version', 14)
def new_source_list_14(source_list):
new_source_list = []
for source in source_list:
(date, private, note_list, confidence, ref, page) = source
new_date = convert_date_14(date)
new_source_list.append((new_date, private, note_list, confidence, ref, page))
return new_source_list
def new_attribute_list_14(attribute_list):
new_attribute_list = []
for attribute in attribute_list:
(private, asource_list, note_list, the_type, value) = attribute
new_asource_list = new_source_list_14(asource_list)
new_attribute_list.append((private, new_asource_list, note_list, the_type, value))
return new_attribute_list
def new_media_list_14(media_list):
# ---------------------------------
# Event Media list
# ---------------------------------
new_media_list = []
for media in media_list:
(private, source_list, note_list,attribute_list,ref,role) = media
new_source_list = new_source_list_14(source_list)
new_attribute_list = new_attribute_list_14(attribute_list)
new_media_list.append((private, new_source_list, note_list, new_attribute_list, ref, role))
return new_media_list
def new_person_ref_list_14(person_ref_list):
new_person_ref_list = []
for person_ref in person_ref_list:
(private, source_list, note_list, ref, rel) = person_ref
new_source_list = new_source_list_14(source_list)
new_person_ref_list.append((private, new_source_list, note_list, ref, rel))
return new_person_ref_list
def new_child_ref_list_14(child_ref_list):
new_child_ref_list = []
for data in child_ref_list:
(private, source_list, note_list, ref, frel, mrel) = data
new_source_list = new_source_list_14(source_list)
new_child_ref_list.append((private, new_source_list, note_list, ref, frel, mrel))
return new_child_ref_list
def convert_date_14(date):
if date:
(calendar, modifier, quality, dateval, text, sortval) = date
return (calendar, modifier, quality, dateval, text, sortval, 0)
else:
return None
def convert_name_14(name):
(privacy, source_list, note_list, date,
first_name, surname, suffix, title,
name_type, prefix, patronymic,
group_as, sort_as, display_as, call) = name
new_date = convert_date_14(date)
new_source_list = new_source_list_14(source_list)
return (privacy, new_source_list, note_list, new_date,
first_name, surname, suffix, title,
name_type, prefix, patronymic,
group_as, sort_as, display_as, call)

1857
src/gen/db/write.py Normal file

File diff suppressed because it is too large Load Diff