Skip to content

Commit

Permalink
add wallet rename functionality, and add to qml client
Browse files Browse the repository at this point in the history
  • Loading branch information
accumulator committed Mar 8, 2024
1 parent ff50487 commit c7520cb
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 24 deletions.
15 changes: 15 additions & 0 deletions electrum/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,21 @@ def get_wallet(self, path: str) -> Optional[Abstract_Wallet]:
def get_wallets(self) -> Dict[str, Abstract_Wallet]:
return dict(self._wallets) # copy

@with_wallet_lock
def rename_wallet(self, wallet: 'Abstract_Wallet', new_basename):
if not wallet.db.storage:
return

path = wallet.db.storage.path
wallet_key = self._wallet_key_from_path(path)
wallet = self._wallets.get(wallet_key)
assert wallet

new_path = wallet.rename_wallet(new_basename)
new_wallet_key = self._wallet_key_from_path(new_path)
self._wallets.pop(wallet_key)
self._wallets[new_wallet_key] = wallet

def delete_wallet(self, path: str) -> bool:
self.stop_wallet(path)
if os.path.exists(path):
Expand Down
87 changes: 87 additions & 0 deletions electrum/gui/qml/components/RenameWalletDialog.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Controls.Material

import org.electrum 1.0

import "controls"

ElDialog {
id: dialog

title: qsTr("Enter wallet name")
iconSource: Qt.resolvedUrl('../../icons/pen.png')

property string infotext

property bool _valid: false

anchors.centerIn: parent
width: parent.width * 4/5
padding: 0

ColumnLayout {
id: rootLayout
width: parent.width
spacing: 0

ColumnLayout {
Layout.leftMargin: constants.paddingXXLarge
Layout.rightMargin: constants.paddingXXLarge

InfoTextArea {
visible: infotext
text: infotext
Layout.bottomMargin: constants.paddingMedium
Layout.fillWidth: true
}

Label {
Layout.fillWidth: true
text: qsTr('Wallet name')
color: Material.accentColor
}

TextField {
id: wallet_name
Layout.fillWidth: true
Layout.leftMargin: constants.paddingXLarge
Layout.rightMargin: constants.paddingXLarge
onTextChanged: {
var name = text.trim()
if (!text || text == Daemon.currentWallet.name) {
_valid = false
infotext = ''
} else {
_valid = Daemon.isValidNewWalletName(name)
if (_valid)
infotext = ''
else
infotext = qsTr('Invalid name')
}
}
}
}

FlatButton {
Layout.fillWidth: true
text: qsTr("Ok")
icon.source: '../../icons/confirmed.png'
enabled: _valid
onClicked: {
var name = wallet_name.text.trim()
if (Daemon.isValidNewWalletName(name)) {
console.log('renaming.. ' + name)
var result = Daemon.renameWallet(Daemon.currentWallet, name)
if (result)
dialog.close()
}
}
}
}

Component.onCompleted: {
wallet_name.text = Daemon.currentWallet.name
}
}
17 changes: 17 additions & 0 deletions electrum/gui/qml/components/WalletDetails.qml
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,16 @@ Pane {
onClicked: Daemon.checkThenDeleteWallet(Daemon.currentWallet)
icon.source: '../../icons/delete.png'
}
FlatButton {
Layout.fillWidth: true
Layout.preferredWidth: 1
text: qsTr('Rename')
onClicked: {
var dialog = renameWalletDialog.createObject(rootItem)
dialog.open()
}
icon.source: '../../icons/pen.png'
}
FlatButton {
Layout.fillWidth: true
Layout.preferredWidth: 1
Expand Down Expand Up @@ -532,6 +542,13 @@ Pane {
}
}

Component {
id: renameWalletDialog
RenameWalletDialog {
onClosed: destroy()
}
}

Binding {
target: AppController
property: 'secureWindow'
Expand Down
40 changes: 39 additions & 1 deletion electrum/gui/qml/qedaemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from electrum.lnchannel import ChannelState
from electrum.bitcoin import is_address
from electrum.ecc import verify_message_with_address
from electrum.storage import StorageReadWriteError
from electrum.storage import StorageReadWriteError, WalletStorage

from .auth import AuthMixin, auth_protect
from .qefx import QEFX
Expand Down Expand Up @@ -382,3 +382,41 @@ def passwordStrength(self, password):
if len(password) == 0:
return 0
return check_password_strength(password)[0]

def wallet_path_from_wallet_name(self, wallet_name: str) -> str:
return os.path.join(self.daemon.config.get_datadir_wallet_path(), wallet_name)

@pyqtSlot(str, result=bool)
def isValidNewWalletName(self, wallet_name: str) -> bool:
if not wallet_name:
return False
if self.availableWallets.wallet_name_exists(wallet_name):
return False
wallet_path = self.wallet_path_from_wallet_name(wallet_name)
# note: we should probably restrict wallet names to be alphanumeric (plus underscore, etc)...
# wallet_name might contain ".." (etc) and hence sketchy path traversals are possible.
# Anyway, this at least validates that the path looks sane to the filesystem:
try:
temp_storage = WalletStorage(wallet_path)
except (StorageReadWriteError, WalletFileException) as e:
return False
except Exception as e:
self._logger.exception("")
return False
if temp_storage.file_exists():
return False
return True

@pyqtSlot(QEWallet, str, result=bool)
def renameWallet(self, wallet, new_name):
self._logger.debug(f'renaming wallet to: {new_name}')
try:
self.daemon.rename_wallet(wallet.wallet, new_name)
self.daemon.config.save_last_wallet(wallet.wallet)
wallet.nameChanged.emit()
if self._available_wallets:
self._available_wallets.reload()
return True
except Exception as e:
self._logger.debug(f'renaming err: {str(e)}')
return False
24 changes: 2 additions & 22 deletions electrum/gui/qml/qewizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,29 +119,9 @@ def verifySeed(self, seed, seed_variant, wallet_type='standard'):
'can_passphrase': can_passphrase
}

def _wallet_path_from_wallet_name(self, wallet_name: str) -> str:
return os.path.join(self._qedaemon.daemon.config.get_datadir_wallet_path(), wallet_name)

@pyqtSlot(str, result=bool)
def isValidNewWalletName(self, wallet_name: str) -> bool:
if not wallet_name:
return False
if self._qedaemon.availableWallets.wallet_name_exists(wallet_name):
return False
wallet_path = self._wallet_path_from_wallet_name(wallet_name)
# note: we should probably restrict wallet names to be alphanumeric (plus underscore, etc)...
# wallet_name might contain ".." (etc) and hence sketchy path traversals are possible.
# Anyway, this at least validates that the path looks sane to the filesystem:
try:
temp_storage = WalletStorage(wallet_path)
except (StorageReadWriteError, WalletFileException) as e:
return False
except Exception as e:
self._logger.exception("")
return False
if temp_storage.file_exists():
return False
return True
return self._qedaemon.isValidNewWalletName(wallet_name)

@pyqtSlot('QJSValue', bool, str)
def createStorage(self, js_data, single_password_enabled, single_password):
Expand All @@ -152,7 +132,7 @@ def createStorage(self, js_data, single_password_enabled, single_password):
data['encrypt'] = True
data['password'] = single_password

path = self._wallet_path_from_wallet_name(data['wallet_name'])
path = self._qedaemon.wallet_path_from_wallet_name(data['wallet_name'])

try:
self.create_storage(path, data)
Expand Down
32 changes: 31 additions & 1 deletion electrum/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,6 @@ def __init__(self, db: WalletDB, *, config: SimpleConfig):
self.config = config
assert self.config is not None, "config must not be None"
self.db = db
self.storage = db.storage # type: Optional[WalletStorage]
# load addresses needs to be called before constructor for sanity checks
db.load_addresses(self.wallet_type)
self.keystore = None # type: Optional[KeyStore] # will be set by load_keystore
Expand Down Expand Up @@ -445,6 +444,10 @@ def __init__(self, db: WalletDB, *, config: SimpleConfig):

self.register_callbacks()

@property
def storage(self):
return self.db.storage

def _init_lnworker(self):
self.lnworker = None

Expand Down Expand Up @@ -494,6 +497,33 @@ def save_backup(self, backup_dir):
new_db.write()
return new_path

def rename_wallet(self, new_basename) -> str:
old_storage = self.storage
if not old_storage:
return

# new storage path
dirname = os.path.dirname(old_storage.path)
new_path = os.path.join(dirname, new_basename)
assert not os.path.exists(new_path)

# new storage
new_storage = WalletStorage(new_path)
new_storage._encryption_version = old_storage._encryption_version
new_storage.pubkey = old_storage.pubkey

with self.db.lock:
# keep existing WalletDB instance, as the initialization normally
# happens in the wallet constructor, which we don't want to replicate,
self.db.storage = new_storage
self.db.set_modified(True)
self.db.write_and_force_consolidation()

assert os.path.exists(new_path)
os.unlink(old_storage.path)

return new_path

def has_lightning(self) -> bool:
return bool(self.lnworker)

Expand Down

0 comments on commit c7520cb

Please sign in to comment.