2009-08-19 22:35:39 +05:30
|
|
|
#
|
|
|
|
# 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
|
|
|
|
#
|
|
|
|
|
2009-12-21 19:13:50 +05:30
|
|
|
# $Id$
|
2009-08-19 22:35:39 +05:30
|
|
|
|
|
|
|
"""
|
2009-12-23 21:25:58 +05:30
|
|
|
Exports the DbTxn class for managing Gramps transactions and the undo
|
2009-08-19 22:35:39 +05:30
|
|
|
database.
|
|
|
|
"""
|
|
|
|
|
|
|
|
#-------------------------------------------------------------------------
|
|
|
|
#
|
|
|
|
# Standard python modules
|
|
|
|
#
|
|
|
|
#-------------------------------------------------------------------------
|
|
|
|
from __future__ import with_statement
|
|
|
|
import cPickle as pickle
|
|
|
|
import logging
|
2011-01-24 02:55:51 +05:30
|
|
|
|
2010-01-13 21:16:12 +05:30
|
|
|
from collections import defaultdict
|
2009-08-19 22:35:39 +05:30
|
|
|
|
|
|
|
#-------------------------------------------------------------------------
|
|
|
|
#
|
|
|
|
# Gramps modules
|
|
|
|
#
|
|
|
|
#-------------------------------------------------------------------------
|
2011-02-01 03:24:58 +05:30
|
|
|
from gen.db.dbconst import (DBLOGNAME, TXNADD, TXNUPD, TXNDEL)
|
2009-08-19 22:35:39 +05:30
|
|
|
|
|
|
|
_LOG = logging.getLogger(DBLOGNAME)
|
|
|
|
|
2011-02-01 03:24:58 +05:30
|
|
|
|
2009-08-19 22:35:39 +05:30
|
|
|
#-------------------------------------------------------------------------
|
|
|
|
#
|
|
|
|
# Gramps transaction class
|
|
|
|
#
|
|
|
|
#-------------------------------------------------------------------------
|
2010-01-13 21:16:12 +05:30
|
|
|
class DbTxn(defaultdict):
|
2009-08-19 22:35:39 +05:30
|
|
|
"""
|
|
|
|
Define a group of database commits that define a single logical operation.
|
|
|
|
"""
|
|
|
|
|
2011-02-01 03:24:58 +05:30
|
|
|
__slots__ = ('msg', 'commitdb', 'db', 'batch', 'first',
|
|
|
|
'last', 'timestamp', '__dict__')
|
2009-08-19 22:35:39 +05:30
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
"""
|
|
|
|
Context manager entry method
|
|
|
|
"""
|
2011-02-20 16:22:06 +05:30
|
|
|
self.db.transaction_begin(self)
|
2009-08-19 22:35:39 +05:30
|
|
|
return self
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
|
|
"""
|
|
|
|
Context manager exit method
|
|
|
|
"""
|
|
|
|
if exc_type is None:
|
2011-02-01 03:24:58 +05:30
|
|
|
self.db.transaction_commit(self)
|
|
|
|
else:
|
|
|
|
self.db.transaction_abort(self)
|
|
|
|
return False
|
2009-08-19 22:35:39 +05:30
|
|
|
|
2011-02-20 16:22:06 +05:30
|
|
|
def __init__(self, msg, grampsdb, batch=False, **kwargs):
|
2009-08-19 22:35:39 +05:30
|
|
|
"""
|
|
|
|
Create a new transaction.
|
|
|
|
|
2011-02-20 16:22:06 +05:30
|
|
|
The grampsdb should have transaction_begin/commit/abort methods, and
|
|
|
|
a get_undodb method to store undo actions.
|
|
|
|
|
|
|
|
A Transaction instance can be created directly, but it is advised to
|
|
|
|
use a context to do this. Like this the user must not worry about
|
|
|
|
calling the transaction_xx methods on the database.
|
|
|
|
|
|
|
|
The grampsdb parameter is a reference to the DbWrite object to which
|
|
|
|
this transaction will be applied.
|
|
|
|
grampsdb.get_undodb() should return a list-like interface that
|
|
|
|
stores the commit data. This could be a simple list, or a RECNO-style
|
|
|
|
database object.
|
|
|
|
|
|
|
|
The data structure used to handle the transactions (see the add method)
|
|
|
|
is a Python dictionary where:
|
2009-08-19 22:35:39 +05:30
|
|
|
|
|
|
|
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
|
|
|
|
"""
|
|
|
|
|
2010-01-13 21:16:12 +05:30
|
|
|
defaultdict.__init__(self, list, {})
|
2009-08-19 22:35:39 +05:30
|
|
|
|
|
|
|
self.msg = msg
|
2011-02-20 16:22:06 +05:30
|
|
|
self.commitdb = grampsdb.get_undodb()
|
2009-08-19 22:35:39 +05:30
|
|
|
self.db = grampsdb
|
2011-02-01 03:24:58 +05:30
|
|
|
self.batch = batch
|
2011-02-20 16:22:06 +05:30
|
|
|
for key, value in kwargs.iteritems():
|
|
|
|
setattr(self, key, value)
|
2009-08-19 22:35:39 +05:30
|
|
|
self.first = None
|
|
|
|
self.last = None
|
|
|
|
self.timestamp = 0
|
|
|
|
|
|
|
|
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
|
2011-02-01 03:24:58 +05:30
|
|
|
_LOG.debug('added to trans: %d %d %s' % (obj_type, trans_type, handle))
|
2010-01-13 21:16:12 +05:30
|
|
|
self[(obj_type, trans_type)] += [(handle, new_data)]
|
|
|
|
return
|
2009-08-19 22:35:39 +05:30
|
|
|
|
|
|
|
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.
|
|
|
|
"""
|
2010-05-21 00:02:08 +05:30
|
|
|
|
|
|
|
if self.first is None or self.last is None:
|
|
|
|
return []
|
2009-08-19 22:35:39 +05:30
|
|
|
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
|
|
|
|
|
|
|
|
# Test functions
|
|
|
|
|
|
|
|
def testtxn():
|
|
|
|
"""
|
|
|
|
Test suite
|
|
|
|
"""
|
2011-02-01 03:24:58 +05:30
|
|
|
class FakeMap(dict):
|
2009-08-19 22:35:39 +05:30
|
|
|
"""Fake database map with just two methods"""
|
2011-02-01 03:24:58 +05:30
|
|
|
def put(self, key, data):
|
|
|
|
"""Set a property"""
|
|
|
|
super(FakeMap, self).__setitem__(key, data)
|
|
|
|
def delete(self, key):
|
|
|
|
"""Delete a proptery"""
|
|
|
|
super(FakeMap, self).__delitem__(key)
|
|
|
|
|
|
|
|
class FakeDb:
|
2009-08-19 22:35:39 +05:30
|
|
|
"""Fake gramps database"""
|
|
|
|
def __init__(self):
|
2011-02-01 03:24:58 +05:30
|
|
|
self.person_map = FakeMap()
|
|
|
|
self.family_map = FakeMap()
|
|
|
|
self.event_map = FakeMap()
|
|
|
|
self.reference_map = FakeMap()
|
2009-08-19 22:35:39 +05:30
|
|
|
self.readonly = False
|
|
|
|
self.env = None
|
2011-02-01 03:24:58 +05:30
|
|
|
self.undodb = FakeCommitDb()
|
|
|
|
def transaction_commit(self, transaction):
|
|
|
|
"""Commit the transaction to the undo database and cleanup."""
|
|
|
|
transaction.clear()
|
|
|
|
self.undodb.commit(transaction)
|
2009-08-19 22:35:39 +05:30
|
|
|
def emit(self, obj, value):
|
2011-02-01 03:24:58 +05:30
|
|
|
"""send signal"""
|
2009-08-19 22:35:39 +05:30
|
|
|
pass
|
|
|
|
|
2011-02-01 03:24:58 +05:30
|
|
|
class FakeCommitDb(list):
|
2009-08-19 22:35:39 +05:30
|
|
|
""" Fake commit database"""
|
2011-02-01 03:24:58 +05:30
|
|
|
def commit(self, transaction):
|
|
|
|
"""commit transaction to undo db"""
|
|
|
|
pass
|
|
|
|
def undo(self):
|
|
|
|
"""undo last transaction"""
|
2009-08-19 22:35:39 +05:30
|
|
|
pass
|
|
|
|
|
2011-02-01 03:24:58 +05:30
|
|
|
grampsdb = FakeDb()
|
|
|
|
commitdb = grampsdb.undodb
|
|
|
|
trans = DbTxn("Test Transaction", commitdb, grampsdb, batch=False)
|
|
|
|
grampsdb.person_map.put('1', "data1")
|
2009-08-19 22:35:39 +05:30
|
|
|
trans.add(0, TXNADD, '1', None, "data1")
|
2011-02-01 03:24:58 +05:30
|
|
|
grampsdb.person_map.put('2', "data2")
|
2009-08-19 22:35:39 +05:30
|
|
|
trans.add(0, TXNADD, '2', None, "data2")
|
2011-02-01 03:24:58 +05:30
|
|
|
grampsdb.person_map.put('2', "data3")
|
2009-08-19 22:35:39 +05:30
|
|
|
trans.add(0, TXNUPD, '2', None, "data3")
|
2011-02-01 03:24:58 +05:30
|
|
|
grampsdb.person_map.delete('1')
|
2009-08-19 22:35:39 +05:30
|
|
|
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))
|
|
|
|
print grampsdb.person_map
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
testtxn()
|