store contacts and invoices in wallet file. fix #1482

This commit is contained in:
ThomasV 2017-03-06 17:12:27 +01:00
parent acd70f55c3
commit dcffea150e
13 changed files with 96 additions and 91 deletions

View File

@ -11,7 +11,6 @@ import electrum
from electrum.bitcoin import TYPE_ADDRESS from electrum.bitcoin import TYPE_ADDRESS
from electrum import WalletStorage, Wallet from electrum import WalletStorage, Wallet
from electrum_gui.kivy.i18n import _ from electrum_gui.kivy.i18n import _
from electrum.contacts import Contacts
from electrum.paymentrequest import InvoiceStore from electrum.paymentrequest import InvoiceStore
from electrum.util import profiler, InvalidPassword from electrum.util import profiler, InvalidPassword
from electrum.plugins import run_hook from electrum.plugins import run_hook
@ -201,9 +200,6 @@ class ElectrumWindow(App):
self.daemon = self.gui_object.daemon self.daemon = self.gui_object.daemon
self.fx = self.daemon.fx 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 # create triggers so as to minimize updation a max of 2 times a sec
self._trigger_update_wallet =\ self._trigger_update_wallet =\
Clock.create_trigger(self.update_wallet, .5) 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 ' ' return os.path.basename(self.wallet.storage.path) if self.wallet else ' '
def on_pr(self, pr): def on_pr(self, pr):
if pr.verify(self.contacts): if pr.verify(self.wallet.contacts):
key = self.invoices.add(pr) key = self.wallet.invoices.add(pr)
if self.invoices_screen: if self.invoices_screen:
self.invoices_screen.update() self.invoices_screen.update()
status = self.invoices.get_status(key) status = self.wallet.invoices.get_status(key)
if status == PR_PAID: if status == PR_PAID:
self.show_error("invoice already paid") self.show_error("invoice already paid")
self.send_screen.do_clear() self.send_screen.do_clear()
@ -731,7 +727,7 @@ class ElectrumWindow(App):
self.show_info(txid) self.show_info(txid)
if ok and pr: if ok and pr:
pr.set_paid(tx.hash()) pr.set_paid(tx.hash())
self.invoices.save() self.wallet.invoices.save()
self.update_tab('invoices') self.update_tab('invoices')
if self.network and self.network.is_connected(): if self.network and self.network.is_connected():

View File

@ -224,7 +224,7 @@ class SendScreen(CScreen):
req['amount'] = amount req['amount'] = amount
pr = make_unsigned_request(req).SerializeToString() pr = make_unsigned_request(req).SerializeToString()
pr = PaymentRequest(pr) pr = PaymentRequest(pr)
self.app.invoices.add(pr) self.app.wallet.invoices.add(pr)
self.app.update_tab('invoices') self.app.update_tab('invoices')
self.app.show_info(_("Invoice saved")) self.app.show_info(_("Invoice saved"))
if pr.is_pr(): 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)] self.menu_actions = [('Pay', self.do_pay), ('Details', self.do_view), ('Delete', self.do_delete)]
invoices_list = self.screen.ids.invoices_container invoices_list = self.screen.ids.invoices_container
invoices_list.clear_widgets() invoices_list.clear_widgets()
_list = self.app.invoices.sorted_list() _list = self.app.wallet.invoices.sorted_list()
for pr in _list: for pr in _list:
ci = self.get_card(pr) ci = self.get_card(pr)
invoices_list.add_widget(ci) invoices_list.add_widget(ci)
@ -458,19 +458,19 @@ class InvoicesScreen(CScreen):
invoices_list.add_widget(EmptyLabel(text=msg)) invoices_list.add_widget(EmptyLabel(text=msg))
def do_pay(self, obj): 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) self.app.on_pr(pr)
def do_view(self, obj): def do_view(self, obj):
pr = self.app.invoices.get(obj.key) pr = self.app.wallet.invoices.get(obj.key)
pr.verify(self.app.contacts) pr.verify(self.app.wallet.contacts)
self.app.show_pr_details(pr.get_dict(), obj.status, True) self.app.show_pr_details(pr.get_dict(), obj.status, True)
def do_delete(self, obj): def do_delete(self, obj):
from dialogs.question import Question from dialogs.question import Question
def cb(result): def cb(result):
if result: if result:
self.app.invoices.remove(obj.key) self.app.wallet.invoices.remove(obj.key)
self.app.update_tab('invoices') self.app.update_tab('invoices')
d = Question(_('Delete invoice?'), cb) d = Question(_('Delete invoice?'), cb)
d.open() d.open()

View File

@ -39,8 +39,6 @@ import PyQt4.QtCore as QtCore
from electrum.i18n import _, set_language from electrum.i18n import _, set_language
from electrum.plugins import run_hook from electrum.plugins import run_hook
from electrum import SimpleConfig, Wallet, WalletStorage from electrum import SimpleConfig, Wallet, WalletStorage
from electrum.paymentrequest import InvoiceStore
from electrum.contacts import Contacts
from electrum.synchronizer import Synchronizer from electrum.synchronizer import Synchronizer
from electrum.verifier import SPV from electrum.verifier import SPV
from electrum.util import DebugMem, UserCancelled, InvalidPassword from electrum.util import DebugMem, UserCancelled, InvalidPassword
@ -89,9 +87,6 @@ class ElectrumGui:
self.app = QApplication(sys.argv) self.app = QApplication(sys.argv)
self.app.installEventFilter(self.efilter) self.app.installEventFilter(self.efilter)
self.timer = Timer() self.timer = Timer()
# shared objects
self.invoices = InvoiceStore(self.config)
self.contacts = Contacts(self.config)
# init tray # init tray
self.dark_icon = self.config.get("dark_icon", False) self.dark_icon = self.config.get("dark_icon", False)
self.tray = QSystemTrayIcon(self.tray_icon(), None) self.tray = QSystemTrayIcon(self.tray_icon(), None)

View File

@ -51,22 +51,29 @@ class ContactList(MyTreeWidget):
self.parent.contacts.pop(prior) self.parent.contacts.pop(prior)
self.parent.set_contact(unicode(item.text(0)), unicode(item.text(1))) 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): def create_menu(self, position):
menu = QMenu() menu = QMenu()
selected = self.selectedItems() selected = self.selectedItems()
if not selected: if not selected:
menu.addAction(_("New contact"), lambda: self.parent.new_contact_dialog()) menu.addAction(_("New contact"), lambda: self.parent.new_contact_dialog())
menu.addAction(_("Import file"), lambda: self.parent.import_contacts())
else: else:
names = [unicode(item.text(0)) for item in selected] names = [unicode(item.text(0)) for item in selected]
keys = [unicode(item.text(1)) for item in selected] keys = [unicode(item.text(1)) for item in selected]
column = self.currentColumn() column = self.currentColumn()
column_title = self.headerItem().text(column) column_title = self.headerItem().text(column)
column_data = '\n'.join([unicode(item.text(column)) for item in selected]) 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)) menu.addAction(_("Copy %s")%column_title, lambda: self.parent.app.clipboard().setText(column_data))
if column in self.editable_columns: if column in self.editable_columns:
menu.addAction(_("Edit %s")%column_title, lambda: self.editItem(item, column)) menu.addAction(_("Edit %s")%column_title, lambda: self.editItem(item, column))
menu.addAction(_("Pay to"), lambda: self.parent.payto_contacts(keys)) menu.addAction(_("Pay to"), lambda: self.parent.payto_contacts(keys))
menu.addAction(_("Delete"), lambda: self.parent.delete_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)] URLs = [block_explorer_URL(self.config, 'addr', key) for key in filter(is_address, keys)]

View File

@ -58,17 +58,23 @@ class InvoiceList(MyTreeWidget):
self.setVisible(len(inv_list)) self.setVisible(len(inv_list))
self.parent.invoices_label.setVisible(len(inv_list)) self.parent.invoices_label.setVisible(len(inv_list))
def create_menu(self, position): def import_invoices(self):
item = self.itemAt(position) wallet_folder = self.parent.get_wallet_folder()
if not item: filename = unicode(QFileDialog.getOpenFileName(self.parent, "Select your wallet file", wallet_folder))
if not filename:
return return
self.parent.invoices.import_file(filename)
self.on_update()
def create_menu(self, position):
menu = QMenu()
item = self.itemAt(position)
key = str(item.data(0, 32).toString()) key = str(item.data(0, 32).toString())
column = self.currentColumn() column = self.currentColumn()
column_title = self.headerItem().text(column) column_title = self.headerItem().text(column)
column_data = item.text(column) column_data = item.text(column)
pr = self.parent.invoices.get(key) pr = self.parent.invoices.get(key)
status = self.parent.invoices.get_status(key) status = self.parent.invoices.get_status(key)
menu = QMenu()
if column_data: if column_data:
menu.addAction(_("Copy %s")%column_title, lambda: self.parent.app.clipboard().setText(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)) menu.addAction(_("Details"), lambda: self.parent.show_invoice(key))

View File

@ -47,7 +47,7 @@ from electrum.plugins import run_hook
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import (block_explorer, block_explorer_info, format_time, from electrum.util import (block_explorer, block_explorer_info, format_time,
block_explorer_URL, format_satoshis, PrintError, block_explorer_URL, format_satoshis, PrintError,
format_satoshis_plain, NotEnoughFunds, StoreDict, format_satoshis_plain, NotEnoughFunds,
UserCancelled) UserCancelled)
from electrum import Transaction, mnemonic from electrum import Transaction, mnemonic
from electrum import util, bitcoin, commands, coinchooser from electrum import util, bitcoin, commands, coinchooser
@ -99,8 +99,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.config = config = gui_object.config self.config = config = gui_object.config
self.network = gui_object.daemon.network self.network = gui_object.daemon.network
self.fx = gui_object.daemon.fx self.fx = gui_object.daemon.fx
self.invoices = gui_object.invoices self.invoices = wallet.invoices
self.contacts = gui_object.contacts self.contacts = wallet.contacts
self.tray = gui_object.tray self.tray = gui_object.tray
self.app = gui_object.app self.app = gui_object.app
self.cleaned_up = False self.cleaned_up = False
@ -434,6 +434,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
wallet_menu = menubar.addMenu(_("&Wallet")) wallet_menu = menubar.addMenu(_("&Wallet"))
wallet_menu.addAction(_("&New contact"), self.new_contact_dialog) 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() wallet_menu.addSeparator()
self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog) self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)

View File

@ -2,7 +2,7 @@ from decimal import Decimal
_ = lambda x:x _ = lambda x:x
#from i18n import _ #from i18n import _
from electrum import WalletStorage, Wallet 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.bitcoin import is_valid, COIN, TYPE_ADDRESS
from electrum.network import filter_protocol from electrum.network import filter_protocol
import sys, getpass, datetime import sys, getpass, datetime
@ -35,7 +35,7 @@ class ElectrumGui:
self.wallet = Wallet(storage) self.wallet = Wallet(storage)
self.wallet.start_threads(self.network) 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.network.register_callback(self.on_network, ['updated', 'banner'])
self.commands = [_("[h] - displays this help text"), \ self.commands = [_("[h] - displays this help text"), \

View File

@ -4,7 +4,6 @@ from decimal import Decimal
import getpass import getpass
from electrum.util import format_satoshis, set_verbosity from electrum.util import format_satoshis, set_verbosity
from electrum.util import StoreDict
from electrum.bitcoin import is_valid, COIN, TYPE_ADDRESS from electrum.bitcoin import is_valid, COIN, TYPE_ADDRESS
from electrum import Wallet, WalletStorage from electrum import Wallet, WalletStorage
@ -27,7 +26,7 @@ class ElectrumGui:
storage.decrypt(password) storage.decrypt(password)
self.wallet = Wallet(storage) self.wallet = Wallet(storage)
self.wallet.start_threads(self.network) self.wallet.start_threads(self.network)
self.contacts = StoreDict(self.config, 'contacts') self.contacts = self.wallet.contacts
locale.setlocale(locale.LC_ALL, '') locale.setlocale(locale.LC_ALL, '')
self.encoding = locale.getpreferredencoding() self.encoding = locale.getpreferredencoding()

View File

@ -93,7 +93,6 @@ class Commands:
self._callback = callback self._callback = callback
self._password = password self._password = password
self.new_password = new_password self.new_password = new_password
self.contacts = contacts.Contacts(self.config)
def _run(self, method, args, password_getter): def _run(self, method, args, password_getter):
cmd = known_commands[method] cmd = known_commands[method]
@ -371,7 +370,7 @@ class Commands:
def _resolver(self, x): def _resolver(self, x):
if x is None: if x is None:
return 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: if out.get('type') == 'openalias' and self.nocheck is False and out.get('validated') is False:
raise BaseException('cannot verify alias', x) raise BaseException('cannot verify alias', x)
return out['address'] return out['address']
@ -464,21 +463,21 @@ class Commands:
transaction ID""" transaction ID"""
self.wallet.set_label(key, label) self.wallet.set_label(key, label)
@command('') @command('w')
def listcontacts(self): def listcontacts(self):
"""Show your list of contacts""" """Show your list of contacts"""
return self.contacts return self.wallet.contacts
@command('') @command('w')
def getalias(self, key): def getalias(self, key):
"""Retrieve alias. Lookup in your list of contacts, and for an OpenAlias DNS record.""" """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): def searchcontacts(self, query):
"""Search through contacts, return matching entries. """ """Search through contacts, return matching entries. """
results = {} results = {}
for key, value in self.contacts.items(): for key, value in self.wallet.contacts.items():
if query.lower() in key.lower(): if query.lower() in key.lower():
results[key] = value results[key] = value
return results return results
@ -603,7 +602,7 @@ class Commands:
alias = self.config.get('alias') alias = self.config.get('alias')
if not alias: if not alias:
raise BaseException('No alias in your configuration') 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) self.wallet.sign_payment_request(address, alias, alias_addr, self._password)
@command('w') @command('w')

View File

@ -24,17 +24,21 @@
import sys import sys
import re import re
import dns import dns
import os
import json
import bitcoin import bitcoin
import dnssec import dnssec
from util import StoreDict, print_error from util import print_error
from i18n import _ from i18n import _
class Contacts(StoreDict): class Contacts(dict):
def __init__(self, config): def __init__(self, storage):
StoreDict.__init__(self, config, 'contacts') self.storage = storage
d = self.storage.get('contacts', {})
self.update(d)
# backward compatibility # backward compatibility
for k, v in self.items(): for k, v in self.items():
_type, n = v _type, n = v
@ -42,6 +46,26 @@ class Contacts(StoreDict):
self.pop(k) self.pop(k)
self[n] = ('address', 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): def resolve(self, k):
if bitcoin.is_address(k): if bitcoin.is_address(k):

View File

@ -457,18 +457,13 @@ def make_request(config, req):
class InvoiceStore(object): class InvoiceStore(object):
def __init__(self, config): def __init__(self, storage):
self.config = config self.storage = storage
self.invoices = {} self.invoices = {}
self.load_invoices() d = self.storage.get('invoices', {})
self.load(d)
def load_invoices(self): def load(self, d):
path = os.path.join(self.config.path, 'invoices')
try:
with open(path, 'r') as f:
d = json.loads(f.read())
except:
return
for k, v in d.items(): for k, v in d.items():
try: try:
pr = PaymentRequest(v.get('hex').decode('hex')) pr = PaymentRequest(v.get('hex').decode('hex'))
@ -478,6 +473,15 @@ class InvoiceStore(object):
except: except:
continue 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): def save(self):
l = {} l = {}
for k, pr in self.invoices.items(): for k, pr in self.invoices.items():
@ -486,10 +490,7 @@ class InvoiceStore(object):
'requestor': pr.requestor, 'requestor': pr.requestor,
'txid': pr.tx 'txid': pr.tx
} }
path = os.path.join(self.config.path, 'invoices') self.storage.put('invoices', l)
with open(path, 'w') as f:
s = json.dumps(l, indent=4, sort_keys=True)
r = f.write(s)
def get_status(self, key): def get_status(self, key):
pr = self.get(key) pr = self.get(key)

View File

@ -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): def check_www_dir(rdir):
import urllib, urlparse, shutil, os import urllib, urlparse, shutil, os
if not os.path.exists(rdir): if not os.path.exists(rdir):

View File

@ -62,6 +62,8 @@ from verifier import SPV
from mnemonic import Mnemonic from mnemonic import Mnemonic
import paymentrequest import paymentrequest
from paymentrequest import InvoiceStore
from contacts import Contacts
TX_STATUS = [ TX_STATUS = [
@ -127,6 +129,11 @@ class Abstract_Wallet(PrintError):
if self.storage.get('wallet_type') is None: if self.storage.get('wallet_type') is None:
self.storage.put('wallet_type', self.wallet_type) 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): def diagnostic_name(self):
return self.basename() return self.basename()