DB-API: added undo-redo infrastructure

This commit is contained in:
Doug Blank 2015-05-25 17:53:14 -04:00
parent d5c9c5114a
commit 1b89239323
3 changed files with 141 additions and 34 deletions

View File

@ -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):
"""

View File

@ -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))

View File

@ -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)