Basic infrastructure for Undo/Redo

This commit is contained in:
Doug Blank 2015-05-14 12:30:30 -04:00
parent 6e0b8ccf86
commit 74330122bd
3 changed files with 143 additions and 1 deletions

136
gramps/gen/db/undoredo.py Normal file
View File

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

View File

@ -37,6 +37,7 @@ import logging
# #
#------------------------------------------------------------------------ #------------------------------------------------------------------------
from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn, KEY_TO_NAME_MAP 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.db.dbconst import *
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
@ -390,6 +391,8 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
self.modified = 0 self.modified = 0
self.txn = DictionaryTxn("DbDictionary Transaction", self) self.txn = DictionaryTxn("DbDictionary Transaction", self)
self.transaction = None self.transaction = None
self.undodb = DbUndo(self)
self.abort_possible = False
self._directory = directory self._directory = directory
if directory: if directory:
self.load(directory) self.load(directory)
@ -1212,7 +1215,7 @@ class DictionaryDb(DbWriteBase, DbReadBase, UpdateCallback, Callback):
if emit: if emit:
self.emit(emit, ([tag.handle],)) 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 emit = None
if not trans.batch: if not trans.batch:
if media.handle in self.media_map: if media.handle in self.media_map:

View File

@ -44,6 +44,7 @@ from gramps.gen.lib import (Person, Family, Event, Place, Repository,
Citation, Source, Note, MediaObject, Tag, Citation, Source, Note, MediaObject, Tag,
Researcher) Researcher)
from gramps.gen.db import DbReadBase, DbWriteBase, DbTxn 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.utils.callback import Callback
from gramps.gen.updatecallback import UpdateCallback from gramps.gen.updatecallback import UpdateCallback
from gramps.gen.db import (PERSON_KEY, from gramps.gen.db import (PERSON_KEY,
@ -335,6 +336,8 @@ class DbDjango(DbWriteBase, DbReadBase, UpdateCallback, Callback):
self.import_cache = {} self.import_cache = {}
self.use_import_cache = False self.use_import_cache = False
self.use_db_cache = True self.use_db_cache = True
self.undodb = DbUndo(self)
self.abort_possible = False
self._directory = directory self._directory = directory
if directory: if directory:
self.load(directory) self.load(directory)