From 87b7d2c0c0cd0b210f2dc04572408e41b8833faf Mon Sep 17 00:00:00 2001 From: ThomasV Date: Wed, 12 Feb 2020 19:13:18 +0100 Subject: [PATCH] wallet backup function for kivy/android --- electrum/gui/kivy/main_window.py | 15 ++++++ electrum/gui/kivy/tools/buildozer.spec | 2 +- .../gui/kivy/uix/dialogs/password_dialog.py | 48 ++++++++----------- electrum/gui/kivy/uix/ui_screens/status.kv | 7 +-- electrum/util.py | 14 ++++++ electrum/wallet.py | 14 ++++-- 6 files changed, 63 insertions(+), 37 deletions(-) diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py index b7b3b57af..d01ac5512 100644 --- a/electrum/gui/kivy/main_window.py +++ b/electrum/gui/kivy/main_window.py @@ -1199,6 +1199,21 @@ class ElectrumWindow(App): on_success=on_success, on_failure=on_failure, is_change=1) 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): if self.wallet.is_watching_only(): self.show_info(_('This is a watching-only wallet. It does not contain private keys.')) diff --git a/electrum/gui/kivy/tools/buildozer.spec b/electrum/gui/kivy/tools/buildozer.spec index f4e4ca78e..df44fbec1 100644 --- a/electrum/gui/kivy/tools/buildozer.spec +++ b/electrum/gui/kivy/tools/buildozer.spec @@ -67,7 +67,7 @@ fullscreen = False # # (list) Permissions -android.permissions = INTERNET, CAMERA +android.permissions = INTERNET, CAMERA, WRITE_EXTERNAL_STORAGE # (int) Android API to use android.api = 28 diff --git a/electrum/gui/kivy/uix/dialogs/password_dialog.py b/electrum/gui/kivy/uix/dialogs/password_dialog.py index 9f73267e5..1642c3cbe 100644 --- a/electrum/gui/kivy/uix/dialogs/password_dialog.py +++ b/electrum/gui/kivy/uix/dialogs/password_dialog.py @@ -19,6 +19,7 @@ Builder.load_string(''' id: popup + is_generic: False title: 'Electrum' message: '' BoxLayout: @@ -26,31 +27,17 @@ Builder.load_string(''' orientation: 'vertical' Widget: size_hint: 1, 0.05 - BoxLayout: - size_hint: 1, None - orientation: 'horizontal' - Label: - size_hint: 0.70, 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 + Label: + size_hint: 0.70, None + font_size: '20dp' + text: root.message + text_size: self.width, None Widget: size_hint: 1, 0.05 BoxLayout: orientation: 'horizontal' id: box_generic_password - visible: False + visible: root.is_generic size_hint_y: 0.05 opacity: 1 if self.visible else 0 disabled: not self.visible @@ -59,10 +46,11 @@ Builder.load_string(''' valign: 'center' multiline: False on_text_validate: - popup.on_password(self.text, is_generic=True) + popup.on_password(self.text) password: True size_hint: 0.9, None unfocus_on_touch: False + focus: root.is_generic Button: size_hint: 0.1, None valign: 'center' @@ -75,7 +63,7 @@ Builder.load_string(''' textinput_generic_password.password = False if textinput_generic_password.password else True Label: id: label_pin - visible: not box_generic_password.visible + visible: not root.is_generic size_hint_y: 0.05 opacity: 1 if self.visible else 0 disabled: not self.visible @@ -86,6 +74,7 @@ Builder.load_string(''' size_hint: 1, 0.05 GridLayout: id: kb + disabled: root.is_generic size_hint: 1, None height: self.minimum_height update_amount: popup.update_password @@ -125,8 +114,9 @@ class PasswordDialog(Factory.Popup): def init(self, app: 'ElectrumWindow', *, wallet: Union['Abstract_Wallet', 'WalletStorage'] = 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.is_backup = is_backup self.wallet = wallet self.message = msg self.on_success = on_success @@ -138,7 +128,7 @@ class PasswordDialog(Factory.Popup): self.pw = None self.new_password = None 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): if self.is_change > 1: @@ -172,8 +162,8 @@ class PasswordDialog(Factory.Popup): text += c kb.password = text - def on_password(self, pw: str, *, is_generic=False): - if is_generic: + def on_password(self, pw: str): + if self.is_generic: if len(pw) < 6: self.app.show_error(_('Password is too short (min {} characters)').format(6)) return @@ -186,18 +176,20 @@ class PasswordDialog(Factory.Popup): self.dismiss() elif self.is_change == 1: 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.textinput_generic_password.text = '' self.is_change = 2 + self.is_generic = self.is_backup elif self.is_change == 2: 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.textinput_generic_password.text = '' self.is_change = 3 elif self.is_change == 3: self.success = pw == self.new_password + self.is_generic = False self.dismiss() else: self.app.show_error(_('Wrong PIN')) diff --git a/electrum/gui/kivy/uix/ui_screens/status.kv b/electrum/gui/kivy/uix/ui_screens/status.kv index f23188bac..e774ac81e 100644 --- a/electrum/gui/kivy/uix/ui_screens/status.kv +++ b/electrum/gui/kivy/uix/ui_screens/status.kv @@ -83,13 +83,14 @@ Popup: Button: size_hint: 0.5, None height: '48dp' - text: _('Disable LN') if app.wallet.has_lightning() else _('Enable LN') + text: _('Save Backup') on_release: root.dismiss() - app.toggle_lightning() + app.save_backup() Button: size_hint: 0.5, None height: '48dp' - text: _('Close') + text: _('Disable LN') if app.wallet.has_lightning() else _('Enable LN') on_release: root.dismiss() + app.toggle_lightning() diff --git a/electrum/util.py b/electrum/util.py index 9555be489..cba0cbbd4 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -425,11 +425,25 @@ def profiler(func): 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(): import jnius PythonActivity = jnius.autoclass('org.kivy.android.PythonActivity') 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): # On modern Linux, no need to do anything. diff --git a/electrum/wallet.py b/electrum/wallet.py index 542cd1178..c6ccaae55 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -263,12 +263,16 @@ class Abstract_Wallet(AddressSynchronizer, ABC): if self.storage: self.db.write(self.storage) - def save_backup(self, path): - # fixme: we need to change password... + def save_backup(self, path, *, old_password=None, new_password=None): + new_db = WalletDB(self.storage.read(), manual_upgrades=False) + new_db.put('is_backup', True) new_storage = WalletStorage(path) - self.db.put('is_backup', True) - self.db.write(new_storage) - self.db.put('is_backup', None) + #new_storage._encryption_version = self.storage.get_encryption_version() + new_storage._encryption_version = StorageEncryptionVersion.PLAINTEXT + 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): return bool(self.lnworker)