#!/usr/bin/python3 # This file is part of DarkFi (https://dark.fi) # # Copyright (C) 2020-2024 Dyne.org foundation # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import sys, toml, json, urwid, asyncio, logging import src.util from os.path import exists, join from pathlib import Path from src.model import Model from src.rpc import JsonRpc from src.view import View # TODO: update to latest urwid version. class Dnetview: def __init__(self): self.ev = asyncio.new_event_loop() asyncio.set_event_loop(self.ev) self.queue = asyncio.Queue() os = src.util.get_os() config_path = src.util.user_config_dir('darkfi', os) suffix = '.toml' filename = 'dnet_config' path = join(config_path, filename + suffix) self.config = src.util.spawn_config(path) self.model = Model() self.view = View(self.model) async def subscribe(self, rpc, node): name = node['name'] host = node['host'] port = node['port'] type = node['type'] info = {} await self.connect_loop(rpc, node, info) if type == 'NORMAL': await self.get_info(rpc, node, info) while True: await asyncio.sleep(0.01) data = await rpc.reader.readline() try: data = json.loads(data) info[name] = (type, data) await self.queue.put(info) except Exception as e: logging.debug(f'{name} RPC on port {port} disconnected {e}') # Attempt reconnection await self.connect_loop(rpc, node, info) await self.get_info(rpc, node, info) await rpc.dnet_switch(False) if type == 'LILITH': while True: await asyncio.sleep(1) try: await self.lilith_spawns(rpc, node, info) except Exception as e: logging.debug(f'{name} RPC on port {port} disconnected {e}') await self.connect_loop(rpc, node, info) await rpc.stop() async def lilith_spawns(self, rpc, node, info): name = node['name'] host = node['host'] port = node['port'] type = node['type'] data = await rpc._make_request('spawns', []) info[name] = (type, data) await self.queue.put(info) async def get_info(self, rpc, node, info): name = node['name'] host = node['host'] port = node['port'] type = node['type'] data = await rpc._make_request('p2p.get_info', []) info[name] = (type, data) await self.queue.put(info) await rpc.dnet_switch(True) await rpc.dnet_subscribe_events() async def connect_loop(self, rpc, node, info): name = node['name'] host = node['host'] port = node['port'] type = node['type'] while True: try: await rpc.start(host, port) logging.debug(f'Started {host} RPC on port {port}') break except Exception as e: logging.debug(f'Cannot connect to {host} RPC on port {port}') info[name] = (type, {}) await self.queue.put(info) # Sleep for 2s before trying again await asyncio.sleep(2) continue async def start_connect_slots(self, nodes): tasks = [] async with asyncio.TaskGroup() as tg: for i, node in enumerate(nodes): rpc = JsonRpc() subscribe = tg.create_task(self.subscribe( rpc, node)) nodes = tg.create_task(self.update_info()) async def update_info(self): while True: info = {} data = await self.queue.get() # We parse in this manner so we can differentiate between # normal nodes and lilith nodes when they are offline. type = list(data.values())[0][0] values = list(data.values())[0][1] key = list(data.keys())[0] info[key] = values if not values and type == 'LILITH': self.model.add_offline(info, True) if not values and type == 'NORMAL': self.model.add_offline(info, False) if 'result' in values: result = values.get('result') if 'spawns' in result: self.model.add_lilith(info) if 'channels' in result: self.model.add_node(info) if 'params' in values: self.model.add_event(info) self.queue.task_done() def main(self): logging.basicConfig(filename='dnet.log', encoding='utf-8', level=logging.DEBUG) nodes = self.config.get('nodes') loop = urwid.MainLoop(self.view.ui, self.view.palette, unhandled_input=self.unhandled_input, event_loop=urwid.AsyncioEventLoop( loop=self.ev)) self.ev.create_task(self.start_connect_slots(nodes)) self.ev.create_task(self.view.update_view(self.ev, loop)) loop.run() def unhandled_input(self, key): if isinstance(key, tuple): return if key in ('q'): for task in asyncio.all_tasks(): task.cancel() raise urwid.ExitMainLoop() if __name__ == '__main__': dnet = Dnetview() dnet.main()