2013-04-08 04:30:43 -07:00
import socket
2015-03-24 05:25:51 -07:00
import requests
2014-09-07 13:57:55 -07:00
import threading
2013-03-10 09:04:00 -07:00
import hashlib
import json
2015-03-24 05:25:51 -07:00
2013-03-10 09:04:00 -07:00
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
2013-03-17 10:27:26 -07:00
import aes
import base64
2014-09-07 15:12:34 -07:00
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
2015-03-24 05:25:51 -07:00
from electrum_gui . qt . util import ThreadedButton , Buttons , CancelButton , OkButton
2013-03-10 09:04:00 -07:00
2013-03-16 15:33:49 -07:00
class Plugin ( BasePlugin ) :
2013-08-05 08:15:01 -07:00
2015-04-11 00:57:01 -07:00
target_host = ' sync.bysh.me:8080 '
2014-09-04 07:44:50 -07:00
encode_password = None
2014-09-04 07:37:51 -07:00
2013-08-05 08:15:01 -07:00
def fullname ( self ) :
2015-04-11 00:57:01 -07:00
return _ ( ' LabelSync ' )
2013-08-05 08:15:01 -07:00
def description ( self ) :
2015-04-11 00:57:01 -07:00
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 " ) )
2013-08-05 08:15:01 -07:00
2013-03-18 09:53:19 -07:00
def version ( self ) :
2015-04-11 00:57:01 -07:00
return " 0.0.1 "
2013-03-12 13:20:18 -07:00
2013-03-17 10:27:26 -07:00
def encode ( self , message ) :
2014-09-10 06:28:41 -07:00
encrypted = electrum . bitcoin . aes_encrypt_with_iv ( self . encode_password , self . iv , message . encode ( ' utf8 ' ) )
2013-03-18 09:53:19 -07:00
encoded_message = base64 . b64encode ( encrypted )
2013-03-17 10:27:26 -07:00
return encoded_message
def decode ( self , message ) :
2014-09-10 06:28:41 -07:00
decoded_message = electrum . bitcoin . aes_decrypt_with_iv ( self . encode_password , self . iv , base64 . b64decode ( message ) ) . decode ( ' utf8 ' )
2013-03-17 10:27:26 -07:00
return decoded_message
2015-04-11 00:57:01 -07:00
def set_nonce ( self , nonce ) :
2015-04-12 03:16:57 -07:00
self . print_error ( " Set nonce to " , nonce )
2015-04-11 00:57:01 -07:00
self . wallet . storage . put ( " wallet_nonce " , nonce , True )
self . wallet_nonce = nonce
2014-08-31 02:42:40 -07:00
@hook
2014-08-31 06:33:20 -07:00
def init_qt ( self , gui ) :
self . window = gui . main_window
2015-03-24 05:25:51 -07:00
self . window . connect ( self . window , SIGNAL ( ' labels:pulled ' ) , self . on_pulled )
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
2015-04-11 00:57:01 -07:00
self . wallet_nonce = self . wallet . storage . get ( " wallet_nonce " )
2015-04-12 03:16:57 -07:00
self . print_error ( " Wallet nonce is " , self . wallet_nonce )
2015-04-11 00:57:01 -07:00
if self . wallet_nonce is None :
self . set_nonce ( 1 )
2015-03-06 02:13:28 -08:00
mpk = ' ' . join ( sorted ( self . wallet . get_master_public_keys ( ) . values ( ) ) )
2013-08-05 08:15:01 -07:00
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 ]
2013-08-05 08:15:01 -07:00
self . wallet_id = hashlib . sha256 ( mpk ) . digest ( ) . encode ( ' hex ' )
2013-03-10 09:04:00 -07:00
2015-04-11 00:57:01 -07:00
addresses = [ ]
2013-08-05 08:15:01 -07:00
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
2015-04-11 00:57:01 -07:00
# 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 :
2015-04-12 03:16:57 -07:00
self . print_error ( " could not retrieve labels: " , e )
2015-04-11 00:57:01 -07:00
t = threading . Thread ( target = do_pull_thread )
t . setDaemon ( True )
t . start ( )
2013-03-16 15:33:49 -07:00
2013-08-05 08:15:01 -07:00
2013-03-16 15:33:49 -07:00
def is_available ( self ) :
return True
2013-03-17 04:08:12 -07:00
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 :
2015-03-24 05:25:51 -07:00
return
2015-04-11 00:57:01 -07:00
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 ] )
2015-03-24 05:25:51 -07:00
t . start ( )
2015-04-11 00:57:01 -07:00
self . set_nonce ( self . wallet . storage . get ( " wallet_nonce " ) + 1 )
2013-03-10 09:44:02 -07:00
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-09-30 07:21:31 -07:00
d = QDialog ( )
2015-03-24 05:25:51 -07:00
vbox = QVBoxLayout ( d )
layout = QGridLayout ( )
vbox . addLayout ( layout )
2013-03-18 09:53:19 -07:00
layout . addWidget ( QLabel ( " Label sync options: " ) , 2 , 0 )
2013-03-16 15:33:49 -07:00
2015-03-24 05:25:51 -07:00
self . upload = ThreadedButton ( " Force upload " , self . push_thread , self . done_processing )
layout . addWidget ( self . upload , 2 , 1 )
2013-03-16 15:33:49 -07:00
2015-03-24 17:05:20 -07:00
self . download = ThreadedButton ( " Force download " , lambda : self . pull_thread ( True ) , self . done_processing )
2015-03-24 05:25:51 -07:00
layout . addWidget ( self . download , 2 , 2 )
2013-03-17 04:08:12 -07:00
2015-03-24 05:25:51 -07:00
self . accept = OkButton ( d , _ ( " Done " ) )
vbox . addLayout ( Buttons ( CancelButton ( d ) , self . accept ) )
2013-03-17 03:52:58 -07:00
2013-03-17 04:08:12 -07:00
if d . exec_ ( ) :
2015-03-24 05:25:51 -07:00
return True
2013-03-17 04:08:12 -07:00
else :
2015-03-24 05:25:51 -07:00
return False
def on_pulled ( self ) :
wallet = self . wallet
wallet . storage . put ( ' labels ' , wallet . labels , True )
self . window . labelsChanged . emit ( )
2013-03-17 03:52:58 -07:00
2014-09-11 04:08:35 -07:00
def done_processing ( self ) :
QMessageBox . information ( None , _ ( " Labels synchronised " ) , _ ( " Your labels have been synchronised. " ) )
2013-03-10 09:04:00 -07:00
2015-04-11 00:57:01 -07:00
def do_request ( self , method , url = " /labels " , is_batch = False , data = None ) :
url = ' http:// ' + self . target_host + url
2015-03-24 05:25:51 -07:00
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
2013-03-10 09:04:00 -07:00
2015-03-24 05:25:51 -07:00
def push_thread ( self ) :
2015-04-11 00:57:01 -07:00
bundle = { " labels " : [ ] , " walletId " : self . wallet_id , " walletNonce " : self . wallet_nonce }
2015-03-24 05:25:51 -07:00
for key , value in self . wallet . labels . iteritems ( ) :
2013-04-08 04:30:43 -07:00
try :
2015-03-24 05:25:51 -07:00
encoded_key = self . encode ( key )
encoded_value = self . encode ( value )
except :
2015-04-12 03:16:57 -07:00
self . print_error ( ' cannot encode ' , repr ( key ) , repr ( value ) )
2015-03-24 05:25:51 -07:00
continue
2015-04-11 00:57:01 -07:00
bundle [ " labels " ] . append ( { ' encryptedLabel ' : encoded_value , ' externalId ' : encoded_key } )
self . do_request ( " POST " , " /labels " , True , bundle )
2013-04-08 04:30:43 -07:00
2015-03-24 05:25:51 -07:00
def pull_thread ( self , force = False ) :
2015-04-12 03:16:57 -07:00
wallet_nonce = 1 if force else self . wallet_nonce - 1
self . print_error ( " Asking for labels since nonce " , wallet_nonce )
2015-04-11 00:57:01 -07:00
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 :
2015-04-12 03:16:57 -07:00
self . print_error ( ' error: no json ' , key )
2015-04-11 00:57:01 -07:00
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 )
2015-04-12 03:16:57 -07:00
self . print_error ( " received %d labels " % len ( response ) )