Browse Source

Merge exchange_rate plugin with main code

* fixes #2037 (tab indexes)
283
ThomasV 8 years ago
parent
commit
13678d9e13
  1. 18
      gui/kivy/main_window.py
  2. 35
      gui/kivy/uix/dialogs/fx_dialog.py
  3. 8
      gui/kivy/uix/dialogs/settings.py
  4. 5
      gui/kivy/uix/screens.py
  5. 16
      gui/qt/history_list.py
  6. 166
      gui/qt/main_window.py
  7. 6
      lib/daemon.py
  8. 68
      lib/exchange_rate.py
  9. 3
      lib/plugins.py
  10. 5
      plugins/exchange_rate/__init__.py
  11. 54
      plugins/exchange_rate/kivy.py
  12. 227
      plugins/exchange_rate/qt.py

18
gui/kivy/main_window.py

@ -92,11 +92,12 @@ class ElectrumWindow(App):
_.switch_lang(language) _.switch_lang(language)
def on_quotes(self, d): def on_quotes(self, d):
#Logger.info("on_quotes") Logger.info("on_quotes")
pass if self.history_screen:
Clock.schedule_once(lambda dt: self.history_screen.update())
def on_history(self, d): def on_history(self, d):
#Logger.info("on_history") Logger.info("on_history")
if self.history_screen: if self.history_screen:
Clock.schedule_once(lambda dt: self.history_screen.update()) Clock.schedule_once(lambda dt: self.history_screen.update())
@ -124,7 +125,7 @@ class ElectrumWindow(App):
def btc_to_fiat(self, amount_str): def btc_to_fiat(self, amount_str):
if not amount_str: if not amount_str:
return '' return ''
rate = run_hook('exchange_rate') rate = self.fx.exchange_rate()
if not rate: if not rate:
return '' return ''
fiat_amount = self.get_amount(amount_str + ' ' + self.base_unit) * rate / pow(10, 8) fiat_amount = self.get_amount(amount_str + ' ' + self.base_unit) * rate / pow(10, 8)
@ -133,7 +134,7 @@ class ElectrumWindow(App):
def fiat_to_btc(self, fiat_amount): def fiat_to_btc(self, fiat_amount):
if not fiat_amount: if not fiat_amount:
return '' return ''
rate = run_hook('exchange_rate') rate = self.fx.exchange_rate()
if not rate: if not rate:
return '' return ''
satoshis = int(pow(10,8) * Decimal(fiat_amount) / Decimal(rate)) satoshis = int(pow(10,8) * Decimal(fiat_amount) / Decimal(rate))
@ -198,6 +199,7 @@ class ElectrumWindow(App):
self.gui_object = kwargs.get('gui_object', None) self.gui_object = kwargs.get('gui_object', None)
self.daemon = self.gui_object.daemon self.daemon = self.gui_object.daemon
self.fx = self.daemon.fx
self.contacts = Contacts(self.electrum_config) self.contacts = Contacts(self.electrum_config)
self.invoices = InvoiceStore(self.electrum_config) self.invoices = InvoiceStore(self.electrum_config)
@ -386,6 +388,12 @@ class ElectrumWindow(App):
self.load_wallet_by_name(self.electrum_config.get_wallet_path()) self.load_wallet_by_name(self.electrum_config.get_wallet_path())
# init plugins # init plugins
run_hook('init_kivy', self) run_hook('init_kivy', self)
# fiat currency
self.fiat_unit = self.fx.ccy if self.fx.is_enabled() else ''
self.network.register_callback(self.on_quotes, ['on_quotes'])
self.network.register_callback(self.on_history, ['on_history'])
# default tab # default tab
self.switch_to('history') self.switch_to('history')
# bind intent for bitcoin: URI scheme # bind intent for bitcoin: URI scheme

35
gui/kivy/uix/dialogs/fx_dialog.py

@ -86,46 +86,35 @@ class FxDialog(Factory.Popup):
self.app = app self.app = app
self.config = config self.config = config
self.callback = callback self.callback = callback
self.plugins = plugins self.fx = self.app.fx
p = self.plugins.get('exchange_rate') self.ids.enabled.active = self.fx.is_enabled()
self.ids.enabled.active = bool(p)
def on_active(self, b): def on_active(self, b):
if b: self.fx.set_enabled(b)
p = self.plugins.get('exchange_rate')
if p is None:
p = self.plugins.enable('exchange_rate')
p.init_kivy(self.app)
else:
self.plugins.disable('exchange_rate')
Clock.schedule_once(lambda dt: self.add_currencies()) Clock.schedule_once(lambda dt: self.add_currencies())
def add_exchanges(self): def add_exchanges(self):
p = self.plugins.get('exchange_rate') exchanges = sorted(self.fx.exchanges_by_ccy.get(self.fx.get_currency())) if self.fx.is_enabled() else []
exchanges = sorted(p.exchanges_by_ccy.get(p.get_currency())) if p else [] mx = self.fx.exchange.name() if self.fx.is_enabled() else ''
mx = p.exchange.name() if p else ''
ex = self.ids.exchanges ex = self.ids.exchanges
ex.values = exchanges ex.values = exchanges
ex.text = (mx if mx in exchanges else exchanges[0]) if p else '' ex.text = (mx if mx in exchanges else exchanges[0]) if self.fx.is_enabled() else ''
def on_exchange(self, text): def on_exchange(self, text):
if not text: if not text:
return return
p = self.plugins.get('exchange_rate') if self.fx.is_enabled() and text != self.fx.exchange.name():
if p and text != p.exchange.name(): self.fx.set_exchange(text)
p.set_exchange(text)
def add_currencies(self): def add_currencies(self):
p = self.plugins.get('exchange_rate') currencies = sorted(self.fx.exchanges_by_ccy.keys()) if self.fx else []
currencies = sorted(p.exchanges_by_ccy.keys()) if p else [] my_ccy = self.fx.get_currency() if self.fx.is_enabled() else ''
my_ccy = p.get_currency() if p else ''
self.ids.ccy.values = currencies self.ids.ccy.values = currencies
self.ids.ccy.text = my_ccy self.ids.ccy.text = my_ccy
def on_currency(self, ccy): def on_currency(self, ccy):
if ccy: if ccy:
p = self.plugins.get('exchange_rate') if self.fx.is_enabled() and ccy != self.fx.get_currency():
if p and ccy != p.get_currency(): self.fx.set_currency(ccy)
p.set_currency(ccy)
self.app.fiat_unit = ccy self.app.fiat_unit = ccy
Clock.schedule_once(lambda dt: self.add_exchanges()) Clock.schedule_once(lambda dt: self.add_exchanges())

8
gui/kivy/uix/dialogs/settings.py

@ -242,10 +242,10 @@ class SettingsDialog(Factory.Popup):
self._rbf_dialog.open() self._rbf_dialog.open()
def fx_status(self): def fx_status(self):
p = self.plugins.get('exchange_rate') fx = self.app.fx
if p: if fx.is_enabled():
source = p.exchange.name() source = fx.exchange.name()
ccy = p.get_currency() ccy = fx.get_currency()
return '%s [%s]' %(ccy, source) return '%s [%s]' %(ccy, source)
else: else:
return 'Disabled' return 'Disabled'

5
gui/kivy/uix/screens.py

@ -20,7 +20,6 @@ from kivy.utils import platform
from electrum.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds from electrum.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds
from electrum import bitcoin from electrum import bitcoin
from electrum.util import timestamp_to_datetime from electrum.util import timestamp_to_datetime
from electrum.plugins import run_hook
from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
from context_menu import ContextMenu from context_menu import ContextMenu
@ -148,9 +147,9 @@ class HistoryScreen(CScreen):
ri.value_known = value is not None ri.value_known = value is not None
ri.confirmations = conf ri.confirmations = conf
if self.app.fiat_unit and date: if self.app.fiat_unit and date:
rate = run_hook('history_rate', date) rate = self.app.fx.history_rate(date)
if rate: if rate:
s = run_hook('value_str', value, rate) s = self.app.fx.value_str(value, rate)
ri.quote_text = '' if s is None else s + ' ' + self.app.fiat_unit ri.quote_text = '' if s is None else s + ' ' + self.app.fiat_unit
return ri return ri

16
gui/qt/history_list.py

@ -30,6 +30,7 @@ from util import *
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import block_explorer_URL, format_satoshis, format_time from electrum.util import block_explorer_URL, format_satoshis, format_time
from electrum.plugins import run_hook from electrum.plugins import run_hook
from electrum.util import timestamp_to_datetime
TX_ICONS = [ TX_ICONS = [
@ -55,8 +56,10 @@ class HistoryList(MyTreeWidget):
self.setColumnHidden(1, True) self.setColumnHidden(1, True)
def refresh_headers(self): def refresh_headers(self):
ccy = self.parent.fx.ccy
headers = ['', '', _('Date'), _('Description') , _('Amount'), _('Balance')] headers = ['', '', _('Date'), _('Description') , _('Amount'), _('Balance')]
run_hook('history_tab_headers', headers) if self.parent.fx.show_history():
headers.extend(['%s '%ccy + _('Amount'), '%s '%ccy + _('Balance')])
self.update_headers(headers) self.update_headers(headers)
def get_domain(self): def get_domain(self):
@ -69,7 +72,10 @@ class HistoryList(MyTreeWidget):
item = self.currentItem() item = self.currentItem()
current_tx = item.data(0, Qt.UserRole).toString() if item else None current_tx = item.data(0, Qt.UserRole).toString() if item else None
self.clear() self.clear()
run_hook('history_tab_update_begin')
fx = self.parent.fx
fx.history_used_spot = False
for h_item in h: for h_item in h:
tx_hash, height, conf, timestamp, value, balance = h_item tx_hash, height, conf, timestamp, value, balance = h_item
status, status_str = self.wallet.get_tx_status(tx_hash, height, conf, timestamp) status, status_str = self.wallet.get_tx_status(tx_hash, height, conf, timestamp)
@ -78,7 +84,11 @@ class HistoryList(MyTreeWidget):
balance_str = self.parent.format_amount(balance, whitespaces=True) balance_str = self.parent.format_amount(balance, whitespaces=True)
label = self.wallet.get_label(tx_hash) label = self.wallet.get_label(tx_hash)
entry = ['', tx_hash, status_str, label, v_str, balance_str] entry = ['', tx_hash, status_str, label, v_str, balance_str]
run_hook('history_tab_update', h_item, entry) if fx.show_history():
date = timestamp_to_datetime(time.time() if conf <= 0 else timestamp)
for amount in [value, balance]:
text = fx.historical_value_str(amount, date)
entry.append(text)
item = QTreeWidgetItem(entry) item = QTreeWidgetItem(entry)
item.setIcon(0, icon) item.setIcon(0, icon)
for i in range(len(entry)): for i in range(len(entry)):

166
gui/qt/main_window.py

@ -54,7 +54,7 @@ from electrum import util, bitcoin, commands, coinchooser
from electrum import SimpleConfig, paymentrequest from electrum import SimpleConfig, paymentrequest
from electrum.wallet import Wallet, Multisig_Wallet from electrum.wallet import Wallet, Multisig_Wallet
from amountedit import BTCAmountEdit, MyLineEdit, BTCkBEdit from amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, BTCkBEdit
from network_dialog import NetworkDialog from network_dialog import NetworkDialog
from qrcodewidget import QRCodeWidget, QRDialog from qrcodewidget import QRCodeWidget, QRDialog
from qrtextedit import ShowQRTextEdit from qrtextedit import ShowQRTextEdit
@ -98,6 +98,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.gui_object = gui_object self.gui_object = gui_object
self.config = config = gui_object.config self.config = config = gui_object.config
self.network = gui_object.daemon.network self.network = gui_object.daemon.network
self.fx = gui_object.daemon.fx
self.invoices = gui_object.invoices self.invoices = gui_object.invoices
self.contacts = gui_object.contacts self.contacts = gui_object.contacts
self.tray = gui_object.tray self.tray = gui_object.tray
@ -166,10 +167,36 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
# set initial message # set initial message
self.console.showMessage(self.network.banner) self.console.showMessage(self.network.banner)
self.network.register_callback(self.on_quotes, ['on_quotes'])
self.network.register_callback(self.on_history, ['on_history'])
self.connect(self, SIGNAL('new_fx_quotes'), self.on_fx_quotes)
self.connect(self, SIGNAL('new_fx_history'), self.on_fx_history)
self.load_wallet(wallet) self.load_wallet(wallet)
self.connect_slots(gui_object.timer) self.connect_slots(gui_object.timer)
self.fetch_alias() self.fetch_alias()
def on_history(self, b):
self.emit(SIGNAL('new_fx_history'))
def on_fx_history(self):
self.history_list.refresh_headers()
self.history_list.update()
def on_quotes(self, b):
self.emit(SIGNAL('new_fx_quotes'))
def on_fx_quotes(self):
self.update_status()
# Refresh edits with the new rate
edit = self.fiat_send_e if self.fiat_send_e.is_last_edited else self.amount_e
edit.textEdited.emit(edit.text())
edit = self.fiat_receive_e if self.fiat_receive_e.is_last_edited else self.receive_amount_e
edit.textEdited.emit(edit.text())
# History tab needs updating if it used spot
if self.fx.history_used_spot:
self.history_list.update()
def toggle_addresses_tab(self): def toggle_addresses_tab(self):
show_addr = not self.config.get('show_addresses_tab', False) show_addr = not self.config.get('show_addresses_tab', False)
self.config.set_key('show_addresses_tab', show_addr) self.config.set_key('show_addresses_tab', show_addr)
@ -528,7 +555,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
def format_amount_and_units(self, amount): def format_amount_and_units(self, amount):
text = self.format_amount(amount) + ' '+ self.base_unit() text = self.format_amount(amount) + ' '+ self.base_unit()
x = run_hook('format_amount_and_units', amount) x = self.fx.format_amount_and_units(amount)
if text and x: if text and x:
text += ' (%s)'%x text += ' (%s)'%x
return text return text
@ -546,6 +573,43 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
return 'BTC' return 'BTC'
raise Exception('Unknown base unit') raise Exception('Unknown base unit')
def connect_fields(self, window, btc_e, fiat_e, fee_e):
def edit_changed(edit):
if edit.follows:
return
edit.setStyleSheet(BLACK_FG)
fiat_e.is_last_edited = (edit == fiat_e)
amount = edit.get_amount()
rate = self.fx.exchange_rate()
if rate is None or amount is None:
if edit is fiat_e:
btc_e.setText("")
if fee_e:
fee_e.setText("")
else:
fiat_e.setText("")
else:
if edit is fiat_e:
btc_e.follows = True
btc_e.setAmount(int(amount / Decimal(rate) * COIN))
btc_e.setStyleSheet(BLUE_FG)
btc_e.follows = False
if fee_e:
window.update_fee()
else:
fiat_e.follows = True
fiat_e.setText(self.fx.ccy_amount_str(
amount * Decimal(rate) / COIN, False))
fiat_e.setStyleSheet(BLUE_FG)
fiat_e.follows = False
btc_e.follows = False
fiat_e.follows = False
fiat_e.textChanged.connect(partial(edit_changed, fiat_e))
btc_e.textChanged.connect(partial(edit_changed, btc_e))
fiat_e.is_last_edited = False
def update_status(self): def update_status(self):
if not self.wallet: if not self.wallet:
return return
@ -573,10 +637,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
text += " [%s unconfirmed]"%(self.format_amount(u, True).strip()) text += " [%s unconfirmed]"%(self.format_amount(u, True).strip())
if x: if x:
text += " [%s unmatured]"%(self.format_amount(x, True).strip()) text += " [%s unmatured]"%(self.format_amount(x, True).strip())
# append fiat balance and price from exchange rate plugin
rate = run_hook('get_fiat_status_text', c + u + x) # append fiat balance and price
if rate: if self.fx.is_enabled():
text += rate text += self.fx.get_fiat_status_text(c + u + x) or ''
icon = QIcon(":icons/status_connected.png") icon = QIcon(":icons/status_connected.png")
else: else:
text = _("Not connected") text = _("Not connected")
@ -641,6 +705,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
grid.addWidget(self.receive_amount_e, 2, 1) grid.addWidget(self.receive_amount_e, 2, 1)
self.receive_amount_e.textChanged.connect(self.update_receive_qr) self.receive_amount_e.textChanged.connect(self.update_receive_qr)
self.fiat_receive_e = AmountEdit(self.fx.get_currency)
grid.addWidget(self.fiat_receive_e, 2, 2, Qt.AlignLeft)
self.connect_fields(self, self.receive_amount_e, self.fiat_receive_e, None)
self.expires_combo = QComboBox() self.expires_combo = QComboBox()
self.expires_combo.addItems(map(lambda x:x[0], expiration_values)) self.expires_combo.addItems(map(lambda x:x[0], expiration_values))
self.expires_combo.setCurrentIndex(1) self.expires_combo.setCurrentIndex(1)
@ -893,6 +961,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
grid.addWidget(amount_label, 4, 0) grid.addWidget(amount_label, 4, 0)
grid.addWidget(self.amount_e, 4, 1) grid.addWidget(self.amount_e, 4, 1)
self.fiat_send_e = AmountEdit(self.fx.get_currency)
grid.addWidget(self.fiat_send_e, 4, 2, Qt.AlignLeft)
self.amount_e.frozen.connect(
lambda: self.fiat_send_e.setFrozen(self.amount_e.isReadOnly()))
self.max_button = EnterButton(_("Max"), self.spend_max) self.max_button = EnterButton(_("Max"), self.spend_max)
hbox = QHBoxLayout() hbox = QHBoxLayout()
hbox.addWidget(self.max_button) hbox.addWidget(self.max_button)
@ -927,6 +1000,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
# This is so that when the user blanks the fee and moves on, # This is so that when the user blanks the fee and moves on,
# we go back to auto-calculate mode and put a fee back. # we go back to auto-calculate mode and put a fee back.
self.fee_e.editingFinished.connect(self.update_fee) self.fee_e.editingFinished.connect(self.update_fee)
self.connect_fields(self, self.amount_e, self.fiat_send_e, self.fee_e)
self.rbf_checkbox = QCheckBox(_('Replaceable')) self.rbf_checkbox = QCheckBox(_('Replaceable'))
msg = [_('If you check this box, your transaction will be marked as non-final,'), msg = [_('If you check this box, your transaction will be marked as non-final,'),
@ -1380,7 +1454,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.not_enough_funds = False self.not_enough_funds = False
self.payment_request = None self.payment_request = None
self.payto_e.is_pr = False self.payto_e.is_pr = False
for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]: for e in [self.payto_e, self.message_e, self.amount_e, self.fiat_send_e, self.fee_e]:
e.setText('') e.setText('')
e.setFrozen(False) e.setFrozen(False)
self.set_pay_from([]) self.set_pay_from([])
@ -2241,6 +2315,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
title, msg = _('Import private keys'), _("Enter private keys") title, msg = _('Import private keys'), _("Enter private keys")
self._do_import(title, msg, lambda x: self.wallet.import_key(x, password)) self._do_import(title, msg, lambda x: self.wallet.import_key(x, password))
def update_fiat(self):
b = self.fx.is_enabled()
self.fiat_send_e.setVisible(b)
self.fiat_receive_e.setVisible(b)
self.history_list.refresh_headers()
self.history_list.update()
self.update_status()
def settings_dialog(self): def settings_dialog(self):
self.need_restart = False self.need_restart = False
d = WindowModalDialog(self, _('Preferences')) d = WindowModalDialog(self, _('Preferences'))
@ -2490,10 +2572,73 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
chooser_combo.currentIndexChanged.connect(on_chooser) chooser_combo.currentIndexChanged.connect(on_chooser)
tx_widgets.append((chooser_label, chooser_combo)) tx_widgets.append((chooser_label, chooser_combo))
# Fiat Currency
hist_checkbox = QCheckBox()
ccy_combo = QComboBox()
ex_combo = QComboBox()
def update_currencies():
currencies = sorted(self.fx.exchanges_by_ccy.keys())
ccy_combo.clear()
ccy_combo.addItems([_('None')] + currencies)
if self.fx.is_enabled():
ccy_combo.setCurrentIndex(ccy_combo.findText(self.fx.get_currency()))
def update_history_cb():
hist_checkbox.setChecked(self.fx.get_history_config())
hist_checkbox.setEnabled(self.fx.is_enabled())
def update_exchanges():
b = self.fx.is_enabled()
ex_combo.setEnabled(b)
if b:
h = self.fx.get_history_config()
c = self.fx.get_currency()
exchanges = self.fx.get_exchanges_by_ccy(c, h)
else:
exchanges = self.fx.exchanges.keys()
ex_combo.clear()
ex_combo.addItems(sorted(exchanges))
ex_combo.setCurrentIndex(ex_combo.findText(self.fx.config_exchange()))
def on_currency(hh):
b = bool(ccy_combo.currentIndex())
ccy = str(ccy_combo.currentText()) if b else None
self.fx.set_enabled(b)
if b and ccy != self.fx.ccy:
self.fx.set_currency(ccy)
update_history_cb()
update_exchanges()
self.update_fiat()
def on_exchange(idx):
exchange = str(ex_combo.currentText())
if self.fx.is_enabled() and exchange != self.fx.exchange.name():
self.fx.set_exchange(exchange)
def on_history(checked):
self.fx.set_history_config(checked)
self.history_list.refresh_headers()
if self.fx.is_enabled() and checked:
self.fx.get_historical_rates()
update_currencies()
update_history_cb()
update_exchanges()
ccy_combo.currentIndexChanged.connect(on_currency)
hist_checkbox.stateChanged.connect(on_history)
ex_combo.currentIndexChanged.connect(on_exchange)
fiat_widgets = []
fiat_widgets.append((QLabel(_('Fiat currency')), ccy_combo))
fiat_widgets.append((QLabel(_('Show history rates')), hist_checkbox))
fiat_widgets.append((QLabel(_('Source')), ex_combo))
tabs_info = [ tabs_info = [
(fee_widgets, _('Fees')), (fee_widgets, _('Fees')),
(tx_widgets, _('Transactions')), (tx_widgets, _('Transactions')),
(gui_widgets, _('Appearance')), (gui_widgets, _('Appearance')),
(fiat_widgets, _('Fiat')),
(id_widgets, _('Identity')), (id_widgets, _('Identity')),
] ]
for widgets, name in tabs_info: for widgets, name in tabs_info:
@ -2517,12 +2662,19 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
# run the dialog # run the dialog
d.exec_() d.exec_()
if self.fx:
self.fx.timeout = 0
self.disconnect(self, SIGNAL('alias_received'), set_alias_color) self.disconnect(self, SIGNAL('alias_received'), set_alias_color)
run_hook('close_settings_dialog') run_hook('close_settings_dialog')
if self.need_restart: if self.need_restart:
self.show_warning(_('Please restart Electrum to activate the new GUI settings'), title=_('Success')) self.show_warning(_('Please restart Electrum to activate the new GUI settings'), title=_('Success'))
def run_network_dialog(self): def run_network_dialog(self):
if not self.network: if not self.network:
self.show_warning(_('You are using Electrum in offline mode; restart Electrum if you want to get connected'), title=_('Offline')) self.show_warning(_('You are using Electrum in offline mode; restart Electrum if you want to get connected'), title=_('Offline'))

6
lib/daemon.py

@ -39,6 +39,7 @@ from wallet import WalletStorage, Wallet
from commands import known_commands, Commands from commands import known_commands, Commands
from simple_config import SimpleConfig from simple_config import SimpleConfig
from plugins import run_hook from plugins import run_hook
from exchange_rate import FxThread
def get_lockfile(config): def get_lockfile(config):
return os.path.join(config.path, 'daemon') return os.path.join(config.path, 'daemon')
@ -100,14 +101,17 @@ class RequestHandler(SimpleJSONRPCRequestHandler):
class Daemon(DaemonThread): class Daemon(DaemonThread):
def __init__(self, config, fd): def __init__(self, config, fd):
DaemonThread.__init__(self) DaemonThread.__init__(self)
self.config = config self.config = config
if config.get('offline'): if config.get('offline'):
self.network = None self.network = None
self.fx = None
else: else:
self.network = Network(config) self.network = Network(config)
self.network.start() self.network.start()
self.fx = FxThread(config, self.network)
self.network.add_jobs([self.fx])
self.gui = None self.gui = None
self.wallets = {} self.wallets = {}
# Setup JSONRPC server # Setup JSONRPC server

68
plugins/exchange_rate/exchange_rate.py → lib/exchange_rate.py

@ -8,11 +8,10 @@ import traceback
import csv import csv
from decimal import Decimal from decimal import Decimal
from electrum.bitcoin import COIN from bitcoin import COIN
from electrum.plugins import BasePlugin, hook from i18n import _
from electrum.i18n import _ from util import PrintError, ThreadJob
from electrum.util import PrintError, ThreadJob from util import format_satoshis
from electrum.util import format_satoshis
# See https://en.wikipedia.org/wiki/ISO_4217 # See https://en.wikipedia.org/wiki/ISO_4217
@ -102,7 +101,7 @@ class BitcoinAverage(ExchangeBase):
def historical_rates(self, ccy): def historical_rates(self, ccy):
history = self.get_csv('api.bitcoinaverage.com', history = self.get_csv('api.bitcoinaverage.com',
"/history/%s/per_day_all_time_history.csv" % ccy) "/history/%s/per_day_all_time_history.csv" % ccy)
return dict([(h['datetime'][:10], h['average']) return dict([(h['DateTime'][:10], h['Average'])
for h in history]) for h in history])
class BitcoinVenezuela(ExchangeBase): class BitcoinVenezuela(ExchangeBase):
@ -298,10 +297,11 @@ def get_exchanges_by_ccy():
class FxPlugin(BasePlugin, ThreadJob): class FxThread(ThreadJob):
def __init__(self, parent, config, name): def __init__(self, config, network):
BasePlugin.__init__(self, parent, config, name) self.config = config
self.network = network
self.ccy = self.get_currency() self.ccy = self.get_currency()
self.history_used_spot = False self.history_used_spot = False
self.ccy_combo = None self.ccy_combo = None
@ -310,19 +310,34 @@ class FxPlugin(BasePlugin, ThreadJob):
self.exchanges_by_ccy = get_exchanges_by_ccy() self.exchanges_by_ccy = get_exchanges_by_ccy()
self.set_exchange(self.config_exchange()) self.set_exchange(self.config_exchange())
def get_exchanges_by_ccy(self, ccy, h):
return self.exchanges_by_ccy.get(ccy)
def ccy_amount_str(self, amount, commas): def ccy_amount_str(self, amount, commas):
prec = CCY_PRECISIONS.get(self.ccy, 2) prec = CCY_PRECISIONS.get(self.ccy, 2)
fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec)) fmt_str = "{:%s.%df}" % ("," if commas else "", max(0, prec))
return fmt_str.format(round(amount, prec)) return fmt_str.format(round(amount, prec))
def thread_jobs(self):
return [self]
def run(self): def run(self):
# This runs from the plugins thread which catches exceptions # This runs from the plugins thread which catches exceptions
if self.timeout <= time.time(): if self.is_enabled():
self.timeout = time.time() + 150 if self.timeout ==0 and self.show_history():
self.exchange.update(self.ccy) self.exchange.get_historical_rates(self.ccy)
if self.timeout <= time.time():
self.timeout = time.time() + 150
self.exchange.update(self.ccy)
def is_enabled(self):
return bool(self.config.get('use_exchange_rate'))
def set_enabled(self, b):
return self.config.set_key('use_exchange_rate', bool(b))
def get_history_config(self):
return bool(self.config.get('history_rates'))
def set_history_config(self, b):
self.config.set_key('history_rates', bool(b))
def get_currency(self): def get_currency(self):
'''Use when dynamic fetching is needed''' '''Use when dynamic fetching is needed'''
@ -332,12 +347,12 @@ class FxPlugin(BasePlugin, ThreadJob):
return self.config.get('use_exchange', 'BitcoinAverage') return self.config.get('use_exchange', 'BitcoinAverage')
def show_history(self): def show_history(self):
return self.ccy in self.exchange.history_ccys() return self.is_enabled() and self.get_history_config() and self.ccy in self.exchange.history_ccys()
def set_currency(self, ccy): def set_currency(self, ccy):
self.ccy = ccy self.ccy = ccy
self.config.set_key('currency', ccy, True) self.config.set_key('currency', ccy, True)
self.get_historical_rates() # Because self.ccy changes self.timeout = 0 # Because self.ccy changes
self.on_quotes() self.on_quotes()
def set_exchange(self, name): def set_exchange(self, name):
@ -351,39 +366,28 @@ class FxPlugin(BasePlugin, ThreadJob):
# A new exchange means new fx quotes, initially empty. Force # A new exchange means new fx quotes, initially empty. Force
# a quote refresh # a quote refresh
self.timeout = 0 self.timeout = 0
self.get_historical_rates()
def on_quotes(self): def on_quotes(self):
pass self.network.trigger_callback('on_quotes')
def on_history(self): def on_history(self):
pass self.network.trigger_callback('on_history')
@hook
def exchange_rate(self): def exchange_rate(self):
'''Returns None, or the exchange rate as a Decimal''' '''Returns None, or the exchange rate as a Decimal'''
rate = self.exchange.quotes.get(self.ccy) rate = self.exchange.quotes.get(self.ccy)
if rate: if rate:
return Decimal(rate) return Decimal(rate)
@hook
def format_amount_and_units(self, btc_balance): def format_amount_and_units(self, btc_balance):
rate = self.exchange_rate() rate = self.exchange_rate()
return '' if rate is None else "%s %s" % (self.value_str(btc_balance, rate), self.ccy) return '' if rate is None else "%s %s" % (self.value_str(btc_balance, rate), self.ccy)
@hook
def get_fiat_status_text(self, btc_balance): def get_fiat_status_text(self, btc_balance):
rate = self.exchange_rate() rate = self.exchange_rate()
return _(" (No FX rate available)") if rate is None else "1 BTC~%s %s" % (self.value_str(COIN, rate), self.ccy) return _(" (No FX rate available)") if rate is None else " 1 BTC~%s %s" % (self.value_str(COIN, rate), self.ccy)
def get_historical_rates(self):
if self.show_history():
self.exchange.get_historical_rates(self.ccy)
def requires_settings(self):
return True
@hook
def value_str(self, satoshis, rate): def value_str(self, satoshis, rate):
if satoshis is None: # Can happen with incomplete history if satoshis is None: # Can happen with incomplete history
return _("Unknown") return _("Unknown")
@ -392,7 +396,6 @@ class FxPlugin(BasePlugin, ThreadJob):
return "%s" % (self.ccy_amount_str(value, True)) return "%s" % (self.ccy_amount_str(value, True))
return _("No data") return _("No data")
@hook
def history_rate(self, d_t): def history_rate(self, d_t):
rate = self.exchange.historical_rate(self.ccy, d_t) rate = self.exchange.historical_rate(self.ccy, d_t)
# Frequently there is no rate for today, until tomorrow :) # Frequently there is no rate for today, until tomorrow :)
@ -402,7 +405,6 @@ class FxPlugin(BasePlugin, ThreadJob):
self.history_used_spot = True self.history_used_spot = True
return rate return rate
@hook
def historical_value_str(self, satoshis, d_t): def historical_value_str(self, satoshis, d_t):
rate = self.history_rate(d_t) rate = self.history_rate(d_t)
return self.value_str(satoshis, rate) return self.value_str(satoshis, rate)

3
lib/plugins.py

@ -63,6 +63,9 @@ class Plugins(DaemonThread):
def load_plugins(self): def load_plugins(self):
for loader, name, ispkg in pkgutil.iter_modules([self.pkgpath]): for loader, name, ispkg in pkgutil.iter_modules([self.pkgpath]):
# do not load deprecated plugins
if name in ['plot', 'exchange_rate']:
continue
m = loader.find_module(name).load_module(name) m = loader.find_module(name).load_module(name)
d = m.__dict__ d = m.__dict__
gui_good = self.gui_name in d.get('available_for', []) gui_good = self.gui_name in d.get('available_for', [])

5
plugins/exchange_rate/__init__.py

@ -1,5 +0,0 @@
from electrum.i18n import _
fullname = _("Exchange rates")
description = _("Exchange rates and currency conversion tools.")
available_for = ['qt','kivy']

54
plugins/exchange_rate/kivy.py

@ -1,54 +0,0 @@
from __future__ import absolute_import
from .exchange_rate import FxPlugin
from electrum.plugins import hook
from kivy.event import EventDispatcher
class MyEventDispatcher(EventDispatcher):
def __init__(self, **kwargs):
self.register_event_type('on_quotes')
self.register_event_type('on_history')
super(MyEventDispatcher, self).__init__(**kwargs)
def on_quotes(self, *args):
pass
def on_history(self, *args):
pass
class Plugin(FxPlugin):
def __init__(self, parent, config, name):
FxPlugin.__init__(self, parent, config, name)
self.dispatcher = MyEventDispatcher()
def on_quotes(self):
self.print_error("on_quotes", self.ccy)
self.dispatcher.dispatch('on_quotes')
def on_history(self):
self.print_error("on_history", self.ccy)
self.dispatcher.dispatch('on_history')
def on_close(self):
self.print_error("on close")
self.window.fiat_unit = ''
self.window.history_screen.update()
@hook
def init_kivy(self, window):
self.print_error("init_kivy")
self.window = window
self.dispatcher.bind(on_quotes=window.on_quotes)
self.dispatcher.bind(on_history=window.on_history)
self.window.fiat_unit = self.ccy
self.dispatcher.dispatch('on_history')
@hook
def load_wallet(self, wallet, window):
self.window = window
self.window.fiat_unit = self.ccy

227
plugins/exchange_rate/qt.py

@ -1,227 +0,0 @@
import time
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from electrum_gui.qt.util import *
from electrum_gui.qt.amountedit import AmountEdit
from electrum.bitcoin import COIN
from electrum.i18n import _
from decimal import Decimal
from functools import partial
from electrum.plugins import hook
from exchange_rate import FxPlugin
from electrum.util import timestamp_to_datetime
class Plugin(FxPlugin, QObject):
def __init__(self, parent, config, name):
FxPlugin.__init__(self, parent, config, name)
QObject.__init__(self)
def connect_fields(self, window, btc_e, fiat_e, fee_e):
def edit_changed(edit):
if edit.follows:
return
edit.setStyleSheet(BLACK_FG)
fiat_e.is_last_edited = (edit == fiat_e)
amount = edit.get_amount()
rate = self.exchange_rate()
if rate is None or amount is None:
if edit is fiat_e:
btc_e.setText("")
if fee_e:
fee_e.setText("")
else:
fiat_e.setText("")
else:
if edit is fiat_e:
btc_e.follows = True
btc_e.setAmount(int(amount / Decimal(rate) * COIN))
btc_e.setStyleSheet(BLUE_FG)
btc_e.follows = False
if fee_e:
window.update_fee()
else:
fiat_e.follows = True
fiat_e.setText(self.ccy_amount_str(
amount * Decimal(rate) / COIN, False))
fiat_e.setStyleSheet(BLUE_FG)
fiat_e.follows = False
btc_e.follows = False
fiat_e.follows = False
fiat_e.textChanged.connect(partial(edit_changed, fiat_e))
btc_e.textChanged.connect(partial(edit_changed, btc_e))
fiat_e.is_last_edited = False
@hook
def init_qt(self, gui):
for window in gui.windows:
self.on_new_window(window)
@hook
def do_clear(self, window):
window.fiat_send_e.setText('')
def on_close(self):
self.emit(SIGNAL('close_fx_plugin'))
def restore_window(self, window):
window.update_status()
window.history_list.refresh_headers()
window.fiat_send_e.hide()
window.fiat_receive_e.hide()
def on_quotes(self):
self.emit(SIGNAL('new_fx_quotes'))
def on_history(self):
self.emit(SIGNAL('new_fx_history'))
def on_fx_history(self, window):
'''Called when historical fx quotes are updated'''
window.history_list.update()
def on_fx_quotes(self, window):
'''Called when fresh spot fx quotes come in'''
window.update_status()
self.populate_ccy_combo()
# Refresh edits with the new rate
edit = window.fiat_send_e if window.fiat_send_e.is_last_edited else window.amount_e
edit.textEdited.emit(edit.text())
edit = window.fiat_receive_e if window.fiat_receive_e.is_last_edited else window.receive_amount_e
edit.textEdited.emit(edit.text())
# History tab needs updating if it used spot
if self.history_used_spot:
self.on_fx_history(window)
def on_ccy_combo_change(self):
'''Called when the chosen currency changes'''
ccy = str(self.ccy_combo.currentText())
if ccy and ccy != self.ccy:
self.set_currency(ccy)
self.hist_checkbox_update()
def hist_checkbox_update(self):
if self.hist_checkbox:
self.hist_checkbox.setEnabled(self.ccy in self.exchange.history_ccys())
self.hist_checkbox.setChecked(self.config_history())
def populate_ccy_combo(self):
# There should be at most one instance of the settings dialog
combo = self.ccy_combo
# NOTE: bool(combo) is False if it is empty. Nuts.
if combo is not None:
combo.blockSignals(True)
combo.clear()
combo.addItems(sorted(self.exchange.quotes.keys()))
combo.blockSignals(False)
combo.setCurrentIndex(combo.findText(self.ccy))
@hook
def on_new_window(self, window):
# Additional send and receive edit boxes
if not hasattr(window, 'fiat_send_e'):
send_e = AmountEdit(self.get_currency)
window.send_grid.addWidget(send_e, 4, 2, Qt.AlignLeft)
window.amount_e.frozen.connect(
lambda: send_e.setFrozen(window.amount_e.isReadOnly()))
receive_e = AmountEdit(self.get_currency)
window.receive_grid.addWidget(receive_e, 2, 2, Qt.AlignLeft)
window.fiat_send_e = send_e
window.fiat_receive_e = receive_e
self.connect_fields(window, window.amount_e, send_e, window.fee_e)
self.connect_fields(window, window.receive_amount_e, receive_e, None)
else:
window.fiat_send_e.show()
window.fiat_receive_e.show()
window.history_list.refresh_headers()
window.update_status()
window.connect(self, SIGNAL('new_fx_quotes'), lambda: self.on_fx_quotes(window))
window.connect(self, SIGNAL('new_fx_history'), lambda: self.on_fx_history(window))
window.connect(self, SIGNAL('close_fx_plugin'), lambda: self.restore_window(window))
window.connect(self, SIGNAL('refresh_headers'), window.history_list.refresh_headers)
def settings_widget(self, window):
return EnterButton(_('Settings'), partial(self.settings_dialog, window))
def settings_dialog(self, window):
d = WindowModalDialog(window, _("Exchange Rate Settings"))
layout = QGridLayout(d)
layout.addWidget(QLabel(_('Exchange rate API: ')), 0, 0)
layout.addWidget(QLabel(_('Currency: ')), 1, 0)
layout.addWidget(QLabel(_('History Rates: ')), 2, 0)
# Currency list
self.ccy_combo = QComboBox()
self.ccy_combo.currentIndexChanged.connect(self.on_ccy_combo_change)
self.populate_ccy_combo()
def on_change_ex(idx):
exchange = str(combo_ex.currentText())
if exchange != self.exchange.name():
self.set_exchange(exchange)
self.hist_checkbox_update()
def on_change_hist(checked):
if checked:
self.config.set_key('history_rates', 'checked')
self.get_historical_rates()
else:
self.config.set_key('history_rates', 'unchecked')
self.emit(SIGNAL('refresh_headers'))
def ok_clicked():
self.timeout = 0
self.ccy_combo = None
d.accept()
combo_ex = QComboBox()
combo_ex.addItems(sorted(self.exchanges.keys()))
combo_ex.setCurrentIndex(combo_ex.findText(self.config_exchange()))
combo_ex.currentIndexChanged.connect(on_change_ex)
self.hist_checkbox = QCheckBox()
self.hist_checkbox.stateChanged.connect(on_change_hist)
self.hist_checkbox_update()
ok_button = QPushButton(_("OK"))
ok_button.clicked.connect(lambda: ok_clicked())
layout.addWidget(self.ccy_combo,1,1)
layout.addWidget(combo_ex,0,1)
layout.addWidget(self.hist_checkbox,2,1)
layout.addWidget(ok_button,3,1)
return d.exec_()
def config_history(self):
return self.config.get('history_rates', 'unchecked') != 'unchecked'
def show_history(self):
return self.config_history() and self.ccy in self.exchange.history_ccys()
@hook
def history_tab_headers(self, headers):
if self.show_history():
headers.extend(['%s '%self.ccy + _('Amount'), '%s '%self.ccy + _('Balance')])
@hook
def history_tab_update_begin(self):
self.history_used_spot = False
@hook
def history_tab_update(self, tx, entry):
if not self.show_history():
return
tx_hash, height, conf, timestamp, value, balance = tx
if conf <= 0:
date = timestamp_to_datetime(time.time())
else:
date = timestamp_to_datetime(timestamp)
for amount in [value, balance]:
text = self.historical_value_str(amount, date)
entry.append(text)
Loading…
Cancel
Save