From 74330122bd90dffbc502b10d6fd07afb5aa8130d Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Thu, 14 May 2015 12:30:30 -0400 Subject: [PATCH] Basic infrastructure for Undo/Redo --- gramps/gen/db/undoredo.py | 136 ++++++++++++++++++++++++ gramps/plugins/database/dictionarydb.py | 5 +- gramps/plugins/database/djangodb.py | 3 + 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 gramps/gen/db/undoredo.py diff --git a/gramps/gen/db/undoredo.py b/gramps/gen/db/undoredo.py new file mode 100644 index 000000000..b12735093 --- /dev/null +++ b/gramps/gen/db/undoredo.py @@ -0,0 +1,136 @@ +import time +from collections import deque + +class DbUndo(object): + """ + Base class for the Gramps undo/redo manager. Needs to be subclassed + for use with a real backend. + """ + + __slots__ = ('undodb', 'db', 'mapbase', 'undo_history_timestamp', + 'txn', 'undoq', 'redoq') + + def __init__(self, grampsdb): + """ + Class constructor. Set up main instance variables + """ + self.db = grampsdb + self.undoq = deque() + self.redoq = deque() + self.undo_history_timestamp = time.time() + self.txn = None + + def clear(self): + """ + Clear the undo/redo list (but not the backing storage) + """ + self.undoq.clear() + self.redoq.clear() + 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 transaction class + """ + txn.set_description(msg) + txn.timestamp = time.time() + self.undoq.append(txn) + + def undo(self, update_history=True): + """ + Undo a previously committed transaction + """ + if self.db.readonly or self.undo_count == 0: + return False + return self.__undo(update_history) + + def redo(self, update_history=True): + """ + Redo a previously committed, then undone, transaction + """ + if self.db.readonly or self.redo_count == 0: + return False + return self.__redo(update_history) + + def undoredo(func): + """ + Decorator function to wrap undo and redo operations within a bsddb + transaction. It also catches bsddb errors and raises an exception + as appropriate + """ + pass + + def __redo(self, update_history=True): + """ + Access the last undone transaction, and revert the data to the state + before the transaction was undone. + """ + pass + + def __undo(self, db=None, update_history=True): + """ + Access the last committed transaction, and revert the data to the + state before the transaction was committed. + """ + pass + + undo_count = property(lambda self:len(self.undoq)) + redo_count = property(lambda self:len(self.redoq)) diff --git a/gramps/plugins/database/dictionarydb.py b/gramps/plugins/database/dictionarydb.py index 5c167fe48..09748295a 100644 --- a/gramps/plugins/database/dictionarydb.py +++ b/gramps/plugins/database/dictionarydb.py @@ -37,6 +37,7 @@ import logging # #------------------------------------------------------------------------ from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP +from gramps.gen.db.undoredo import DbUndo from gramps.gen.db.dbconst import * from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback @@ -390,6 +391,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.modified = 0 self.txn = DictionaryTxn("DbDictionary Transaction", self) self.transaction = None + self.undodb = DbUndo(self) + self.abort_possible = False self._directory = directory if directory: self.load(directory) @@ -1212,7 +1215,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback): if emit: self.emit(emit, ([tag.handle],)) - def commit_media_object(self, media, transaction, change_time=None): + def commit_media_object(self, media, trans, change_time=None): emit = None if not trans.batch: if media.handle in self.media_map: diff --git a/gramps/plugins/database/djangodb.py b/gramps/plugins/database/djangodb.py index 852afe779..0a88733cd 100644 --- a/gramps/plugins/database/djangodb.py +++ b/gramps/plugins/database/djangodb.py @@ -44,6 +44,7 @@ from gramps.gen.lib import (Person, Family, Event, Place, Repository, Citation, Source, Note, MediaObject, Tag, Researcher) from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn +from gramps.gen.db.undoredo import DbUndo from gramps.gen.utils.callback import Callback from gramps.gen.updatecallback import UpdateCallback from gramps.gen.db import (PERSON_KEY, @@ -335,6 +336,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback): self.import_cache = {} self.use_import_cache = False self.use_db_cache = True + self.undodb = DbUndo(self) + self.abort_possible = False self._directory = directory if directory: self.load(directory)