Labels plugin now working for multiple windows

This commit is contained in:
Neil Booth 2015-09-04 17:38:14 +09:00
parent 31a4f38db4
commit 89fbda30e0
3 changed files with 107 additions and 87 deletions

View File

@ -367,7 +367,7 @@ class Abstract_Wallet(object):
if changed: if changed:
self.storage.put('labels', self.labels, True) self.storage.put('labels', self.labels, True)
run_hook('set_label', name, text, changed) run_hook('set_label', self, name, text, changed)
return changed return changed
def addresses(self, include_change = True): def addresses(self, include_change = True):

View File

@ -83,7 +83,6 @@ class Plugin(BasePlugin):
def __init__(self, parent, config, name): def __init__(self, parent, config, name):
BasePlugin.__init__(self, parent, config, name) BasePlugin.__init__(self, parent, config, name)
self.daemon = True
self.listener = None self.listener = None
self.obj = QObject() self.obj = QObject()
self.obj.connect(self.obj, SIGNAL('cosigner:receive'), self.on_receive) self.obj.connect(self.obj, SIGNAL('cosigner:receive'), self.on_receive)

View File

@ -3,6 +3,9 @@ import requests
import threading import threading
import hashlib import hashlib
import json import json
import sys
import traceback
from functools import partial
try: try:
import PyQt4 import PyQt4
@ -25,92 +28,98 @@ from electrum_gui.qt.util import ThreadedButton, Buttons, CancelButton, OkButton
class Plugin(BasePlugin): class Plugin(BasePlugin):
target_host = 'sync.bytesized-hosting.com:9090' def __init__(self, parent, config, name):
encode_password = None BasePlugin.__init__(self, parent, config, name)
self.target_host = 'sync.bytesized-hosting.com:9090'
self.wallets = {}
self.obj = QObject()
self.obj.connect(self.obj, SIGNAL('labels:pulled'), self.on_pulled)
def on_new_window(self, window):
wallet = window.wallet
nonce = self.get_nonce(wallet)
self.print_error("wallet", wallet.basename(), "nonce is", nonce)
mpk = ''.join(sorted(wallet.get_master_public_keys().values()))
if not mpk:
return
password = hashlib.sha1(mpk).digest().encode('hex')[:32]
iv = hashlib.sha256(password).digest()[:16]
wallet_id = hashlib.sha256(mpk).digest().encode('hex')
self.wallets[wallet] = (password, iv, wallet_id)
# If there is an auth token we can try to actually start syncing
t = threading.Thread(target=self.pull_thread, args=(window, False))
t.setDaemon(True)
t.start()
def version(self): def version(self):
return "0.0.1" return "0.0.1"
def encode(self, message): def encode(self, wallet, msg):
encrypted = electrum.bitcoin.aes_encrypt_with_iv(self.encode_password, self.iv, message.encode('utf8')) password, iv, wallet_id = self.wallets[wallet]
encoded_message = base64.b64encode(encrypted) encrypted = electrum.bitcoin.aes_encrypt_with_iv(password, iv,
return encoded_message msg.encode('utf8'))
return base64.b64encode(encrypted)
def decode(self, message): def decode(self, wallet, message):
decoded_message = electrum.bitcoin.aes_decrypt_with_iv(self.encode_password, self.iv, base64.b64decode(message)).decode('utf8') password, iv, wallet_id = self.wallets[wallet]
return decoded_message decoded = base64.b64decode(message)
decrypted = electrum.bitcoin.aes_decrypt_with_iv(password, iv, decoded)
return decrypted.decode('utf8')
def set_nonce(self, nonce): def get_nonce(self, wallet):
self.print_error("Set nonce to", nonce) # nonce is the nonce to be used with the next change
self.wallet.storage.put("wallet_nonce", nonce, True) nonce = wallet.storage.get('wallet_nonce')
self.wallet_nonce = nonce if nonce is None:
nonce = 1
self.set_nonce(wallet, nonce)
return nonce
@hook def set_nonce(self, wallet, nonce):
def init_qt(self, gui): self.print_error("set", wallet.basename(), "nonce to", nonce)
self.window = gui.main_window wallet.storage.put("wallet_nonce", nonce, True)
self.window.connect(self.window, SIGNAL('labels:pulled'), self.on_pulled)
@hook
def load_wallet(self, wallet, window):
self.wallet = wallet
self.wallet_nonce = self.wallet.storage.get("wallet_nonce")
self.print_error("Wallet nonce is", self.wallet_nonce)
if self.wallet_nonce is None:
self.set_nonce(1)
mpk = ''.join(sorted(self.wallet.get_master_public_keys().values()))
if not mpk:
return
self.encode_password = hashlib.sha1(mpk).digest().encode('hex')[:32]
self.iv = hashlib.sha256(self.encode_password).digest()[:16]
self.wallet_id = hashlib.sha256(mpk).digest().encode('hex')
addresses = []
for account in self.wallet.accounts.values():
for address in account.get_addresses(0):
addresses.append(address)
self.addresses = addresses
# If there is an auth token we can try to actually start syncing
def do_pull_thread():
try:
self.pull_thread()
except Exception as e:
self.print_error("could not retrieve labels:", e)
t = threading.Thread(target=do_pull_thread)
t.setDaemon(True)
t.start()
def requires_settings(self): def requires_settings(self):
return True return True
@hook @hook
def set_label(self, item,label, changed): def set_label(self, wallet, item, label, changed):
if self.encode_password is None: if not changed or not wallet in self.wallets:
return return
if not changed: nonce = self.get_nonce(wallet)
return wallet_id = self.wallets[wallet][2]
bundle = {"walletId": self.wallet_id, "walletNonce": self.wallet.storage.get("wallet_nonce"), "externalId": self.encode(item), "encryptedLabel": self.encode(label)} bundle = {"walletId": wallet_id,
t = threading.Thread(target=self.do_request, args=["POST", "/label", False, bundle]) "walletNonce": nonce,
"externalId": self.encode(wallet, item),
"encryptedLabel": self.encode(wallet, label)}
t = threading.Thread(target=self.do_request,
args=["POST", "/label", False, bundle])
t.setDaemon(True) t.setDaemon(True)
t.start() t.start()
self.set_nonce(self.wallet.storage.get("wallet_nonce") + 1) self.set_nonce(wallet, nonce + 1)
def settings_widget(self, window): def settings_widget(self, window):
return EnterButton(_('Settings'), self.settings_dialog) return EnterButton(_('Settings'),
partial(self.settings_dialog, window))
def settings_dialog(self): def settings_dialog(self, window):
d = QDialog() print "window:", window
d = QDialog(window)
vbox = QVBoxLayout(d) vbox = QVBoxLayout(d)
layout = QGridLayout() layout = QGridLayout()
vbox.addLayout(layout) vbox.addLayout(layout)
layout.addWidget(QLabel("Label sync options: "),2,0) layout.addWidget(QLabel("Label sync options: "),2,0)
self.upload = ThreadedButton("Force upload", self.push_thread, self.done_processing) self.upload = ThreadedButton("Force upload",
partial(self.push_thread, window),
self.done_processing)
layout.addWidget(self.upload, 2, 1) layout.addWidget(self.upload, 2, 1)
self.download = ThreadedButton("Force download", lambda: self.pull_thread(True), self.done_processing) self.download = ThreadedButton("Force download",
partial(self.pull_thread, window, True),
self.done_processing)
layout.addWidget(self.download, 2, 2) layout.addWidget(self.download, 2, 2)
self.accept = OkButton(d, _("Done")) self.accept = OkButton(d, _("Done"))
@ -121,13 +130,15 @@ class Plugin(BasePlugin):
else: else:
return False return False
def on_pulled(self): def on_pulled(self, window, nonce):
wallet = self.wallet wallet = window.wallet
wallet.storage.put('labels', wallet.labels, True) wallet.storage.put('labels', wallet.labels, False)
self.window.labelsChanged.emit() self.set_nonce(wallet, nonce)
window.labelsChanged.emit()
def done_processing(self): def done_processing(self):
QMessageBox.information(None, _("Labels synchronised"), _("Your labels have been synchronised.")) QMessageBox.information(None, _("Labels synchronised"),
_("Your labels have been synchronised."))
def do_request(self, method, url = "/labels", is_batch=False, data=None): def do_request(self, method, url = "/labels", is_batch=False, data=None):
url = 'https://' + self.target_host + url url = 'https://' + self.target_host + url
@ -145,28 +156,37 @@ class Plugin(BasePlugin):
raise BaseException(response["error"]) raise BaseException(response["error"])
return response return response
def push_thread(self): def push_thread(self, window):
bundle = {"labels": [], "walletId": self.wallet_id, "walletNonce": self.wallet_nonce} wallet = window.wallet
for key, value in self.wallet.labels.iteritems(): wallet_id = self.wallets[wallet][2]
bundle = {"labels": [],
"walletId": wallet_id,
"walletNonce": self.get_nonce(wallet)}
for key, value in wallet.labels.iteritems():
try: try:
encoded_key = self.encode(key) encoded_key = self.encode(wallet, key)
encoded_value = self.encode(value) encoded_value = self.encode(wallet, value)
except: except:
self.print_error('cannot encode', repr(key), repr(value)) self.print_error('cannot encode', repr(key), repr(value))
continue continue
bundle["labels"].append({'encryptedLabel': encoded_value, 'externalId': encoded_key}) bundle["labels"].append({'encryptedLabel': encoded_value,
'externalId': encoded_key})
self.do_request("POST", "/labels", True, bundle) self.do_request("POST", "/labels", True, bundle)
def pull_thread(self, force = False): def pull_thread(self, window, force):
wallet_nonce = 1 if force else self.wallet_nonce - 1 wallet = window.wallet
self.print_error("Asking for labels since nonce", wallet_nonce) wallet_id = self.wallets[wallet][2]
response = self.do_request("GET", ("/labels/since/%d/for/%s" % (wallet_nonce, self.wallet_id) )) nonce = 1 if force else self.get_nonce(wallet) - 1
self.print_error("asking for labels since nonce", nonce)
try:
response = self.do_request("GET", ("/labels/since/%d/for/%s" % (nonce, wallet_id) ))
if response["labels"] is None:
return
result = {} result = {}
if not response["labels"] is None:
for label in response["labels"]: for label in response["labels"]:
try: try:
key = self.decode(label["externalId"]) key = self.decode(wallet, label["externalId"])
value = self.decode(label["encryptedLabel"]) value = self.decode(wallet, label["encryptedLabel"])
except: except:
continue continue
try: try:
@ -177,13 +197,14 @@ class Plugin(BasePlugin):
continue continue
result[key] = value result[key] = value
wallet = self.wallet
if not wallet:
return
for key, value in result.items(): for key, value in result.items():
if force or not wallet.labels.get(key): if force or not wallet.labels.get(key):
wallet.labels[key] = value wallet.labels[key] = value
self.window.emit(SIGNAL('labels:pulled'))
self.set_nonce(response["nonce"] + 1)
self.print_error("received %d labels" % len(response)) self.print_error("received %d labels" % len(response))
self.obj.emit(SIGNAL('labels:pulled'), window,
response["nonce"] + 1)
except Exception as e:
traceback.print_exc(file=sys.stderr)
self.print_error("could not retrieve labels")