diff --git a/src/cutecoin/core/account.py b/src/cutecoin/core/account.py index 36118a054821c80a99f4ee982526fb20f3a3e18f..bc11e9ad2c4e491d1a0668f0324691040e7b4848 100644 --- a/src/cutecoin/core/account.py +++ b/src/cutecoin/core/account.py @@ -69,9 +69,11 @@ class Account(object): dead_communities = [] for data in json_data['communities']: try: - communities.append(Community.load(data)) + community = Community.load(data) + communities.append(community) except NoPeerAvailable: - dead_communities.append(data['currency']) + community = Community.without_network(data) + dead_communities.append(community) account = cls(salt, pubkey, name, communities, wallets, contacts, dead_communities) @@ -210,7 +212,8 @@ class Account(object): def jsonify(self): data_communities = [] - for c in self.communities: + communities = self.communities + self.dead_communities + for c in communities: data_communities.append(c.jsonify()) data_wallets = [] diff --git a/src/cutecoin/core/app.py b/src/cutecoin/core/app.py index ec276baedc808e087cd770bfa70b6e03c9e321b5..e351f3ef84b0f9b10acd622ea6823503b55ec517 100644 --- a/src/cutecoin/core/app.py +++ b/src/cutecoin/core/app.py @@ -32,8 +32,7 @@ class Application(object): self.load() def get_account(self, name): - if not self.accounts[name]: - self.load_account(name) + self.load_account(name) if name in self.accounts.keys(): return self.accounts[name] else: @@ -63,7 +62,6 @@ class Application(object): if self.current_account is not None: self.save_cache(self.current_account) self.current_account = account - self.load_cache(account) def load(self): if (os.path.exists(config.parameters['data']) @@ -71,7 +69,6 @@ class Application(object): logging.debug("Loading data...") with open(config.parameters['data'], 'r') as json_data: data = json.load(json_data) - json_data.close() if 'default_account' in data.keys(): self.default_account = data['default_account'] for account_name in data['local_accounts']: @@ -83,6 +80,7 @@ class Application(object): with open(account_path, 'r') as json_data: data = json.load(json_data) account = Account.load(data) + self.load_cache(account) self.accounts[account_name] = account def load_cache(self, account): @@ -90,8 +88,8 @@ class Application(object): wallet_path = os.path.join(config.parameters['home'], account.name, '__cache__', wallet.pubkey) if os.path.exists(wallet_path): - json_data = open(wallet_path, 'r') - data = json.load(json_data) + with open(wallet_path, 'r') as json_data: + data = json.load(json_data) wallet.load_caches(data) for community in account.communities: wallet.refresh_cache(community) diff --git a/src/cutecoin/core/community.py b/src/cutecoin/core/community.py index 8fe7efaef0d6983aca50a1c4cf0098a12ec2a275..a28ef1d6c3d5e2b2c818d018d3289e6f3c65cf73 100644 --- a/src/cutecoin/core/community.py +++ b/src/cutecoin/core/community.py @@ -12,7 +12,7 @@ from ..tools.exceptions import NoPeerAvailable import logging import inspect import hashlib -from requests.exceptions import ConnectTimeout +from requests.exceptions import RequestException, Timeout class Cache(): @@ -59,18 +59,16 @@ class Community(object): self.peers = [p for p in peers if p.currency == currency] self._cache = Cache(self) - # After initializing the community from latest peers, - # we refresh its peers tree - logging.debug("Creating community") - self.peers = self.peering() - logging.debug("{0} peers found".format(len(self.peers))) - logging.debug([peer.pubkey for peer in peers]) - self._cache.refresh() @classmethod def create(cls, currency, peer): - return cls(currency, [peer]) + community = cls(currency, [peer]) + logging.debug("Creating community") + community.peers = community.peering() + logging.debug("{0} peers found".format(len(community.peers))) + logging.debug([peer.pubkey for peer in community.peers]) + return community @classmethod def load(cls, json_data): @@ -92,6 +90,28 @@ class Community(object): except: pass + community = cls(currency, peers) + logging.debug("Creating community") + community.peers = community.peering() + logging.debug("{0} peers found".format(len(community.peers))) + logging.debug([peer.pubkey for peer in community.peers]) + return community + + @classmethod + def without_network(cls, json_data): + peers = [] + + currency = json_data['currency'] + + for data in json_data['peers']: + endpoints = [] + for e in data['endpoints']: + endpoints.append(Endpoint.from_inline(e)) + peer = Peer(PROTOCOL_VERSION, currency, data['pubkey'], + "0-DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", + endpoints, None) + peers.append(peer) + community = cls(currency, peers) return community @@ -128,14 +148,14 @@ class Community(object): (next_peer.pubkey not in traversed_pubkeys))) if next_peer.pubkey not in traversed_pubkeys: self._peering_traversal(next_peer, found_peers, traversed_pubkeys) - except ConnectTimeout: - pass - except TimeoutError: + except Timeout: pass except ConnectionError: pass except ValueError: pass + except RequestException as e: + pass def peering(self): peers = [] @@ -210,17 +230,11 @@ class Community(object): continue else: raise - except ConnectTimeout: - # Move the timeout peer to the end - self.peers.remove(peer) - self.peers.append(peer) - continue - except TimeoutError: + except Timeout: # Move the timeout peer to the end self.peers.remove(peer) self.peers.append(peer) continue - raise NoPeerAvailable(self.currency, len(self.peers)) def post(self, request, req_args={}, post_args={}): @@ -233,12 +247,7 @@ class Community(object): return except ValueError as e: raise - except ConnectTimeout: - # Move the timeout peer to the end - self.peers.remove(peer) - self.peers.append(peer) - continue - except TimeoutError: + except Timeout: # Move the timeout peer to the end self.peers.remove(peer) self.peers.append(peer) @@ -261,13 +270,7 @@ class Community(object): except ValueError as e: value_error = e continue - except ConnectTimeout: - tries = tries + 1 - # Move the timeout peer to the end - self.peers.remove(peer) - self.peers.append(peer) - continue - except TimeoutError: + except Timeout: tries = tries + 1 # Move the timeout peer to the end self.peers.remove(peer) diff --git a/src/cutecoin/gui/currency_tab.py b/src/cutecoin/gui/currency_tab.py index 12347ee99da01a6ee6fb01cf58b67215b0981e65..8fe38a858d4422f3667096b1d49c84458084b834 100644 --- a/src/cutecoin/gui/currency_tab.py +++ b/src/cutecoin/gui/currency_tab.py @@ -6,9 +6,10 @@ Created on 2 févr. 2014 import logging import time +import requests from ucoinpy.api import bma -from PyQt5.QtWidgets import QWidget, QMenu, QAction, QApplication +from PyQt5.QtWidgets import QWidget, QMenu, QAction, QApplication, QMessageBox from PyQt5.QtCore import QModelIndex, Qt, pyqtSlot, QObject, QThread, pyqtSignal from PyQt5.QtGui import QIcon from ..gen_resources.currency_tab_uic import Ui_CurrencyTabWidget @@ -46,8 +47,11 @@ class BlockchainWatcher(QObject): self.last_block = block_number except NoPeerAvailable: return + except requests.exceptions.RequestException as e: + self.connection_error.emit("Cannot check new block : {0}".format(str(e))) new_block_mined = pyqtSignal(int) + connection_error = pyqtSignal(str) class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget): @@ -72,6 +76,7 @@ class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget): self.bc_watcher = BlockchainWatcher(self.app.current_account, community) self.bc_watcher.new_block_mined.connect(self.refresh_block) + self.bc_watcher.connection_error.connect(self.display_error) self.watcher_thread = QThread() self.bc_watcher.moveToThread(self.watcher_thread) @@ -100,6 +105,12 @@ class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget): self.status_label.setText("Connected : Block {0}" .format(block_number)) + @pyqtSlot(str) + def display_error(self, error): + QMessageBox.critical(self, ":(", + error, + QMessageBox.Ok) + @pyqtSlot(int) def refresh_block(self, block_number): if self.list_wallets.model(): diff --git a/src/cutecoin/gui/mainwindow.py b/src/cutecoin/gui/mainwindow.py index 4bb208c999ebe14d5603e5919ca84b459d1a8e4a..675c4c4c4e21c8f5b0ba869e026e2264cc186b14 100644 --- a/src/cutecoin/gui/mainwindow.py +++ b/src/cutecoin/gui/mainwindow.py @@ -18,6 +18,7 @@ from ..tools.exceptions import NoPeerAvailable from ..__init__ import __version__ import logging +import requests class Loader(QObject): @@ -27,6 +28,7 @@ class Loader(QObject): self.account_name = "" loaded = pyqtSignal() + connection_error = pyqtSignal(str) def set_account_name(self, name): self.account_name = name @@ -34,7 +36,12 @@ class Loader(QObject): @pyqtSlot() def load(self): if self.account_name != "": - self.app.change_current_account(self.app.get_account(self.account_name)) + try: + self.app.change_current_account(self.app.get_account(self.account_name)) + except requests.exceptions.RequestException as e: + self.connection_error.emit(str(e)) + self.loaded.emit() + self.loaded.emit() @@ -69,6 +76,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.loader.moveToThread(self.loader_thread) self.loader.loaded.connect(self.loader_finished) self.loader.loaded.connect(self.loader_thread.quit) + self.loader.connection_error.connect(self.display_error) self.loader_thread.started.connect(self.loader.load) self.setWindowTitle("CuteCoin {0}".format(__version__)) self.refresh() @@ -83,9 +91,13 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.refresh() self.busybar.hide() + @pyqtSlot(str) + def display_error(self, error): + QMessageBox.critical(self, ":(", + error, + QMessageBox.Ok) + def action_change_account(self, account_name): - if self.app.current_account: - self.app.save_cache(self.app.current_account) self.busybar.show() self.status_label.setText("Loading account {0}".format(account_name)) self.loader.set_account_name(account_name) @@ -160,11 +172,6 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.action_configure_parameters.setEnabled(False) self.action_set_as_default.setEnabled(False) else: - for dead in self.app.current_account.dead_communities: - QMessageBox.critical(self, ":(", - "No {0} peers could be joined. Community was lost.".format(dead), - QMessageBox.Ok) - self.action_set_as_default.setEnabled(self.app.current_account.name != self.app.default_account) self.password_asker = PasswordAskerDialog(self.app.current_account) @@ -189,6 +196,10 @@ class MainWindow(QMainWindow, Ui_MainWindow): str(e), QMessageBox.Ok) continue + except requests.exceptions.RequestException as e: + QMessageBox.critical(self, ":(", + str(e), + QMessageBox.Ok) self.menu_contacts_list.clear() for contact in self.app.current_account.contacts: diff --git a/src/cutecoin/gui/process_cfg_account.py b/src/cutecoin/gui/process_cfg_account.py index 7ef72a8a435e56428af419cf03caf10d758e2b31..4384dba4b181268ba99599d0aa75609d0da9c9e2 100644 --- a/src/cutecoin/gui/process_cfg_account.py +++ b/src/cutecoin/gui/process_cfg_account.py @@ -4,6 +4,7 @@ Created on 6 mars 2014 @author: inso ''' import logging +import requests from ucoinpy.documents.peer import Peer from ucoinpy.key import SigningKey from ..gen_resources.account_cfg_uic import Ui_AccountConfigurationDialog @@ -197,6 +198,10 @@ class ProcessConfigureAccount(QDialog, Ui_AccountConfigurationDialog): QMessageBox.critical(self, "Error", str(e), QMessageBox.Ok) return + except requests.exceptions.RequestException as e: + QMessageBox.critical(self, "Error", + str(e), QMessageBox.Ok) + return 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 e3f75e115c2ed4fe3d9cd5f5fe14fb4e99e9d3c6..a841d9f15fabe248d626e3da7f4ac1e79f08ddbc 100644 --- a/src/cutecoin/gui/process_cfg_community.py +++ b/src/cutecoin/gui/process_cfg_community.py @@ -5,6 +5,8 @@ Created on 8 mars 2014 ''' import logging +import requests + from ucoinpy.api import bma from ucoinpy.api.bma import ConnectionHandler from ucoinpy.documents.peer import Peer @@ -65,6 +67,11 @@ class StepPageInit(Step): QMessageBox.critical(self.config_dialog, "Server Error", "Cannot join any peer in this community.") raise + except requests.exceptions.RequestException as e: + QMessageBox.critical(self.config_dialog, ":(", + str(e), + QMessageBox.Ok) + raise def display_page(self): self.config_dialog.button_previous.setEnabled(False) @@ -87,7 +94,11 @@ class StepPageAddpeers(Step): # We add already known peers to the displayed list for peer in self.config_dialog.community.peers: self.config_dialog.peers.append(peer) - tree_model = PeeringTreeModel(self.config_dialog.community) + try: + tree_model = PeeringTreeModel(self.config_dialog.community) + except requests.exceptions.RequestException: + raise + self.config_dialog.tree_peers.setModel(tree_model) self.config_dialog.button_previous.setEnabled(False) self.config_dialog.button_next.setText("Ok") @@ -129,11 +140,19 @@ class ProcessConfigureCommunity(QDialog, Ui_CommunityConfigurationDialog): def next(self): if self.step.next_step is not None: if self.step.is_valid(): - 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() + 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 else: self.accept() @@ -155,8 +174,12 @@ class ProcessConfigureCommunity(QDialog, Ui_CommunityConfigurationDialog): peer = Peer.from_signed_raw("{0}{1}\n".format(peer_data['raw'], peer_data['signature'])) - self.community.peers.append(peer) - except: + if peer.currency == self.community.currency: + self.community.peers.append(peer) + else: + QMessageBox.critical(self, "Error", + "This peer doesn't use this community currency.") + except requests.exceptions.RequestException as e: QMessageBox.critical(self, "Server error", "Cannot get node peering") self.tree_peers.setModel(PeeringTreeModel(self.community)) @@ -187,6 +210,15 @@ Would you like to publish the key ?""".format(self.account.pubkey)) except ValueError as e: QMessageBox.critical(self, "Pubkey publishing error", e.message) + except NoPeerAvailable as e: + QMessageBox.critical(self, "Network error", + "Couldn't connect to network : {0}".format(e), + QMessageBox.Ok) + except Exception as e: + QMessageBox.critical(self, "Error", + "{0}".format(e), + QMessageBox.Ok) + else: return diff --git a/src/cutecoin/gui/views/wot.py b/src/cutecoin/gui/views/wot.py index 20fac1cc2de0184900eb38c37e18e098bfa2a20b..36baa15dac06e32b8ad54e8cadcf990a3a4b0bef 100644 --- a/src/cutecoin/gui/views/wot.py +++ b/src/cutecoin/gui/views/wot.py @@ -241,9 +241,13 @@ class Node(QGraphicsEllipseItem): def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent): """ Right click on node to show node menu + Except on wallet node :param event: scene context menu event """ + # no menu on the wallet node + if self.status_wallet: + return None # create node context menus self.menu = QMenu() # action add identity as contact diff --git a/src/cutecoin/gui/wot_tab.py b/src/cutecoin/gui/wot_tab.py index 0d8e4bafbe013ab9b94caa871d3505150da39364..94785709c2268543acecdd925f33de4492a3138d 100644 --- a/src/cutecoin/gui/wot_tab.py +++ b/src/cutecoin/gui/wot_tab.py @@ -28,8 +28,7 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): self.setupUi(self) # add combobox events - self.comboBoxSearch.lineEdit().textEdited.connect(self.search) - self.comboBoxSearch.lineEdit().returnPressed.connect(self.combobox_return_pressed) + self.comboBoxSearch.lineEdit().returnPressed.connect(self.search) # add scene events self.graphicsView.scene().node_clicked.connect(self.draw_graph) @@ -54,14 +53,35 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): :param public_key: Public key of the identity """ + # reset graph + graph = dict() try: certifiers = self.community.request(bma.wot.CertifiersOf, {'search': public_key}) except ValueError as e: logging.debug('bma.wot.CertifiersOf request error : ' + str(e)) + try: + results = self.community.request(bma.wot.Lookup, {'search': public_key}) + except ValueError as e: + logging.debug('bma.wot.CertifiersOf request error : ' + str(e)) + return False + + # show only node of this non member (to certify him) + node_status = 0 + if public_key == self.account.pubkey: + node_status += NODE_STATUS_HIGHLIGHTED + node_status += NODE_STATUS_OUT + node_status += NODE_STATUS_SELECTED + + # selected node + graph[public_key] = {'id': public_key, 'arcs': list(), 'text': results['results'][0]['uids'][0]['uid'], 'tooltip': public_key, 'status': node_status} + + # draw graph in qt scene + self.graphicsView.scene().update_wot(graph) return False - # reset graph - graph = dict() + except Exception as e: + logging.debug('bma.wot.CertifiersOf request error : ' + str(e)) + return False # add wallet node node_status = 0 @@ -173,24 +193,23 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): self.account.pubkey ) - def combobox_return_pressed(self): + def search(self): """ Search nodes when return is pressed in combobox lineEdit """ - self.search(self.comboBoxSearch.lineEdit().text()) + text = self.comboBoxSearch.lineEdit().text() - def search(self, text): - """ - Search nodes when text is edited in combobox lineEdit - """ if len(text) < 2: return False + try: + response = self.community.request(bma.wot.Lookup, {'search': text}) + except Exception as e: + logging.debug('bma.wot.Lookup request error : ' + str(e)) + return False - response = self.community.request(bma.wot.Lookup, {'search': text}) nodes = {} for identity in response['results']: - if identity['uids'][0]['others']: - nodes[identity['pubkey']] = identity['uids'][0]['uid'] + nodes[identity['pubkey']] = identity['uids'][0]['uid'] if nodes: self.nodes = list() @@ -201,6 +220,11 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): self.comboBoxSearch.addItem(uid) self.comboBoxSearch.showPopup() + if len(nodes) == 1: + self.draw_graph( + list(nodes.keys())[0] + ) + def select_node(self, index): """ Select node in graph when item is selected in combobox @@ -215,6 +239,7 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): def sign_node(self, metadata): # open certify dialog dialog = CertificationDialog(self.account, self.password_asker) + dialog.combo_community.setCurrentText(self.community.name()) dialog.edit_pubkey.setText(metadata['id']) dialog.radio_pubkey.setChecked(True) dialog.combo_community.setCurrentText(self.community.name()) diff --git a/src/cutecoin/models/peering.py b/src/cutecoin/models/peering.py index dffe96397dd9135589017e951d4ff5a213fa97c0..079218b2e4275aa021d0acaf1a71dff2a98ebc49 100644 --- a/src/cutecoin/models/peering.py +++ b/src/cutecoin/models/peering.py @@ -8,7 +8,7 @@ from ucoinpy.api import bma from ucoinpy.documents.peer import BMAEndpoint, Peer from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt from .peer import PeerItem, RootItem -from requests.exceptions import ConnectTimeout +from requests.exceptions import Timeout import logging @@ -111,9 +111,7 @@ class PeeringTreeModel(QAbstractItemModel): peer_data['value']['signature'])) child_node_item = PeerItem(peer, peer_item) peer_item.appendChild(child_node_item) - except ConnectTimeout: - continue - except TimeoutError: + except Timeout: continue except StopIteration as e: