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
2013-03-10 09:04:00 -07:00
import hashlib
import json
from urlparse import urlparse , parse_qs
try :
import PyQt4
except :
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
2013-09-11 02:45:58 -07:00
from electrum import bmp , pyqrnative
from electrum . plugins import BasePlugin
from electrum . i18n import _
2013-03-18 09:53:19 -07:00
from electrum_gui . gui_classic import HelpButton
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
def fullname ( self ) :
return _ ( ' Label Sync ' )
def description ( self ) :
2013-09-16 20:19:23 -07:00
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 and addresses are all sent and stored encrypted on 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. " ) )
2013-08-05 08:15:01 -07:00
2013-03-18 09:53:19 -07:00
def version ( self ) :
2013-04-08 04:30:43 -07:00
return " 0.2.1 "
2013-03-12 13:20:18 -07:00
2013-03-17 10:27:26 -07:00
def encode ( self , message ) :
2013-03-18 09:53:19 -07:00
encrypted = aes . encryptData ( self . encode_password , unicode ( message ) )
encoded_message = base64 . b64encode ( encrypted )
2013-03-17 10:27:26 -07:00
return encoded_message
def decode ( self , message ) :
2013-03-18 09:53:19 -07:00
decoded_message = aes . decryptData ( self . encode_password , base64 . b64decode ( unicode ( message ) ) )
2013-03-17 10:27:26 -07:00
return decoded_message
2013-03-16 15:33:49 -07:00
2013-08-05 08:15:01 -07:00
def init ( self ) :
self . target_host = ' labelectrum.herokuapp.com '
2013-09-23 07:14:28 -07:00
self . window = self . gui . main_window
self . wallet = self . window . wallet
2013-03-16 15:33:49 -07:00
self . labels = self . wallet . labels
self . transactions = self . wallet . transactions
2013-08-05 08:15:01 -07:00
mpk = self . wallet . master_public_keys [ " m/0 ' / " ] [ 1 ]
self . encode_password = hashlib . sha1 ( mpk ) . digest ( ) . encode ( ' hex ' ) [ : 32 ]
self . wallet_id = hashlib . sha256 ( mpk ) . digest ( ) . encode ( ' hex ' )
2013-03-10 09:04:00 -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
2013-08-05 08:15:01 -07:00
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
self . full_pull ( )
2013-08-05 08:15:01 -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
2013-03-17 04:08:12 -07:00
def requires_settings ( self ) :
return True
2013-03-16 15:33:49 -07:00
def set_label ( self , item , label , changed ) :
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-03-10 09:44:02 -07:00
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 )
2013-03-17 04:08:12 -07:00
self . accept . setEnabled ( True )
2013-03-16 15:33:49 -07:00
else :
self . upload . setEnabled ( False )
self . download . setEnabled ( False )
2013-03-17 04:08:12 -07:00
self . accept . setEnabled ( False )
2013-03-16 15:33:49 -07:00
2013-09-23 07:14:28 -07:00
d = QDialog ( self . window )
2013-03-17 04:08:12 -07:00
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 )
2013-03-18 09:53:19 -07:00
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 )
2013-03-18 09:53:19 -07:00
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 )
2013-03-18 09:53:19 -07:00
layout . addWidget ( self . upload , 2 , 1 )
2013-03-16 15:33:49 -07:00
self . download = QPushButton ( " Force download " )
self . download . clicked . connect ( lambda : self . full_pull ( True ) )
2013-03-18 09:53:19 -07:00
layout . addWidget ( self . download , 2 , 2 )
2013-03-16 15:33:49 -07:00
2013-03-17 04:08:12 -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
2013-03-18 09:53:19 -07:00
layout . addWidget ( c , 3 , 1 )
layout . addWidget ( self . accept , 3 , 2 )
2013-03-17 03:52:58 -07:00
2013-03-17 04:08:12 -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-08-05 08:15:01 -07:00
def enable ( self ) :
if not self . auth_token ( ) : # First run, throw plugin settings in your face
2013-08-07 20:47:24 -07:00
self . init ( )
2013-03-17 04:08:12 -07:00
if self . settings_dialog ( ) :
2013-08-05 08:15:01 -07:00
self . set_enabled ( True )
return True
2013-03-17 04:08:12 -07:00
else :
2013-08-05 08:15:01 -07:00
self . set_enabled ( False )
return False
2013-03-17 04:08:12 -07:00
return enabled
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 , force = False ) :
if self . do_full_pull ( force ) and force :
2013-03-17 04:56:53 -07:00
QMessageBox . information ( None , _ ( " Labels synchronized " ) , _ ( " Your labels have been synchronized. " ) )
2013-09-23 07:14:28 -07:00
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 " : { } }
for key , value in self . labels . iteritems ( ) :
encoded = self . encode ( key )
bundle [ " labels " ] [ encoded ] = self . encode ( value )
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 ) :
try :
2013-04-08 04:30:43 -07:00
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 . 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
for label in response :
decoded_key = self . decode ( label [ " external_id " ] )
decoded_label = self . decode ( label [ " text " ] )
if force or not self . labels . get ( decoded_key ) :
self . labels [ decoded_key ] = decoded_label
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