commit 8f98ea4acacfadb4d6188d503f5c8d9e6be86e1f
parent 89c277de9d70b29918b5c6c3596bf4486584adb2
Author: ThomasV <thomasv@gitorious>
Date: Sat, 23 May 2015 10:38:19 +0200
make plugins available without the GUI
Diffstat:
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