electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit dcffea150e5af6b456412e93778b8f491f1bb171
parent acd70f55c33dce8004f51fa6e77984f522d504ef
Author: ThomasV <thomasv@electrum.org>
Date:   Mon,  6 Mar 2017 17:12:27 +0100

store contacts and invoices in wallet file. fix #1482

Diffstat:
Mgui/kivy/main_window.py | 12++++--------
Mgui/kivy/uix/screens.py | 12++++++------
Mgui/qt/__init__.py | 5-----
Mgui/qt/contact_list.py | 11+++++++++--
Mgui/qt/invoice_list.py | 14++++++++++----
Mgui/qt/main_window.py | 8+++++---
Mgui/stdio.py | 4++--
Mgui/text.py | 3+--
Mlib/commands.py | 17++++++++---------
Mlib/contacts.py | 32++++++++++++++++++++++++++++----
Mlib/paymentrequest.py | 29+++++++++++++++--------------
Mlib/util.py | 31-------------------------------
Mlib/wallet.py | 7+++++++
13 files changed, 95 insertions(+), 90 deletions(-)

diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py @@ -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 @@ -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) @@ -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() @@ -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(): diff --git a/gui/kivy/uix/screens.py b/gui/kivy/uix/screens.py @@ -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(): @@ -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) @@ -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() diff --git a/gui/qt/__init__.py b/gui/qt/__init__.py @@ -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 @@ -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) diff --git a/gui/qt/contact_list.py b/gui/qt/contact_list.py @@ -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)] diff --git a/gui/qt/invoice_list.py b/gui/qt/invoice_list.py @@ -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)) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py @@ -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 @@ -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 @@ -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) diff --git a/gui/stdio.py b/gui/stdio.py @@ -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 @@ -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"), \ diff --git a/gui/text.py b/gui/text.py @@ -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 @@ -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() diff --git a/lib/commands.py b/lib/commands.py @@ -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] @@ -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'] @@ -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 @@ -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') diff --git a/lib/contacts.py b/lib/contacts.py @@ -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 @@ -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): diff --git a/lib/paymentrequest.py b/lib/paymentrequest.py @@ -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')) @@ -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(): @@ -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) diff --git a/lib/util.py b/lib/util.py @@ -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): diff --git a/lib/wallet.py b/lib/wallet.py @@ -62,6 +62,8 @@ from verifier import SPV from mnemonic import Mnemonic import paymentrequest +from paymentrequest import InvoiceStore +from contacts import Contacts TX_STATUS = [ @@ -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()