wallet backup function for kivy/android
This commit is contained in:
@@ -1199,6 +1199,21 @@ class ElectrumWindow(App):
|
|||||||
on_success=on_success, on_failure=on_failure, is_change=1)
|
on_success=on_success, on_failure=on_failure, is_change=1)
|
||||||
self._password_dialog.open()
|
self._password_dialog.open()
|
||||||
|
|
||||||
|
def save_backup(self):
|
||||||
|
from .uix.dialogs.password_dialog import PasswordDialog
|
||||||
|
from electrum.util import get_backup_dir
|
||||||
|
if self._password_dialog is None:
|
||||||
|
self._password_dialog = PasswordDialog()
|
||||||
|
message = _("Create backup.") + '\n' + _("Enter your current PIN:")
|
||||||
|
def on_success(old_password, new_password):
|
||||||
|
new_path = os.path.join(get_backup_dir(self.electrum_config), self.wallet.basename() + '.backup')
|
||||||
|
self.wallet.save_backup(new_path, old_password=old_password, new_password=new_password)
|
||||||
|
self.show_info(_("Backup saved:") + f"\n{new_path}")
|
||||||
|
on_failure = lambda: self.show_error(_("PIN codes do not match"))
|
||||||
|
self._password_dialog.init(self, wallet=self.wallet, msg=message,
|
||||||
|
on_success=on_success, on_failure=on_failure, is_change=1, is_backup=True)
|
||||||
|
self._password_dialog.open()
|
||||||
|
|
||||||
def export_private_keys(self, pk_label, addr):
|
def export_private_keys(self, pk_label, addr):
|
||||||
if self.wallet.is_watching_only():
|
if self.wallet.is_watching_only():
|
||||||
self.show_info(_('This is a watching-only wallet. It does not contain private keys.'))
|
self.show_info(_('This is a watching-only wallet. It does not contain private keys.'))
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ fullscreen = False
|
|||||||
#
|
#
|
||||||
|
|
||||||
# (list) Permissions
|
# (list) Permissions
|
||||||
android.permissions = INTERNET, CAMERA
|
android.permissions = INTERNET, CAMERA, WRITE_EXTERNAL_STORAGE
|
||||||
|
|
||||||
# (int) Android API to use
|
# (int) Android API to use
|
||||||
android.api = 28
|
android.api = 28
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ Builder.load_string('''
|
|||||||
|
|
||||||
<PasswordDialog@Popup>
|
<PasswordDialog@Popup>
|
||||||
id: popup
|
id: popup
|
||||||
|
is_generic: False
|
||||||
title: 'Electrum'
|
title: 'Electrum'
|
||||||
message: ''
|
message: ''
|
||||||
BoxLayout:
|
BoxLayout:
|
||||||
@@ -26,31 +27,17 @@ Builder.load_string('''
|
|||||||
orientation: 'vertical'
|
orientation: 'vertical'
|
||||||
Widget:
|
Widget:
|
||||||
size_hint: 1, 0.05
|
size_hint: 1, 0.05
|
||||||
BoxLayout:
|
Label:
|
||||||
size_hint: 1, None
|
size_hint: 0.70, None
|
||||||
orientation: 'horizontal'
|
font_size: '20dp'
|
||||||
Label:
|
text: root.message
|
||||||
size_hint: 0.70, None
|
text_size: self.width, None
|
||||||
font_size: '20dp'
|
|
||||||
text: root.message
|
|
||||||
text_size: self.width, None
|
|
||||||
Label:
|
|
||||||
size_hint: 0.23, None
|
|
||||||
font_size: '9dp'
|
|
||||||
text: _('Generic password')
|
|
||||||
CheckBox:
|
|
||||||
size_hint: 0.07, None
|
|
||||||
id: cb_generic_password
|
|
||||||
on_active:
|
|
||||||
box_generic_password.visible = self.active
|
|
||||||
kb.disabled = box_generic_password.visible
|
|
||||||
textinput_generic_password.focus = box_generic_password.visible
|
|
||||||
Widget:
|
Widget:
|
||||||
size_hint: 1, 0.05
|
size_hint: 1, 0.05
|
||||||
BoxLayout:
|
BoxLayout:
|
||||||
orientation: 'horizontal'
|
orientation: 'horizontal'
|
||||||
id: box_generic_password
|
id: box_generic_password
|
||||||
visible: False
|
visible: root.is_generic
|
||||||
size_hint_y: 0.05
|
size_hint_y: 0.05
|
||||||
opacity: 1 if self.visible else 0
|
opacity: 1 if self.visible else 0
|
||||||
disabled: not self.visible
|
disabled: not self.visible
|
||||||
@@ -59,10 +46,11 @@ Builder.load_string('''
|
|||||||
valign: 'center'
|
valign: 'center'
|
||||||
multiline: False
|
multiline: False
|
||||||
on_text_validate:
|
on_text_validate:
|
||||||
popup.on_password(self.text, is_generic=True)
|
popup.on_password(self.text)
|
||||||
password: True
|
password: True
|
||||||
size_hint: 0.9, None
|
size_hint: 0.9, None
|
||||||
unfocus_on_touch: False
|
unfocus_on_touch: False
|
||||||
|
focus: root.is_generic
|
||||||
Button:
|
Button:
|
||||||
size_hint: 0.1, None
|
size_hint: 0.1, None
|
||||||
valign: 'center'
|
valign: 'center'
|
||||||
@@ -75,7 +63,7 @@ Builder.load_string('''
|
|||||||
textinput_generic_password.password = False if textinput_generic_password.password else True
|
textinput_generic_password.password = False if textinput_generic_password.password else True
|
||||||
Label:
|
Label:
|
||||||
id: label_pin
|
id: label_pin
|
||||||
visible: not box_generic_password.visible
|
visible: not root.is_generic
|
||||||
size_hint_y: 0.05
|
size_hint_y: 0.05
|
||||||
opacity: 1 if self.visible else 0
|
opacity: 1 if self.visible else 0
|
||||||
disabled: not self.visible
|
disabled: not self.visible
|
||||||
@@ -86,6 +74,7 @@ Builder.load_string('''
|
|||||||
size_hint: 1, 0.05
|
size_hint: 1, 0.05
|
||||||
GridLayout:
|
GridLayout:
|
||||||
id: kb
|
id: kb
|
||||||
|
disabled: root.is_generic
|
||||||
size_hint: 1, None
|
size_hint: 1, None
|
||||||
height: self.minimum_height
|
height: self.minimum_height
|
||||||
update_amount: popup.update_password
|
update_amount: popup.update_password
|
||||||
@@ -125,8 +114,9 @@ class PasswordDialog(Factory.Popup):
|
|||||||
def init(self, app: 'ElectrumWindow', *,
|
def init(self, app: 'ElectrumWindow', *,
|
||||||
wallet: Union['Abstract_Wallet', 'WalletStorage'] = None,
|
wallet: Union['Abstract_Wallet', 'WalletStorage'] = None,
|
||||||
msg: str, on_success: Callable = None, on_failure: Callable = None,
|
msg: str, on_success: Callable = None, on_failure: Callable = None,
|
||||||
is_change: int = 0):
|
is_change: int = 0, is_backup: bool = False):
|
||||||
self.app = app
|
self.app = app
|
||||||
|
self.is_backup = is_backup
|
||||||
self.wallet = wallet
|
self.wallet = wallet
|
||||||
self.message = msg
|
self.message = msg
|
||||||
self.on_success = on_success
|
self.on_success = on_success
|
||||||
@@ -138,7 +128,7 @@ class PasswordDialog(Factory.Popup):
|
|||||||
self.pw = None
|
self.pw = None
|
||||||
self.new_password = None
|
self.new_password = None
|
||||||
self.title = 'Electrum' + (' - ' + self.wallet.basename() if self.wallet else '')
|
self.title = 'Electrum' + (' - ' + self.wallet.basename() if self.wallet else '')
|
||||||
self.ids.cb_generic_password.active = False
|
#self.ids.cb_generic_password.active = False
|
||||||
|
|
||||||
def check_password(self, password):
|
def check_password(self, password):
|
||||||
if self.is_change > 1:
|
if self.is_change > 1:
|
||||||
@@ -172,8 +162,8 @@ class PasswordDialog(Factory.Popup):
|
|||||||
text += c
|
text += c
|
||||||
kb.password = text
|
kb.password = text
|
||||||
|
|
||||||
def on_password(self, pw: str, *, is_generic=False):
|
def on_password(self, pw: str):
|
||||||
if is_generic:
|
if self.is_generic:
|
||||||
if len(pw) < 6:
|
if len(pw) < 6:
|
||||||
self.app.show_error(_('Password is too short (min {} characters)').format(6))
|
self.app.show_error(_('Password is too short (min {} characters)').format(6))
|
||||||
return
|
return
|
||||||
@@ -186,18 +176,20 @@ class PasswordDialog(Factory.Popup):
|
|||||||
self.dismiss()
|
self.dismiss()
|
||||||
elif self.is_change == 1:
|
elif self.is_change == 1:
|
||||||
self.pw = pw
|
self.pw = pw
|
||||||
self.message = _('Enter new PIN')
|
self.message = _('Enter a strong password for your backup') if self.is_backup else _('Enter new PIN')
|
||||||
self.ids.kb.password = ''
|
self.ids.kb.password = ''
|
||||||
self.ids.textinput_generic_password.text = ''
|
self.ids.textinput_generic_password.text = ''
|
||||||
self.is_change = 2
|
self.is_change = 2
|
||||||
|
self.is_generic = self.is_backup
|
||||||
elif self.is_change == 2:
|
elif self.is_change == 2:
|
||||||
self.new_password = pw
|
self.new_password = pw
|
||||||
self.message = _('Confirm new PIN')
|
self.message = _('Confirm backup password') if self.is_backup else _('Confirm new PIN')
|
||||||
self.ids.kb.password = ''
|
self.ids.kb.password = ''
|
||||||
self.ids.textinput_generic_password.text = ''
|
self.ids.textinput_generic_password.text = ''
|
||||||
self.is_change = 3
|
self.is_change = 3
|
||||||
elif self.is_change == 3:
|
elif self.is_change == 3:
|
||||||
self.success = pw == self.new_password
|
self.success = pw == self.new_password
|
||||||
|
self.is_generic = False
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
else:
|
else:
|
||||||
self.app.show_error(_('Wrong PIN'))
|
self.app.show_error(_('Wrong PIN'))
|
||||||
|
|||||||
@@ -80,6 +80,13 @@ Popup:
|
|||||||
on_release:
|
on_release:
|
||||||
root.dismiss()
|
root.dismiss()
|
||||||
app.delete_wallet()
|
app.delete_wallet()
|
||||||
|
Button:
|
||||||
|
size_hint: 0.5, None
|
||||||
|
height: '48dp'
|
||||||
|
text: _('Save Backup')
|
||||||
|
on_release:
|
||||||
|
root.dismiss()
|
||||||
|
app.save_backup()
|
||||||
Button:
|
Button:
|
||||||
size_hint: 0.5, None
|
size_hint: 0.5, None
|
||||||
height: '48dp'
|
height: '48dp'
|
||||||
@@ -87,9 +94,3 @@ Popup:
|
|||||||
on_release:
|
on_release:
|
||||||
root.dismiss()
|
root.dismiss()
|
||||||
app.toggle_lightning()
|
app.toggle_lightning()
|
||||||
Button:
|
|
||||||
size_hint: 0.5, None
|
|
||||||
height: '48dp'
|
|
||||||
text: _('Close')
|
|
||||||
on_release:
|
|
||||||
root.dismiss()
|
|
||||||
|
|||||||
@@ -425,11 +425,25 @@ def profiler(func):
|
|||||||
return lambda *args, **kw_args: do_profile(args, kw_args)
|
return lambda *args, **kw_args: do_profile(args, kw_args)
|
||||||
|
|
||||||
|
|
||||||
|
def android_ext_dir():
|
||||||
|
import jnius
|
||||||
|
env = jnius.autoclass('android.os.Environment')
|
||||||
|
return env.getExternalStorageDirectory().getPath()
|
||||||
|
|
||||||
|
def android_backup_dir():
|
||||||
|
d = android_ext_dir() + '/org.electrum.electrum'
|
||||||
|
if not os.path.exists(d):
|
||||||
|
os.mkdir(d)
|
||||||
|
return d
|
||||||
|
|
||||||
def android_data_dir():
|
def android_data_dir():
|
||||||
import jnius
|
import jnius
|
||||||
PythonActivity = jnius.autoclass('org.kivy.android.PythonActivity')
|
PythonActivity = jnius.autoclass('org.kivy.android.PythonActivity')
|
||||||
return PythonActivity.mActivity.getFilesDir().getPath() + '/data'
|
return PythonActivity.mActivity.getFilesDir().getPath() + '/data'
|
||||||
|
|
||||||
|
def get_backup_dir(config):
|
||||||
|
return android_backup_dir() if 'ANDROID_DATA' in os.environ else config.path
|
||||||
|
|
||||||
|
|
||||||
def ensure_sparse_file(filename):
|
def ensure_sparse_file(filename):
|
||||||
# On modern Linux, no need to do anything.
|
# On modern Linux, no need to do anything.
|
||||||
|
|||||||
@@ -263,12 +263,16 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||||||
if self.storage:
|
if self.storage:
|
||||||
self.db.write(self.storage)
|
self.db.write(self.storage)
|
||||||
|
|
||||||
def save_backup(self, path):
|
def save_backup(self, path, *, old_password=None, new_password=None):
|
||||||
# fixme: we need to change password...
|
new_db = WalletDB(self.storage.read(), manual_upgrades=False)
|
||||||
|
new_db.put('is_backup', True)
|
||||||
new_storage = WalletStorage(path)
|
new_storage = WalletStorage(path)
|
||||||
self.db.put('is_backup', True)
|
#new_storage._encryption_version = self.storage.get_encryption_version()
|
||||||
self.db.write(new_storage)
|
new_storage._encryption_version = StorageEncryptionVersion.PLAINTEXT
|
||||||
self.db.put('is_backup', None)
|
w2 = Wallet(new_db, new_storage, config=self.config)
|
||||||
|
if new_password:
|
||||||
|
w2.update_password(old_password, new_password, encrypt_storage=True)
|
||||||
|
w2.save_db()
|
||||||
|
|
||||||
def has_lightning(self):
|
def has_lightning(self):
|
||||||
return bool(self.lnworker)
|
return bool(self.lnworker)
|
||||||
|
|||||||
Reference in New Issue
Block a user