store contacts in a separate file, shared between wallets
This commit is contained in:
parent
065145e557
commit
e8189490e9
|
@ -34,7 +34,7 @@ from electrum.plugins import run_hook
|
||||||
|
|
||||||
import icons_rc
|
import icons_rc
|
||||||
|
|
||||||
from electrum.util import format_satoshis, format_time, NotEnoughFunds
|
from electrum.util import format_satoshis, format_time, NotEnoughFunds, StoreDict
|
||||||
from electrum import Transaction
|
from electrum import Transaction
|
||||||
from electrum import mnemonic
|
from electrum import mnemonic
|
||||||
from electrum import util, bitcoin, commands, Interface, Wallet
|
from electrum import util, bitcoin, commands, Interface, Wallet
|
||||||
|
@ -117,6 +117,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
self.app = gui_object.app
|
self.app = gui_object.app
|
||||||
|
|
||||||
self.invoices = InvoiceStore(self.config)
|
self.invoices = InvoiceStore(self.config)
|
||||||
|
self.contacts = StoreDict(self.config, 'contacts')
|
||||||
|
|
||||||
self.create_status_bar()
|
self.create_status_bar()
|
||||||
self.need_update = threading.Event()
|
self.need_update = threading.Event()
|
||||||
|
@ -201,11 +202,12 @@ class ElectrumWindow(QMainWindow):
|
||||||
def load_wallet(self, wallet):
|
def load_wallet(self, wallet):
|
||||||
import electrum
|
import electrum
|
||||||
self.wallet = wallet
|
self.wallet = wallet
|
||||||
|
# backward compatibility
|
||||||
self.update_wallet_format()
|
self.update_wallet_format()
|
||||||
|
self.import_old_contacts()
|
||||||
# address used to create a dummy transaction and estimate transaction fee
|
# address used to create a dummy transaction and estimate transaction fee
|
||||||
a = self.wallet.addresses(False)
|
a = self.wallet.addresses(False)
|
||||||
self.dummy_address = a[0] if a else None
|
self.dummy_address = a[0] if a else None
|
||||||
|
|
||||||
self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
|
self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
|
||||||
self.current_account = self.wallet.storage.get("current_account", None)
|
self.current_account = self.wallet.storage.get("current_account", None)
|
||||||
title = 'Electrum ' + self.wallet.electrum_version + ' - ' + os.path.basename(self.wallet.storage.path)
|
title = 'Electrum ' + self.wallet.electrum_version + ' - ' + os.path.basename(self.wallet.storage.path)
|
||||||
|
@ -224,16 +226,22 @@ class ElectrumWindow(QMainWindow):
|
||||||
self.mpk_menu.setEnabled(self.wallet.is_deterministic())
|
self.mpk_menu.setEnabled(self.wallet.is_deterministic())
|
||||||
self.import_menu.setVisible(self.wallet.can_import())
|
self.import_menu.setVisible(self.wallet.can_import())
|
||||||
self.export_menu.setEnabled(self.wallet.can_export())
|
self.export_menu.setEnabled(self.wallet.can_export())
|
||||||
|
|
||||||
self.update_lock_icon()
|
self.update_lock_icon()
|
||||||
self.update_buttons_on_seed()
|
self.update_buttons_on_seed()
|
||||||
self.update_console()
|
self.update_console()
|
||||||
|
|
||||||
self.clear_receive_tab()
|
self.clear_receive_tab()
|
||||||
self.update_receive_tab()
|
self.update_receive_tab()
|
||||||
self.show()
|
self.show()
|
||||||
run_hook('load_wallet', wallet)
|
run_hook('load_wallet', wallet)
|
||||||
|
|
||||||
|
def import_old_contacts(self):
|
||||||
|
# backward compatibility: import contacts
|
||||||
|
addressbook = set(self.wallet.storage.get('contacts', []))
|
||||||
|
for k in addressbook:
|
||||||
|
l = self.wallet.labels.get(k)
|
||||||
|
if bitcoin.is_address(k) and l:
|
||||||
|
self.contacts[l] = ('address', k)
|
||||||
|
self.wallet.storage.put('contacts', None)
|
||||||
|
|
||||||
def update_wallet_format(self):
|
def update_wallet_format(self):
|
||||||
# convert old-format imported keys
|
# convert old-format imported keys
|
||||||
|
@ -248,7 +256,6 @@ class ElectrumWindow(QMainWindow):
|
||||||
if self.wallet.get_master_public_keys() and self.wallet.addresses() == []:
|
if self.wallet.get_master_public_keys() and self.wallet.addresses() == []:
|
||||||
self.wallet.synchronize()
|
self.wallet.synchronize()
|
||||||
|
|
||||||
|
|
||||||
def open_wallet(self):
|
def open_wallet(self):
|
||||||
wallet_folder = self.wallet.storage.path
|
wallet_folder = self.wallet.storage.path
|
||||||
filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
|
filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
|
||||||
|
@ -1032,21 +1039,21 @@ class ElectrumWindow(QMainWindow):
|
||||||
for item in self.pay_from:
|
for item in self.pay_from:
|
||||||
self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))
|
self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))
|
||||||
|
|
||||||
def update_completions(self):
|
def get_contact_payto(self, key):
|
||||||
l = self.wallet.get_completions()
|
_type, value = self.contacts.get(key)
|
||||||
self.completions.setStringList(l)
|
return key + ' <' + value + '>' if _type == 'address' else key
|
||||||
|
|
||||||
|
def update_completions(self):
|
||||||
|
l = [self.get_contact_payto(key) for key in self.contacts.keys()]
|
||||||
|
self.completions.setStringList(l)
|
||||||
|
|
||||||
def protected(func):
|
def protected(func):
|
||||||
return lambda s, *args: s.do_protect(func, args)
|
return lambda s, *args: s.do_protect(func, args)
|
||||||
|
|
||||||
|
|
||||||
def read_send_tab(self):
|
def read_send_tab(self):
|
||||||
|
|
||||||
if self.payment_request and self.payment_request.has_expired():
|
if self.payment_request and self.payment_request.has_expired():
|
||||||
QMessageBox.warning(self, _('Error'), _('Payment request has expired'), _('OK'))
|
QMessageBox.warning(self, _('Error'), _('Payment request has expired'), _('OK'))
|
||||||
return
|
return
|
||||||
|
|
||||||
label = unicode( self.message_e.text() )
|
label = unicode( self.message_e.text() )
|
||||||
|
|
||||||
if self.payment_request:
|
if self.payment_request:
|
||||||
|
@ -1304,7 +1311,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
return self.create_list_tab(l)
|
return self.create_list_tab(l)
|
||||||
|
|
||||||
def create_contacts_tab(self):
|
def create_contacts_tab(self):
|
||||||
l = MyTreeWidget(self, self.create_contact_menu, [_('Address'), _('Label'), _('Tx')], [350, None])
|
l = MyTreeWidget(self, self.create_contact_menu, [_('Key'), _('Value'), _('Type')], [350, None, 130])
|
||||||
self.contacts_list = l
|
self.contacts_list = l
|
||||||
return self.create_list_tab(l)
|
return self.create_list_tab(l)
|
||||||
|
|
||||||
|
@ -1430,22 +1437,19 @@ class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
|
|
||||||
def payto(self, addr):
|
def payto(self, addr):
|
||||||
if not addr: return
|
if not addr:
|
||||||
label = self.wallet.labels.get(addr)
|
return
|
||||||
m_addr = label + ' <' + addr + '>' if label else addr
|
|
||||||
self.tabs.setCurrentIndex(1)
|
self.tabs.setCurrentIndex(1)
|
||||||
self.payto_e.setText(m_addr)
|
self.payto_e.setText(addr)
|
||||||
self.amount_e.setFocus()
|
self.amount_e.setFocus()
|
||||||
|
|
||||||
|
|
||||||
def delete_contact(self, x):
|
def delete_contact(self, x):
|
||||||
if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
|
if not self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
|
||||||
self.wallet.delete_contact(x)
|
return
|
||||||
self.wallet.set_label(x, None)
|
self.contacts.pop(x)
|
||||||
self.update_history_tab()
|
self.update_history_tab()
|
||||||
self.update_contacts_tab()
|
self.update_contacts_tab()
|
||||||
self.update_completions()
|
self.update_completions()
|
||||||
|
|
||||||
|
|
||||||
def create_contact_menu(self, position):
|
def create_contact_menu(self, position):
|
||||||
item = self.contacts_list.itemAt(position)
|
item = self.contacts_list.itemAt(position)
|
||||||
|
@ -1453,16 +1457,10 @@ class ElectrumWindow(QMainWindow):
|
||||||
if not item:
|
if not item:
|
||||||
menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
|
menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
|
||||||
else:
|
else:
|
||||||
addr = unicode(item.text(0))
|
key = unicode(item.text(0))
|
||||||
label = unicode(item.text(1))
|
menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(key))
|
||||||
is_editable = item.data(0,32).toBool()
|
menu.addAction(_("Pay to"), lambda: self.payto(self.get_contact_payto(key)))
|
||||||
payto_addr = item.data(0,33).toString()
|
menu.addAction(_("Delete"), lambda: self.delete_contact(key))
|
||||||
menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
|
|
||||||
menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
|
|
||||||
menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
|
|
||||||
if is_editable:
|
|
||||||
menu.addAction(_("Edit label"), lambda: self.contacts_list.edit_label(item))
|
|
||||||
menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
|
|
||||||
|
|
||||||
run_hook('create_contact_menu', menu, item)
|
run_hook('create_contact_menu', menu, item)
|
||||||
menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
|
menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
|
||||||
|
@ -1590,17 +1588,14 @@ class ElectrumWindow(QMainWindow):
|
||||||
def update_contacts_tab(self):
|
def update_contacts_tab(self):
|
||||||
l = self.contacts_list
|
l = self.contacts_list
|
||||||
item = l.currentItem()
|
item = l.currentItem()
|
||||||
current_address = item.data(0, Qt.UserRole).toString() if item else None
|
current_key = item.data(0, Qt.UserRole).toString() if item else None
|
||||||
l.clear()
|
l.clear()
|
||||||
for address in self.wallet.addressbook:
|
for key, v in self.contacts.items():
|
||||||
label = self.wallet.labels.get(address,'')
|
_type, value = v
|
||||||
n = self.wallet.get_num_tx(address)
|
item = QTreeWidgetItem([key, value, _type])
|
||||||
item = QTreeWidgetItem( [ address, label, "%d"%n] )
|
item.setData(0, Qt.UserRole, key)
|
||||||
item.setFont(0, QFont(MONOSPACE_FONT))
|
|
||||||
item.setData(0, Qt.UserRole, address)
|
|
||||||
item.setData(0, Qt.UserRole+1, True)
|
|
||||||
l.addTopLevelItem(item)
|
l.addTopLevelItem(item)
|
||||||
if address == current_address:
|
if key == current_key:
|
||||||
l.setCurrentItem(item)
|
l.setCurrentItem(item)
|
||||||
run_hook('update_contacts_tab', l)
|
run_hook('update_contacts_tab', l)
|
||||||
|
|
||||||
|
@ -1697,12 +1692,10 @@ class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
|
|
||||||
def new_contact_dialog(self):
|
def new_contact_dialog(self):
|
||||||
|
|
||||||
d = QDialog(self)
|
d = QDialog(self)
|
||||||
d.setWindowTitle(_("New Contact"))
|
d.setWindowTitle(_("New Contact"))
|
||||||
vbox = QVBoxLayout(d)
|
vbox = QVBoxLayout(d)
|
||||||
vbox.addWidget(QLabel(_('New Contact')+':'))
|
vbox.addWidget(QLabel(_('New Contact') + ':'))
|
||||||
|
|
||||||
grid = QGridLayout()
|
grid = QGridLayout()
|
||||||
line1 = QLineEdit()
|
line1 = QLineEdit()
|
||||||
line2 = QLineEdit()
|
line2 = QLineEdit()
|
||||||
|
@ -1724,9 +1717,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
|
QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
|
||||||
return
|
return
|
||||||
|
|
||||||
self.wallet.add_contact(address)
|
self.contacts[label] = ('address', address)
|
||||||
if label:
|
|
||||||
self.wallet.set_label(address, label)
|
|
||||||
|
|
||||||
self.update_contacts_tab()
|
self.update_contacts_tab()
|
||||||
self.update_history_tab()
|
self.update_history_tab()
|
||||||
|
|
30
lib/util.py
30
lib/util.py
|
@ -397,3 +397,33 @@ class QueuePipe:
|
||||||
def send_all(self, requests):
|
def send_all(self, requests):
|
||||||
for request in requests:
|
for request in requests:
|
||||||
self.send(request)
|
self.send(request)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
|
@ -159,7 +159,6 @@ class Abstract_Wallet(object):
|
||||||
self.seed = storage.get('seed', '') # encrypted
|
self.seed = storage.get('seed', '') # encrypted
|
||||||
self.labels = storage.get('labels', {})
|
self.labels = storage.get('labels', {})
|
||||||
self.frozen_addresses = storage.get('frozen_addresses',[])
|
self.frozen_addresses = storage.get('frozen_addresses',[])
|
||||||
self.addressbook = set(storage.get('contacts', []))
|
|
||||||
|
|
||||||
self.history = storage.get('addr_history',{}) # address -> list(txid, height)
|
self.history = storage.get('addr_history',{}) # address -> list(txid, height)
|
||||||
self.fee_per_kb = int(storage.get('fee_per_kb', RECOMMENDED_FEE))
|
self.fee_per_kb = int(storage.get('fee_per_kb', RECOMMENDED_FEE))
|
||||||
|
@ -380,28 +379,6 @@ class Abstract_Wallet(object):
|
||||||
def is_found(self):
|
def is_found(self):
|
||||||
return self.history.values() != [[]] * len(self.history)
|
return self.history.values() != [[]] * len(self.history)
|
||||||
|
|
||||||
def add_contact(self, address, label=None):
|
|
||||||
self.addressbook.add(address)
|
|
||||||
self.storage.put('contacts', list(self.addressbook), True)
|
|
||||||
if label:
|
|
||||||
self.set_label(address, label)
|
|
||||||
|
|
||||||
def delete_contact(self, addr):
|
|
||||||
if addr in self.addressbook:
|
|
||||||
self.addressbook.remove(addr)
|
|
||||||
self.storage.put('contacts', list(self.addressbook), True)
|
|
||||||
|
|
||||||
def get_completions(self):
|
|
||||||
l = []
|
|
||||||
for x in self.addressbook:
|
|
||||||
if bitcoin.is_address(x):
|
|
||||||
label = self.labels.get(x)
|
|
||||||
if label:
|
|
||||||
l.append( label + ' <' + x + '>')
|
|
||||||
else:
|
|
||||||
l.append(x)
|
|
||||||
return l
|
|
||||||
|
|
||||||
def get_num_tx(self, address):
|
def get_num_tx(self, address):
|
||||||
""" return number of transactions where address is involved """
|
""" return number of transactions where address is involved """
|
||||||
return len(self.history.get(address, []))
|
return len(self.history.get(address, []))
|
||||||
|
|
|
@ -110,7 +110,7 @@ class Plugin(BasePlugin):
|
||||||
self.win.previous_payto_e = new_url
|
self.win.previous_payto_e = new_url
|
||||||
|
|
||||||
if self.config.get('openalias_autoadd') == 'checked':
|
if self.config.get('openalias_autoadd') == 'checked':
|
||||||
self.win.wallet.add_contact(url, name)
|
self.win.contacts[url] = ('openalias', name)
|
||||||
self.win.update_contacts_tab()
|
self.win.update_contacts_tab()
|
||||||
|
|
||||||
self.win.payto_e.setFrozen(True)
|
self.win.payto_e.setFrozen(True)
|
||||||
|
|
Loading…
Reference in New Issue