electrum-bitcoinprivate/plugins/labels.py

251 lines
9.1 KiB
Python
Raw Normal View History

2013-03-10 09:04:00 -07:00
from electrum.util import print_error
2013-09-11 02:45:58 -07:00
2013-03-10 09:04:00 -07:00
import httplib, urllib
2013-04-08 04:30:43 -07:00
import socket
import threading
2013-03-10 09:04:00 -07:00
import hashlib
import json
from urlparse import urlparse, parse_qs
try:
import PyQt4
2013-11-10 12:30:57 -08:00
except Exception:
2013-03-10 09:04:00 -07:00
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
2014-08-31 02:42:40 -07:00
from electrum.plugins import BasePlugin, hook
2013-09-11 02:45:58 -07:00
from electrum.i18n import _
2013-10-08 02:38:40 -07:00
from electrum_gui.qt import HelpButton, EnterButton
2013-03-10 09:04:00 -07:00
2013-03-16 15:33:49 -07:00
class Plugin(BasePlugin):
2014-09-04 07:37:51 -07:00
target_host = 'labelectrum.herokuapp.com'
2014-09-04 07:44:50 -07:00
encode_password = None
2014-09-04 07:37:51 -07:00
def fullname(self):
return _('Label Sync')
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."))
def version(self):
2013-04-08 04:30:43 -07:00
return "0.2.1"
2013-03-12 13:20:18 -07:00
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
2014-09-04 07:37:51 -07:00
2014-08-31 02:42:40 -07:00
@hook
def init_qt(self, gui):
self.window = gui.main_window
2014-09-04 07:37:51 -07:00
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
2013-09-29 01:52:47 -07:00
2014-08-31 02:42:40 -07:00
@hook
2013-09-29 01:52:47 -07:00
def load_wallet(self, wallet):
self.wallet = wallet
2014-09-04 07:37:51 -07:00
mpk = self.wallet.get_master_public_key()
self.encode_password = hashlib.sha1(mpk).digest().encode('hex')[:32]
2014-09-07 15:54:52 -07:00
self.iv = hashlib.sha256(self.encode_password).digest()[:16]
self.wallet_id = hashlib.sha256(mpk).digest().encode('hex')
2013-03-10 09:04:00 -07:00
addresses = []
for account in self.wallet.accounts.values():
for address in account.get_addresses(0):
2013-03-10 09:04:00 -07:00
addresses.append(address)
self.addresses = addresses
if self.auth_token():
2013-03-16 15:33:49 -07:00
# If there is an auth token we can try to actually start syncing
threading.Thread(target=self.do_full_pull).start()
2013-03-16 15:33:49 -07:00
def auth_token(self):
return self.config.get("plugin_label_api_key")
2013-03-16 15:33:49 -07:00
def is_available(self):
return True
def requires_settings(self):
return True
2014-08-31 02:42:40 -07:00
@hook
2013-03-16 15:33:49 -07:00
def set_label(self, item,label, changed):
2014-09-04 07:44:50 -07:00
if self.encode_password is None:
return
2013-03-16 15:33:49 -07:00
if not changed:
return
2013-04-08 04:30:43 -07:00
try:
bundle = {"label": {"external_id": self.encode(item), "text": self.encode(label)}}
params = json.dumps(bundle)
connection = httplib.HTTPConnection(self.target_host)
connection.request("POST", ("/api/wallets/%s/labels.json?auth_token=%s" % (self.wallet_id, self.auth_token())), params, {'Content-Type': 'application/json'})
response = connection.getresponse()
if response.reason == httplib.responses[httplib.NOT_FOUND]:
return
response = json.loads(response.read())
except socket.gaierror as e:
print_error('Error connecting to service: %s ' % e)
return False
2013-10-08 02:38:40 -07:00
def settings_widget(self, window):
return EnterButton(_('Settings'), self.settings_dialog)
2013-03-17 03:52:58 -07:00
def settings_dialog(self):
2013-03-16 15:33:49 -07:00
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)
2013-03-16 15:33:49 -07:00
else:
self.upload.setEnabled(False)
self.download.setEnabled(False)
self.accept.setEnabled(False)
2013-03-16 15:33:49 -07:00
d = QDialog()
layout = QGridLayout(d)
2013-03-16 15:33:49 -07:00
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)
2013-03-16 15:33:49 -07:00
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)
2013-03-16 15:33:49 -07:00
self.upload = QPushButton("Force upload")
self.upload.clicked.connect(self.full_push)
layout.addWidget(self.upload, 2,1)
2013-03-16 15:33:49 -07:00
self.download = QPushButton("Force download")
self.download.clicked.connect(self.full_pull)
layout.addWidget(self.download, 2,2)
2013-03-16 15:33:49 -07:00
c = QPushButton(_("Cancel"))
c.clicked.connect(d.reject)
self.accept = QPushButton(_("Done"))
self.accept.clicked.connect(d.accept)
2013-03-16 15:33:49 -07:00
layout.addWidget(c,3,1)
layout.addWidget(self.accept,3,2)
2013-03-17 03:52:58 -07:00
check_for_api_key(self.auth_token())
if d.exec_():
return True
else:
return False
2013-03-17 03:52:58 -07:00
2013-03-10 09:04:00 -07:00
def full_push(self):
2013-03-16 15:33:49 -07:00
if self.do_full_push():
2013-03-17 04:56:53 -07:00
QMessageBox.information(None, _("Labels uploaded"), _("Your labels have been uploaded."))
2013-03-16 15:33:49 -07:00
def full_pull(self):
try:
self.do_full_pull(True)
except BaseException as e:
QMessageBox.information(None, _("Error"), str(e))
return
QMessageBox.information(None, _("Labels synchronized"), _("Your labels have been synchronized."))
self.window.update_history_tab()
self.window.update_completions()
self.window.update_receive_tab()
self.window.update_contacts_tab()
2013-03-10 09:04:00 -07:00
2013-03-16 15:33:49 -07:00
def do_full_push(self):
2013-03-12 13:20:18 -07:00
try:
2013-04-08 04:30:43 -07:00
bundle = {"labels": {}}
2013-09-29 01:52:47 -07:00
for key, value in self.wallet.labels.iteritems():
try:
encoded_key = self.encode(key)
except:
print_error('cannot encode', repr(key))
continue
2014-09-07 13:26:07 -07:00
try:
encoded_value = self.encode(value)
except:
2014-09-07 14:04:04 -07:00
print_error('cannot encode', repr(value))
2014-09-07 13:26:07 -07:00
continue
bundle["labels"][encoded_key] = encoded_value
2013-04-08 04:30:43 -07:00
params = json.dumps(bundle)
connection = httplib.HTTPConnection(self.target_host)
connection.request("POST", ("/api/wallets/%s/labels/batch.json?auth_token=%s" % (self.wallet_id, self.auth_token())), params, {'Content-Type': 'application/json'})
response = connection.getresponse()
if response.reason == httplib.responses[httplib.NOT_FOUND]:
return
try:
response = json.loads(response.read())
except ValueError as e:
return False
if "error" in response:
QMessageBox.warning(None, _("Error"),_("Could not sync labels: %s" % response["error"]))
return False
return True
except socket.gaierror as e:
print_error('Error connecting to service: %s ' % e)
2013-03-16 15:33:49 -07:00
return False
def do_full_pull(self, force = False):
connection = httplib.HTTPConnection(self.target_host)
connection.request("GET", ("/api/wallets/%s/labels.json?auth_token=%s" % (self.wallet_id, self.auth_token())),"", {'Content-Type': 'application/json'})
response = connection.getresponse()
if response.status != 200:
print_error("Cannot retrieve labels:", response.status, response.reason)
return
response = json.loads(response.read())
if "error" in response:
raise BaseException(_("Could not sync labels: %s" % response["error"]))
2013-04-08 04:30:43 -07:00
for label in response:
try:
key = self.decode(label["external_id"])
except:
continue
try:
value = self.decode(label["text"])
except:
continue
try:
json.dumps(key)
json.dumps(value)
except:
print_error('error: no json', key)
continue
if force or not self.wallet.labels.get(key):
self.wallet.labels[key] = value
self.wallet.storage.put('labels', self.wallet.labels)
print_error("received %d labels"%len(response))
2014-09-08 02:45:19 -07:00
self.window.labelsChanged.emit()