DB-API: added undo-redo infrastructure
This commit is contained in:
parent
72cfc3f826
commit
5e96f8a72e
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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))
|
||||||
|
@ -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:
|
||||||
@ -2245,6 +2303,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):
|
||||||
self.set_person_id_prefix(person)
|
self.set_person_id_prefix(person)
|
||||||
@ -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
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user