electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 8f98ea4acacfadb4d6188d503f5c8d9e6be86e1f
parent 89c277de9d70b29918b5c6c3596bf4486584adb2
Author: ThomasV <thomasv@gitorious>
Date:   Sat, 23 May 2015 10:38:19 +0200

make plugins available without the GUI

Diffstat:
Melectrum | 7++++---
Mgui/qt/main_window.py | 45++++++++++++++++++++++++++-------------------
Mlib/plugins.py | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mplugins/__init__.py | 107++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mplugins/audio_modem.py | 7-------
Mplugins/btchipwallet.py | 14+++-----------
Mplugins/cosigner_pool.py | 11-----------
Mplugins/exchange_rate.py | 35+++++++++++++++--------------------
Mplugins/greenaddress_instant.py | 8--------
Mplugins/labels.py | 6------
Mplugins/openalias.py | 6------
Mplugins/plot.py | 7-------
Mplugins/trezor.py | 14+++-----------
Mplugins/trustedcoin.py | 16++--------------
Mplugins/virtualkeyboard.py | 6------
15 files changed, 241 insertions(+), 157 deletions(-)

diff --git a/electrum b/electrum @@ -222,6 +222,10 @@ if __name__ == '__main__': else: cmd = args[0] + # initialize plugins. + # FIXME: check gui + init_plugins(config, is_bundle or is_local or is_android, cmd=='gui') + if cmd == 'gui': gui_name = config.get('gui', 'classic') if gui_name in ['lite', 'classic']: @@ -233,9 +237,6 @@ if __name__ == '__main__': sys.exit() #sys.exit("Error: Unknown GUI: " + gui_name ) - if gui_name=='qt': - init_plugins(config, is_bundle or is_local or is_android) - # network interface if not options.offline: s = get_daemon(config, False) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py @@ -2657,7 +2657,7 @@ class ElectrumWindow(QMainWindow): def plugins_dialog(self): - from electrum.plugins import plugins + from electrum.plugins import plugins, descriptions, is_available, loader self.pluginsdialog = d = QDialog(self) d.setWindowTitle(_('Electrum Plugins')) @@ -2680,37 +2680,44 @@ class ElectrumWindow(QMainWindow): grid.setColumnStretch(0,1) w.setLayout(grid) - def do_toggle(cb, p, w): - if p.is_enabled(): - if p.disable(): - p.close() + def do_toggle(cb, name, w): + p = plugins.get(name) + if p: + p.disable() + p.close() + plugins.pop(name) else: - if p.enable(): - p.load_wallet(self.wallet) - p.init_qt(self.gui_object) + module = loader(name) + plugins[name] = p = module.Plugin(self.config, name) + p.enable() + p.wallet = self.wallet + p.load_wallet(self.wallet) + p.init_qt(self.gui_object) r = p.is_enabled() cb.setChecked(r) if w: w.setEnabled(r) - def mk_toggle(cb, p, w): - return lambda: do_toggle(cb,p,w) + def mk_toggle(cb, name, w): + return lambda: do_toggle(cb, name, w) - for i, p in enumerate(plugins): + for i, descr in enumerate(descriptions): + name = descr['name'] + p = plugins.get(name) try: - cb = QCheckBox(p.fullname()) - cb.setDisabled(not p.is_available()) - cb.setChecked(p.is_enabled()) + cb = QCheckBox(descr['fullname']) + cb.setEnabled(is_available(name, self.wallet)) + cb.setChecked(p is not None) grid.addWidget(cb, i, 0) - if p.requires_settings(): + if p and p.requires_settings(): w = p.settings_widget(self) - w.setEnabled( p.is_enabled() ) + w.setEnabled(p.is_enabled()) grid.addWidget(w, i, 1) else: w = None - cb.clicked.connect(mk_toggle(cb,p,w)) - grid.addWidget(HelpButton(p.description()), i, 2) + cb.clicked.connect(mk_toggle(cb, name, w)) + grid.addWidget(HelpButton(descr['description']), i, 2) except Exception: - print_msg("Error: cannot display plugin", p) + print_msg("Error: cannot display plugin", name) traceback.print_exc(file=sys.stdout) grid.setRowStretch(i+1,1) vbox.addLayout(Buttons(CloseButton(d))) diff --git a/lib/plugins.py b/lib/plugins.py @@ -1,34 +1,89 @@ -from util import print_error -import traceback, sys +#!/usr/bin/env python +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2015 Thomas Voegtlin +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import traceback +import sys +import os +import imp +import pkgutil + from util import * from i18n import _ +from util import print_error -plugins = [] - - -def init_plugins(config, local): - import imp, pkgutil, __builtin__, os - global plugins +plugins = {} +descriptions = [] +loader = None - if local: +def is_available(name, w): + for d in descriptions: + if d.get('name') == name: + break + else: + return False + deps = d.get('requires', []) + for dep in deps: + try: + __import__(dep) + except ImportError: + return False + wallet_types = d.get('requires_wallet_type') + if wallet_types: + if w.wallet_type not in wallet_types: + return False + return True + + +def init_plugins(config, is_local, is_gui): + global plugins, descriptions, loader + if is_local: fp, pathname, description = imp.find_module('plugins') - plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])] - plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names) - imp.load_module('electrum_plugins', fp, pathname, description) - plugin_modules = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names) + electrum_plugins = imp.load_module('electrum_plugins', fp, pathname, description) + loader = lambda name: imp.load_source('electrum_plugins.' + name, os.path.join(pathname, name + '.py')) else: - import electrum_plugins - plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)] - plugin_modules = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names] + electrum_plugins = __import__('electrum_plugins') + loader = lambda name: __import__('electrum_plugins.' + name, fromlist=['electrum_plugins']) - for name, p in zip(plugin_names, plugin_modules): + def register_wallet_type(name): + # fixme: load plugins only if really needed + import wallet try: - plugins.append( p.Plugin(config, name) ) + p = loader(name) + plugins[name] = p.Plugin(config, name) + except: + return + x = plugins[name].get_wallet_type() + wallet.wallet_types.append(x) + + descriptions = electrum_plugins.descriptions + for item in descriptions: + name = item['name'] + if item.get('registers_wallet_type'): + register_wallet_type(name) + if not config.get('use_' + name): + continue + try: + p = loader(name) + plugins[name] = p.Plugin(config, name) except Exception: - print_msg(_("Error: cannot initialize plugin"),p) + print_msg(_("Error: cannot initialize plugin"), p) traceback.print_exc(file=sys.stdout) - hook_names = set() hooks = {} @@ -81,15 +136,17 @@ class BasePlugin: l.append((self, getattr(self, k))) hooks[k] = l - def fullname(self): - return self.name + def close(self): + # remove self from hooks + for k in dir(self): + if k in hook_names: + l = hooks.get(k, []) + l.remove((self, getattr(self, k))) + hooks[k] = l def print_error(self, *msg): print_error("[%s]"%self.name, *msg) - def description(self): - return 'undefined' - def requires_settings(self): return False @@ -111,8 +168,6 @@ class BasePlugin: #def init(self): pass - def close(self): pass - def is_enabled(self): return self.is_available() and self.config.get('use_'+self.name) is True diff --git a/plugins/__init__.py b/plugins/__init__.py @@ -1 +1,106 @@ -# plugins +#!/usr/bin/env python +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2015 Thomas Voegtlin +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +from electrum.i18n import _ + +descriptions = [ + { + 'name': 'audio_modem', + 'fullname': _('Audio MODEM'), + 'description': ('Provides support for air-gapped transaction signing.\n\n' + 'Requires http://github.com/romanz/amodem/'), + 'requires': ['amodem'], + 'GUI': ['qt'] + }, + { + 'name': 'btchipwallet', + 'fullname': _('BTChip Wallet'), + 'description': _('Provides support for BTChip hardware wallet') + '\n\n' + _('Requires github.com/btchip/btchip-python'), + 'requires': ['btchip'], + 'requires_wallet_type': ['btchip'], + 'registers_wallet_type': True + }, + { + 'name': 'cosigner_pool', + 'fullname': _('Cosigner Pool'), + 'description': ' '.join([ + _("This plugin facilitates the use of multi-signatures wallets."), + _("It sends and receives partially signed transactions from/to your cosigner wallet."), + _("Transactions are encrypted and stored on a remote server.") + ]), + 'GUI': ['qt'], + 'requires_wallet_type': ['2of2', '2of3'] + }, + { + 'name': 'exchange_rate', + 'fullname': _("Exchange rates"), + 'description': """exchange rates, retrieved from blockchain.info, CoinDesk, or Coinbase""" + }, + { + 'name': 'greenaddress_instant', + 'fullname': 'GreenAddress instant', + 'description': _("Allows validating if your transactions have instant confirmations by GreenAddress") + }, + { + 'name': 'labels', + 'fullname': _('LabelSync'), + 'description': '%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")) + }, + { + 'name': 'openalias', + 'fullname': 'OpenAlias', + 'description': 'Allow for payments to OpenAlias addresses.\nRequires dnspython', + 'requires': ['dns'] + }, + { + 'name': 'plot', + 'fullname': 'Plot History', + 'description': '\n'.join([ + _("Ability to plot transaction history in graphical mode."), + _("Warning: Requires matplotlib library.") + ]), + 'requires': ['matplotlib'], + 'GUI': ['qt'] + }, + { + 'name':'trezor', + 'fullname': 'Trezor Wallet', + 'description': 'Provides support for Trezor hardware wallet\n\nRequires github.com/trezor/python-trezor', + 'GUI': ['qt'], + 'requires': ['trezorlib'], + 'requires_wallet_type': ['trezor'], + 'registers_wallet_type': True + }, + { + 'name': 'trustedcoin', + 'fullname': _('Two Factor Authentication'), + 'description': ''.join([ + _("This plugin adds two-factor authentication to your wallet."), '<br/>', + _("For more information, visit"), + " <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>" + ]), + 'GUI': ['none', 'qt'], + 'requires_wallet_type': ['2fa'], + 'registers_wallet_type': True + }, + { + 'name': 'virtualkeyboard', + 'fullname': 'Virtual Keyboard', + 'description': '%s\n%s' % (_("Add an optional virtual keyboard to the password dialog."), _("Warning: do not use this if it makes you pick a weaker password.")), + } +] diff --git a/plugins/audio_modem.py b/plugins/audio_modem.py @@ -35,13 +35,6 @@ class Plugin(BasePlugin): 'Linux': 'libportaudio.so' }[platform.system()] - def fullname(self): - return 'Audio MODEM' - - def description(self): - return ('Provides support for air-gapped transaction signing.\n\n' - 'Requires http://github.com/romanz/amodem/') - def is_available(self): return amodem is not None diff --git a/plugins/btchipwallet.py b/plugins/btchipwallet.py @@ -35,18 +35,13 @@ except ImportError: class Plugin(BasePlugin): - def fullname(self): - return 'BTChip Wallet' - - def description(self): - return 'Provides support for BTChip hardware wallet\n\nRequires github.com/btchip/btchip-python' - def __init__(self, gui, name): BasePlugin.__init__(self, gui, name) self._is_available = self._init() self.wallet = None - if self._is_available: - electrum.wallet.wallet_types.append(('hardware', 'btchip', _("BTChip wallet"), BTChipWallet)) + + def get_wallet_type(self): + return ('hardware', 'btchip', _("BTChip wallet"), BTChipWallet) def _init(self): return BTCHIP @@ -70,9 +65,6 @@ class Plugin(BasePlugin): return False return True - def enable(self): - return BasePlugin.enable(self) - def btchip_is_connected(self): try: self.wallet.get_client().getFirmwareVersion() diff --git a/plugins/cosigner_pool.py b/plugins/cosigner_pool.py @@ -37,7 +37,6 @@ import traceback PORT = 12344 HOST = 'ecdsa.net' -description = _("This plugin facilitates the use of multi-signatures wallets. It sends and receives partially signed transactions from/to your cosigner wallet. Transactions are encrypted and stored on a remote server.") server = xmlrpclib.ServerProxy('http://%s:%d'%(HOST,PORT), allow_none=True) @@ -85,21 +84,11 @@ class Plugin(BasePlugin): wallet = None listener = None - def fullname(self): - return 'Cosigner Pool' - - def description(self): - return description - @hook def init_qt(self, gui): self.win = gui.main_window self.win.connect(self.win, SIGNAL('cosigner:receive'), self.on_receive) - def enable(self): - self.set_enabled(True) - return True - def is_available(self): if self.wallet is None: return True diff --git a/plugins/exchange_rate.py b/plugins/exchange_rate.py @@ -48,8 +48,8 @@ class Exchanger(threading.Thread): self.query_rates = threading.Event() self.use_exchange = self.parent.config.get('use_exchange', "Blockchain") self.parent.exchanges = EXCHANGES - self.parent.win.emit(SIGNAL("refresh_exchanges_combo()")) - self.parent.win.emit(SIGNAL("refresh_currencies_combo()")) + #self.parent.win.emit(SIGNAL("refresh_exchanges_combo()")) + #self.parent.win.emit(SIGNAL("refresh_currencies_combo()")) self.is_running = False def get_json(self, site, get_string): @@ -180,18 +180,14 @@ class Exchanger(threading.Thread): class Plugin(BasePlugin): - def fullname(self): - return "Exchange rates" - - def description(self): - return """exchange rates, retrieved from blockchain.info, CoinDesk, or Coinbase""" - - def __init__(self,a,b): BasePlugin.__init__(self,a,b) self.currencies = [self.fiat_unit()] self.exchanges = [self.config.get('use_exchange', "Blockchain")] - self.exchanger = None + # Do price discovery + self.exchanger = Exchanger(self) + self.exchanger.start() + self.win = None @hook def init_qt(self, gui): @@ -201,26 +197,25 @@ class Plugin(BasePlugin): self.btc_rate = Decimal("0.0") self.resp_hist = {} self.tx_list = {} - if self.exchanger is None: - # Do price discovery - self.exchanger = Exchanger(self) - self.exchanger.start() - self.gui.exchanger = self.exchanger # - self.add_send_edit() - self.add_receive_edit() - self.win.update_status() + self.gui.exchanger = self.exchanger # + self.add_send_edit() + self.add_receive_edit() + self.win.update_status() def close(self): + BasePlugin.close(self) self.exchanger.stop() self.exchanger = None + self.gui.exchanger = None self.send_fiat_e.hide() self.receive_fiat_e.hide() self.win.update_status() def set_currencies(self, currency_options): self.currencies = sorted(currency_options) - self.win.emit(SIGNAL("refresh_currencies()")) - self.win.emit(SIGNAL("refresh_currencies_combo()")) + if self.win: + self.win.emit(SIGNAL("refresh_currencies()")) + self.win.emit(SIGNAL("refresh_currencies_combo()")) @hook def get_fiat_balance_text(self, btc_balance, r): diff --git a/plugins/greenaddress_instant.py b/plugins/greenaddress_instant.py @@ -31,19 +31,11 @@ from electrum.i18n import _ from electrum.bitcoin import regenerate_key -description = _("Allows validating if your transactions have instant confirmations by GreenAddress") - class Plugin(BasePlugin): button_label = _("Verify GA instant") - def fullname(self): - return 'GreenAddress instant' - - def description(self): - return description - @hook def init_qt(self, gui): self.win = gui.main_window diff --git a/plugins/labels.py b/plugins/labels.py @@ -28,12 +28,6 @@ class Plugin(BasePlugin): target_host = 'sync.bytesized-hosting.com:9090' encode_password = None - def fullname(self): - return _('LabelSync') - - def description(self): - 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")) - def version(self): return "0.0.1" diff --git a/plugins/openalias.py b/plugins/openalias.py @@ -54,12 +54,6 @@ except ImportError: class Plugin(BasePlugin): - def fullname(self): - return 'OpenAlias' - - def description(self): - return 'Allow for payments to OpenAlias addresses.\nRequires dnspython' - def is_available(self): return OA_READY diff --git a/plugins/plot.py b/plugins/plot.py @@ -22,13 +22,6 @@ except: class Plugin(BasePlugin): - - def fullname(self): - return 'Plot History' - - def description(self): - return '%s\n%s' % (_("Ability to plot transaction history in graphical mode."), _("Warning: Requires matplotlib library.")) - def is_available(self): if flag_matlib: return True diff --git a/plugins/trezor.py b/plugins/trezor.py @@ -41,19 +41,14 @@ def give_error(message): class Plugin(BasePlugin): - def fullname(self): - return 'Trezor Wallet' - - def description(self): - return 'Provides support for Trezor hardware wallet\n\nRequires github.com/trezor/python-trezor' - def __init__(self, config, name): BasePlugin.__init__(self, config, name) self._is_available = self._init() self._requires_settings = True self.wallet = None - if self._is_available: - electrum.wallet.wallet_types.append(('hardware', 'trezor', _("Trezor wallet"), TrezorWallet)) + + def get_wallet_type(self): + return ('hardware', 'trezor', _("Trezor wallet"), TrezorWallet) def _init(self): return TREZOR @@ -80,9 +75,6 @@ class Plugin(BasePlugin): return False return True - def enable(self): - return BasePlugin.enable(self) - def trezor_is_connected(self): try: self.wallet.get_client().ping('t') diff --git a/plugins/trustedcoin.py b/plugins/trustedcoin.py @@ -210,17 +210,12 @@ class Plugin(BasePlugin): def __init__(self, x, y): BasePlugin.__init__(self, x, y) - electrum.wallet.wallet_types.append(('twofactor', '2fa', _("Wallet with two-factor authentication"), Wallet_2fa)) self.seed_func = lambda x: bitcoin.is_new_seed(x, SEED_PREFIX) self.billing_info = None self.is_billing = False - def fullname(self): - return 'Two Factor Authentication' - - def description(self): - return _("This plugin adds two-factor authentication to your wallet.") + '<br/>'\ - + _("For more information, visit") + " <a href=\"https://api.trustedcoin.com/#/electrum-help\">https://api.trustedcoin.com/#/electrum-help</a>" + def get_wallet_type(self): + return ('twofactor', '2fa', _("Wallet with two-factor authentication"), Wallet_2fa) def is_available(self): if not self.wallet: @@ -266,13 +261,6 @@ class Plugin(BasePlugin): address = public_key_to_bc_address( cK ) return address - def enable(self): - if self.is_enabled(): - self.window.show_message('Error: Two-factor authentication is already activated on this wallet') - return - self.set_enabled(True) - self.window.show_message('Two-factor authentication is enabled.') - def create_extended_seed(self, wallet, window): seed = wallet.make_seed() if not window.show_seed(seed, None): diff --git a/plugins/virtualkeyboard.py b/plugins/virtualkeyboard.py @@ -5,12 +5,6 @@ import random class Plugin(BasePlugin): - def fullname(self): - return 'Virtual Keyboard' - - def description(self): - return '%s\n%s' % (_("Add an optional virtual keyboard to the password dialog."), _("Warning: do not use this if it makes you pick a weaker password.")) - @hook def init_qt(self, gui): self.gui = gui