10 changed files with 332 additions and 258 deletions
@ -0,0 +1,187 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
# |
||||||
|
# Electrum - lightweight Bitcoin client |
||||||
|
# Copyright (C) 2014 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 socket |
||||||
|
import time |
||||||
|
import sys |
||||||
|
import os |
||||||
|
import threading |
||||||
|
import traceback |
||||||
|
import json |
||||||
|
import Queue |
||||||
|
from network import Network |
||||||
|
from util import print_error, print_stderr |
||||||
|
from simple_config import SimpleConfig |
||||||
|
|
||||||
|
from daemon import parse_json, NetworkServer, DAEMON_PORT |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkProxy(threading.Thread): |
||||||
|
|
||||||
|
def __init__(self, socket, config=None): |
||||||
|
if config is None: |
||||||
|
config = {} # Do not use mutables as default arguments! |
||||||
|
threading.Thread.__init__(self) |
||||||
|
self.config = SimpleConfig(config) if type(config) == type({}) else config |
||||||
|
self.socket = socket |
||||||
|
self.socket.settimeout(0.1) |
||||||
|
self.message_id = 0 |
||||||
|
self.unanswered_requests = {} |
||||||
|
self.subscriptions = {} |
||||||
|
self.debug = False |
||||||
|
self.lock = threading.Lock() |
||||||
|
self.pending_transactions_for_notifications = [] |
||||||
|
self.banner = '' |
||||||
|
self.callbacks = {} |
||||||
|
self.running = True |
||||||
|
self.daemon = True |
||||||
|
|
||||||
|
def is_running(self): |
||||||
|
return self.running |
||||||
|
|
||||||
|
def run(self): |
||||||
|
# read responses and trigger callbacks |
||||||
|
message = '' |
||||||
|
while self.is_running(): |
||||||
|
try: |
||||||
|
data = self.socket.recv(1024) |
||||||
|
except socket.timeout: |
||||||
|
continue |
||||||
|
except: |
||||||
|
data = '' |
||||||
|
if not data: |
||||||
|
break |
||||||
|
message += data |
||||||
|
while True: |
||||||
|
response, message = parse_json(message) |
||||||
|
if response is not None: |
||||||
|
self.process(response) |
||||||
|
else: |
||||||
|
break |
||||||
|
# fixme: server does not detect if we don't call shutdown |
||||||
|
self.socket.shutdown(2) |
||||||
|
self.socket.close() |
||||||
|
print_error("NetworkProxy thread terminating") |
||||||
|
|
||||||
|
def process(self, response): |
||||||
|
if self.debug: |
||||||
|
print_error("<--", response) |
||||||
|
|
||||||
|
if response.get('method') == 'network.subscribe': |
||||||
|
status = response.get('status') |
||||||
|
self.trigger_callback(status) |
||||||
|
return |
||||||
|
|
||||||
|
msg_id = response.get('id') |
||||||
|
with self.lock: |
||||||
|
method, params, callback = self.unanswered_requests.pop(msg_id) |
||||||
|
|
||||||
|
result = response.get('result') |
||||||
|
callback(None, {'method':method, 'params':params, 'result':result, 'id':msg_id}) |
||||||
|
|
||||||
|
|
||||||
|
def subscribe(self, messages, callback): |
||||||
|
# detect if it is a subscription |
||||||
|
with self.lock: |
||||||
|
if self.subscriptions.get(callback) is None: |
||||||
|
self.subscriptions[callback] = [] |
||||||
|
for message in messages: |
||||||
|
if message not in self.subscriptions[callback]: |
||||||
|
self.subscriptions[callback].append(message) |
||||||
|
|
||||||
|
self.send( messages, callback ) |
||||||
|
|
||||||
|
|
||||||
|
def send(self, messages, callback): |
||||||
|
"""return the ids of the requests that we sent""" |
||||||
|
with self.lock: |
||||||
|
out = '' |
||||||
|
ids = [] |
||||||
|
for m in messages: |
||||||
|
method, params = m |
||||||
|
request = json.dumps( { 'id':self.message_id, 'method':method, 'params':params } ) |
||||||
|
self.unanswered_requests[self.message_id] = method, params, callback |
||||||
|
ids.append(self.message_id) |
||||||
|
if self.debug: |
||||||
|
print_error("-->", request) |
||||||
|
self.message_id += 1 |
||||||
|
out += request + '\n' |
||||||
|
while out: |
||||||
|
sent = self.socket.send( out ) |
||||||
|
out = out[sent:] |
||||||
|
return ids |
||||||
|
|
||||||
|
|
||||||
|
def synchronous_get(self, requests, timeout=100000000): |
||||||
|
queue = Queue.Queue() |
||||||
|
ids = self.send(requests, lambda i,x: queue.put(x)) |
||||||
|
id2 = ids[:] |
||||||
|
res = {} |
||||||
|
while ids: |
||||||
|
r = queue.get(True, timeout) |
||||||
|
_id = r.get('id') |
||||||
|
if _id in ids: |
||||||
|
ids.remove(_id) |
||||||
|
res[_id] = r.get('result') |
||||||
|
else: |
||||||
|
raise |
||||||
|
out = [] |
||||||
|
for _id in id2: |
||||||
|
out.append(res[_id]) |
||||||
|
return out |
||||||
|
|
||||||
|
|
||||||
|
def get_servers(self): |
||||||
|
return self.synchronous_get([('network.get_servers',[])])[0] |
||||||
|
|
||||||
|
def get_header(self, height): |
||||||
|
return self.synchronous_get([('network.get_header',[height])])[0] |
||||||
|
|
||||||
|
def get_local_height(self): |
||||||
|
return self.synchronous_get([('network.get_local_height',[])])[0] |
||||||
|
|
||||||
|
def is_connected(self): |
||||||
|
return self.synchronous_get([('network.is_connected',[])])[0] |
||||||
|
|
||||||
|
def is_up_to_date(self): |
||||||
|
return self.synchronous_get([('network.is_up_to_date',[])])[0] |
||||||
|
|
||||||
|
def main_server(self): |
||||||
|
return self.synchronous_get([('network.main_server',[])])[0] |
||||||
|
|
||||||
|
def stop(self): |
||||||
|
self.running = False |
||||||
|
|
||||||
|
|
||||||
|
def register_callback(self, event, callback): |
||||||
|
with self.lock: |
||||||
|
if not self.callbacks.get(event): |
||||||
|
self.callbacks[event] = [] |
||||||
|
self.callbacks[event].append(callback) |
||||||
|
|
||||||
|
|
||||||
|
def trigger_callback(self, event): |
||||||
|
with self.lock: |
||||||
|
callbacks = self.callbacks.get(event,[])[:] |
||||||
|
if callbacks: |
||||||
|
[callback() for callback in callbacks] |
||||||
|
|
||||||
|
print_error("trigger_callback", event, len(callbacks)) |
||||||
Loading…
Reference in new issue