9810: Fix undo/redo reference updates
Also rewrite to avoid using table maps.
This commit is contained in:
parent
e1dca2bfaa
commit
baff7cddb0
@ -146,9 +146,9 @@ class DbGenericUndo(DbUndo):
|
|||||||
pickle.loads(self.undodb[record_id])
|
pickle.loads(self.undodb[record_id])
|
||||||
|
|
||||||
if key == REFERENCE_KEY:
|
if key == REFERENCE_KEY:
|
||||||
self.undo_reference(new_data, handle, self.mapbase[key])
|
self.undo_reference(new_data, handle)
|
||||||
else:
|
else:
|
||||||
self.undo_data(new_data, handle, self.mapbase[key],
|
self.undo_data(new_data, handle, key,
|
||||||
db.emit, SIGBASE[key])
|
db.emit, SIGBASE[key])
|
||||||
self.db.transaction_backend_commit()
|
self.db.transaction_backend_commit()
|
||||||
except:
|
except:
|
||||||
@ -184,15 +184,22 @@ class DbGenericUndo(DbUndo):
|
|||||||
subitems = transaction.get_recnos(reverse=True)
|
subitems = transaction.get_recnos(reverse=True)
|
||||||
|
|
||||||
# Process all records in the transaction
|
# Process all records in the transaction
|
||||||
for record_id in subitems:
|
try:
|
||||||
(key, trans_type, handle, old_data, new_data) = \
|
self.db.transaction_backend_begin()
|
||||||
pickle.loads(self.undodb[record_id])
|
for record_id in subitems:
|
||||||
|
(key, trans_type, handle, old_data, new_data) = \
|
||||||
|
pickle.loads(self.undodb[record_id])
|
||||||
|
|
||||||
|
if key == REFERENCE_KEY:
|
||||||
|
self.undo_reference(old_data, handle)
|
||||||
|
else:
|
||||||
|
self.undo_data(old_data, handle, key, db.emit, SIGBASE[key])
|
||||||
|
|
||||||
|
self.db.transaction_backend_commit()
|
||||||
|
except:
|
||||||
|
self.db.transaction_backend_abort()
|
||||||
|
raise
|
||||||
|
|
||||||
if key == REFERENCE_KEY:
|
|
||||||
self.undo_reference(old_data, handle, self.mapbase[key])
|
|
||||||
else:
|
|
||||||
self.undo_data(old_data, handle, self.mapbase[key],
|
|
||||||
db.emit, SIGBASE[key])
|
|
||||||
# Notify listeners
|
# Notify listeners
|
||||||
if db.undo_callback:
|
if db.undo_callback:
|
||||||
if self.undo_count > 0:
|
if self.undo_count > 0:
|
||||||
@ -209,6 +216,43 @@ class DbGenericUndo(DbUndo):
|
|||||||
db.undo_history_callback()
|
db.undo_history_callback()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def undo_reference(self, data, handle):
|
||||||
|
"""
|
||||||
|
Helper method to undo a reference map entry
|
||||||
|
"""
|
||||||
|
if data is None:
|
||||||
|
sql = ("DELETE FROM reference " +
|
||||||
|
"WHERE obj_handle = ? AND ref_handle = ?")
|
||||||
|
self.db.dbapi.execute(sql, [handle[0], handle[1]])
|
||||||
|
else:
|
||||||
|
sql = ("INSERT INTO reference " +
|
||||||
|
"(obj_handle, obj_class, ref_handle, ref_class) " +
|
||||||
|
"VALUES(?, ?, ?, ?)")
|
||||||
|
self.db.dbapi.execute(sql, data)
|
||||||
|
|
||||||
|
def undo_data(self, data, handle, obj_key, emit, signal_root):
|
||||||
|
"""
|
||||||
|
Helper method to undo/redo the changes made
|
||||||
|
"""
|
||||||
|
cls = KEY_TO_CLASS_MAP[obj_key]
|
||||||
|
table = cls.lower()
|
||||||
|
if data is None:
|
||||||
|
emit(signal_root + '-delete', ([handle],))
|
||||||
|
sql = "DELETE FROM %s WHERE handle = ?" % table
|
||||||
|
self.db.dbapi.execute(sql, [handle])
|
||||||
|
else:
|
||||||
|
if self.db.has_handle(obj_key, handle):
|
||||||
|
signal = signal_root + '-update'
|
||||||
|
sql = "UPDATE %s SET blob_data = ? WHERE handle = ?" % table
|
||||||
|
self.db.dbapi.execute(sql, [pickle.dumps(data), handle])
|
||||||
|
else:
|
||||||
|
signal = signal_root + '-add'
|
||||||
|
sql = "INSERT INTO %s (handle, blob_data) VALUES (?, ?)" % table
|
||||||
|
self.db.dbapi.execute(sql, [handle, pickle.dumps(data)])
|
||||||
|
obj = self.db.get_table_func(cls)["class_func"].create(data)
|
||||||
|
self.db.update_secondary_values(obj)
|
||||||
|
emit(signal, ([handle],))
|
||||||
|
|
||||||
class Table:
|
class Table:
|
||||||
"""
|
"""
|
||||||
Implements Table interface.
|
Implements Table interface.
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
import time
|
import time
|
||||||
import pickle
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
class DbUndo(metaclass=ABCMeta):
|
class DbUndo(metaclass=ABCMeta):
|
||||||
@ -14,33 +13,16 @@ class DbUndo(metaclass=ABCMeta):
|
|||||||
for use with a real backend.
|
for use with a real backend.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('undodb', 'db', 'mapbase', 'undo_history_timestamp',
|
__slots__ = ('undodb', 'db', 'undo_history_timestamp', 'undoq', 'redoq')
|
||||||
'txn', 'undoq', 'redoq')
|
|
||||||
|
|
||||||
def __init__(self, grampsdb):
|
def __init__(self, db):
|
||||||
"""
|
"""
|
||||||
Class constructor. Set up main instance variables
|
Class constructor. Set up main instance variables
|
||||||
"""
|
"""
|
||||||
self.db = grampsdb
|
self.db = db
|
||||||
self.undoq = deque()
|
self.undoq = deque()
|
||||||
self.redoq = deque()
|
self.redoq = deque()
|
||||||
self.undo_history_timestamp = time.time()
|
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,
|
|
||||||
None,
|
|
||||||
self.db.note_map,
|
|
||||||
self.db.tag_map,
|
|
||||||
self.db.citation_map,
|
|
||||||
)
|
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""
|
"""
|
||||||
@ -49,7 +31,6 @@ class DbUndo(metaclass=ABCMeta):
|
|||||||
self.undoq.clear()
|
self.undoq.clear()
|
||||||
self.redoq.clear()
|
self.redoq.clear()
|
||||||
self.undo_history_timestamp = time.time()
|
self.undo_history_timestamp = time.time()
|
||||||
self.txn = None
|
|
||||||
|
|
||||||
def __enter__(self, value):
|
def __enter__(self, value):
|
||||||
"""
|
"""
|
||||||
@ -142,30 +123,5 @@ class DbUndo(metaclass=ABCMeta):
|
|||||||
return False
|
return False
|
||||||
return self._redo(update_history)
|
return self._redo(update_history)
|
||||||
|
|
||||||
def undo_reference(self, data, handle, db_map):
|
|
||||||
"""
|
|
||||||
Helper method to undo a reference map entry
|
|
||||||
"""
|
|
||||||
if data is None:
|
|
||||||
db_map.delete(handle)
|
|
||||||
else:
|
|
||||||
db_map[handle] = data
|
|
||||||
|
|
||||||
def undo_data(self, data, handle, db_map, emit, signal_root):
|
|
||||||
"""
|
|
||||||
Helper method to undo/redo the changes made
|
|
||||||
"""
|
|
||||||
if data is None:
|
|
||||||
emit(signal_root + '-delete', ([handle],))
|
|
||||||
db_map.delete(handle)
|
|
||||||
else:
|
|
||||||
ex_data = db_map[handle]
|
|
||||||
if ex_data:
|
|
||||||
signal = signal_root + '-update'
|
|
||||||
else:
|
|
||||||
signal = signal_root + '-add'
|
|
||||||
db_map[handle] = data
|
|
||||||
emit(signal, ([handle],))
|
|
||||||
|
|
||||||
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))
|
||||||
|
@ -41,7 +41,8 @@ from gramps.gen.db.dbconst import (DBLOGNAME, DBBACKEND, KEY_TO_NAME_MAP,
|
|||||||
TXNADD, TXNUPD, TXNDEL,
|
TXNADD, TXNUPD, TXNDEL,
|
||||||
PERSON_KEY, FAMILY_KEY, SOURCE_KEY,
|
PERSON_KEY, FAMILY_KEY, SOURCE_KEY,
|
||||||
EVENT_KEY, MEDIA_KEY, PLACE_KEY, NOTE_KEY,
|
EVENT_KEY, MEDIA_KEY, PLACE_KEY, NOTE_KEY,
|
||||||
TAG_KEY, CITATION_KEY, REPOSITORY_KEY)
|
TAG_KEY, CITATION_KEY, REPOSITORY_KEY,
|
||||||
|
REFERENCE_KEY)
|
||||||
from gramps.gen.db.generic import DbGeneric
|
from gramps.gen.db.generic import DbGeneric
|
||||||
from gramps.gen.lib import (Tag, Media, Person, Family, Source,
|
from gramps.gen.lib import (Tag, Media, Person, Family, Source,
|
||||||
Citation, Event, Place, Repository, Note)
|
Citation, Event, Place, Repository, Note)
|
||||||
@ -370,6 +371,8 @@ class DBAPI(DbGeneric):
|
|||||||
if not txn.batch:
|
if not txn.batch:
|
||||||
# Now, emit signals:
|
# Now, emit signals:
|
||||||
for (obj_type_val, txn_type_val) in list(txn):
|
for (obj_type_val, txn_type_val) in list(txn):
|
||||||
|
if obj_type_val == REFERENCE_KEY:
|
||||||
|
continue
|
||||||
if txn_type_val == TXNDEL:
|
if txn_type_val == TXNDEL:
|
||||||
handles = [handle for (handle, data) in
|
handles = [handle for (handle, data) in
|
||||||
txn[(obj_type_val, txn_type_val)]]
|
txn[(obj_type_val, txn_type_val)]]
|
||||||
@ -721,7 +724,7 @@ class DBAPI(DbGeneric):
|
|||||||
pickle.dumps(obj.serialize())])
|
pickle.dumps(obj.serialize())])
|
||||||
self.update_secondary_values(obj)
|
self.update_secondary_values(obj)
|
||||||
if not trans.batch:
|
if not trans.batch:
|
||||||
self.update_backlinks(obj)
|
self.update_backlinks(obj, trans)
|
||||||
if old_data:
|
if old_data:
|
||||||
trans.add(obj_key, TXNUPD, obj.handle,
|
trans.add(obj_key, TXNUPD, obj.handle,
|
||||||
old_data,
|
old_data,
|
||||||
@ -941,21 +944,48 @@ class DBAPI(DbGeneric):
|
|||||||
[str(attr.type) for attr in media.attribute_list
|
[str(attr.type) for attr in media.attribute_list
|
||||||
if attr.type.is_custom() and str(attr.type)])
|
if attr.type.is_custom() and str(attr.type)])
|
||||||
|
|
||||||
def update_backlinks(self, obj):
|
def update_backlinks(self, obj, transaction):
|
||||||
# First, delete the current references:
|
|
||||||
self.dbapi.execute("DELETE FROM reference WHERE obj_handle = ?;",
|
# Find existing references
|
||||||
|
sql = ("SELECT ref_class, ref_handle " +
|
||||||
|
"FROM reference WHERE obj_handle = ?")
|
||||||
|
self.dbapi.execute(sql, [obj.handle])
|
||||||
|
existing_references = set(self.dbapi.fetchall())
|
||||||
|
|
||||||
|
# Once we have the list of rows that already have a reference
|
||||||
|
# we need to compare it with the list of objects that are
|
||||||
|
# still references from the primary object.
|
||||||
|
current_references = set(obj.get_referenced_handles_recursively())
|
||||||
|
no_longer_required_references = existing_references.difference(
|
||||||
|
current_references)
|
||||||
|
new_references = current_references.difference(existing_references)
|
||||||
|
|
||||||
|
# Delete the existing references
|
||||||
|
self.dbapi.execute("DELETE FROM reference WHERE obj_handle = ?",
|
||||||
[obj.handle])
|
[obj.handle])
|
||||||
# Now, add the current ones:
|
|
||||||
references = set(obj.get_referenced_handles_recursively())
|
# Now, add the current ones
|
||||||
for (ref_class_name, ref_handle) in references:
|
for (ref_class_name, ref_handle) in current_references:
|
||||||
self.dbapi.execute("""INSERT INTO reference
|
sql = ("INSERT INTO reference " +
|
||||||
(obj_handle, obj_class, ref_handle, ref_class)
|
"(obj_handle, obj_class, ref_handle, ref_class)" +
|
||||||
VALUES(?, ?, ?, ?);""",
|
"VALUES(?, ?, ?, ?)")
|
||||||
[obj.handle,
|
self.dbapi.execute(sql, [obj.handle, obj.__class__.__name__,
|
||||||
obj.__class__.__name__,
|
ref_handle, ref_class_name])
|
||||||
ref_handle,
|
|
||||||
ref_class_name])
|
if not transaction.batch:
|
||||||
# This function is followed by a commit.
|
# Add new references to the transaction
|
||||||
|
for (ref_class_name, ref_handle) in new_references:
|
||||||
|
key = (obj.handle, ref_handle)
|
||||||
|
data = (obj.handle, obj.__class__.__name__,
|
||||||
|
ref_handle, ref_class_name)
|
||||||
|
transaction.add(REFERENCE_KEY, TXNADD, key, None, data)
|
||||||
|
|
||||||
|
# Add old references to the transaction
|
||||||
|
for (ref_class_name, ref_handle) in no_longer_required_references:
|
||||||
|
key = (obj.handle, ref_handle)
|
||||||
|
old_data = (obj.handle, obj.__class__.__name__,
|
||||||
|
ref_handle, ref_class_name)
|
||||||
|
transaction.add(REFERENCE_KEY, TXNDEL, key, old_data, None)
|
||||||
|
|
||||||
def _do_remove(self, handle, transaction, obj_key):
|
def _do_remove(self, handle, transaction, obj_key):
|
||||||
if isinstance(handle, bytes):
|
if isinstance(handle, bytes):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user