commit 1179a4cf9e11a41ada5f8dfc006a92c71da3aa8d
parent 1bbb211671e46702ec913f5bddffbb0665ff6bbc
Author: qua-non <akshayaurora@gmail.com>
Date: Tue, 11 Mar 2014 00:18:12 +0530
manage exchange plugins, make sure ui doesn't stall while saving seed
and numerous other small fixes.
Diffstat:
7 files changed, 316 insertions(+), 119 deletions(-)
diff --git a/gui/kivy/dialog.py b/gui/kivy/dialog.py
@@ -192,7 +192,7 @@ class InfoBubble(Bubble):
anim.start(self)
- def hide(self, *dt):
+ def hide(self, now=False):
''' Auto fade out the Bubble
'''
def on_stop(*l):
@@ -205,6 +205,8 @@ class InfoBubble(Bubble):
App.get_running_app().stop()
import sys
sys.exit()
+ if now:
+ return on_stop()
anim = Animation(opacity=0, d=.25)
anim.bind(on_complete=on_stop)
diff --git a/gui/kivy/drawer.py b/gui/kivy/drawer.py
@@ -82,11 +82,13 @@ class Drawer(StencilView):
if app.ui_mode[0] == 't':
return super(Drawer, self).on_touch_down(touch)
+ state = self.state
touch.ud['send_touch_down'] = False
+ start = 0 if state[0] == 'c' else self._hidden_widget.right
drag_area = ((self.width * self.drag_area)
if self.state[0] == 'c' else
- self._hidden_widget.width)
- if touch.x > drag_area:
+ self.width)
+ if touch.x not in range(int(start), int(drag_area)):
return super(Drawer, self).on_touch_down(touch)
self._touch = touch
Clock.schedule_once(self._change_touch_mode,
@@ -106,8 +108,13 @@ class Drawer(StencilView):
if not touch.ud.get('in_drag_area', None):
return super(Drawer, self).on_touch_move(touch)
- self._overlay_widget.x = min(self._hidden_widget.width,
- max(self._overlay_widget.x + touch.dx*2, 0))
+ ov = self._overlay_widget
+ ov.x=min(self._hidden_widget.width,
+ max(ov.x + touch.dx*2, 0))
+ #_anim = Animation(x=x, duration=1/60)
+ #_anim.cancel_all(ov)
+ #_anim.start(ov)
+
if abs(touch.x - touch.ox) < self.scroll_distance:
return
touch.ud['send_touch_down'] = False
diff --git a/gui/kivy/installwizard.py b/gui/kivy/installwizard.py
@@ -45,10 +45,16 @@ class InstallWizard(Widget):
'''
def target():
+
# run your threaded function
- task()
+ try:
+ task()
+ except Exception as err:
+ Clock.schedule_once(lambda dt: app.show_error(str(err)))
+
# on completion hide message
- Clock.schedule_once(lambda dt: app.info_bubble.hide())
+ Clock.schedule_once(lambda dt: app.info_bubble.hide(now=True), -1)
+
# call completion routine
if on_complete:
Clock.schedule_once(lambda dt: on_complete())
@@ -138,14 +144,14 @@ class InstallWizard(Widget):
brainwallet = seed
- msg2 = _("[color=#414141][b]"+\
+ msg2 = _("[color=#414141]"+\
"[b]PLEASE WRITE DOWN YOUR SEED PASS[/b][/color]"+\
"[size=9]\n\n[/size]" +\
"[color=#929292]If you ever forget your pincode, your seed" +\
" phrase will be the [color=#EB984E]"+\
"[b]only way to recover[/b][/color] your wallet. Your " +\
" [color=#EB984E][b]Bitcoins[/b][/color] will otherwise be" +\
- " [color=#EB984E]lost forever![/color]")
+ " [color=#EB984E][b]lost forever![/b][/color]")
if wallet.imported_keys:
msg2 += "[b][color=#ff0000ff]" + _("WARNING") + "[/color]:[/b] " +\
@@ -234,13 +240,13 @@ class InstallWizard(Widget):
return app.show_error(_('Passwords do not match'), duration=.5)
if mode == 'restore':
- try:
- wallet.save_seed(new_password)
- except Exception as err:
- app.show_error(str(err))
- return
- _dlg.close()
- self.load_network(wallet, mode='restore')
+ def on_complete(*l):
+ _dlg.close()
+ self.load_network(wallet, mode='restore')
+
+ self.waiting_dialog(lambda: wallet.save_seed(new_password),
+ msg=_("saving seed"),
+ on_complete=on_complete)
return
if not instance:
# create
diff --git a/gui/kivy/main.kv b/gui/kivy/main.kv
@@ -2,6 +2,7 @@
#:import _ electrum.i18n._
#:import partial functools.partial
+
# Custom Global Widgets
<VGridLayout@GridLayout>:
@@ -27,7 +28,7 @@
<Label>
markup: True
- font_name: 'data/fonts/Roboto.ttf'
+ font_name: 'Roboto'
font_size: '16sp'
<ListItemButton>
diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py
@@ -93,7 +93,7 @@ class ElectrumWindow(App):
'While trying to save value to config')
base_unit = AliasProperty(_get_bu, _set_bu, bind=('decimal_point',))
- '''BTC or UBTC or ...
+ '''BTC or UBTC or mBTC...
:attr:`base_unit` is a `AliasProperty` defaults to the unit set in
electrum config.
@@ -148,13 +148,13 @@ class ElectrumWindow(App):
self.network = network = kwargs.get('network')
self.electrum_config = config = kwargs.get('config')
- # create triggers so as to minimize updation a max of 5 times a sec
+ # create triggers so as to minimize updation a max of 2 times a sec
self._trigger_update_status =\
- Clock.create_trigger(self.update_status, .2)
+ Clock.create_trigger(self.update_status, .5)
self._trigger_update_console =\
- Clock.create_trigger(self.update_console, .2)
+ Clock.create_trigger(self.update_console, .5)
self._trigger_notify_transactions = \
- Clock.create_trigger(self.notify_transactions, .2)
+ Clock.create_trigger(self.notify_transactions, .5)
def build(self):
from kivy.lang import Builder
@@ -174,6 +174,15 @@ class ElectrumWindow(App):
Window.bind(size=self.on_size,
on_keyboard=self.on_keyboard)
Window.bind(on_key_down=self.on_key_down)
+
+ # register fonts
+ from kivy.core.text import Label
+ Label.register('Roboto',
+ 'data/fonts/Roboto.ttf',
+ 'data/fonts/Roboto.ttf',
+ 'data/fonts/Roboto-Bold.ttf',
+ 'data/fonts/Roboto-Bold.ttf')
+
if platform == 'android':
#
Window.bind(keyboard_height=self.on_keyboard_height)
@@ -261,17 +270,20 @@ class ElectrumWindow(App):
self.load_wallet(wallet)
- # check and remove this load_wallet calls update_wallet no
- # need for this here
- #Clock.schedule_once(update_wallet)
-
+ #TODO: URI handling
#self.windows.append(w)
#if url: w.set_url(url)
- #w.app = self.app
- #w.connect_slots(s)
- #w.update_wallet()
- #self.app.exec_()
+ # TODO:remove properties are used instead
+ #Clock.schedule_interval(self.timer_actions, .5)
+
+
+ #TODO: remove not needed properties allow on_property events
+ #def timer_actions(self):
+ # if self.need_update.is_set():
+ # self.update_wallet()
+ # self.need_update.clear()
+ # run_hook('timer_actions')
def init_ui(self):
''' Initialize The Ux part of electrum. This function performs the basic
@@ -326,12 +338,12 @@ class ElectrumWindow(App):
from electrum_gui.kivy.plugins.exchange_rate import Exchanger
self.exchanger = Exchanger(self)
self.exchanger.start()
- quote_currency = self.electrum_config.get("currency", 'EUR')
+ quote_currency = self.exchanger.currency
quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
- if mode == 'symbol':
- if quote_currency:
- quote_currency = self.exchanger.symbols[quote_currency]
+ if quote_currency and mode == 'symbol':
+ quote_currency = self.exchanger.symbols.get(quote_currency,
+ quote_currency)
if quote_balance is None:
quote_text = ""
@@ -340,8 +352,9 @@ class ElectrumWindow(App):
return quote_text
def set_currencies(self, quote_currencies):
- self._trigger_update_status
- #self.currencies = sorted(quote_currencies.keys())
+ #TODO remove this and just directly update a observable property
+ self._trigger_update_status()
+ self.currencies = sorted(quote_currencies.keys())
def update_console(self, *dt):
if self.console:
@@ -399,7 +412,7 @@ class ElectrumWindow(App):
#if quote:
# text += " (%s)"%quote
- self.notify(_("Balance: ") + text)
+ #self.notify(_("Balance: ") + text)
#icon = QIcon(":icons/status_connected.png")
else:
text = _("Not connected")
diff --git a/gui/kivy/plugins/exchange_rate.py b/gui/kivy/plugins/exchange_rate.py
@@ -2,109 +2,240 @@
'''Module exchange_rate:
-This module is responsible for getting the conversion rates between different
-currencies.
+This module is responsible for getting the conversion rates from different
+bitcoin exchanges.
'''
from kivy.network.urlrequest import UrlRequest
-#kivy.event import EventDispatcher
+from kivy.event import EventDispatcher
+from kivy.properties import (OptionProperty, StringProperty, AliasProperty,
+ ListProperty)
from kivy.clock import Clock
import decimal
import json
-class Exchanger(object):
- '''
+EXCHANGES = ["BitcoinAverage",
+ "BitcoinVenezuela",
+ "BitPay",
+ "Blockchain",
+ "BTCChina",
+ "CaVirtEx",
+ "Coinbase",
+ "CoinDesk",
+ "LocalBitcoins",
+ "Winkdex"]
+
+
+class Exchanger(EventDispatcher):
+ ''' Provide exchanges rate between crypto and different national
+ currencies. See Module Documentation for details.
'''
symbols = {'ALL': 'Lek', 'AED': 'د.إ', 'AFN':'؋', 'ARS': '$', 'AMD': '֏',
'AWG': 'ƒ', 'ANG': 'ƒ', 'AOA': 'Kz', 'BDT': '৳', 'BHD': 'BD',
- 'BIF': 'FBu', 'BTC': 'BTC', 'BTN': 'Nu',
- 'AUD': '$', 'AZN': 'ман', 'BSD': '$', 'BBD': '$', 'BYR': 'p',
+ 'BIF': 'FBu', 'BTC': 'BTC', 'BTN': 'Nu', 'CDF': 'FC', 'CHF': 'CHF',
+ 'CLF': 'UF', 'CLP':'$', 'CVE': '$', 'DJF':'Fdj', 'DZD': 'دج',
+ 'AUD': '$', 'AZN': 'ман', 'BSD': '$', 'BBD': '$', 'BYR': 'p', 'CRC': '₡',
'BZD': 'BZ$', 'BMD': '$', 'BOB': '$b', 'BAM': 'KM', 'BWP': 'P',
'BGN': 'лв', 'BRL': 'R$', 'BND': '$', 'KHR': '៛', 'CAD': '$',
- 'KYD': '$', 'USD': '$', 'CLP': '$', 'CNY': '¥', 'COP': '$', 'CRC': '₡',
+ 'ERN': 'Nfk', 'ETB': 'Br', 'KYD': '$', 'USD': '$', 'CLP': '$',
'HRK': 'kn', 'CUP':'₱', 'CZK': 'Kč', 'DKK': 'kr', 'DOP': 'RD$',
'XCD': '$', 'EGP': '£', 'SVC': '$' , 'EEK': 'kr', 'EUR': '€',
'FKP': '£', 'FJD': '$', 'GHC': '¢', 'GIP': '£', 'GTQ': 'Q', 'GBP': '£',
'GYD': '$', 'HNL': 'L', 'HKD': '$', 'HUF': 'Ft', 'ISK': 'kr',
- 'INR': '₹', 'IDR': 'Rp', 'IRR': '﷼', 'IMP': '£', 'ILS': '₪',
+ 'INR': '₹', 'IDR': 'Rp', 'IRR': '﷼', 'IMP': '£', 'ILS': '₪', 'COP': '$',
'JMD': 'J$', 'JPY': '¥', 'JEP': '£', 'KZT': 'лв', 'KPW': '₩',
- 'KRW': '₩', 'KGS': 'лв', 'LAK': '₭', 'LVL': 'Ls'}
+ 'KRW': '₩', 'KGS': 'лв', 'LAK': '₭', 'LVL': 'Ls', 'CNY': '¥'}
+
+ _use_exchange = OptionProperty('Blockchain', options=EXCHANGES)
+ '''This is the exchange to be used for getting the currency exchange rates
+ '''
+
+ _currency = StringProperty('EUR')
+ '''internal use only
+ '''
+
+ def _set_currency(self, value):
+ exchanger = self.exchanger
+ if self.use_exchange == 'CoinDesk':
+ self._update_cd_currency(self.currency)
+ return
+ try:
+ self._currency = value
+ self.electrum_cinfig.set_key('currency', value, True)
+ except AttributeError:
+ self._currency = 'EUR'
+
+ def _get_currency(self):
+ try:
+ self._currency = self.electrum_config.get('currency', 'EUR')
+ except AttributeError:
+ pass
+ finally:
+ return self._currency
+
+ currency = AliasProperty(_get_currency, _set_currency, bind=('_currency',))
+
+ currencies = ListProperty(['EUR', 'GBP', 'USD'])
+ '''List of currencies supported by the current exchanger plugin.
+
+ :attr:`currencies` is a `ListProperty` default to ['Eur', 'GBP'. 'USD'].
+ '''
+
+ def _get_useex(self):
+ if not self.parent:
+ return self._use_exchange
+
+ self._use_exchange = self.parent.electrum_config.get('use_exchange',
+ 'Blockchain')
+ return self._use_exchange
+
+ def _set_useex(self, value):
+ if not self.parent:
+ return self._use_exchange
+ self.parent.electrum_config.set_key('use_exchange', value, True)
+ self._use_exchange = value
+
+ use_exchange = AliasProperty(_get_useex, _set_useex,
+ bind=('_use_exchange', ))
def __init__(self, parent):
+ super(Exchanger, self).__init__()
self.parent = parent
self.quote_currencies = None
- self.exchanges = ('BlockChain', 'Coinbase', 'CoinDesk')
- try:
- self.use_exchange = parent.electrum_config.get('use_exchange',
- 'BlockChain')
- except AttributeError:
- self.use_exchange = 'BlockChain'
- self.currencies = self.symbols.keys()
+ self.exchanges = EXCHANGES
def exchange(self, btc_amount, quote_currency):
if self.quote_currencies is None:
return None
+
quote_currencies = self.quote_currencies.copy()
if quote_currency not in quote_currencies:
return None
- if self.use_exchange == "CoinDesk":
- try:
- connection = httplib.HTTPSConnection('api.coindesk.com')
- connection.request("GET", "/v1/bpi/currentprice/" + str(quote_currency) + ".json")
- except Exception:
- return
- resp = connection.getresponse()
- if resp.reason == httplib.responses[httplib.NOT_FOUND]:
- return
- try:
- resp_rate = json.loads(resp.read())
- except Exception:
- return
- return btc_amount * decimal.Decimal(str(resp_rate["bpi"][str(quote_currency)]["rate_float"]))
+
return btc_amount * decimal.Decimal(quote_currencies[quote_currency])
- def check_rates(self, dt):
- if self.use_exchange == 'BlockChain':
- self.check_blockchain()
- elif self.use_exchange == 'CoinDesk':
- self.check_coindesk()
- elif self.use_exchange == 'Coinbase':
- self.check_coinbase()
+ def update_rate(self, dt):
+ ''' This is called from :method:`start` every X seconds; to update the
+ rates for currencies for the currently selected exchange.
+ '''
+ update_rates = {
+ "BitcoinAverage": self.update_ba,
+ "BitcoinVenezuela": self.update_bv,
+ "BitPay": self.update_bp,
+ "Blockchain": self.update_bc,
+ "BTCChina": self.update_CNY,
+ "CaVirtEx": self.update_cv,
+ "CoinDesk": self.update_cd,
+ "Coinbase": self.update_cb,
+ "LocalBitcoins": self.update_lb,
+ "Winkdex": self.update_wd,
+ }
+ try:
+ update_rates[self.use_exchange]()
+ except KeyError:
+ return
- def check_coindesk(self):
+ def update_wd(self):
- def _lookup_rate(response, quote_id):
- return decimal.Decimal(str(response[str(quote_id)]["15m"]))
+ def on_success(request, response):
+ response = json.loads(response)
+ quote_currencies = {'USD': 0.0}
+ lenprices = len(response["prices"])
+ usdprice = response['prices'][lenprices-1]['y']
+
+ try:
+ quote_currencies["USD"] = decimal.Decimal(usdprice)
+ except KeyError:
+ pass
+
+ self.quote_currencies = quote_currencies
+ self.parent.set_currencies(quote_currencies)
+
+ req = UrlRequest(
+ url='https://winkdex.com/static/data/0_600_288.json',
+ on_success=on_success,
+ timeout=5)
+
+ def update_cd_currency(self, currency):
+
+ def on_success(request, response):
+ response = json.loads(response)
+ quote_currencies = self.quote_currencies
+ quote_currencies[currency] =\
+ str(response['bpi'][str(currency)]['rate_float'])
+ self.parent.set_currencies(quote_currencies)
+
+ req = UrlRequest(
+ url='https://api.coindesk.com/v1/bpi/currentprice/'\
+ + str(currency) + '.json',on_success=on_success, timeout=5)
+
+ def update_cd(self):
def on_success(request, response):
quote_currencies = {}
+ response = json.loads(response)
+
+ for cur in response:
+ quote_currencies[str(cur["currency"])] = 0.0
+
+ self.quote_currencies = quote_currencies
+ self.update_cd_currency(self.currency)
+
+ req = UrlRequest(
+ url='https://api.coindesk.com/v1/bpi/supported-currencies.json',
+ on_success=on_success,
+ timeout=5)
+
+ def update_cv(self):
+ def on_success(request, response):
+ response = json.loads(response)
+ quote_currencies = {"CAD": 0.0}
+ cadprice = response["last"]
try:
- for r in response:
- quote_currencies[r] = _lookup_rate(response, r)
+ quote_currencies["CAD"] = decimal.Decimal(cadprice)
self.quote_currencies = quote_currencies
except KeyError:
pass
self.parent.set_currencies(quote_currencies)
- def on_failure(*args):
- pass
+ req = UrlRequest(url='https://www.cavirtex.com/api/CAD/ticker.json',
+ on_success=on_success,
+ timeout=5)
- def on_error(*args):
- pass
+ def update_CNY(self):
- def on_redirect(*args):
- pass
+ def on_success(request, response):
+ quote_currencies = {"CNY": 0.0}
+ cnyprice = response["ticker"]["last"]
+ try:
+ quote_currencies["CNY"] = decimal.Decimal(cnyprice)
+ self.quote_currencies = quote_currencies
+ except KeyError:
+ pass
+ self.parent.set_currencies(quote_currencies)
- req = UrlRequest(
- url='https://api.coindesk.com/v1/bpi/supported-currencies.json',
+ req = UrlRequest(url='https://data.btcchina.com/data/ticker',
+ on_success=on_success,
+ timeout=5)
+
+ def update_bp(self):
+
+ def on_success(request, response):
+ quote_currencies = {}
+ try:
+ for r in response:
+ quote_currencies[str(r['code'])] = decimal.Decimal(r['rate'])
+ self.quote_currencies = quote_currencies
+ except KeyError:
+ pass
+ self.parent.set_currencies(quote_currencies)
+
+ req = UrlRequest(url='https://bitpay.com/api/rates',
on_success=on_success,
- on_failure=on_failure,
- on_error=on_error,
- on_redirect=on_redirect,
timeout=5)
- def check_coinbase(self):
+ def update_cb(self):
def _lookup_rate(response, quote_id):
return decimal.Decimal(str(response[str(quote_id)]))
@@ -121,24 +252,12 @@ class Exchanger(object):
pass
self.parent.set_currencies(quote_currencies)
- def on_failure(*args):
- pass
-
- def on_error(*args):
- pass
-
- def on_redirect(*args):
- pass
-
req = UrlRequest(
url='https://coinbase.com/api/v1/currencies/exchange_rates',
on_success=on_success,
- on_failure=on_failure,
- on_error=on_error,
- on_redirect=on_redirect,
timeout=5)
- def check_blockchain(self):
+ def update_bc(self):
def _lookup_rate(response, quote_id):
return decimal.Decimal(str(response[str(quote_id)]["15m"]))
@@ -153,27 +272,69 @@ class Exchanger(object):
pass
self.parent.set_currencies(quote_currencies)
- def on_failure(*args):
- pass
+ req = UrlRequest(url='https://blockchain.info/ticker',
+ on_success=on_success,
+ timeout=5)
- def on_error(*args):
- pass
+ def update_lb(self):
+ def _lookup_rate(response, quote_id):
+ return decimal.Decimal(response[str(quote_id)]["rates"]["last"])
- def on_redirect(*args):
- pass
+ def on_success(request, response):
+ quote_currencies = {}
+ try:
+ for r in response:
+ quote_currencies[r] = _lookup_rate(response, r)
+ self.quote_currencies = quote_currencies
+ except KeyError:
+ pass
+ self.parent.set_currencies(quote_currencies)
- req = UrlRequest(url='https://blockchain.info/ticker',
+ req = UrlRequest(
+ url='https://localbitcoins.com/bitcoinaverage/ticker-all-currencies/',
+ on_success=on_success,
+ timeout=5)
+
+
+ def update_ba(self):
+
+ def on_success(request, response):
+ quote_currencies = {}
+ try:
+ for r in response:
+ quote_currencies[r] = decimal.Decimal(response[r][u'last'])
+ self.quote_currencies = quote_currencies
+ except TypeError:
+ pass
+ self.parent.set_currencies(quote_currencies)
+
+ req = UrlRequest(url='https://api.bitcoinaverage.com/ticker/global/all',
+ on_success=on_success,
+ timeout=5)
+
+ def update_bv(self):
+
+ def on_success(request, response):
+ quote_currencies = {}
+ try:
+ for r in response["BTC"]:
+ quote_currencies[r] = decimal.Decimal(response['BTC'][r])
+ self.quote_currencies = quote_currencies
+ except KeyError:
+ pass
+ self.parent.set_currencies(quote_currencies)
+
+ req = UrlRequest(url='https://api.bitcoinvenezuela.com/',
on_success=on_success,
- on_failure=on_failure,
- on_error=on_error,
- on_redirect=on_redirect,
timeout=5)
def start(self):
- # check every 5 seconds
- self.check_rates(0)
- Clock.schedule_interval(self.check_rates, 5)
+ # check rates every few seconds
+ self.update_rate(0)
+ # check every few seconds
+ Clock.unschedule(self.update_rate)
+ Clock.schedule_interval(self.update_rate, 20)
def stop(self):
- Clock.unschedule(self.check_rates)
+ Clock.unschedule(self.update_rate)
diff --git a/lib/util.py b/lib/util.py
@@ -142,6 +142,13 @@ def user_dir():
elif "LOCALAPPDATA" in os.environ:
return os.path.join(os.environ["LOCALAPPDATA"], "Electrum")
elif 'ANDROID_DATA' in os.environ:
+ try:
+ import jnius
+ env = jnius.autoclass('android.os.Environment')
+ _dir = env.getExternalStorageDirectory().getPath()
+ return _dir + '/electrum/'
+ except ImportError:
+ pass
return "/sdcard/electrum/"
else:
#raise Exception("No home directory found in environment variables.")