make plugins available without the GUI

This commit is contained in:
ThomasV 2015-05-23 10:38:19 +02:00
parent 89c277de9d
commit 8f98ea4aca
15 changed files with 244 additions and 160 deletions

View File

@ -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)

View File

@ -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)))

View File

@ -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 = []
plugins = {}
descriptions = []
loader = None
def init_plugins(config, local):
import imp, pkgutil, __builtin__, os
global plugins
if 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)
def is_available(name, w):
for d in descriptions:
if d.get('name') == name:
break
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]
for name, p in zip(plugin_names, plugin_modules):
return False
deps = d.get('requires', [])
for dep in deps:
try:
plugins.append( p.Plugin(config, name) )
except Exception:
print_msg(_("Error: cannot initialize plugin"),p)
traceback.print_exc(file=sys.stdout)
__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')
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:
electrum_plugins = __import__('electrum_plugins')
loader = lambda name: __import__('electrum_plugins.' + name, fromlist=['electrum_plugins'])
def register_wallet_type(name):
# fixme: load plugins only if really needed
import wallet
try:
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)
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

View File

@ -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.")),
}
]

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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):

View File

@ -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