From 4682d95a76459a5fe837630f83f60653c3f32c5a Mon Sep 17 00:00:00 2001 From: ThomasV Date: Mon, 30 Nov 2015 10:09:54 +0100 Subject: [PATCH] merge jsonrpc gui and daemon --- electrum | 151 +++++++--------------------------------------- gui/jsonrpc.py | 72 ---------------------- lib/daemon.py | 158 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/wallet.py | 4 +- 4 files changed, 181 insertions(+), 204 deletions(-) delete mode 100644 gui/jsonrpc.py create mode 100644 lib/daemon.py diff --git a/electrum b/electrum index 0b5cc5d15..b151dd733 100755 --- a/electrum +++ b/electrum @@ -81,6 +81,7 @@ from electrum import SimpleConfig, Network, Wallet, WalletStorage from electrum.util import print_msg, print_error, print_stderr, json_encode, json_decode, set_verbosity, InvalidPassword from electrum.plugins import Plugins, run_hook, always_hook from electrum.commands import get_parser, known_commands, Commands, config_variables +from electrum.daemon import Daemon, get_daemon # get password routine @@ -233,138 +234,20 @@ def init_cmdline(config): return cmd, password -def run_command(config, cmd, network, wallet, password): - if wallet and network: - wallet.wait_until_synchronized() +def run_offline_command(config, cmd, wallet, password): # arguments passed to function args = map(lambda x: config.get(x), cmd.params) # decode json arguments args = map(json_decode, args) # options args += map(lambda x: config.get(x), cmd.options) - cmd_runner = Commands(config, wallet, network) + cmd_runner = Commands(config, wallet, None) cmd_runner.password = password func = getattr(cmd_runner, cmd.name) result = func(*args) return result -class ClientThread(util.DaemonThread): - - def __init__(self, server, s): - util.DaemonThread.__init__(self) - self.server = server - self.client_pipe = util.SocketPipe(s) - self.network = self.server.network - - def run(self): - config_options = self.client_pipe.get() - password = config_options.get('password') - config = SimpleConfig(config_options) - cmd = config.get('cmd') - if cmd == 'gui': - if self.server.gui: - if hasattr(server.gui, 'new_window'): - path = config.get_wallet_path() - self.server.gui.new_window(path, config.get('url')) - response = "ok" - else: - response = "error: current GUI does not support multiple windows" - else: - response = "Error: Electrum is running in daemon mode. Please stop the daemon first." - elif cmd == 'daemon': - sub = config.get('subcommand') - assert sub in ['start', 'stop', 'status'] - if sub == 'start': - response = "Daemon already running" - elif sub == 'status': - p = self.network.get_parameters() - response = { - 'path': self.network.config.path, - 'server': p[0], - 'blockchain_height': self.network.get_local_height(), - 'server_height': self.network.get_server_height(), - 'nodes': self.network.get_interfaces(), - 'connected': self.network.is_connected(), - 'auto_connect': p[4], - 'wallets': self.server.wallets.keys(), - } - elif sub == 'stop': - self.server.stop() - response = "Daemon stopped" - else: - c = known_commands[cmd] - wallet = self.server.load_wallet(config) if c.requires_wallet else None - try: - response = run_command(config, c, self.network, wallet, password) - except BaseException as e: - err = traceback.format_exc() - response = {'error':err} - # send response and exit - self.client_pipe.send(response) - - - - -class NetworkServer(util.DaemonThread): - - def __init__(self, config, network): - util.DaemonThread.__init__(self) - self.debug = False - self.config = config - self.pipe = util.QueuePipe() - self.network = network - self.lock = threading.RLock() - # gui is None is we run as daemon - self.gui = None - self.wallets = {} - - def load_wallet(self, config): - path = config.get_wallet_path() - if path in self.wallets: - wallet = self.wallets[path] - else: - storage = WalletStorage(path) - wallet = Wallet(storage) - wallet.start_threads(self.network) - self.wallets[path] = wallet - return wallet - - def run(self): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind(('', 0)) - lockfile = os.path.join(self.config.path, 'lock') - with open(lockfile, 'w') as f: - f.write("%d"%s.getsockname()[1]) - s.listen(5) - s.settimeout(0.1) - while self.is_running(): - try: - connection, address = s.accept() - except socket.timeout: - continue - client = ClientThread(self, connection) - client.start() - print_error("Daemon exiting") - - def stop(self): - for k, wallet in self.wallets.items(): - wallet.stop_threads() - util.DaemonThread.stop(self) - - -def get_daemon(config): - lockfile = os.path.join(config.path, 'lock') - try: - with open(lockfile) as f: - num = int(f.read()) - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect(('', num)) - return s - except: - return False - - if __name__ == '__main__': # make sure that certificates are here @@ -441,20 +324,23 @@ if __name__ == '__main__': wallet = Wallet(storage) else: wallet = None - result = run_command(config, cmd, None, wallet, password) + result = run_offline_command(config, cmd, wallet, password) print_msg(json_encode(result)) sys.exit(0) else: config_options['password'] = password - # check if daemon is running - s = get_daemon(config) - if s: - p = util.SocketPipe(s) - p.set_timeout(1000000) - p.send(config_options) - result = p.get() - s.close() + server = get_daemon(config) + + # daemon is running + if server is not None: + cmdname = config_options.get('cmd') + if cmdname == 'daemon': + result = server.daemon(config_options) + elif cmdname == 'gui': + result = server.gui(config_options) + else: + result = server.run_cmdline(config_options) if type(result) in [str, unicode]: print_msg(result) elif type(result) is dict and result.get('error'): @@ -470,10 +356,13 @@ if __name__ == '__main__': network.start() else: network = None - server = NetworkServer(config, network) + server = Daemon(config, network) server.start() server.gui = init_gui(config, network, plugins) server.gui.main() + server.stop() + sys.exit(0) + elif cmd_name == 'daemon': subcommand = config.get('subcommand') if subcommand in ['status', 'stop']: @@ -484,7 +373,7 @@ if __name__ == '__main__': if p == 0: network = Network(config, plugins) network.start() - server = NetworkServer(config, network) + server = Daemon(config, network) if config.get('websocket_server'): from electrum import websockets websockets.WebSocketServer(config, network).start() diff --git a/gui/jsonrpc.py b/gui/jsonrpc.py deleted file mode 100644 index ec9b4c607..000000000 --- a/gui/jsonrpc.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/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 . - - -""" -jsonrpc interface for webservers. -may be called from your php script. -""" - -import socket, os -from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer, SimpleJSONRPCRequestHandler - -from electrum.wallet import WalletStorage, Wallet -from electrum.commands import known_commands, Commands - - -class RequestHandler(SimpleJSONRPCRequestHandler): - - def do_OPTIONS(self): - self.send_response(200) - self.end_headers() - - def end_headers(self): - self.send_header("Access-Control-Allow-Headers", - "Origin, X-Requested-With, Content-Type, Accept") - self.send_header("Access-Control-Allow-Origin", "*") - SimpleJSONRPCRequestHandler.end_headers(self) - - - -class ElectrumGui: - - def __init__(self, config, network, plugins): - self.network = network - self.config = config - storage = WalletStorage(self.config.get_wallet_path()) - if not storage.file_exists: - raise BaseException("Wallet not found") - self.wallet = Wallet(storage) - self.cmd_runner = Commands(self.config, self.wallet, self.network) - host = config.get('rpchost', 'localhost') - port = config.get('rpcport', 7777) - self.server = SimpleJSONRPCServer((host, port), requestHandler=RequestHandler) - self.server.socket.settimeout(1) - for cmdname in known_commands: - self.server.register_function(getattr(self.cmd_runner, cmdname), cmdname) - - def main(self): - self.wallet.start_threads(self.network) - while True: - try: - self.server.handle_request() - except socket.timeout: - continue - except: - break - self.wallet.stop_threads() diff --git a/lib/daemon.py b/lib/daemon.py new file mode 100644 index 000000000..4dbcebd2f --- /dev/null +++ b/lib/daemon.py @@ -0,0 +1,158 @@ +#!/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 . + +import socket, os +import jsonrpclib +from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer, SimpleJSONRPCRequestHandler + +import util +from util import print_msg, print_error, print_stderr, json_encode, json_decode, set_verbosity, InvalidPassword +from wallet import WalletStorage, Wallet +from commands import known_commands, Commands +from simple_config import SimpleConfig +from network import Network + + +def get_daemon(config): + host = config.get('rpchost', 'localhost') + port = config.get('rpcport', 7777) + server = jsonrpclib.Server('http://%s:%d' % (host, port)) + # check if daemon is running + try: + server.ping() + return server + except: + pass + + +class RequestHandler(SimpleJSONRPCRequestHandler): + + def do_OPTIONS(self): + self.send_response(200) + self.end_headers() + + def end_headers(self): + self.send_header("Access-Control-Allow-Headers", + "Origin, X-Requested-With, Content-Type, Accept") + self.send_header("Access-Control-Allow-Origin", "*") + SimpleJSONRPCRequestHandler.end_headers(self) + + + +class Daemon(util.DaemonThread): + + def __init__(self, config, network): + util.DaemonThread.__init__(self) + self.config = config + self.network = network + self.wallets = {} + self.wallet = self.load_wallet(config) + self.cmd_runner = Commands(self.config, self.wallet, self.network) + host = config.get('rpchost', 'localhost') + port = config.get('rpcport', 7777) + self.server = SimpleJSONRPCServer((host, port), requestHandler=RequestHandler, logRequests=False) + self.server.socket.settimeout(1) + for cmdname in known_commands: + self.server.register_function(getattr(self.cmd_runner, cmdname), cmdname) + self.server.register_function(self.run_cmdline, 'run_cmdline') + self.server.register_function(self.ping, 'ping') + self.server.register_function(self.daemon, 'daemon') + self.server.register_function(self.gui, 'gui') + + def ping(self): + return True + + def daemon(self, config): + sub = config.get('subcommand') + assert sub in ['start', 'stop', 'status'] + if sub == 'start': + response = "Daemon already running" + elif sub == 'status': + p = self.network.get_parameters() + response = { + 'path': self.network.config.path, + 'server': p[0], + 'blockchain_height': self.network.get_local_height(), + 'server_height': self.network.get_server_height(), + 'nodes': self.network.get_interfaces(), + 'connected': self.network.is_connected(), + 'auto_connect': p[4], + 'wallets': self.wallets.keys(), + } + elif sub == 'stop': + self.stop() + response = "Daemon stopped" + return response + + def gui(self, config_options): + config = SimpleConfig(config_options) + if self.gui: + if hasattr(self.gui, 'new_window'): + path = config.get_wallet_path() + self.gui.new_window(path, config.get('url')) + response = "ok" + else: + response = "error: current GUI does not support multiple windows" + else: + response = "Error: Electrum is running in daemon mode. Please stop the daemon first." + return response + + def load_wallet(self, config): + path = config.get_wallet_path() + if path in self.wallets: + wallet = self.wallets[path] + else: + storage = WalletStorage(path) + wallet = Wallet(storage) + wallet.start_threads(self.network) + self.wallets[path] = wallet + return wallet + + def run_cmdline(self, config_options): + password = config_options.get('password') + config = SimpleConfig(config_options) + cmdname = config.get('cmd') + cmd = known_commands[cmdname] + wallet = self.load_wallet(config) if cmd.requires_wallet else None + if wallet: + wallet.wait_until_synchronized() + # arguments passed to function + args = map(lambda x: config.get(x), cmd.params) + # decode json arguments + args = map(json_decode, args) + # options + args += map(lambda x: config.get(x), cmd.options) + cmd_runner = Commands(config, wallet, self.network) + cmd_runner.password = password + func = getattr(cmd_runner, cmd.name) + result = func(*args) + return result + + def run(self): + while self.is_running(): + try: + self.server.handle_request() + except socket.timeout: + continue + except: + break + + def stop(self): + for k, wallet in self.wallets.items(): + wallet.stop_threads() + util.DaemonThread.stop(self) diff --git a/lib/wallet.py b/lib/wallet.py index 563c3291e..ed60220e6 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -118,7 +118,9 @@ class WalletStorage(PrintError): self.write() def write(self): - assert not threading.currentThread().isDaemon() + if threading.currentThread().isDaemon(): + self.print_error('warning: daemon thread cannot write wallet') + return if not self.modified: return s = json.dumps(self.data, indent=4, sort_keys=True)