Browse Source

store contacts and invoices in wallet file. fix #1482

283
ThomasV 8 years ago
parent
commit
dcffea150e
  1. 12
      gui/kivy/main_window.py
  2. 12
      gui/kivy/uix/screens.py
  3. 5
      gui/qt/__init__.py
  4. 11
      gui/qt/contact_list.py
  5. 14
      gui/qt/invoice_list.py
  6. 8
      gui/qt/main_window.py
  7. 4
      gui/stdio.py
  8. 3
      gui/text.py
  9. 17
      lib/commands.py
  10. 32
      lib/contacts.py
  11. 29
      lib/paymentrequest.py
  12. 31
      lib/util.py
  13. 7
      lib/wallet.py

12
gui/kivy/main_window.py

@ -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():

12
gui/kivy/uix/screens.py

@ -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()

5
gui/qt/__init__.py

@ -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)

11
gui/qt/contact_list.py

@ -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)]

14
gui/qt/invoice_list.py

@ -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 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): def create_menu(self, position):
menu = QMenu()
item = self.itemAt(position) item = self.itemAt(position)
if not item:
return
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))

8
gui/qt/main_window.py

@ -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)

4
gui/stdio.py

@ -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"), \

3
gui/text.py

@ -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()

17
lib/commands.py

@ -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')

32
lib/contacts.py

@ -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):

29
lib/paymentrequest.py

@ -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)

31
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): 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):

7
lib/wallet.py

@ -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()

Loading…
Cancel
Save