tstore contacts and invoices in wallet file. fix #1482 - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit dcffea150e5af6b456412e93778b8f491f1bb171
 (DIR) parent acd70f55c33dce8004f51fa6e77984f522d504ef
 (HTM) Author: ThomasV <thomasv@electrum.org>
       Date:   Mon,  6 Mar 2017 17:12:27 +0100
       
       store contacts and invoices in wallet file. fix #1482
       
       Diffstat:
         M gui/kivy/main_window.py             |      12 ++++--------
         M gui/kivy/uix/screens.py             |      12 ++++++------
         M gui/qt/__init__.py                  |       5 -----
         M gui/qt/contact_list.py              |      11 +++++++++--
         M gui/qt/invoice_list.py              |      14 ++++++++++----
         M gui/qt/main_window.py               |       8 +++++---
         M gui/stdio.py                        |       4 ++--
         M gui/text.py                         |       3 +--
         M lib/commands.py                     |      17 ++++++++---------
         M lib/contacts.py                     |      32 +++++++++++++++++++++++++++----
         M lib/paymentrequest.py               |      29 +++++++++++++++--------------
         M lib/util.py                         |      31 -------------------------------
         M lib/wallet.py                       |       7 +++++++
       
       13 files changed, 95 insertions(+), 90 deletions(-)
       ---
 (DIR) diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py
       t@@ -11,7 +11,6 @@ import electrum
        from electrum.bitcoin import TYPE_ADDRESS
        from electrum import WalletStorage, Wallet
        from electrum_gui.kivy.i18n import _
       -from electrum.contacts import Contacts
        from electrum.paymentrequest import InvoiceStore
        from electrum.util import profiler, InvalidPassword
        from electrum.plugins import run_hook
       t@@ -201,9 +200,6 @@ class ElectrumWindow(App):
                self.daemon = self.gui_object.daemon
                self.fx = self.daemon.fx
        
       -        self.contacts = Contacts(self.electrum_config)
       -        self.invoices = InvoiceStore(self.electrum_config)
       -
                # create triggers so as to minimize updation a max of 2 times a sec
                self._trigger_update_wallet =\
                    Clock.create_trigger(self.update_wallet, .5)
       t@@ -217,11 +213,11 @@ class ElectrumWindow(App):
                return os.path.basename(self.wallet.storage.path) if self.wallet else ' '
        
            def on_pr(self, pr):
       -        if pr.verify(self.contacts):
       -            key = self.invoices.add(pr)
       +        if pr.verify(self.wallet.contacts):
       +            key = self.wallet.invoices.add(pr)
                    if self.invoices_screen:
                        self.invoices_screen.update()
       -            status = self.invoices.get_status(key)
       +            status = self.wallet.invoices.get_status(key)
                    if status == PR_PAID:
                        self.show_error("invoice already paid")
                        self.send_screen.do_clear()
       t@@ -731,7 +727,7 @@ class ElectrumWindow(App):
                    self.show_info(txid)
                    if ok and pr:
                        pr.set_paid(tx.hash())
       -                self.invoices.save()
       +                self.wallet.invoices.save()
                        self.update_tab('invoices')
        
                if self.network and self.network.is_connected():
 (DIR) diff --git a/gui/kivy/uix/screens.py b/gui/kivy/uix/screens.py
       t@@ -224,7 +224,7 @@ class SendScreen(CScreen):
                req['amount'] = amount
                pr = make_unsigned_request(req).SerializeToString()
                pr = PaymentRequest(pr)
       -        self.app.invoices.add(pr)
       +        self.app.wallet.invoices.add(pr)
                self.app.update_tab('invoices')
                self.app.show_info(_("Invoice saved"))
                if pr.is_pr():
       t@@ -449,7 +449,7 @@ class InvoicesScreen(CScreen):
                self.menu_actions = [('Pay', self.do_pay), ('Details', self.do_view), ('Delete', self.do_delete)]
                invoices_list = self.screen.ids.invoices_container
                invoices_list.clear_widgets()
       -        _list = self.app.invoices.sorted_list()
       +        _list = self.app.wallet.invoices.sorted_list()
                for pr in _list:
                    ci = self.get_card(pr)
                    invoices_list.add_widget(ci)
       t@@ -458,19 +458,19 @@ class InvoicesScreen(CScreen):
                    invoices_list.add_widget(EmptyLabel(text=msg))
        
            def do_pay(self, obj):
       -        pr = self.app.invoices.get(obj.key)
       +        pr = self.app.wallet.invoices.get(obj.key)
                self.app.on_pr(pr)
        
            def do_view(self, obj):
       -        pr = self.app.invoices.get(obj.key)
       -        pr.verify(self.app.contacts)
       +        pr = self.app.wallet.invoices.get(obj.key)
       +        pr.verify(self.app.wallet.contacts)
                self.app.show_pr_details(pr.get_dict(), obj.status, True)
        
            def do_delete(self, obj):
                from dialogs.question import Question
                def cb(result):
                    if result:
       -                self.app.invoices.remove(obj.key)
       +                self.app.wallet.invoices.remove(obj.key)
                        self.app.update_tab('invoices')
                d = Question(_('Delete invoice?'), cb)
                d.open()
 (DIR) diff --git a/gui/qt/__init__.py b/gui/qt/__init__.py
       t@@ -39,8 +39,6 @@ import PyQt4.QtCore as QtCore
        from electrum.i18n import _, set_language
        from electrum.plugins import run_hook
        from electrum import SimpleConfig, Wallet, WalletStorage
       -from electrum.paymentrequest import InvoiceStore
       -from electrum.contacts import Contacts
        from electrum.synchronizer import Synchronizer
        from electrum.verifier import SPV
        from electrum.util import DebugMem, UserCancelled, InvalidPassword
       t@@ -89,9 +87,6 @@ class ElectrumGui:
                self.app = QApplication(sys.argv)
                self.app.installEventFilter(self.efilter)
                self.timer = Timer()
       -        # shared objects
       -        self.invoices = InvoiceStore(self.config)
       -        self.contacts = Contacts(self.config)
                # init tray
                self.dark_icon = self.config.get("dark_icon", False)
                self.tray = QSystemTrayIcon(self.tray_icon(), None)
 (DIR) diff --git a/gui/qt/contact_list.py b/gui/qt/contact_list.py
       t@@ -51,22 +51,29 @@ class ContactList(MyTreeWidget):
                    self.parent.contacts.pop(prior)
                self.parent.set_contact(unicode(item.text(0)), unicode(item.text(1)))
        
       +    def import_contacts(self):
       +        wallet_folder = self.parent.get_wallet_folder()
       +        filename = unicode(QFileDialog.getOpenFileName(self.parent, "Select your wallet file", wallet_folder))
       +        if not filename:
       +            return
       +        self.parent.contacts.import_file(filename)
       +        self.on_update()
       +
            def create_menu(self, position):
                menu = QMenu()
                selected = self.selectedItems()
                if not selected:
                    menu.addAction(_("New contact"), lambda: self.parent.new_contact_dialog())
       +            menu.addAction(_("Import file"), lambda: self.parent.import_contacts())
                else:
                    names = [unicode(item.text(0)) for item in selected]
                    keys = [unicode(item.text(1)) for item in selected]
                    column = self.currentColumn()
                    column_title = self.headerItem().text(column)
                    column_data = '\n'.join([unicode(item.text(column)) for item in selected])
       -
                    menu.addAction(_("Copy %s")%column_title, lambda: self.parent.app.clipboard().setText(column_data))
                    if column in self.editable_columns:
                        menu.addAction(_("Edit %s")%column_title, lambda: self.editItem(item, column))
       -
                    menu.addAction(_("Pay to"), lambda: self.parent.payto_contacts(keys))
                    menu.addAction(_("Delete"), lambda: self.parent.delete_contacts(keys))
                    URLs = [block_explorer_URL(self.config, 'addr', key) for key in filter(is_address, keys)]
 (DIR) diff --git a/gui/qt/invoice_list.py b/gui/qt/invoice_list.py
       t@@ -58,17 +58,23 @@ class InvoiceList(MyTreeWidget):
                self.setVisible(len(inv_list))
                self.parent.invoices_label.setVisible(len(inv_list))
        
       +    def import_invoices(self):
       +        wallet_folder = self.parent.get_wallet_folder()
       +        filename = unicode(QFileDialog.getOpenFileName(self.parent, "Select your wallet file", wallet_folder))
       +        if not filename:
       +            return
       +        self.parent.invoices.import_file(filename)
       +        self.on_update()
       +
            def create_menu(self, position):
       +        menu = QMenu()
                item = self.itemAt(position)
       -        if not item:
       -            return
                key = str(item.data(0, 32).toString())
       -        column = self.currentColumn()        
       +        column = self.currentColumn()
                column_title = self.headerItem().text(column)
                column_data = item.text(column)
                pr = self.parent.invoices.get(key)
                status = self.parent.invoices.get_status(key)
       -        menu = QMenu()
                if column_data:
                    menu.addAction(_("Copy %s")%column_title, lambda: self.parent.app.clipboard().setText(column_data))
                menu.addAction(_("Details"), lambda: self.parent.show_invoice(key))
 (DIR) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -47,7 +47,7 @@ from electrum.plugins import run_hook
        from electrum.i18n import _
        from electrum.util import (block_explorer, block_explorer_info, format_time,
                                   block_explorer_URL, format_satoshis, PrintError,
       -                           format_satoshis_plain, NotEnoughFunds, StoreDict,
       +                           format_satoshis_plain, NotEnoughFunds,
                                   UserCancelled)
        from electrum import Transaction, mnemonic
        from electrum import util, bitcoin, commands, coinchooser
       t@@ -99,8 +99,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                self.config = config = gui_object.config
                self.network = gui_object.daemon.network
                self.fx = gui_object.daemon.fx
       -        self.invoices = gui_object.invoices
       -        self.contacts = gui_object.contacts
       +        self.invoices = wallet.invoices
       +        self.contacts = wallet.contacts
                self.tray = gui_object.tray
                self.app = gui_object.app
                self.cleaned_up = False
       t@@ -434,6 +434,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
        
                wallet_menu = menubar.addMenu(_("&Wallet"))
                wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
       +        wallet_menu.addAction(_("Import invoices"), lambda: self.invoice_list.import_invoices())
       +        wallet_menu.addAction(_("Import contacts"), lambda: self.contact_list.import_contacts())
                wallet_menu.addSeparator()
        
                self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
 (DIR) diff --git a/gui/stdio.py b/gui/stdio.py
       t@@ -2,7 +2,7 @@ from decimal import Decimal
        _ = lambda x:x
        #from i18n import _
        from electrum import WalletStorage, Wallet
       -from electrum.util import format_satoshis, set_verbosity, StoreDict
       +from electrum.util import format_satoshis, set_verbosity
        from electrum.bitcoin import is_valid, COIN, TYPE_ADDRESS
        from electrum.network import filter_protocol
        import sys, getpass, datetime
       t@@ -35,7 +35,7 @@ class ElectrumGui:
        
                self.wallet = Wallet(storage)
                self.wallet.start_threads(self.network)
       -        self.contacts = StoreDict(self.config, 'contacts')
       +        self.contacts = self.wallet.contacts
        
                self.network.register_callback(self.on_network, ['updated', 'banner'])
                self.commands = [_("[h] - displays this help text"), \
 (DIR) diff --git a/gui/text.py b/gui/text.py
       t@@ -4,7 +4,6 @@ from decimal import Decimal
        import getpass
        
        from electrum.util import format_satoshis, set_verbosity
       -from electrum.util import StoreDict
        from electrum.bitcoin import is_valid, COIN, TYPE_ADDRESS
        from electrum import Wallet, WalletStorage
        
       t@@ -27,7 +26,7 @@ class ElectrumGui:
                    storage.decrypt(password)
                self.wallet = Wallet(storage)
                self.wallet.start_threads(self.network)
       -        self.contacts = StoreDict(self.config, 'contacts')
       +        self.contacts = self.wallet.contacts
        
                locale.setlocale(locale.LC_ALL, '')
                self.encoding = locale.getpreferredencoding()
 (DIR) diff --git a/lib/commands.py b/lib/commands.py
       t@@ -93,7 +93,6 @@ class Commands:
                self._callback = callback
                self._password = password
                self.new_password = new_password
       -        self.contacts = contacts.Contacts(self.config)
        
            def _run(self, method, args, password_getter):
                cmd = known_commands[method]
       t@@ -371,7 +370,7 @@ class Commands:
            def _resolver(self, x):
                if x is None:
                    return None
       -        out = self.contacts.resolve(x)
       +        out = self.wallet.contacts.resolve(x)
                if out.get('type') == 'openalias' and self.nocheck is False and out.get('validated') is False:
                    raise BaseException('cannot verify alias', x)
                return out['address']
       t@@ -464,21 +463,21 @@ class Commands:
                transaction ID"""
                self.wallet.set_label(key, label)
        
       -    @command('')
       +    @command('w')
            def listcontacts(self):
                """Show your list of contacts"""
       -        return self.contacts
       +        return self.wallet.contacts
        
       -    @command('')
       +    @command('w')
            def getalias(self, key):
                """Retrieve alias. Lookup in your list of contacts, and for an OpenAlias DNS record."""
       -        return self.contacts.resolve(key)
       +        return self.wallet.contacts.resolve(key)
        
       -    @command('')
       +    @command('w')
            def searchcontacts(self, query):
                """Search through contacts, return matching entries. """
                results = {}
       -        for key, value in self.contacts.items():
       +        for key, value in self.wallet.contacts.items():
                    if query.lower() in key.lower():
                        results[key] = value
                return results
       t@@ -603,7 +602,7 @@ class Commands:
                alias = self.config.get('alias')
                if not alias:
                    raise BaseException('No alias in your configuration')
       -        alias_addr = self.contacts.resolve(alias)['address']
       +        alias_addr = self.wallet.contacts.resolve(alias)['address']
                self.wallet.sign_payment_request(address, alias, alias_addr, self._password)
        
            @command('w')
 (DIR) diff --git a/lib/contacts.py b/lib/contacts.py
       t@@ -24,17 +24,21 @@
        import sys
        import re
        import dns
       +import os
       +import json
        
        import bitcoin
        import dnssec
       -from util import StoreDict, print_error
       +from util import print_error
        from i18n import _
        
        
       -class Contacts(StoreDict):
       +class Contacts(dict):
        
       -    def __init__(self, config):
       -        StoreDict.__init__(self, config, 'contacts')
       +    def __init__(self, storage):
       +        self.storage = storage
       +        d = self.storage.get('contacts', {})
       +        self.update(d)
                # backward compatibility
                for k, v in self.items():
                    _type, n = v
       t@@ -42,6 +46,26 @@ class Contacts(StoreDict):
                        self.pop(k)
                        self[n] = ('address', k)
        
       +    def save(self):
       +        self.storage.put('contacts', dict(self))
       +
       +    def import_file(self, path):
       +        try:
       +            with open(path, 'r') as f:
       +                d = json.loads(f.read())
       +        except:
       +            return
       +        self.update(d)
       +        self.save()
       +
       +    def __setitem__(self, key, value):
       +        dict.__setitem__(self, key, value)
       +        self.save()
       +
       +    def pop(self, key):
       +        if key in self.keys():
       +            dict.pop(self, key)
       +            self.save()
        
            def resolve(self, k):
                if bitcoin.is_address(k):
 (DIR) diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py
       t@@ -457,18 +457,13 @@ def make_request(config, req):
        
        class InvoiceStore(object):
        
       -    def __init__(self, config):
       -        self.config = config
       +    def __init__(self, storage):
       +        self.storage = storage
                self.invoices = {}
       -        self.load_invoices()
       +        d = self.storage.get('invoices', {})
       +        self.load(d)
        
       -    def load_invoices(self):
       -        path = os.path.join(self.config.path, 'invoices')
       -        try:
       -            with open(path, 'r') as f:
       -                d = json.loads(f.read())
       -        except:
       -            return
       +    def load(self, d):
                for k, v in d.items():
                    try:
                        pr = PaymentRequest(v.get('hex').decode('hex'))
       t@@ -478,6 +473,15 @@ class InvoiceStore(object):
                    except:
                        continue
        
       +    def import_file(self, path):
       +        try:
       +            with open(path, 'r') as f:
       +                d = json.loads(f.read())
       +                self.load(d)
       +        except:
       +            return
       +        self.save()
       +
            def save(self):
                l = {}
                for k, pr in self.invoices.items():
       t@@ -486,10 +490,7 @@ class InvoiceStore(object):
                        'requestor': pr.requestor,
                        'txid': pr.tx
                    }
       -        path = os.path.join(self.config.path, 'invoices')
       -        with open(path, 'w') as f:
       -            s = json.dumps(l, indent=4, sort_keys=True)
       -            r = f.write(s)
       +        self.storage.put('invoices', l)
        
            def get_status(self, key):
                pr = self.get(key)
 (DIR) diff --git a/lib/util.py b/lib/util.py
       t@@ -622,37 +622,6 @@ class QueuePipe:
        
        
        
       -class StoreDict(dict):
       -
       -    def __init__(self, config, name):
       -        self.config = config
       -        self.path = os.path.join(self.config.path, name)
       -        self.load()
       -
       -    def load(self):
       -        try:
       -            with open(self.path, 'r') as f:
       -                self.update(json.loads(f.read()))
       -        except:
       -            pass
       -
       -    def save(self):
       -        with open(self.path, 'w') as f:
       -            s = json.dumps(self, indent=4, sort_keys=True)
       -            r = f.write(s)
       -
       -    def __setitem__(self, key, value):
       -        dict.__setitem__(self, key, value)
       -        self.save()
       -
       -    def pop(self, key):
       -        if key in self.keys():
       -            dict.pop(self, key)
       -            self.save()
       -
       -
       -
       -
        def check_www_dir(rdir):
            import urllib, urlparse, shutil, os
            if not os.path.exists(rdir):
 (DIR) diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -62,6 +62,8 @@ from verifier import SPV
        from mnemonic import Mnemonic
        
        import paymentrequest
       +from paymentrequest import InvoiceStore
       +from contacts import Contacts
        
        
        TX_STATUS = [
       t@@ -127,6 +129,11 @@ class Abstract_Wallet(PrintError):
                if self.storage.get('wallet_type') is None:
                    self.storage.put('wallet_type', self.wallet_type)
        
       +        # invoices and contacts
       +        self.invoices = InvoiceStore(self.storage)
       +        self.contacts = Contacts(self.storage)
       +
       +
            def diagnostic_name(self):
                return self.basename()