Guarantee order on db emits Partial fix #10068

This commit is contained in:
prculley 2017-06-15 16:24:41 -05:00
parent a1205009f4
commit 48eb507f98
4 changed files with 123 additions and 96 deletions

View File

@ -44,7 +44,8 @@ import glob
from . import (DbReadBase, DbWriteBase, DbUndo, DBLOGNAME, DBUNDOFN, from . import (DbReadBase, DbWriteBase, DbUndo, DBLOGNAME, DBUNDOFN,
KEY_TO_CLASS_MAP, REFERENCE_KEY, PERSON_KEY, FAMILY_KEY, KEY_TO_CLASS_MAP, REFERENCE_KEY, PERSON_KEY, FAMILY_KEY,
CITATION_KEY, SOURCE_KEY, EVENT_KEY, MEDIA_KEY, PLACE_KEY, CITATION_KEY, SOURCE_KEY, EVENT_KEY, MEDIA_KEY, PLACE_KEY,
REPOSITORY_KEY, NOTE_KEY, TAG_KEY, TXNADD, TXNDEL) REPOSITORY_KEY, NOTE_KEY, TAG_KEY, TXNADD, TXNUPD, TXNDEL,
KEY_TO_NAME_MAP)
from ..errors import HandleError from ..errors import HandleError
from ..utils.callback import Callback from ..utils.callback import Callback
from ..updatecallback import UpdateCallback from ..updatecallback import UpdateCallback
@ -132,6 +133,8 @@ class DbGenericUndo(DbUndo):
transaction = txn transaction = txn
db = self.db db = self.db
subitems = transaction.get_recnos() subitems = transaction.get_recnos()
# sigs[obj_type][trans_type]
sigs = [[[] for trans_type in range(3)] for key in range(11)]
# Process all records in the transaction # Process all records in the transaction
try: try:
@ -144,14 +147,10 @@ class DbGenericUndo(DbUndo):
self.undo_reference(new_data, handle) self.undo_reference(new_data, handle)
else: else:
self.undo_data(new_data, handle, key) self.undo_data(new_data, handle, key)
sigs[key][trans_type].append(handle)
# now emit the signals # now emit the signals
for record_id in subitems: self.undo_sigs(sigs, False)
(key, trans_type, handle, old_data, new_data) = \
pickle.loads(self.undodb[record_id])
if key != REFERENCE_KEY:
self.undo_signals(trans_type, handle,
db.emit, SIGBASE[key], False)
self.db._txn_commit() self.db._txn_commit()
except: except:
self.db._txn_abort() self.db._txn_abort()
@ -183,6 +182,8 @@ class DbGenericUndo(DbUndo):
transaction = txn transaction = txn
db = self.db db = self.db
subitems = transaction.get_recnos(reverse=True) subitems = transaction.get_recnos(reverse=True)
# sigs[obj_type][trans_type]
sigs = [[[] for trans_type in range(3)] for key in range(11)]
# Process all records in the transaction # Process all records in the transaction
try: try:
@ -195,14 +196,10 @@ class DbGenericUndo(DbUndo):
self.undo_reference(old_data, handle) self.undo_reference(old_data, handle)
else: else:
self.undo_data(old_data, handle, key) self.undo_data(old_data, handle, key)
sigs[key][trans_type].append(handle)
# now emit the signals # now emit the signals
for record_id in subitems: self.undo_sigs(sigs, True)
(key, trans_type, handle, old_data, new_data) = \
pickle.loads(self.undodb[record_id])
if key != REFERENCE_KEY:
self.undo_signals(trans_type, handle,
db.emit, SIGBASE[key], True)
self.db._txn_commit() self.db._txn_commit()
except: except:
self.db._txn_abort() self.db._txn_abort()
@ -257,19 +254,34 @@ class DbGenericUndo(DbUndo):
obj = self.db._get_table_func(cls)["class_func"].create(data) obj = self.db._get_table_func(cls)["class_func"].create(data)
self.db._update_secondary_values(obj) self.db._update_secondary_values(obj)
def undo_signals(self, trans_type, handle, emit, signal_root, reverse): def undo_sigs(self, sigs, undo):
""" """
Helper method to undo/redo the changes made Helper method to undo/redo the signals for changes made
We want to do deletes and adds first
Note that if 'undo' we swap emits
""" """
if ((not reverse) and trans_type == TXNADD) \ for trans_type in [TXNDEL, TXNADD, TXNUPD]:
or (reverse and trans_type == TXNDEL): for obj_type in range(11):
typ = '-add' handles = sigs[obj_type][trans_type]
elif not reverse and trans_type == TXNDEL \ if handles:
or reverse and trans_type == TXNADD: if not undo and trans_type == TXNDEL \
typ = '-delete' or undo and trans_type == TXNADD:
else: # TXNUPD typ = '-delete'
typ = '-update' else:
emit(signal_root + typ, ([handle],)) # don't update a handle if its been deleted, and note
# that 'deleted' handles are in the 'add' list if we
# are undoing
handles = [handle for handle in handles
if handle not in
sigs[obj_type][TXNADD if undo else TXNDEL]]
if ((not undo) and trans_type == TXNADD) \
or (undo and trans_type == TXNDEL):
typ = '-add'
else: # TXNUPD
typ = '-update'
if handles:
self.db.emit(KEY_TO_NAME_MAP[obj_type] + typ,
(handles,))
class Cursor: class Cursor:
def __init__(self, iterator): def __init__(self, iterator):

View File

@ -51,7 +51,8 @@ _ = glocale.translation.gettext
# Gramps modules # Gramps modules
# #
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
from gramps.gen.db.dbconst import * from gramps.gen.db.dbconst import (REFERENCE_KEY, KEY_TO_NAME_MAP, TXNDEL,
TXNADD, TXNUPD)
from . import BSDDBTxn from . import BSDDBTxn
from gramps.gen.errors import DbError from gramps.gen.errors import DbError
@ -226,8 +227,9 @@ class DbUndo:
txn = self.undoq.pop() txn = self.undoq.pop()
self.redoq.append(txn) self.redoq.append(txn)
transaction = txn transaction = txn
db = self.db
subitems = transaction.get_recnos(reverse=True) subitems = transaction.get_recnos(reverse=True)
# sigs[obj_type][trans_type]
sigs = [[[] for trans_type in range(3)] for key in range(11)]
# Process all records in the transaction # Process all records in the transaction
for record_id in subitems: for record_id in subitems:
@ -238,29 +240,25 @@ class DbUndo:
self.undo_reference(old_data, handle, self.mapbase[key]) self.undo_reference(old_data, handle, self.mapbase[key])
else: else:
self.undo_data(old_data, handle, self.mapbase[key]) self.undo_data(old_data, handle, self.mapbase[key])
handle = handle.decode('utf-8')
sigs[key][trans_type].append(handle)
# now emit the signals # now emit the signals
for record_id in subitems: self.undo_sigs(sigs, True)
(key, trans_type, handle, old_data, new_data) = \
pickle.loads(self.undodb[record_id])
if key != REFERENCE_KEY:
self.undo_signals(trans_type, handle,
db.emit, _SIGBASE[key], True)
# Notify listeners # Notify listeners
if db.undo_callback: if self.db.undo_callback:
if self.undo_count > 0: if self.undo_count > 0:
db.undo_callback(_("_Undo %s") self.db.undo_callback(_("_Undo %s")
% self.undoq[-1].get_description()) % self.undoq[-1].get_description())
else: else:
db.undo_callback(None) self.db.undo_callback(None)
if db.redo_callback: if self.db.redo_callback:
db.redo_callback(_("_Redo %s") self.db.redo_callback(_("_Redo %s")
% transaction.get_description()) % transaction.get_description())
if update_history and db.undo_history_callback: if update_history and self.db.undo_history_callback:
db.undo_history_callback() self.db.undo_history_callback()
return True return True
@undoredo @undoredo
@ -272,8 +270,9 @@ class DbUndo:
txn = self.redoq.pop() txn = self.redoq.pop()
self.undoq.append(txn) self.undoq.append(txn)
transaction = txn transaction = txn
db = self.db
subitems = transaction.get_recnos() subitems = transaction.get_recnos()
# sigs[obj_type][trans_type]
sigs = [[[] for trans_type in range(3)] for key in range(11)]
# Process all records in the transaction # Process all records in the transaction
for record_id in subitems: for record_id in subitems:
@ -284,29 +283,26 @@ class DbUndo:
self.undo_reference(new_data, handle, self.mapbase[key]) self.undo_reference(new_data, handle, self.mapbase[key])
else: else:
self.undo_data(new_data, handle, self.mapbase[key]) self.undo_data(new_data, handle, self.mapbase[key])
# Process all signals in the transaction handle = handle.decode('utf-8')
for record_id in subitems: sigs[key][trans_type].append(handle)
(key, trans_type, handle, old_data, new_data) = \ # now emit the signals
pickle.loads(self.undodb[record_id]) self.undo_sigs(sigs, False)
if key != REFERENCE_KEY:
self.undo_signals(trans_type, handle,
db.emit, _SIGBASE[key], False)
# Notify listeners # Notify listeners
if db.undo_callback: if self.db.undo_callback:
db.undo_callback(_("_Undo %s") self.db.undo_callback(_("_Undo %s")
% transaction.get_description()) % transaction.get_description())
if db.redo_callback: if self.db.redo_callback:
if self.redo_count > 1: if self.redo_count > 1:
new_transaction = self.redoq[-2] new_transaction = self.redoq[-2]
db.redo_callback(_("_Redo %s") self.db.redo_callback(_("_Redo %s")
% new_transaction.get_description()) % new_transaction.get_description())
else: else:
db.redo_callback(None) self.db.redo_callback(None)
if update_history and db.undo_history_callback: if update_history and self.db.undo_history_callback:
db.undo_history_callback() self.db.undo_history_callback()
return True return True
def undo_reference(self, data, handle, db_map): def undo_reference(self, data, handle, db_map):
@ -337,19 +333,34 @@ class DbUndo:
self.db._log_error() self.db._log_error()
raise DbError(msg) raise DbError(msg)
def undo_signals(self, trans_type, handle, emit, signal_root, reverse): def undo_sigs(self, sigs, undo):
""" """
Helper method to undo/redo the changes made Helper method to undo/redo the signals for changes made
We want to do deletes and adds first
Note that if 'undo' we swap emits
""" """
if ((not reverse) and trans_type == TXNADD) \ for trans_type in [TXNDEL, TXNADD, TXNUPD]:
or (reverse and trans_type == TXNDEL): for obj_type in range(11):
typ = '-add' handles = sigs[obj_type][trans_type]
elif not reverse and trans_type == TXNDEL \ if handles:
or reverse and trans_type == TXNADD: if not undo and trans_type == TXNDEL \
typ = '-delete' or undo and trans_type == TXNADD:
else: # TXNUPD typ = '-delete'
typ = '-update' else:
emit(signal_root + typ, ([handle.decode('utf-8')],)) # don't update a handle if its been deleted, and note
# that 'deleted' handles are in the 'add' list if we
# are undoing
handles = [handle for handle in handles
if handle not in
sigs[obj_type][TXNADD if undo else TXNDEL]]
if ((not undo) and trans_type == TXNADD) \
or (undo and trans_type == TXNDEL):
typ = '-add'
else: # TXNUPD
typ = '-update'
if handles:
self.db.emit(KEY_TO_NAME_MAP[obj_type] + typ,
(handles,))
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))

View File

@ -2034,11 +2034,11 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
self.txn = None self.txn = None
self.env.log_flush() self.env.log_flush()
if not transaction.batch: if not transaction.batch:
emit = self.__emit # do deletes and adds first
for obj_type, obj_name in KEY_TO_NAME_MAP.items(): for trans_type in [TXNDEL, TXNADD, TXNUPD]:
emit(transaction, obj_type, TXNADD, obj_name, '-add') for obj_type in range(11):
emit(transaction, obj_type, TXNUPD, obj_name, '-update') if obj_type != REFERENCE_KEY:
emit(transaction, obj_type, TXNDEL, obj_name, '-delete') self.__emit(transaction, obj_type, trans_type)
self.transaction = None self.transaction = None
transaction.clear() transaction.clear()
self.undodb.commit(transaction, msg) self.undodb.commit(transaction, msg)
@ -2051,21 +2051,23 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
hex(id(self)), hex(id(self)),
transaction.get_description())) transaction.get_description()))
def __emit(self, transaction, obj_type, trans_type, obj, suffix): def __emit(self, transaction, obj_type, trans_type):
""" """
Define helper function to do the actual emits Define helper function to do the actual emits
""" """
if (obj_type, trans_type) in transaction: if (obj_type, trans_type) in transaction:
if trans_type == TXNDEL: if trans_type == TXNDEL:
handles = [handle.decode('utf-8') for handle, data in handles = [handle.decode('utf-8') for handle, data in
transaction[(obj_type, trans_type)]] transaction[(obj_type, trans_type)]]
else: else:
handles = [handle.decode('utf-8') for handle, data in handles = [handle.decode('utf-8') for handle, data in
transaction[(obj_type, trans_type)] transaction[(obj_type, trans_type)]
if (handle, None) not in transaction[(obj_type, if (handle, None) not in transaction[(obj_type,
TXNDEL)]] TXNDEL)]]
if handles: if handles:
self.emit(obj + suffix, (handles, )) self.emit(KEY_TO_NAME_MAP[obj_type] +
['-add', '-update', '-delete'][trans_type],
(handles, ))
def transaction_abort(self, transaction): def transaction_abort(self, transaction):
""" """

View File

@ -308,21 +308,23 @@ class DBAPI(DbGeneric):
self.dbapi.commit() self.dbapi.commit()
if not txn.batch: if not txn.batch:
# Now, emit signals: # Now, emit signals:
for (obj_type_val, txn_type_val) in list(txn): # do deletes and adds first
if obj_type_val == REFERENCE_KEY: for trans_type in [TXNDEL, TXNADD, TXNUPD]:
continue for obj_type in range(11):
if txn_type_val == TXNDEL: if obj_type != REFERENCE_KEY and \
handles = [handle for (handle, data) in (obj_type, trans_type) in txn:
txn[(obj_type_val, txn_type_val)]] if trans_type == TXNDEL:
else: handles = [handle for (handle, data) in
handles = [handle for (handle, data) in txn[(obj_type, trans_type)]]
txn[(obj_type_val, txn_type_val)] else:
if (handle, None) handles = [handle for (handle, data) in
not in txn[(obj_type_val, TXNDEL)]] txn[(obj_type, trans_type)]
if handles: if (handle, None)
signal = KEY_TO_NAME_MAP[ not in txn[(obj_type, TXNDEL)]]
obj_type_val] + action[txn_type_val] if handles:
self.emit(signal, (handles, )) signal = KEY_TO_NAME_MAP[
obj_type] + action[trans_type]
self.emit(signal, (handles, ))
self.transaction = None self.transaction = None
msg = txn.get_description() msg = txn.get_description()
self.undodb.commit(txn, msg) self.undodb.commit(txn, msg)