timport/exports to json files: - fix #5737 - add import/export or requests - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit 56f4932f1085bf9b686b7cba92e495009eb9ed30
 (DIR) parent 2571669a32e62af08f759ca8c826a9095289e6ef
 (HTM) Author: ThomasV <thomasv@electrum.org>
       Date:   Fri,  5 Jun 2020 01:28:00 +0200
       
       import/exports to json files:
        - fix #5737
        - add import/export or requests
       
       Diffstat:
         M electrum/contacts.py                |      11 +++++------
         M electrum/gui/qt/contact_list.py     |       8 +-------
         M electrum/gui/qt/invoice_list.py     |       9 +--------
         M electrum/gui/qt/main_window.py      |      47 +++++++++++++++++++------------
         M electrum/paymentrequest.py          |       2 +-
         M electrum/util.py                    |      13 ++++++-------
         M electrum/wallet.py                  |      27 +++++++++++++++++++++++++++
       
       7 files changed, 70 insertions(+), 47 deletions(-)
       ---
 (DIR) diff --git a/electrum/contacts.py b/electrum/contacts.py
       t@@ -27,7 +27,7 @@ from dns.exception import DNSException
        
        from . import bitcoin
        from . import dnssec
       -from .util import export_meta, import_meta, to_string
       +from .util import read_json_file, write_json_file, to_string
        from .logging import Logger
        
        
       t@@ -52,14 +52,13 @@ class Contacts(dict, Logger):
                self.db.put('contacts', dict(self))
        
            def import_file(self, path):
       -        import_meta(path, self._validate, self.load_meta)
       -
       -    def load_meta(self, data):
       +        data = read_json_file(path)
       +        data = self._validate(data)
                self.update(data)
                self.save()
        
       -    def export_file(self, filename):
       -        export_meta(self, filename)
       +    def export_file(self, path):
       +        write_json_file(path, self)
        
            def __setitem__(self, key, value):
                dict.__setitem__(self, key, value)
 (DIR) diff --git a/electrum/gui/qt/contact_list.py b/electrum/gui/qt/contact_list.py
       t@@ -34,7 +34,7 @@ from electrum.bitcoin import is_address
        from electrum.util import block_explorer_URL
        from electrum.plugin import run_hook
        
       -from .util import MyTreeView, import_meta_gui, export_meta_gui, webopen
       +from .util import MyTreeView, webopen
        
        
        class ContactList(MyTreeView):
       t@@ -63,12 +63,6 @@ class ContactList(MyTreeView):
                self.parent.set_contact(text, user_role)
                self.update()
        
       -    def import_contacts(self):
       -        import_meta_gui(self.parent, _('contacts'), self.parent.contacts.import_file, self.update)
       -
       -    def export_contacts(self):
       -        export_meta_gui(self.parent, _('contacts'), self.parent.contacts.export_file)
       -
            def create_menu(self, position):
                menu = QMenu()
                idx = self.indexAt(position)
 (DIR) diff --git a/electrum/gui/qt/invoice_list.py b/electrum/gui/qt/invoice_list.py
       t@@ -36,8 +36,7 @@ from electrum.util import format_time
        from electrum.invoices import Invoice, PR_UNPAID, PR_PAID, PR_INFLIGHT, PR_FAILED, PR_TYPE_ONCHAIN, PR_TYPE_LN
        from electrum.lnutil import PaymentAttemptLog
        
       -from .util import (MyTreeView, read_QIcon, MySortModel,
       -                   import_meta_gui, export_meta_gui, pr_icons)
       +from .util import MyTreeView, read_QIcon, MySortModel, pr_icons
        from .util import CloseButton, Buttons
        from .util import WindowModalDialog
        
       t@@ -136,12 +135,6 @@ class InvoiceList(MyTreeView):
                    self.setVisible(b)
                    self.parent.invoices_label.setVisible(b)
        
       -    def import_invoices(self):
       -        import_meta_gui(self.parent, _('invoices'), self.parent.invoices.import_file, self.update)
       -
       -    def export_invoices(self):
       -        export_meta_gui(self.parent, _('invoices'), self.parent.invoices.export_file)
       -
            def create_menu(self, position):
                wallet = self.parent.wallet
                items = self.selected_in_column(0)
 (DIR) diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -57,7 +57,7 @@ from electrum.i18n import _
        from electrum.util import (format_time, format_satoshis, format_fee_satoshis,
                                   format_satoshis_plain,
                                   UserCancelled, profiler,
       -                           export_meta, import_meta, bh2u, bfh, InvalidPassword,
       +                           bh2u, bfh, InvalidPassword,
                                   UserFacingException,
                                   get_new_wallet_name, send_exception_to_crash_reporter,
                                   InvalidBitcoinURI, maybe_extract_bolt11_invoice, NotEnoughFunds,
       t@@ -670,11 +670,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                history_menu.addAction(_("&Export"), self.history_list.export_history_dialog)
                contacts_menu = wallet_menu.addMenu(_("Contacts"))
                contacts_menu.addAction(_("&New"), self.new_contact_dialog)
       -        contacts_menu.addAction(_("Import"), lambda: self.contact_list.import_contacts())
       -        contacts_menu.addAction(_("Export"), lambda: self.contact_list.export_contacts())
       +        contacts_menu.addAction(_("Import"), lambda: self.import_contacts())
       +        contacts_menu.addAction(_("Export"), lambda: self.export_contacts())
                invoices_menu = wallet_menu.addMenu(_("Invoices"))
       -        invoices_menu.addAction(_("Import"), lambda: self.invoice_list.import_invoices())
       -        invoices_menu.addAction(_("Export"), lambda: self.invoice_list.export_invoices())
       +        invoices_menu.addAction(_("Import"), lambda: self.import_invoices())
       +        invoices_menu.addAction(_("Export"), lambda: self.export_invoices())
       +        requests_menu = wallet_menu.addMenu(_("Requests"))
       +        requests_menu.addAction(_("Import"), lambda: self.import_requests())
       +        requests_menu.addAction(_("Export"), lambda: self.export_requests())
        
                wallet_menu.addSeparator()
                wallet_menu.addAction(_("Find"), self.toggle_search).setShortcut(QKeySequence("Ctrl+F"))
       t@@ -2754,23 +2757,31 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                        f.write(json.dumps(pklist, indent = 4))
        
            def do_import_labels(self):
       -        def import_labels(path):
       -            def _validate(data):
       -                return data  # TODO
       -
       -            def import_labels_assign(data):
       -                for key, value in data.items():
       -                    self.wallet.set_label(key, value)
       -            import_meta(path, _validate, import_labels_assign)
       -
                def on_import():
                    self.need_update.set()
       -        import_meta_gui(self, _('labels'), import_labels, on_import)
       +        import_meta_gui(self, _('labels'), self.wallet.import_labels, on_import)
        
            def do_export_labels(self):
       -        def export_labels(filename):
       -            export_meta(self.wallet.labels, filename)
       -        export_meta_gui(self, _('labels'), export_labels)
       +        export_meta_gui(self, _('labels'), self.wallet.export_labels)
       +
       +    def import_invoices(self):
       +        import_meta_gui(self, _('invoices'), self.wallet.import_invoices, self.invoice_list.update)
       +
       +    def export_invoices(self):
       +        export_meta_gui(self, _('invoices'), self.wallet.export_invoices)
       +
       +    def import_requests(self):
       +        import_meta_gui(self, _('requests'), self.wallet.import_requests, self.request_list.update)
       +
       +    def export_requests(self):
       +        export_meta_gui(self, _('requests'), self.wallet.export_requests)
       +
       +    def import_contacts(self):
       +        import_meta_gui(self, _('contacts'), self.contacts.import_file, self.contact_list.update)
       +
       +    def export_contacts(self):
       +        export_meta_gui(self, _('contacts'), self.contacts.export_file)
       +
        
            def sweep_key_dialog(self):
                d = WindowModalDialog(self, title=_('Sweep private keys'))
 (DIR) diff --git a/electrum/paymentrequest.py b/electrum/paymentrequest.py
       t@@ -40,7 +40,7 @@ except ImportError:
            sys.exit("Error: could not find paymentrequest_pb2.py. Create it with 'protoc --proto_path=electrum/ --python_out=electrum/ electrum/paymentrequest.proto'")
        
        from . import bitcoin, ecc, util, transaction, x509, rsakey
       -from .util import bh2u, bfh, export_meta, import_meta, make_aiohttp_session
       +from .util import bh2u, bfh, make_aiohttp_session
        from .invoices import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_UNKNOWN, PR_INFLIGHT
        from .crypto import sha256
        from .bitcoin import address_to_script
 (DIR) diff --git a/electrum/util.py b/electrum/util.py
       t@@ -952,11 +952,10 @@ def versiontuple(v):
            return tuple(map(int, (v.split("."))))
        
        
       -def import_meta(path, validater, load_meta):
       +def read_json_file(path):
            try:
                with open(path, 'r', encoding='utf-8') as f:
       -            d = validater(json.loads(f.read()))
       -        load_meta(d)
       +            data = json.loads(f.read())
            #backwards compatibility for JSONDecodeError
            except ValueError:
                _logger.exception('')
       t@@ -964,12 +963,12 @@ def import_meta(path, validater, load_meta):
            except BaseException as e:
                _logger.exception('')
                raise FileImportFailed(e)
       +    return data
        
       -
       -def export_meta(meta, fileName):
       +def write_json_file(path, data):
            try:
       -        with open(fileName, 'w+', encoding='utf-8') as f:
       -            json.dump(meta, f, indent=4, sort_keys=True)
       +        with open(path, 'w+', encoding='utf-8') as f:
       +            json.dump(data, f, indent=4, sort_keys=True, cls=MyEncoder)
            except (IOError, os.error) as e:
                _logger.exception('')
                raise FileExportFailed(e)
 (DIR) diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -76,6 +76,7 @@ from .mnemonic import Mnemonic
        from .logging import get_logger
        from .lnworker import LNWallet, LNBackups
        from .paymentrequest import PaymentRequest
       +from .util import read_json_file, write_json_file
        
        if TYPE_CHECKING:
            from .network import Network
       t@@ -421,6 +422,14 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
                    run_hook('set_label', self, name, text)
                return changed
        
       +    def import_labels(self, path):
       +        data = read_json_file(path)
       +        for key, value in data.items():
       +            self.set_label(key, value)
       +
       +    def export_labels(self, path):
       +        write_json_file(path, self.labels)
       +
            def set_fiat_value(self, txid, ccy, text, fx, value_sat):
                if not self.db.get_transaction(txid):
                    return
       t@@ -707,6 +716,24 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
            def get_invoice(self, key):
                return self.invoices.get(key)
        
       +    def import_requests(self, path):
       +        data = read_json_file(path)
       +        for x in data:
       +            req = invoice_from_json(x)
       +            self.add_payment_request(req)
       +
       +    def export_requests(self, path):
       +        write_json_file(path, list(self.receive_requests.values()))
       +
       +    def import_invoices(self, path):
       +        data = read_json_file(path)
       +        for x in data:
       +            invoice = invoice_from_json(x)
       +            self.save_invoice(invoice)
       +
       +    def export_invoices(self, path):
       +        write_json_file(path, list(self.invoices.values()))
       +
            def _get_relevant_invoice_keys_for_tx(self, tx: Transaction) -> Set[str]:
                relevant_invoice_keys = set()
                for txout in tx.outputs():