diff --git a/res/ui/community_cfg.ui b/res/ui/community_cfg.ui index 9c17e89ff35b30851b3d74b4c1aa6d76966ed9eb..0d18585d3a33202199ab2f5ae604aa3032e3099a 100644 --- a/res/ui/community_cfg.ui +++ b/res/ui/community_cfg.ui @@ -75,7 +75,31 @@ </layout> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"/> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="topMargin"> + <number>6</number> + </property> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="button_checknode"> + <property name="text"> + <string>Check node connectivity</string> + </property> + </widget> + </item> + </layout> </item> <item> <spacer name="verticalSpacer"> diff --git a/src/cutecoin/core/__init__.py b/src/cutecoin/core/__init__.py index cfb1a39923b9f777837050d8cc56de2cbe9207d6..6de4a62c9259773dd9a301d9f7e5b616c7328489 100644 --- a/src/cutecoin/core/__init__.py +++ b/src/cutecoin/core/__init__.py @@ -1,3 +1,4 @@ from .community import Community from .wallet import Wallet -from .account import Account \ No newline at end of file +from .account import Account +from .app import Application \ No newline at end of file diff --git a/src/cutecoin/core/account.py b/src/cutecoin/core/account.py index e8b0152d1f9ddbd15e24b77becd66dd242f54e1f..cd4151d409e528aee7357818a64e59e4033ebbe1 100644 --- a/src/cutecoin/core/account.py +++ b/src/cutecoin/core/account.py @@ -125,7 +125,7 @@ class Account(QObject): wallets_changed = pyqtSignal() membership_broadcasted = pyqtSignal() certification_broadcasted = pyqtSignal() - selfcert_broadcased = pyqtSignal() + selfcert_broadcasted = pyqtSignal() revoke_broadcasted = pyqtSignal() broadcast_error = pyqtSignal(int, str) diff --git a/src/cutecoin/core/app.py b/src/cutecoin/core/app.py index 75f7961242e97649a5933e46069c8794fae82bce..10c6e59881903ea73505b6ea4d2b3ee8ce2ef0d3 100644 --- a/src/cutecoin/core/app.py +++ b/src/cutecoin/core/app.py @@ -100,6 +100,10 @@ class Application(QObject): def identities_registry(self): return self._identities_registry + @property + def network_manager(self): + return self._network_manager + def add_account(self, account): self.accounts[account.name] = account diff --git a/src/cutecoin/core/community.py b/src/cutecoin/core/community.py index 23409733a5db1285e368547be381fdf099a628f2..2bbd110518b474f4c2758e22256a491c8abbc03f 100644 --- a/src/cutecoin/core/community.py +++ b/src/cutecoin/core/community.py @@ -46,13 +46,13 @@ class Community(QObject): self._bma_access = bma_access @classmethod - def create(cls, node): + def create(cls, network_manager, node): """ Create a community from its first node. :param node: The first Node of the community """ - network = Network.create(node) + network = Network.create(network_manager, node) bma_access = BmaAccess.create(network) community = cls(node.currency, network, bma_access) logging.debug("Creating community") diff --git a/src/cutecoin/core/net/__init__.py b/src/cutecoin/core/net/__init__.py index 39ab2a0b56350baad834cb7fb0cfecb8223e1fcd..6ea6364fb9f0854431d8352f088cf781026b1a1d 100644 --- a/src/cutecoin/core/net/__init__.py +++ b/src/cutecoin/core/net/__init__.py @@ -1 +1,2 @@ -__author__ = 'inso' +from .node import Node +from .network import Network \ No newline at end of file diff --git a/src/cutecoin/core/net/node.py b/src/cutecoin/core/net/node.py index ef8bd443286882e96801d53c3b6ffaf79d81d08e..a6803df3f07857b047f96393fa3ae6c981f0ba97 100644 --- a/src/cutecoin/core/net/node.py +++ b/src/cutecoin/core/net/node.py @@ -5,16 +5,13 @@ Created on 21 févr. 2015 """ from ucoinpy.documents.peer import Peer, BMAEndpoint, Endpoint -from requests.exceptions import RequestException, ConnectionError -from cutecoin.tools.exceptions import InvalidNodeCurrency, LookupFailureError -from ..registry import IdentitiesRegistry -from cutecoin.core.net.api import bma as qtbma -from cutecoin.core.net.api.bma import ConnectionHandler +from ...tools.exceptions import InvalidNodeCurrency +from ..net.api import bma as qtbma +from ..net.api.bma import ConnectionHandler +import asyncio import logging import time -import ctypes -import sys import json from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot @@ -58,6 +55,7 @@ class Node(QObject): self._version = version @classmethod + @asyncio.coroutine def from_address(cls, network_manager, currency, address, port): """ Factory method to get a node from a given address @@ -67,20 +65,36 @@ class Node(QObject): :param str address: The node address :param int port: The node port """ - peer_data = qtbma.network.Peering(ConnectionHandler(network_manager, address, port)).get() - - peer = Peer.from_signed_raw("{0}{1}\n".format(peer_data['raw'], - peer_data['signature'])) - - if currency is not None: - if peer.currency != currency: - raise InvalidNodeCurrency(peer.currency, currency) - - node = cls(network_manager, peer.currency, peer.endpoints, - "", peer.pubkey, 0, Node.ONLINE, time.time(), - {'root': "", 'leaves': []}) - logging.debug("Node from address : {:}".format(str(node))) - return node + def handle_reply(reply): + if reply.error() == QNetworkReply.NoError: + strdata = bytes(reply.readAll()).decode('utf-8') + nonlocal peer_data + peer_data = json.loads(strdata) + future_reply.set_result(True) + else: + future_reply.set_result(False) + + future_reply = asyncio.Future() + peer_data = {} + reply = qtbma.network.Peering(ConnectionHandler(network_manager, address, port)).get() + reply.finished.connect(lambda: handle_reply(reply)) + + yield from future_reply + if future_reply.result(): + peer = Peer.from_signed_raw("{0}{1}\n".format(peer_data['raw'], + peer_data['signature'])) + + if currency is not None: + if peer.currency != currency: + raise InvalidNodeCurrency(peer.currency, currency) + + node = cls(network_manager, peer.currency, peer.endpoints, + "", peer.pubkey, 0, Node.ONLINE, time.time(), + {'root': "", 'leaves': []}, "", "") + logging.debug("Node from address : {:}".format(str(node))) + return node + else: + raise @classmethod def from_peer(cls, network_manager, currency, peer): diff --git a/src/cutecoin/gui/community_tab.py b/src/cutecoin/gui/community_tab.py index 1096583e991852deecf572c9bccabdcc47afb8cd..8869848572194e5aecf19d9bd0ee0b93d9cbd898 100644 --- a/src/cutecoin/gui/community_tab.py +++ b/src/cutecoin/gui/community_tab.py @@ -73,7 +73,7 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): self.account.revoke_broadcasted.connect(lambda: toast.display(self.tr("Revoke"), self.tr("Success sending Revoke demand"))) - self.account.selfcert_broadcased.connect(lambda: + self.account.selfcert_broadcasted.connect(lambda: toast.display(self.tr("Self Certification"), self.tr("Success sending Self Certification document"))) self.refresh_quality_buttons() diff --git a/src/cutecoin/gui/process_cfg_account.py b/src/cutecoin/gui/process_cfg_account.py index a13516f607710098b66ddb82d1f0e451b8b20794..5906db17f4bd003e747350dd19ddf6fcae45baef 100644 --- a/src/cutecoin/gui/process_cfg_account.py +++ b/src/cutecoin/gui/process_cfg_account.py @@ -174,7 +174,8 @@ class ProcessConfigureAccount(QDialog, Ui_AccountConfigurationDialog): def open_process_add_community(self): logging.debug("Opening configure community dialog") logging.debug(self.password_asker) - dialog = ProcessConfigureCommunity(self.account, None, + dialog = ProcessConfigureCommunity(self.app, + self.account, None, self.password_asker) dialog.accepted.connect(self.action_add_community) dialog.exec_() @@ -215,16 +216,7 @@ class ProcessConfigureAccount(QDialog, Ui_AccountConfigurationDialog): def open_process_edit_community(self, index): community = self.account.communities[index.row()] - try: - dialog = ProcessConfigureCommunity(self.account, community, self.password_asker) - except NoPeerAvailable as e: - QMessageBox.critical(self, self.tr("Error"), - str(e), QMessageBox.Ok) - return - except requests.exceptions.RequestException as e: - QMessageBox.critical(self, self.tr("Error"), - str(e), QMessageBox.Ok) - return + dialog = ProcessConfigureCommunity(self.app, self.account, community, self.password_asker) dialog.accepted.connect(self.action_edit_community) dialog.exec_() diff --git a/src/cutecoin/gui/process_cfg_community.py b/src/cutecoin/gui/process_cfg_community.py index 05e16fdc3b9fa7e9dd5107476ebf7b32e95234c9..6ebd82b5f0d38bd4426085df9d0865275a33cad4 100644 --- a/src/cutecoin/gui/process_cfg_community.py +++ b/src/cutecoin/gui/process_cfg_community.py @@ -5,16 +5,18 @@ Created on 8 mars 2014 """ import logging -import requests +import asyncio -from PyQt5.QtWidgets import QDialog, QMenu, QMessageBox +from PyQt5.QtWidgets import QDialog, QMenu, QMessageBox, QApplication from PyQt5.QtGui import QCursor +from PyQt5.QtCore import pyqtSlot from ..gen_resources.community_cfg_uic import Ui_CommunityConfigurationDialog from ..models.peering import PeeringTreeModel -from ..core.community import Community -from ..core.net.node import Node -from ..tools.exceptions import LookupFailureError, NoPeerAvailable +from ..core import Community +from ..core.registry import Identity +from ..core.net import Node +from . import toast class Step(): @@ -32,19 +34,28 @@ class StepPageInit(Step): super().__init__(config_dialog) self.node = None logging.debug("Init") + self.config_dialog.button_next.setEnabled(False) + self.config_dialog.button_checknode.clicked.connect(self.check_node) - def is_valid(self): + @asyncio.coroutine + def coroutine_check_node(self): server = self.config_dialog.lineedit_server.text() port = self.config_dialog.spinbox_port.value() logging.debug("Is valid ? ") - try: - self.node = Node.from_address(None, server, port) - except Exception as e: - QMessageBox.critical(self.config_dialog, ":(", - str(e), - QMessageBox.Ok) + self.node = yield from Node.from_address(self.config_dialog.app.network_manager, None, server, port) + if self.node: + self.config_dialog.button_next.setEnabled(True) + self.config_dialog.button_check_node.setText("Ok !") + else: + self.config_dialog.button_next.setEnabled(False) + self.config_dialog.button_check_node.setText("Could not connect.") - return True + @pyqtSlot() + def check_node(self): + asyncio.async(self.coroutine_check_node()) + + def is_valid(self): + return self.node is not None def process_next(self): """ @@ -52,7 +63,7 @@ class StepPageInit(Step): """ account = self.config_dialog.account logging.debug("Account : {0}".format(account)) - self.config_dialog.community = Community.create(self.node) + self.config_dialog.community = Community.create(self.config_dialog.app.network_manager, self.node) def display_page(self): self.config_dialog.button_previous.setEnabled(False) @@ -74,10 +85,7 @@ class StepPageAddpeers(Step): def display_page(self): # We add already known peers to the displayed list self.config_dialog.nodes = self.config_dialog.community.network.root_nodes - try: - tree_model = PeeringTreeModel(self.config_dialog.community) - except requests.exceptions.RequestException: - raise + tree_model = PeeringTreeModel(self.config_dialog.community) self.config_dialog.tree_peers.setModel(tree_model) self.config_dialog.button_previous.setEnabled(False) @@ -89,12 +97,18 @@ class ProcessConfigureCommunity(QDialog, Ui_CommunityConfigurationDialog): Dialog to configure or add a community """ - def __init__(self, account, community, password_asker, default_node=None): + def __init__(self, app, account, community, password_asker): """ Constructor + + :type app: cutecoin.core.App + :type account: cutecoin.core.Account + :type community: cutecoin.core.Community + :type password_asker: cutecoin.gui.password_asker.Password_Asker """ super().__init__() self.setupUi(self) + self.app = app self.community = community self.account = account self.password_asker = password_asker @@ -119,21 +133,13 @@ class ProcessConfigureCommunity(QDialog, Ui_CommunityConfigurationDialog): def next(self): if self.step.next_step is not None: if self.step.is_valid(): - try: - self.step.process_next() - self.step = self.step.next_step - next_index = self.stacked_pages.currentIndex() + 1 - self.stacked_pages.setCurrentIndex(next_index) - self.step.display_page() - except NoPeerAvailable: - return - except requests.exceptions.RequestException as e: - QMessageBox.critical(self.config_dialog, ":(", - str(e), - QMessageBox.Ok) - return + self.step.process_next() + self.step = self.step.next_step + next_index = self.stacked_pages.currentIndex() + 1 + self.stacked_pages.setCurrentIndex(next_index) + self.step.display_page() else: - self.accept() + asyncio.async(self.final()) def previous(self): if self.step.previous_step is not None: @@ -142,7 +148,8 @@ class ProcessConfigureCommunity(QDialog, Ui_CommunityConfigurationDialog): self.stacked_pages.setCurrentIndex(previous_index) self.step.display_page() - def add_node(self): + @asyncio.coroutine + def start_add_node(self): """ Add node slot """ @@ -150,13 +157,16 @@ class ProcessConfigureCommunity(QDialog, Ui_CommunityConfigurationDialog): port = self.spinbox_add_port.value() try: - node = Node.from_address(self.community.currency, server, port) + node = yield from Node.from_address(self.app.network_manager, self.community.currency, server, port) self.community.add_node(node) except Exception as e: QMessageBox.critical(self, self.tr("Error"), str(e)) self.tree_peers.setModel(PeeringTreeModel(self.community)) + def add_node(self): + asyncio.async(self.start_add_node()) + def remove_node(self): """ Remove node slot @@ -186,10 +196,30 @@ class ProcessConfigureCommunity(QDialog, Ui_CommunityConfigurationDialog): action.setEnabled(False) menu.exec_(QCursor.pos()) - def accept(self): - try: - yield from Person.lookup(self.account.pubkey, self.community, cached=False) - except LookupFailureError as e: + def selfcert_sent(self, pubkey, currency): + toast.display(self.tr("UID Publishing"), + self.tr("Success publishing your UID").format(pubkey, currency)) + self.account.certification_broadcasted.disconnect() + self.account.broadcast_error.disconnect(self.handle_error) + QApplication.restoreOverrideCursor() + self.add_community_and_close() + + @pyqtSlot(int, str) + def handle_error(self, error_code, text): + toast.display(self.tr("Error"), self.tr("{0} : {1}".format(error_code, text))) + self.account.certification_broadcasted.disconnect() + self.account.broadcast_error.disconnect(self.handle_error) + QApplication.restoreOverrideCursor() + + def add_community_and_close(self): + if self.community not in self.account.communities: + self.account.add_community(self.community) + self.accept() + + @asyncio.coroutine + def final(self): + identity = yield from self.app.identities_registry.future_lookup(self.account.pubkey, self.community) + if identity.status == Identity.NOT_FOUND: reply = QMessageBox.question(self, self.tr("Pubkey not found"), self.tr("""The public key of your account wasn't found in the community. :\n {0}\n @@ -198,20 +228,10 @@ Would you like to publish the key ?""").format(self.account.pubkey)) password = self.password_asker.exec_() if self.password_asker.result() == QDialog.Rejected: return - try: - self.account.send_selfcert(password, self.community) - except ValueError as e: - QMessageBox.critical(self, self.tr("Pubkey publishing error"), - e.message) - except NoPeerAvailable as e: - QMessageBox.critical(self, self.tr("Network error"), - self.tr("Couldn't connect to network : {0}").format(e), - QMessageBox.Ok) - except Exception as e: - QMessageBox.critical(self, self.tr("Error"), - "{0}".format(e), - QMessageBox.Ok) - - if self.community not in self.account.communities: - self.account.add_community(self.community) - super().accept() + self.account.selfcert_broadcasted.connect(self.handle_broadcast) + self.account.broadcast_error.connect(self.handle_error) + asyncio.async(self.account.send_selfcert(password, self.community)) + else: + self.add_community_and_close() + else: + self.add_community_and_close()