fix(qt): auto-close Plugins manager, read-only field styling, RLock-safe heirs persistence
GUI / plugin lifecycle: - Auto-close Electrum's native 'Electrum Plugins' manager dialog after the BAL plugin is hot-enabled. Electrum 4.7.x no longer calls the old init_qt hook, so the close is now triggered from the create_status_bar, init_menubar and load_wallet hooks (fired when reload_windows() recreates the window). - Robust dialog matching (isinstance / class name / localized window title) to cope with zipimport module-identity mismatches. - Robust dismissal of the modal dialog (reject()/done()/close()) with a retry schedule [400, 800, 1500] ms; if it still cannot be closed, fall back to bringing it to the front (showNormal/raise_/activateWindow) so it never lingers hidden in the background. Counting only visible top-levels avoids treating an already-closed dialog as still open. Read-only field styling: - Paint the locked Delivery time / Check Alive date editors and the mining-fee spinbox with a light-grey background (#f0f0f0) so the user can see at a glance that they are not editable outside the 'Build your will' wizard; the styling is cleared when the fields are made editable again. Pickle/RLock crash on 'Build will': - heirs.save() now sanitises the heirs mapping via _json_safe() before handing it to json_db.put(), which deep-copies the value. A live runtime object (holding a threading.RLock) slipping into an heir value previously raised 'TypeError: cannot pickle _thread.RLock object' and aborted the task; such values are now coerced to str and logged with their path. - init_heirs_to_locktime() coerces the locktime to a plain serializable scalar. - log_error() now accepts both a sys.exc_info() triple and a single exception instance, fixing the secondary 'TypeError object is not subscriptable' that masked the real error.
This commit is contained in:
@@ -306,6 +306,42 @@ def get_change_output(wallet, in_amount, out_amount, fee):
|
||||
return out
|
||||
|
||||
|
||||
def _json_safe(value, _path="heirs", _depth=0):
|
||||
"""Return a JSON-serializable deep copy of *value*.
|
||||
|
||||
The wallet DB persists the heirs dict via ``json_db.put``, which calls
|
||||
``copy.deepcopy`` on the value. If any nested element is a live runtime
|
||||
object (e.g. one holding a ``threading.RLock``), deepcopy raises
|
||||
``TypeError: cannot pickle '_thread.RLock' object`` and the whole
|
||||
"Build will" task fails.
|
||||
|
||||
To make persistence robust we coerce the structure to plain
|
||||
JSON-compatible types (dict / list / str / int / float / bool / None).
|
||||
Anything else is converted to ``str(value)`` and logged with its path so
|
||||
the offending field can be identified, instead of crashing the task.
|
||||
"""
|
||||
# Primitive JSON scalars are kept as-is.
|
||||
if value is None or isinstance(value, (bool, int, float, str)):
|
||||
return value
|
||||
if isinstance(value, dict):
|
||||
return {
|
||||
str(k): _json_safe(v, "{}[{!r}]".format(_path, k), _depth + 1)
|
||||
for k, v in value.items()
|
||||
}
|
||||
if isinstance(value, (list, tuple)):
|
||||
return [
|
||||
_json_safe(v, "{}[{}]".format(_path, i), _depth + 1)
|
||||
for i, v in enumerate(value)
|
||||
]
|
||||
# Unexpected runtime object: do not let it reach deepcopy. Log where it
|
||||
# was found so the real source can be fixed, then store a safe string.
|
||||
_logger.error(
|
||||
"heirs.save: non-serializable value at {} (type={}); coercing to str. "
|
||||
"value={!r}".format(_path, type(value).__name__, value)
|
||||
)
|
||||
return str(value)
|
||||
|
||||
|
||||
class Heirs(dict, Logger):
|
||||
|
||||
def __init__(self, wallet):
|
||||
@@ -322,7 +358,11 @@ class Heirs(dict, Logger):
|
||||
invalidate_inheritance_transactions(wallet)
|
||||
|
||||
def save(self):
|
||||
self.db.put("heirs", dict(self))
|
||||
# Sanitise the heirs mapping before handing it to the wallet DB: this
|
||||
# guarantees only JSON-serializable values are stored and prevents the
|
||||
# "cannot pickle '_thread.RLock' object" failure that aborted the
|
||||
# Build-will task when a runtime object slipped into an heir value.
|
||||
self.db.put("heirs", _json_safe(dict(self)))
|
||||
|
||||
def import_file(self, path):
|
||||
data = read_json_file(path)
|
||||
|
||||
Reference in New Issue
Block a user