Files
purple-electrumwallet/electrum/gui/qml/qedaemon.py
T

339 lines
12 KiB
Python
Raw Normal View History

2021-04-06 14:13:51 +02:00
import os
2023-02-22 14:17:57 +01:00
import threading
2021-04-06 14:13:51 +02:00
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex
2022-07-27 10:23:35 +02:00
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
2021-04-06 14:13:51 +02:00
from electrum.i18n import _
2021-04-06 14:13:51 +02:00
from electrum.logging import get_logger
2022-07-27 10:23:35 +02:00
from electrum.util import WalletFileException, standardize_path
from electrum.wallet import Abstract_Wallet
from electrum.plugin import run_hook
from electrum.lnchannel import ChannelState
2021-04-06 14:13:51 +02:00
2022-07-27 10:23:35 +02:00
from .auth import AuthMixin, auth_protect
from .qefx import QEFX
2021-04-06 14:13:51 +02:00
from .qewallet import QEWallet
from .qewalletdb import QEWalletDB
from .qewizard import QENewWalletWizard, QEServerConnectWizard
2021-04-06 14:13:51 +02:00
# wallet list model. supports both wallet basenames (wallet file basenames)
# and whole Wallet instances (loaded wallets)
class QEWalletListModel(QAbstractListModel):
_logger = get_logger(__name__)
# define listmodel rolemap
_ROLE_NAMES= ('name','path','active')
_ROLE_KEYS = range(Qt.UserRole, Qt.UserRole + len(_ROLE_NAMES))
2021-04-06 14:13:51 +02:00
_ROLE_MAP = dict(zip(_ROLE_KEYS, [bytearray(x.encode()) for x in _ROLE_NAMES]))
def __init__(self, daemon, parent=None):
QAbstractListModel.__init__(self, parent)
self.daemon = daemon
self.reload()
2021-04-06 14:13:51 +02:00
def rowCount(self, index):
return len(self.wallets)
def roleNames(self):
return self._ROLE_MAP
def data(self, index, role):
(wallet_name, wallet_path) = self.wallets[index.row()]
role_index = role - Qt.UserRole
2021-04-06 14:13:51 +02:00
role_name = self._ROLE_NAMES[role_index]
if role_name == 'name':
return wallet_name
if role_name == 'path':
return wallet_path
2021-04-06 14:13:51 +02:00
if role_name == 'active':
return self.daemon.get_wallet(wallet_path) is not None
2021-04-06 14:13:51 +02:00
@pyqtSlot()
def reload(self):
self._logger.debug('enumerating available wallets')
self.beginResetModel()
self.wallets = []
self.endResetModel()
available = []
wallet_folder = os.path.dirname(self.daemon.config.get_wallet_path())
with os.scandir(wallet_folder) as it:
for i in it:
if i.is_file() and not i.name.startswith('.'):
available.append(i.path)
for path in sorted(available):
wallet = self.daemon.get_wallet(path)
self.add_wallet(wallet_path = path)
def add_wallet(self, wallet_path):
2022-10-31 16:13:22 +00:00
self.beginInsertRows(QModelIndex(), len(self.wallets), len(self.wallets))
wallet_name = os.path.basename(wallet_path)
wallet_path = standardize_path(wallet_path)
item = (wallet_name, wallet_path)
2022-10-31 16:13:22 +00:00
self.wallets.append(item)
self.endInsertRows()
2021-04-06 14:13:51 +02:00
def remove_wallet(self, path):
i = 0
wallets = []
remove = -1
for wallet_name, wallet_path in self.wallets:
if wallet_path == path:
remove = i
else:
self._logger.debug('HM, %s is not %s', wallet_path, path)
wallets.append((wallet_name, wallet_path))
i += 1
if remove >= 0:
self.beginRemoveRows(QModelIndex(), i, i)
self.wallets = wallets
self.endRemoveRows()
def wallet_name_exists(self, name):
for wallet_name, wallet_path in self.wallets:
if name == wallet_name:
return True
return False
@pyqtSlot(str)
def updateWallet(self, path):
i = 0
for wallet_name, wallet_path in self.wallets:
if wallet_path == path:
mi = self.createIndex(i, i)
self.dataChanged.emit(mi, mi, self._ROLE_KEYS)
return
i += 1
class QEDaemon(AuthMixin, QObject):
2021-04-06 14:13:51 +02:00
_logger = get_logger(__name__)
_available_wallets = None
2022-03-10 12:25:18 +01:00
_current_wallet = None
_new_wallet_wizard = None
_server_connect_wizard = None
2022-03-10 12:25:18 +01:00
_path = None
_name = None
2022-07-06 11:10:00 +02:00
_use_single_password = False
_password = None
_loading = False
2023-02-22 14:17:57 +01:00
_backendWalletLoaded = pyqtSignal([str], arguments=['password'])
availableWalletsChanged = pyqtSignal()
fxChanged = pyqtSignal()
newWalletWizardChanged = pyqtSignal()
serverConnectWizardChanged = pyqtSignal()
loadingChanged = pyqtSignal()
2023-02-22 14:17:57 +01:00
walletLoaded = pyqtSignal([str,str], arguments=['name','path'])
walletRequiresPassword = pyqtSignal([str,str], arguments=['name','path'])
walletOpenError = pyqtSignal([str], arguments=["error"])
walletDeleteError = pyqtSignal([str,str], arguments=['code', 'message'])
def __init__(self, daemon, parent=None):
super().__init__(parent)
self.daemon = daemon
self.qefx = QEFX(daemon.fx, daemon.config)
2023-02-22 14:17:57 +01:00
self._backendWalletLoaded.connect(self._on_backend_wallet_loaded)
self._walletdb = QEWalletDB()
self._walletdb.validPasswordChanged.connect(self.passwordValidityCheck)
2023-02-28 14:11:52 +01:00
self._walletdb.walletOpenProblem.connect(self.onWalletOpenProblem)
@pyqtSlot()
def passwordValidityCheck(self):
if not self._walletdb._validPassword:
self.walletRequiresPassword.emit(self._name, self._path)
@pyqtSlot(str)
2023-02-28 14:11:52 +01:00
def onWalletOpenProblem(self, error):
self.walletOpenError.emit(error)
2021-04-06 14:13:51 +02:00
@pyqtSlot()
@pyqtSlot(str)
@pyqtSlot(str, str)
2021-04-06 14:13:51 +02:00
def load_wallet(self, path=None, password=None):
2022-10-31 16:13:22 +00:00
if path is None:
self._path = self.daemon.config.get('wallet_path') # command line -w option
if self._path is None:
self._path = self.daemon.config.get('gui_last_wallet')
else:
self._path = path
if self._path is None:
return
2022-07-12 16:49:07 +02:00
self._path = standardize_path(self._path)
self._name = os.path.basename(self._path)
self._logger.debug('load wallet ' + str(self._path))
# map empty string password to None
if password == '':
password = None
2022-07-12 16:49:07 +02:00
if not password:
password = self._password
wallet_already_open = self._path in self.daemon._wallets
if not wallet_already_open:
# pre-checks, let walletdb trigger any necessary user interactions
self._walletdb.path = self._path
self._walletdb.password = password
2022-07-12 16:49:07 +02:00
self._walletdb.verify()
if not self._walletdb.ready:
return
2023-02-22 14:17:57 +01:00
def load_wallet_task():
self._loading = True
self.loadingChanged.emit()
2023-02-22 14:17:57 +01:00
try:
wallet = self.daemon.load_wallet(self._path, password)
if wallet is None:
self._logger.info('could not open wallet')
self.walletOpenError.emit('could not open wallet')
return
2022-07-06 11:10:00 +02:00
if self.daemon.config.get('single_password'):
2022-07-07 19:10:20 +02:00
self._use_single_password = self.daemon.update_password_for_directory(old_password=password, new_password=password)
2022-07-06 11:10:00 +02:00
self._password = password
self.singlePasswordChanged.emit()
self._logger.info(f'use single password: {self._use_single_password}')
else:
self._logger.info('use single password disabled by config')
2022-07-06 11:10:00 +02:00
self.daemon.config.save_last_wallet(wallet)
2023-02-22 14:17:57 +01:00
run_hook('load_wallet', wallet)
2023-02-22 14:17:57 +01:00
self._backendWalletLoaded.emit(password)
except WalletFileException as e:
self._logger.error(str(e))
self.walletOpenError.emit(str(e))
finally:
self._loading = False
self.loadingChanged.emit()
2023-02-22 14:17:57 +01:00
threading.Thread(target=load_wallet_task, daemon=True).start()
2023-02-22 14:17:57 +01:00
@pyqtSlot()
@pyqtSlot(str)
def _on_backend_wallet_loaded(self, password = None):
self._logger.debug('_on_backend_wallet_loaded')
wallet = self.daemon._wallets[self._path]
self._current_wallet = QEWallet.getInstanceFor(wallet)
self.availableWallets.updateWallet(self._path)
self._current_wallet.password = password if password else None
2023-02-22 14:17:57 +01:00
self.walletLoaded.emit(self._name, self._path)
2021-04-06 14:13:51 +02:00
@pyqtSlot(QEWallet)
@pyqtSlot(QEWallet, bool)
@pyqtSlot(QEWallet, bool, bool)
def checkThenDeleteWallet(self, wallet, confirm_requests=False, confirm_balance=False):
if wallet.wallet.lnworker:
lnchannels = wallet.wallet.lnworker.get_channel_objects()
if any([channel.get_state() != ChannelState.REDEEMED and not channel.is_backup() for channel in lnchannels.values()]):
self.walletDeleteError.emit('unclosed_channels', _('There are still channels that are not fully closed'))
return
num_requests = len(wallet.wallet.get_unpaid_requests())
if num_requests > 0 and not confirm_requests:
self.walletDeleteError.emit('unpaid_requests', _('There are still unpaid requests. Really delete?'))
return
c, u, x = wallet.wallet.get_balance()
if c+u+x > 0 and not wallet.wallet.is_watching_only() and not confirm_balance:
self.walletDeleteError.emit('balance', _('There are still coins present in this wallet. Really delete?'))
return
self.delete_wallet(wallet)
@auth_protect
def delete_wallet(self, wallet):
path = standardize_path(wallet.wallet.storage.path)
self._logger.debug('deleting wallet with path %s' % path)
2022-06-21 14:11:03 +02:00
self._current_wallet = None
# TODO walletLoaded signal is confusing
2022-06-21 14:11:03 +02:00
self.walletLoaded.emit()
if not self.daemon.delete_wallet(path):
self.walletDeleteError.emit('error', _('Problem deleting wallet'))
return
self.availableWallets.remove_wallet(path)
@pyqtProperty(bool, notify=loadingChanged)
def loading(self):
return self._loading
@pyqtProperty(QEWallet, notify=walletLoaded)
2021-04-06 14:13:51 +02:00
def currentWallet(self):
return self._current_wallet
@pyqtProperty(QEWalletListModel, notify=availableWalletsChanged)
2021-04-06 14:13:51 +02:00
def availableWallets(self):
if not self._available_wallets:
self._available_wallets = QEWalletListModel(self.daemon)
return self._available_wallets
2022-04-04 13:21:05 +02:00
@pyqtProperty(QEFX, notify=fxChanged)
def fx(self):
return self.qefx
singlePasswordChanged = pyqtSignal()
@pyqtProperty(bool, notify=singlePasswordChanged)
def singlePasswordEnabled(self):
return self._use_single_password
@pyqtProperty(str, notify=singlePasswordChanged)
def singlePassword(self):
return self._password
@pyqtSlot(result=str)
def suggestWalletName(self):
i = 1
while self.availableWallets.wallet_name_exists(f'wallet_{i}'):
i = i + 1
return f'wallet_{i}'
2022-07-06 11:10:00 +02:00
requestNewPassword = pyqtSignal()
@pyqtSlot()
@auth_protect
def startChangePassword(self):
2022-07-06 11:10:00 +02:00
if self._use_single_password:
self.requestNewPassword.emit()
else:
self.currentWallet.requestNewPassword.emit()
@pyqtSlot(str)
def setPassword(self, password):
2022-07-06 11:10:00 +02:00
assert self._use_single_password
# map empty string password to None
if password == '':
password = None
2022-07-12 18:58:36 +02:00
self._logger.debug('about to set password for ALL wallets')
self.daemon.update_password_for_directory(old_password=self._password, new_password=password)
2022-07-12 16:49:07 +02:00
self._password = password
@pyqtProperty(QENewWalletWizard, notify=newWalletWizardChanged)
def newWalletWizard(self):
if not self._new_wallet_wizard:
self._new_wallet_wizard = QENewWalletWizard(self)
return self._new_wallet_wizard
@pyqtProperty(QEServerConnectWizard, notify=serverConnectWizardChanged)
def serverConnectWizard(self):
if not self._server_connect_wizard:
self._server_connect_wizard = QEServerConnectWizard(self)
return self._server_connect_wizard