Update howto
[electrum-nvc.git] / plugins / labels.py
1 from electrum.util import print_error
2 from electrum_gui.i18n import _
3 import httplib, urllib
4 import hashlib
5 import json
6 from urlparse import urlparse, parse_qs
7 try:
8     import PyQt4
9 except:
10     sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
11
12 from PyQt4.QtGui import *
13 from PyQt4.QtCore import *
14 import PyQt4.QtCore as QtCore
15 import PyQt4.QtGui as QtGui
16
17 target_host = 'labelectrum.herokuapp.com'
18 config = {}
19
20 def is_available():
21     return True
22
23 def auth_token():
24     global config
25     return config.get("plugin_label_api_key")
26
27 def init(gui):
28     global config
29     config = gui.config
30
31     if config.get('plugin_label_enabled'):
32         gui.set_hook('create_settings_tab', add_settings_tab)
33         gui.set_hook('close_settings_dialog', close_settings_dialog)
34
35         if not auth_token():
36           return 
37
38         cloud_wallet = CloudWallet(gui.wallet)
39         gui.set_hook('set_label', set_label)
40
41         cloud_wallet.full_pull()
42
43 def wallet_id():
44     global config
45     return hashlib.sha256(str(config.get("master_public_key"))).digest().encode('hex')
46
47 def set_label(gui, item,label, changed):
48     if not changed:
49         return 
50
51     print "Label changed! Item: %s Label: %s label" % ( item, label)
52     global target_host
53     hashed = hashlib.sha256(item).digest().encode('hex')
54     bundle = {"label": {"external_id": hashed, "text": label}}
55     params = json.dumps(bundle)
56     connection = httplib.HTTPConnection(target_host)
57     connection.request("POST", ("/api/wallets/%s/labels.json?auth_token=%s" % (wallet_id(), auth_token())), params, {'Content-Type': 'application/json'})
58
59     response = connection.getresponse()
60     if response.reason == httplib.responses[httplib.NOT_FOUND]:
61         return
62     response = json.loads(response.read())
63
64 def close_settings_dialog(gui):
65     global config
66
67     # When you enable the plugin for the first time this won't exist.
68     if is_enabled():
69         if hasattr(gui, 'auth_token_edit'):
70             config.set_key("plugin_label_api_key", str(gui.auth_token_edit.text()))
71         else:
72             QMessageBox.information(None, _("Cloud plugin loaded"), _("Please open the settings again to configure the label-cloud plugin."))
73
74 def add_settings_tab(gui, tabs):
75     def check_for_api_key(api_key):
76         global config
77         if api_key and len(api_key) > 12:
78           config.set_key("plugin_label_api_key", str(gui.auth_token_edit.text()))
79           upload.setEnabled(True)
80           download.setEnabled(True)
81         else:
82           upload.setEnabled(False)
83           download.setEnabled(False)
84
85     cloud_tab = QWidget()
86     layout = QGridLayout(cloud_tab)
87     layout.addWidget(QLabel("API Key: "),0,0)
88
89     # TODO: I need to add it to the Electrum GUI here so I can retrieve it later when the settings dialog is closed, is there a better way to do this?
90     gui.auth_token_edit = QLineEdit(auth_token())
91     gui.auth_token_edit.textChanged.connect(check_for_api_key)
92
93     layout.addWidget(gui.auth_token_edit, 0,1,1,2)
94     layout.addWidget(QLabel("Label cloud options: "),1,0)
95
96     upload = QPushButton("Force upload")
97     upload.clicked.connect(lambda: full_push(gui.wallet))
98     layout.addWidget(upload, 1,1)
99
100     download = QPushButton("Force download")
101     download.clicked.connect(lambda: full_pull(gui.wallet))
102     layout.addWidget(download, 1,2)
103
104     gui.cloud_tab = cloud_tab
105     check_for_api_key(auth_token())
106
107     tabs.addTab(cloud_tab, "Label cloud")
108
109 def full_push(wallet):
110     cloud_wallet = CloudWallet(wallet)
111     cloud_wallet.full_push()
112     QMessageBox.information(None, _("Labels synced"), _("Your labels have been uploaded."))
113
114 def full_pull(wallet):
115     cloud_wallet = CloudWallet(wallet)
116     cloud_wallet.full_pull(True)
117     QMessageBox.information(None, _("Labels synced"), _("Your labels have been synced, please restart Electrum for the changes to take effect."))
118
119 def show():
120     print 'showing'
121
122 def get_info():
123     return 'Label cloud', "This plugin can sync your labels accross multiple Electrum instances by using a remote database to save your data. Labels are not encrypted, \
124 transactions and addresses are however. This code might increase the load of your wallet with a few micoseconds as it will sync labels on each startup.\n\n\
125 To get started visit http://labelectrum.herokuapp.com/ to sign up for an account."
126
127 def is_enabled():
128     return config.get('plugin_label_enabled') is True
129
130 def toggle(gui):
131     if not is_enabled():
132         enabled = True
133     else:
134       enabled = False
135       gui.unset_hook('create_settings_tab', add_settings_tab)
136       gui.unset_hook('close_settings_dialog', close_settings_dialog)
137          
138     config.set_key('plugin_label_enabled', enabled, True)
139
140     if enabled:
141         init(gui)
142     return enabled
143
144 # This can probably be refactored into plain top level methods instead of a class
145 class CloudWallet():
146     def __init__(self, wallet):
147         self.labels = wallet.labels
148         self.transactions = wallet.transactions
149
150         addresses = [] 
151         for k, account in wallet.accounts.items():
152             for address in account[0]:
153                 addresses.append(address)
154
155         self.addresses = addresses
156
157     def full_pull(self, force = False):
158         global target_host
159         connection = httplib.HTTPConnection(target_host)
160         connection.request("GET", ("/api/wallets/%s/labels.json?auth_token=%s" % (wallet_id(), auth_token())),"", {'Content-Type': 'application/json'})
161         response = connection.getresponse()
162         if response.reason == httplib.responses[httplib.NOT_FOUND]:
163             return
164         try:
165             response = json.loads(response.read())
166         except ValueError as e:
167             return
168
169         if "error" in response:
170             QMessageBox.warning(None, _("Error"),_("Could not sync labels: %s" % response["error"]))
171             return 
172
173         for label in response:
174             for key in self.addresses:
175                 target_hashed = hashlib.sha256(key).digest().encode('hex')
176                 if label["external_id"] == target_hashed:
177                    if force or not self.labels.get(key):
178                        self.labels[key] = label["text"] 
179             for key, value in self.transactions.iteritems():
180                 target_hashed = hashlib.sha256(key).digest().encode('hex')
181                 if label["external_id"] == target_hashed:
182                    if force or not self.labels.get(key):
183                        self.labels[key] = label["text"] 
184
185     def full_push(self):
186         global target_host
187
188         bundle = {"labels": {}}
189         for key, value in self.labels.iteritems():
190             hashed = hashlib.sha256(key).digest().encode('hex')
191             bundle["labels"][hashed] = value
192
193         params = json.dumps(bundle)
194         connection = httplib.HTTPConnection(target_host)
195         connection.request("POST", ("/api/wallets/%s/labels/batch.json?auth_token=%s" % (wallet_id(), auth_token())), params, {'Content-Type': 'application/json'})
196
197         response = connection.getresponse()
198         if response.reason == httplib.responses[httplib.NOT_FOUND]:
199             return
200         try:
201             response = json.loads(response.read())
202         except ValueError as e:
203             return
204
205         if "error" in response:
206             QMessageBox.warning(None, _("Error"),_("Could not sync labels: %s" % response["error"]))
207             return