Merge pull request #9791 from f321x/structured_plugin_storage
plugins: structure plugin storage in wallet db and prune uninstalled plugins data
This commit is contained in:
@@ -189,6 +189,11 @@ class StoredDict(dict):
|
|||||||
self.db.add_patch({'op': 'remove', 'path': key_path(self.path, key)})
|
self.db.add_patch({'op': 'remove', 'path': key_path(self.path, key)})
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
def setdefault(self, key, default = None, /):
|
||||||
|
if key not in self:
|
||||||
|
self.__setitem__(key, default)
|
||||||
|
return self[key]
|
||||||
|
|
||||||
|
|
||||||
class StoredList(list):
|
class StoredList(list):
|
||||||
|
|
||||||
|
|||||||
@@ -654,6 +654,10 @@ class BasePlugin(Logger):
|
|||||||
def read_file(self, filename: str) -> bytes:
|
def read_file(self, filename: str) -> bytes:
|
||||||
return self.parent.read_file(self.name, filename)
|
return self.parent.read_file(self.name, filename)
|
||||||
|
|
||||||
|
def get_storage(self, wallet: 'Abstract_Wallet') -> dict:
|
||||||
|
"""Returns a dict which is persisted in the per-wallet database."""
|
||||||
|
plugin_storage = wallet.db.get_plugin_storage()
|
||||||
|
return plugin_storage.setdefault(self.name, {})
|
||||||
|
|
||||||
class DeviceUnpairableError(UserFacingException): pass
|
class DeviceUnpairableError(UserFacingException): pass
|
||||||
class HardwarePluginLibraryUnavailable(Exception): pass
|
class HardwarePluginLibraryUnavailable(Exception): pass
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ if TYPE_CHECKING:
|
|||||||
from aiohttp_socks import ProxyConnector
|
from aiohttp_socks import ProxyConnector
|
||||||
|
|
||||||
|
|
||||||
STORAGE_NAME = 'nwc_plugin'
|
|
||||||
|
|
||||||
class NWCServerPlugin(BasePlugin):
|
class NWCServerPlugin(BasePlugin):
|
||||||
URI_SCHEME = 'nostr+walletconnect://'
|
URI_SCHEME = 'nostr+walletconnect://'
|
||||||
|
|
||||||
@@ -47,10 +45,10 @@ class NWCServerPlugin(BasePlugin):
|
|||||||
if self.initialized:
|
if self.initialized:
|
||||||
# this might be called for several wallets. only use one.
|
# this might be called for several wallets. only use one.
|
||||||
return
|
return
|
||||||
storage = self.get_plugin_storage(wallet)
|
storage = self.get_storage(wallet)
|
||||||
self.connections = storage['connections']
|
self.connections = storage.setdefault('connections', {})
|
||||||
self.delete_expired_connections()
|
self.delete_expired_connections()
|
||||||
self.nwc_server = NWCServer(self.config, wallet, self.taskgroup)
|
self.nwc_server = NWCServer(self.config, wallet, self.taskgroup, self.connections)
|
||||||
asyncio.run_coroutine_threadsafe(self.taskgroup.spawn(self.nwc_server.run()), get_asyncio_loop())
|
asyncio.run_coroutine_threadsafe(self.taskgroup.spawn(self.nwc_server.run()), get_asyncio_loop())
|
||||||
self.initialized = True
|
self.initialized = True
|
||||||
|
|
||||||
@@ -67,13 +65,6 @@ class NWCServerPlugin(BasePlugin):
|
|||||||
)
|
)
|
||||||
self.logger.debug(f"NWCServerPlugin closed, stopping taskgroup")
|
self.logger.debug(f"NWCServerPlugin closed, stopping taskgroup")
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_plugin_storage(wallet: 'Abstract_Wallet') -> dict:
|
|
||||||
storage = wallet.db.get_dict(STORAGE_NAME)
|
|
||||||
if 'connections' not in storage:
|
|
||||||
storage['connections'] = {}
|
|
||||||
return storage
|
|
||||||
|
|
||||||
def delete_expired_connections(self):
|
def delete_expired_connections(self):
|
||||||
if self.connections is None:
|
if self.connections is None:
|
||||||
return
|
return
|
||||||
@@ -167,12 +158,17 @@ class NWCServer(Logger, EventListener):
|
|||||||
'notifications']
|
'notifications']
|
||||||
SUPPORTED_NOTIFICATIONS: list[str] = ["payment_sent", "payment_received"]
|
SUPPORTED_NOTIFICATIONS: list[str] = ["payment_sent", "payment_received"]
|
||||||
|
|
||||||
def __init__(self, config: 'SimpleConfig', wallet: 'Abstract_Wallet', taskgroup: 'OldTaskGroup'):
|
def __init__(
|
||||||
|
self,
|
||||||
|
config: 'SimpleConfig',
|
||||||
|
wallet: 'Abstract_Wallet',
|
||||||
|
taskgroup: 'OldTaskGroup',
|
||||||
|
connection_storage: dict,
|
||||||
|
):
|
||||||
Logger.__init__(self)
|
Logger.__init__(self)
|
||||||
self.config = config # type: 'SimpleConfig'
|
self.config = config # type: 'SimpleConfig'
|
||||||
self.wallet = wallet # type: 'Abstract_Wallet'
|
self.wallet = wallet # type: 'Abstract_Wallet'
|
||||||
storage = wallet.db.get_dict(STORAGE_NAME) # type: dict
|
self.connections = connection_storage # type: dict[str, dict] # client hex pubkey -> connection data
|
||||||
self.connections = storage['connections'] # type: dict[str, dict] # client hex pubkey -> connection data
|
|
||||||
self.relays = config.NOSTR_RELAYS.split(",") or [] # type: List[str]
|
self.relays = config.NOSTR_RELAYS.split(",") or [] # type: List[str]
|
||||||
self.do_stop = False
|
self.do_stop = False
|
||||||
self.taskgroup = taskgroup # type: 'OldTaskGroup'
|
self.taskgroup = taskgroup # type: 'OldTaskGroup'
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class CosignerWallet(Logger):
|
|||||||
|
|
||||||
KEEP_DELAY = 24*60*60
|
KEEP_DELAY = 24*60*60
|
||||||
|
|
||||||
def __init__(self, wallet: 'Multisig_Wallet'):
|
def __init__(self, wallet: 'Multisig_Wallet', db_storage: dict):
|
||||||
assert isinstance(wallet, Multisig_Wallet)
|
assert isinstance(wallet, Multisig_Wallet)
|
||||||
self.wallet = wallet
|
self.wallet = wallet
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ class CosignerWallet(Logger):
|
|||||||
self.pending = asyncio.Event()
|
self.pending = asyncio.Event()
|
||||||
self.wallet_uptodate = asyncio.Event()
|
self.wallet_uptodate = asyncio.Event()
|
||||||
|
|
||||||
self.known_events = wallet.db.get_dict('cosigner_events')
|
self.known_events = db_storage.setdefault('cosigner_events', {})
|
||||||
|
|
||||||
for k, v in list(self.known_events.items()):
|
for k, v in list(self.known_events.items()):
|
||||||
if v < now() - self.KEEP_DELAY:
|
if v < now() - self.KEEP_DELAY:
|
||||||
|
|||||||
@@ -117,7 +117,8 @@ class Plugin(PsbtNostrPlugin):
|
|||||||
class QmlCosignerWallet(EventListener, CosignerWallet):
|
class QmlCosignerWallet(EventListener, CosignerWallet):
|
||||||
|
|
||||||
def __init__(self, wallet: 'Multisig_Wallet', plugin: 'Plugin'):
|
def __init__(self, wallet: 'Multisig_Wallet', plugin: 'Plugin'):
|
||||||
CosignerWallet.__init__(self, wallet)
|
db_storage = plugin.get_storage(wallet)
|
||||||
|
CosignerWallet.__init__(self, wallet, db_storage)
|
||||||
self.plugin = plugin
|
self.plugin = plugin
|
||||||
self.register_callbacks()
|
self.register_callbacks()
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class Plugin(PsbtNostrPlugin):
|
|||||||
return
|
return
|
||||||
if wallet.wallet_type == '2fa':
|
if wallet.wallet_type == '2fa':
|
||||||
return
|
return
|
||||||
self.add_cosigner_wallet(wallet, QtCosignerWallet(wallet, window))
|
self.add_cosigner_wallet(wallet, QtCosignerWallet(wallet, window, self))
|
||||||
|
|
||||||
@hook
|
@hook
|
||||||
def on_close_window(self, window):
|
def on_close_window(self, window):
|
||||||
@@ -83,8 +83,9 @@ class Plugin(PsbtNostrPlugin):
|
|||||||
|
|
||||||
|
|
||||||
class QtCosignerWallet(EventListener, CosignerWallet):
|
class QtCosignerWallet(EventListener, CosignerWallet):
|
||||||
def __init__(self, wallet: 'Multisig_Wallet', window: 'ElectrumWindow'):
|
def __init__(self, wallet: 'Multisig_Wallet', window: 'ElectrumWindow', plugin: 'Plugin'):
|
||||||
CosignerWallet.__init__(self, wallet)
|
db_storage = plugin.get_storage(wallet)
|
||||||
|
CosignerWallet.__init__(self, wallet, db_storage)
|
||||||
self.window = window
|
self.window = window
|
||||||
self.obj = QReceiveSignalObject()
|
self.obj = QReceiveSignalObject()
|
||||||
self.obj.cosignerReceivedPsbt.connect(self.on_receive)
|
self.obj.cosignerReceivedPsbt.connect(self.on_receive)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import time
|
|||||||
import os
|
import os
|
||||||
import stat
|
import stat
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import Union, Optional, Dict, Sequence, Tuple, Any, Set, Callable
|
from typing import Union, Optional, Dict, Sequence, Tuple, Any, Set, Callable, AbstractSet
|
||||||
from numbers import Real
|
from numbers import Real
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
@@ -349,6 +349,10 @@ class SimpleConfig(Logger):
|
|||||||
def is_plugin_enabled(self, name: str) -> bool:
|
def is_plugin_enabled(self, name: str) -> bool:
|
||||||
return bool(self.get(f'plugins.{name}.enabled'))
|
return bool(self.get(f'plugins.{name}.enabled'))
|
||||||
|
|
||||||
|
def get_installed_plugins(self) -> AbstractSet[str]:
|
||||||
|
"""Returns all plugin names registered in the config."""
|
||||||
|
return self.get('plugins', {}).keys()
|
||||||
|
|
||||||
def enable_plugin(self, name: str):
|
def enable_plugin(self, name: str):
|
||||||
self.set_key(f'plugins.{name}.enabled', True, save=True)
|
self.set_key(f'plugins.{name}.enabled', True, save=True)
|
||||||
|
|
||||||
|
|||||||
@@ -553,6 +553,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
|||||||
finally: # even if we get cancelled
|
finally: # even if we get cancelled
|
||||||
if any([ks.is_requesting_to_be_rewritten_to_wallet_file for ks in self.get_keystores()]):
|
if any([ks.is_requesting_to_be_rewritten_to_wallet_file for ks in self.get_keystores()]):
|
||||||
self.save_keystore()
|
self.save_keystore()
|
||||||
|
self.db.prune_uninstalled_plugin_data(self.config.get_installed_plugins())
|
||||||
self.save_db()
|
self.save_db()
|
||||||
|
|
||||||
def is_up_to_date(self) -> bool:
|
def is_up_to_date(self) -> bool:
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ import json
|
|||||||
import copy
|
import copy
|
||||||
import threading
|
import threading
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Dict, Optional, List, Tuple, Set, Iterable, NamedTuple, Sequence, TYPE_CHECKING, Union
|
from typing import (Dict, Optional, List, Tuple, Set, Iterable, NamedTuple, Sequence, TYPE_CHECKING,
|
||||||
|
Union, AbstractSet)
|
||||||
import binascii
|
import binascii
|
||||||
import time
|
import time
|
||||||
from functools import partial
|
from functools import partial
|
||||||
@@ -1725,5 +1726,16 @@ class WalletDB(JsonDB):
|
|||||||
if wallet_type in plugin_loaders:
|
if wallet_type in plugin_loaders:
|
||||||
plugin_loaders[wallet_type]()
|
plugin_loaders[wallet_type]()
|
||||||
|
|
||||||
|
def get_plugin_storage(self) -> dict:
|
||||||
|
return self.get_dict('plugin_data')
|
||||||
|
|
||||||
|
def prune_uninstalled_plugin_data(self, installed_plugins: AbstractSet[str]) -> None:
|
||||||
|
"""Remove plugin data for plugins that are not installed anymore."""
|
||||||
|
plugin_storage = self.get_plugin_storage()
|
||||||
|
for name in list(plugin_storage.keys()):
|
||||||
|
if name not in installed_plugins:
|
||||||
|
plugin_storage.pop(name)
|
||||||
|
self.logger.info(f"deleting plugin data: {name=}")
|
||||||
|
|
||||||
def set_keystore_encryption(self, enable):
|
def set_keystore_encryption(self, enable):
|
||||||
self.put('use_encryption', enable)
|
self.put('use_encryption', enable)
|
||||||
|
|||||||
Reference in New Issue
Block a user