DB-API: added undo-redo infrastructure

This commit is contained in:
Doug Blank 2015-05-25 17:53:14 -04:00
parent 72cfc3f826
commit 5e96f8a72e
3 changed files with 141 additions and 34 deletions

View File

@ -88,6 +88,7 @@ from .base import *
from .dbconst import * from .dbconst import *
from .txn import * from .txn import *
from .exceptions import * from .exceptions import *
from .undoredo import *
def find_surname_name(key, data): def find_surname_name(key, data):
""" """

View File

@ -1,4 +1,10 @@
#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
import time import time
import pickle
from collections import deque from collections import deque
class DbUndo(object): class DbUndo(object):
@ -19,6 +25,20 @@ class DbUndo(object):
self.redoq = deque() self.redoq = deque()
self.undo_history_timestamp = time.time() self.undo_history_timestamp = time.time()
self.txn = None 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): def clear(self):
""" """
@ -85,6 +105,16 @@ class DbUndo(object):
""" """
raise NotImplementedError raise NotImplementedError
def __redo(self, update_history):
"""
"""
raise NotImplementedError
def __undo(self, update_history):
"""
"""
raise NotImplementedError
def commit(self, txn, msg): def commit(self, txn, msg):
""" """
Commit the transaction to the undo/redo database. "txn" should be Commit the transaction to the undo/redo database. "txn" should be
@ -110,27 +140,40 @@ class DbUndo(object):
return False return False
return self.__redo(update_history) 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 Helper method to undo a reference map entry
transaction. It also catches bsddb errors and raises an exception
as appropriate
""" """
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): except DBERRS as msg:
""" self.db._log_error()
Access the last undone transaction, and revert the data to the state raise DbError(msg)
before the transaction was undone.
"""
pass
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 Helper method to undo/redo the changes made
state before the transaction was committed.
""" """
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)) undo_count = property(lambda self:len(self.undoq))
redo_count = property(lambda self:len(self.redoq)) redo_count = property(lambda self:len(self.redoq))

View File

@ -19,12 +19,11 @@ import shutil
import gramps import gramps
from gramps.gen.const import GRAMPS_LOCALE as glocale from gramps.gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext _ = 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, KEY_TO_NAME_MAP, KEY_TO_CLASS_MAP,
CLASS_TO_KEY_MAP) CLASS_TO_KEY_MAP)
from gramps.gen.utils.callback import Callback from gramps.gen.utils.callback import Callback
from gramps.gen.updatecallback import UpdateCallback from gramps.gen.updatecallback import UpdateCallback
from gramps.gen.db.undoredo import DbUndo
from gramps.gen.db.dbconst import * from gramps.gen.db.dbconst import *
from gramps.gen.db import (PERSON_KEY, from gramps.gen.db import (PERSON_KEY,
FAMILY_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, os.utime(f.fileno() if os.utime in os.supports_fd else fname,
dir_fd=None if os.supports_fd else dir_fd, **kwargs) 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): class Environment(object):
""" """
Implements the Environment API. Implements the Environment API.
@ -427,6 +491,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
self.set_repository_id_prefix('R%04d') self.set_repository_id_prefix('R%04d')
self.set_note_id_prefix('N%04d') self.set_note_id_prefix('N%04d')
# ---------------------------------- # ----------------------------------
self.undodb = None
self.id_trans = DBAPITxn("ID Transaction", self) self.id_trans = DBAPITxn("ID Transaction", self)
self.fid_trans = DBAPITxn("FID Transaction", self) self.fid_trans = DBAPITxn("FID Transaction", self)
self.pid_trans = DBAPITxn("PID 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") contains_func="has_gramps_id_func")
self.tag_map = Map(Table(self._tables["Tag"])) self.tag_map = Map(Table(self._tables["Tag"]))
self.metadata = Map(Table({"cursor_func": lambda: MetaCursor()})) 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.undo_callback = None
self.redo_callback = None self.redo_callback = None
self.undo_history_callback = None self.undo_history_callback = None
self.modified = 0 self.modified = 0
self.txn = DBAPITxn("DBAPI Transaction", self) self.txn = DBAPITxn("DBAPI Transaction", self)
self.transaction = None self.transaction = None
self.undodb = DbUndo(self)
self.abort_possible = False self.abort_possible = False
self._bm_changes = 0 self._bm_changes = 0
self._directory = directory self._directory = directory
@ -984,7 +1042,7 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
return (handle for handle in self.family_map.keys()) return (handle for handle in self.family_map.keys())
def get_tag_from_name(self, name): 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(): for data in self.tag_map.values():
tag = Tag.create(data) tag = Tag.create(data)
if tag.name == name: if tag.name == name:
@ -2244,6 +2302,11 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
# surname list # surname list
self.surname_list = self.get_metadata('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, def set_prefixes(self, person, media, family, source, citation,
place, event, repository, note): place, event, repository, note):
@ -2576,22 +2639,22 @@ class DBAPI(DbWriteBase, DbReadBase, UpdateCallback, Callback):
return glocale.sort_key(tag.get_name()) return glocale.sort_key(tag.get_name())
def backup(self): def backup(self):
## FIXME """
If you wish to support an optional backup routine, put it here.
"""
pass pass
def restore(self): def restore(self):
## FIXME """
If you wish to support an optional restore routine, put it here.
"""
pass pass
def get_undodb(self): def get_undodb(self):
## FIXME return self.undodb
return None
def undo(self, update_history=True): def undo(self, update_history=True):
## FIXME return self.undodb.undo(update_history)
pass
def redo(self, update_history=True): def redo(self, update_history=True):
## FIXME return self.undodb.redo(update_history)
pass