From 5e96f8a72e610b3800d5af1f1da4f437f65f7342 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Mon, 25 May 2015 17:53:14 -0400 Subject: [PATCH] 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)