Migrate the new function into the old plugin

This commit is contained in:
Maran 2015-04-11 09:57:01 +02:00
parent ebb9faeb6e
commit 7356d41240
2 changed files with 63 additions and 285 deletions

View File

@ -1,204 +0,0 @@
from electrum.util import print_error
import socket
import requests
import threading
import hashlib
import json
try:
import PyQt4
except Exception:
sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore
import PyQt4.QtGui as QtGui
import aes
import base64
import electrum
from electrum.plugins import BasePlugin, hook
from electrum.i18n import _
from electrum_gui.qt import HelpButton, EnterButton
from electrum_gui.qt.util import ThreadedButton, Buttons, CancelButton, OkButton
class Plugin(BasePlugin):
target_host = 'sync.bysh.me:8080'
encode_password = None
def fullname(self):
return _('Electrum Sync')
def description(self):
return '%s\n\n%s' % (_("The new and improved LabelSync plugin. This can sync your labels across multiple Electrum installs by using a remote database to save your data. Labels, transactions ids and addresses are encrypted before they are sent to the remote server."), _("The label sync's server software is open-source as well and can be found on github.com/maran/electrum-sync-server"))
def version(self):
return "0.0.1"
def encode(self, message):
encrypted = electrum.bitcoin.aes_encrypt_with_iv(self.encode_password, self.iv, message.encode('utf8'))
encoded_message = base64.b64encode(encrypted)
return encoded_message
def decode(self, message):
decoded_message = electrum.bitcoin.aes_decrypt_with_iv(self.encode_password, self.iv, base64.b64decode(message)).decode('utf8')
return decoded_message
def set_nonce(self, nonce):
print_error("Set nonce to", nonce)
self.wallet.storage.put("wallet_nonce", nonce, True)
self.wallet_nonce = nonce
@hook
def init_qt(self, gui):
self.window = gui.main_window
self.window.connect(self.window, SIGNAL('labels:pulled'), self.on_pulled)
@hook
def load_wallet(self, wallet):
self.wallet = wallet
self.wallet_nonce = self.wallet.storage.get("wallet_nonce")
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()))
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:
print_error("could not retrieve labels:", e)
t = threading.Thread(target=do_pull_thread)
t.setDaemon(True)
t.start()
def is_available(self):
return True
def requires_settings(self):
return True
@hook
def set_label(self, item,label, changed):
if self.encode_password is None:
return
if not changed:
return
bundle = {"walletId": self.wallet_id, "walletNonce": self.wallet.storage.get("wallet_nonce"), "externalId": self.encode(item), "encryptedLabel": self.encode(label)}
t = threading.Thread(target=self.do_request, args=["POST", "/label", False, bundle])
t.start()
self.set_nonce(self.wallet.storage.get("wallet_nonce") + 1)
def settings_widget(self, window):
return EnterButton(_('Settings'), self.settings_dialog)
def settings_dialog(self):
d = QDialog()
vbox = QVBoxLayout(d)
layout = QGridLayout()
vbox.addLayout(layout)
layout.addWidget(QLabel("Label sync options: "),2,0)
self.upload = ThreadedButton("Force upload", self.push_thread, self.done_processing)
layout.addWidget(self.upload, 2, 1)
self.download = ThreadedButton("Force download", lambda: self.pull_thread(True), self.done_processing)
layout.addWidget(self.download, 2, 2)
self.accept = OkButton(d, _("Done"))
vbox.addLayout(Buttons(CancelButton(d), self.accept))
if d.exec_():
return True
else:
return False
def on_pulled(self):
wallet = self.wallet
wallet.storage.put('labels', wallet.labels, True)
self.window.labelsChanged.emit()
def done_processing(self):
QMessageBox.information(None, _("Labels synchronised"), _("Your labels have been synchronised."))
def do_request(self, method, url = "/labels", is_batch=False, data=None):
url = 'http://' + self.target_host + url
kwargs = {'headers': {}}
if method == 'GET' and data:
kwargs['params'] = data
elif method == 'POST' and data:
kwargs['data'] = json.dumps(data)
kwargs['headers']['Content-Type'] = 'application/json'
response = requests.request(method, url, **kwargs)
if response.status_code != 200:
raise BaseException(response.status_code, response.text)
response = response.json()
if "error" in response:
raise BaseException(response["error"])
return response
def push_thread(self):
bundle = {"labels": [], "walletId": self.wallet_id, "walletNonce": self.wallet_nonce}
for key, value in self.wallet.labels.iteritems():
try:
encoded_key = self.encode(key)
encoded_value = self.encode(value)
except:
print_error('cannot encode', repr(key), repr(value))
continue
bundle["labels"].append({'encryptedLabel': encoded_value, 'externalId': encoded_key})
self.do_request("POST", "/labels", True, bundle)
def pull_thread(self, force = False):
if force:
wallet_nonce = 1
else:
wallet_nonce = self.wallet_nonce - 1
print_error("Asking for labels since nonce", wallet_nonce)
response = self.do_request("GET", ("/labels/since/%d/for/%s" % (wallet_nonce, self.wallet_id) ))
result = {}
if not response["labels"] is None:
for label in response["labels"]:
try:
key = self.decode(label["externalId"])
value = self.decode(label["encryptedLabel"])
except:
continue
try:
json.dumps(key)
json.dumps(value)
except:
print_error('error: no json', key)
continue
result[key] = value
wallet = self.wallet
if not wallet:
return
for key, value in result.items():
if force or not wallet.labels.get(key):
wallet.labels[key] = value
self.window.emit(SIGNAL('labels:pulled'))
self.set_nonce(response["nonce"] + 1)
print_error("received %d labels"%len(response))

View File

@ -1,6 +1,5 @@
from electrum.util import print_error
import socket
import requests
import threading
@ -28,17 +27,17 @@ from electrum_gui.qt.util import ThreadedButton, Buttons, CancelButton, OkButton
class Plugin(BasePlugin):
target_host = 'labelectrum.herokuapp.com'
target_host = 'sync.bysh.me:8080'
encode_password = None
def fullname(self):
return _('Label Sync')
return _('LabelSync')
def description(self):
return '%s\n\n%s%s%s' % (_("This plugin can sync your labels across multiple Electrum installs by using a remote database to save your data. Labels, transactions ids and addresses are encrypted before they are sent to the remote server. This code might increase the load of your wallet with a few microseconds as it will sync labels on each startup."), _("To get started visit"), " http://labelectrum.herokuapp.com/ ", _(" to sign up for an account."))
return '%s\n\n%s' % (_("The new and improved LabelSync plugin. This can sync your labels across multiple Electrum installs by using a remote database to save your data. Labels, transactions ids and addresses are encrypted before they are sent to the remote server."), _("The label sync's server software is open-source as well and can be found on github.com/maran/electrum-sync-server"))
def version(self):
return "0.2.1"
return "0.0.1"
def encode(self, message):
encrypted = electrum.bitcoin.aes_encrypt_with_iv(self.encode_password, self.iv, message.encode('utf8'))
@ -49,48 +48,47 @@ class Plugin(BasePlugin):
decoded_message = electrum.bitcoin.aes_decrypt_with_iv(self.encode_password, self.iv, base64.b64decode(message)).decode('utf8')
return decoded_message
def set_nonce(self, nonce):
print_error("Set nonce to", nonce)
self.wallet.storage.put("wallet_nonce", nonce, True)
self.wallet_nonce = nonce
@hook
def init_qt(self, gui):
self.window = gui.main_window
if not self.auth_token(): # First run, throw plugin settings in your face
self.load_wallet(self.window.wallet)
if self.settings_dialog():
self.set_enabled(True)
return True
else:
self.set_enabled(False)
return False
self.window.connect(self.window, SIGNAL('labels:pulled'), self.on_pulled)
@hook
def load_wallet(self, wallet):
self.wallet = wallet
self.wallet_nonce = self.wallet.storage.get("wallet_nonce")
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()))
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 = []
addresses = []
for account in self.wallet.accounts.values():
for address in account.get_addresses(0):
addresses.append(address)
self.addresses = addresses
if self.auth_token():
# If there is an auth token we can try to actually start syncing
def do_pull_thread():
try:
self.pull_thread()
except:
print_error("could not retrieve labels")
t = threading.Thread(target=do_pull_thread)
t.setDaemon(True)
t.start()
# 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:
print_error("could not retrieve labels:", e)
t = threading.Thread(target=do_pull_thread)
t.setDaemon(True)
t.start()
def auth_token(self):
return self.config.get("plugin_label_api_key")
def is_available(self):
return True
@ -104,43 +102,21 @@ class Plugin(BasePlugin):
return
if not changed:
return
bundle = {"label": {"external_id": self.encode(item), "text": self.encode(label)}}
t = threading.Thread(target=self.do_request, args=["POST", False, bundle])
bundle = {"walletId": self.wallet_id, "walletNonce": self.wallet.storage.get("wallet_nonce"), "externalId": self.encode(item), "encryptedLabel": self.encode(label)}
t = threading.Thread(target=self.do_request, args=["POST", "/label", False, bundle])
t.start()
self.set_nonce(self.wallet.storage.get("wallet_nonce") + 1)
def settings_widget(self, window):
return EnterButton(_('Settings'), self.settings_dialog)
def settings_dialog(self):
def check_for_api_key(api_key):
if api_key and len(api_key) > 12:
self.config.set_key("plugin_label_api_key", str(self.auth_token_edit.text()))
self.upload.setEnabled(True)
self.download.setEnabled(True)
self.accept.setEnabled(True)
else:
self.upload.setEnabled(False)
self.download.setEnabled(False)
self.accept.setEnabled(False)
d = QDialog()
vbox = QVBoxLayout(d)
layout = QGridLayout()
vbox.addLayout(layout)
layout.addWidget(QLabel("API Key: "), 0, 0)
self.auth_token_edit = QLineEdit(self.auth_token())
self.auth_token_edit.textChanged.connect(check_for_api_key)
layout.addWidget(QLabel("Label sync options: "),2,0)
layout.addWidget(self.auth_token_edit, 0,1,1,2)
decrypt_key_text = QLineEdit(self.encode_password)
decrypt_key_text.setReadOnly(True)
layout.addWidget(decrypt_key_text, 1,1)
layout.addWidget(QLabel("Decryption key: "),1,0)
layout.addWidget(HelpButton("This key can be used on the LabElectrum website to decrypt your data in case you want to review it online."),1,2)
self.upload = ThreadedButton("Force upload", self.push_thread, self.done_processing)
layout.addWidget(self.upload, 2, 1)
@ -151,8 +127,6 @@ class Plugin(BasePlugin):
self.accept = OkButton(d, _("Done"))
vbox.addLayout(Buttons(CancelButton(d), self.accept))
check_for_api_key(self.auth_token())
if d.exec_():
return True
else:
@ -166,8 +140,8 @@ class Plugin(BasePlugin):
def done_processing(self):
QMessageBox.information(None, _("Labels synchronised"), _("Your labels have been synchronised."))
def do_request(self, method, is_batch=False, data=None):
url = 'https://' + self.target_host + "/api/wallets/%s/%s?auth_token=%s" % (self.wallet_id, 'labels/batch.json' if is_batch else 'labels.json', self.auth_token())
def do_request(self, method, url = "/labels", is_batch=False, data=None):
url = 'http://' + self.target_host + url
kwargs = {'headers': {}}
if method == 'GET' and data:
kwargs['params'] = data
@ -183,7 +157,7 @@ class Plugin(BasePlugin):
return response
def push_thread(self):
bundle = {"labels": {}}
bundle = {"labels": [], "walletId": self.wallet_id, "walletNonce": self.wallet_nonce}
for key, value in self.wallet.labels.iteritems():
try:
encoded_key = self.encode(key)
@ -191,32 +165,40 @@ class Plugin(BasePlugin):
except:
print_error('cannot encode', repr(key), repr(value))
continue
bundle["labels"][encoded_key] = encoded_value
self.do_request("POST", True, bundle)
bundle["labels"].append({'encryptedLabel': encoded_value, 'externalId': encoded_key})
self.do_request("POST", "/labels", True, bundle)
def pull_thread(self, force = False):
response = self.do_request("GET")
if force:
wallet_nonce = 1
else:
wallet_nonce = self.wallet_nonce - 1
print_error("Asking for labels since nonce", wallet_nonce)
response = self.do_request("GET", ("/labels/since/%d/for/%s" % (wallet_nonce, self.wallet_id) ))
result = {}
for label in response:
try:
key = self.decode(label["external_id"])
value = self.decode(label["text"])
except:
continue
try:
json.dumps(key)
json.dumps(value)
except:
print_error('error: no json', key)
continue
result[key] = value
if not response["labels"] is None:
for label in response["labels"]:
try:
key = self.decode(label["externalId"])
value = self.decode(label["encryptedLabel"])
except:
continue
try:
json.dumps(key)
json.dumps(value)
except:
print_error('error: no json', key)
continue
result[key] = value
wallet = self.wallet
if not wallet:
return
for key, value in result.items():
if force or not wallet.labels.get(key):
wallet.labels[key] = value
wallet = self.wallet
if not wallet:
return
for key, value in result.items():
if force or not wallet.labels.get(key):
wallet.labels[key] = value
self.window.emit(SIGNAL('labels:pulled'))
print_error("received %d labels"%len(response))
self.window.emit(SIGNAL('labels:pulled'))
self.set_nonce(response["nonce"] + 1)
print_error("received %d labels"%len(response))