From ba72c701aff159cf4a80ea58c636a8b64013c2a5 Mon Sep 17 00:00:00 2001 From: Inso Date: Sat, 6 Feb 2016 00:39:13 +0100 Subject: [PATCH] Refactor current account handling and app closing --- src/sakia/core/account.py | 4 +- src/sakia/core/app.py | 23 ++++++---- src/sakia/core/community.py | 4 +- src/sakia/gui/community_view.py | 8 +++- src/sakia/gui/graphs/explorer_tab.py | 14 ++++-- src/sakia/gui/mainwindow.py | 68 ++++++++++++++-------------- src/sakia/gui/process_cfg_account.py | 11 +++-- src/sakia/main.py | 4 ++ 8 files changed, 79 insertions(+), 57 deletions(-) diff --git a/src/sakia/core/account.py b/src/sakia/core/account.py index 77eff883..de06266d 100644 --- a/src/sakia/core/account.py +++ b/src/sakia/core/account.py @@ -565,9 +565,9 @@ def start_coroutines(self): for c in self.communities: c.start_coroutines() - def stop_coroutines(self): + async def stop_coroutines(self): for c in self.communities: - c.stop_coroutines() + await c.stop_coroutines() for w in self.wallets: w.stop_coroutines() diff --git a/src/sakia/core/app.py b/src/sakia/core/app.py index a5f9f589..4becc909 100644 --- a/src/sakia/core/app.py +++ b/src/sakia/core/app.py @@ -11,6 +11,7 @@ import json import datetime import aiohttp +import asyncio from pkg_resources import parse_version from PyQt5.QtCore import QObject, pyqtSignal, QTranslator, QCoreApplication, QLocale @@ -37,6 +38,7 @@ class Application(QObject): view_identity_in_wot = pyqtSignal(Identity) refresh_transfers = pyqtSignal() account_imported = pyqtSignal(str) + account_changed = pyqtSignal() def __init__(self, qapp, loop, identities_registry): """ @@ -161,12 +163,13 @@ def identities_registry(self): def add_account(self, account): self.accounts[account.name] = account - def delete_account(self, account): + @asyncify + async def delete_account(self, account): """ Delete an account. Current account changes to None if it is deleted. """ - account.stop_coroutines() + await account.stop_coroutines() self.accounts.pop(account.name) if self._current_account == account: self._current_account = None @@ -176,7 +179,8 @@ def delete_account(self, account): self.preferences['account'] = "" self.save_preferences(self.preferences) - def change_current_account(self, account): + @asyncify + async def change_current_account(self, account): """ Change current account displayed and refresh its cache. @@ -185,20 +189,21 @@ def change_current_account(self, account): during cache refresh """ if self._current_account is not None: - self.stop_current_account() + await self.stop_current_account() self._current_account = account if self._current_account is not None: self._current_account.start_coroutines() + self.account_changed.emit() - def stop_current_account(self): + async def stop_current_account(self): """ Save the account to the cache and stop the coroutines """ self.save_cache(self._current_account) self.save_notifications(self._current_account) - self._current_account.stop_coroutines() + await self._current_account.stop_coroutines() def load(self): """ @@ -502,10 +507,10 @@ def jsonify(self): data = {'local_accounts': self.jsonify_accounts()} return data - def stop(self): + async def stop(self): if self._current_account: - self.stop_current_account() - + await self.stop_current_account() + await asyncio.sleep(0) self.save_registries() @asyncify diff --git a/src/sakia/core/community.py b/src/sakia/core/community.py index be3f0086..04ddbe1a 100644 --- a/src/sakia/core/community.py +++ b/src/sakia/core/community.py @@ -328,8 +328,8 @@ async def members_pubkeys(self): def start_coroutines(self): self.network.start_coroutines() - def stop_coroutines(self): - self.network.stop_coroutines() + async def stop_coroutines(self): + await self.network.stop_coroutines() def rollback_cache(self): self._bma_access.rollback() diff --git a/src/sakia/gui/community_view.py b/src/sakia/gui/community_view.py index 7c1a19b8..c6e65450 100644 --- a/src/sakia/gui/community_view.py +++ b/src/sakia/gui/community_view.py @@ -109,7 +109,7 @@ def __init__(self, app, status_label, label_icon): self.button_membership.clicked.connect(self.send_membership_demand) - def show_closable_tab(self, tab, icon, title): + def show_closable_tab(self, tab, icon, title): if self.tabs.indexOf(tab) == -1: self.tabs.addTab(tab, icon, title) style = self.app.qapp.style() @@ -122,6 +122,7 @@ def show_closable_tab(self, tab, icon, title): def cancel_once_tasks(self): cancel_once_task(self, self.refresh_block) cancel_once_task(self, self.refresh_status) + logging.debug("Cancelled status") cancel_once_task(self, self.refresh_quality_buttons) def change_account(self, account, password_asker): @@ -153,6 +154,7 @@ def change_community(self, community): community.network.new_block_mined.connect(self.refresh_block) community.network.nodes_changed.connect(self.refresh_status) self.label_currency.setText(community.currency) + logging.debug("Changed community to {0}".format(community)) self.community = community self.refresh_status() self.refresh_quality_buttons() @@ -229,11 +231,14 @@ async def refresh_status(self): if self.community: text = "" + logging.debug("Here I am with {0}".format(self.community)) current_block_number = self.community.network.current_blockid.number if current_block_number: text += self.tr(" Block {0}").format(current_block_number) try: + logging.debug("Before get _block {0}".format(self.community)) block = await self.community.get_block(current_block_number) + logging.debug("After get _block {0}".format(self.community)) text += " ({0})".format(QLocale.toString( QLocale(), QDateTime.fromTime_t(block['medianTime']), @@ -245,6 +250,7 @@ async def refresh_status(self): except ValueError as e: logging.debug(str(e)) + logging.debug("I can crash now {0}".format(self.community)) if len(self.community.network.synced_nodes) == 0: self.button_membership.setEnabled(False) self.button_certification.setEnabled(False) diff --git a/src/sakia/gui/graphs/explorer_tab.py b/src/sakia/gui/graphs/explorer_tab.py index b39ee555..78da1379 100644 --- a/src/sakia/gui/graphs/explorer_tab.py +++ b/src/sakia/gui/graphs/explorer_tab.py @@ -3,6 +3,7 @@ from PyQt5.QtCore import QEvent, pyqtSignal from PyQt5.QtWidgets import QWidget +from ...tools.exceptions import NoPeerAvailable from ...tools.decorators import asyncify, once_at_a_time, cancel_once_task from ...core.graph import ExplorerGraph from .graph_tab import GraphTabWidget @@ -102,11 +103,14 @@ async def reset(self, checked=False): Reset graph scene to wallet identity """ if self.account and self.community: - parameters = await self.community.parameters() - self.ui.steps_slider.setMaximum(parameters['stepMax']) - self.ui.steps_slider.setValue(int(0.33 * parameters['stepMax'])) - identity = await self.account.identity(self.community) - self.draw_graph(identity) + try: + parameters = await self.community.parameters() + self.ui.steps_slider.setMaximum(parameters['stepMax']) + self.ui.steps_slider.setValue(int(0.33 * parameters['stepMax'])) + identity = await self.account.identity(self.community) + self.draw_graph(identity) + except NoPeerAvailable: + logging.debug("No peer available") def changeEvent(self, event): """ diff --git a/src/sakia/gui/mainwindow.py b/src/sakia/gui/mainwindow.py index 84c25203..e361494a 100644 --- a/src/sakia/gui/mainwindow.py +++ b/src/sakia/gui/mainwindow.py @@ -35,12 +35,13 @@ class MainWindow(QObject): classdocs """ - def __init__(self, app, homescreen, community_view, widget, ui, + def __init__(self, app, account, homescreen, community_view, widget, ui, label_icon, label_status, label_time, combo_referential, password_asker): """ Init :param sakia.core.app.Application app: application + :param sakia.core.Account account: the account :param HomeScreenWidgetcreen homescreen: the homescreen :param CommunityView community_view: the community view :param QMainWindow widget: the widget of the main window @@ -55,6 +56,7 @@ def __init__(self, app, homescreen, community_view, widget, ui, # Set up the user interface from Designer. super().__init__() self.app = app + self.account = account self.initialized = False self.password_asker = password_asker self.import_dialog = None @@ -130,7 +132,7 @@ def _init_community_view(self): @classmethod def startup(cls, app): qmainwindow = QMainWindow() - main_window = cls(app, HomeScreenWidget(app, None), + main_window = cls(app, None, HomeScreenWidget(app, None), CommunityWidget(app, None, None), qmainwindow, Ui_MainWindow(), QLabel("", qmainwindow), QLabel("", qmainwindow), @@ -138,12 +140,12 @@ def startup(cls, app): PasswordAskerDialog(None)) app.version_requested.connect(main_window.latest_version_requested) app.account_imported.connect(main_window.import_account_accepted) + app.account_changed.connect(main_window.change_account) main_window._init_ui() main_window._init_homescreen() main_window._init_community_view() main_window.update_time() - # FIXME : Need python 3.5 self.app.get_last_version() if app.preferences['maximized']: main_window.widget.showMaximized() else: @@ -155,20 +157,30 @@ def startup(cls, app): main_window.refresh() return main_window + def change_account(self): + if self.account: + self.account.contacts_changed.disconnect(self.refresh_contacts) + self.account = self.app.current_account + self.password_asker.change_account(self.account) + self.community_view.change_account(self.account, self.password_asker) + if self.account: + self.account.contacts_changed.connect(self.refresh_contacts) + self.refresh() + @asyncify async def open_add_account_dialog(self, checked=False): dialog = ProcessConfigureAccount(self.app, None) result = await dialog.async_exec() if result == QDialog.Accepted: - self.action_change_account(self.app.current_account.name) + self.action_change_account(self.account.name) @asyncify async def open_configure_account_dialog(self, checked=False): - dialog = ProcessConfigureAccount(self.app, self.app.current_account) + dialog = ProcessConfigureAccount(self.app, self.account) result = await dialog.async_exec() if result == QDialog.Accepted: - if self.app.current_account: - self.action_change_account(self.app.current_account.name) + if self.account: + self.action_change_account(self.account.name) else: self.refresh() @@ -180,8 +192,8 @@ def display_error(self, error): @pyqtSlot(str) def referential_changed(self, index): - if self.app.current_account: - self.app.current_account.set_display_referential(index) + if self.account: + self.account.set_display_referential(index) if self.community_view: self.community_view.referential_changed() self.homescreen.referential_changed() @@ -201,37 +213,30 @@ def update_time(self): @pyqtSlot() def delete_contact(self): contact = self.sender().data() - self.app.current_account.remove_contacts(contact) + self.account.remove_contacts(contact) @pyqtSlot() def edit_contact(self): index = self.sender().data() - dialog = ConfigureContactDialog(self.app, self.app.current_account, self, None, index) + dialog = ConfigureContactDialog(self.app, self.account, self, None, index) dialog.exec_() def action_change_account(self, account_name): - if self.app.current_account: - self.app.current_account.contacts_changed.disconnect(self.refresh_contacts) self.app.change_current_account(self.app.get_account(account_name)) - self.password_asker.change_account(self.app.current_account) - self.community_view.change_account(self.app.current_account, self.password_asker) - if self.app.current_account: - self.app.current_account.contacts_changed.connect(self.refresh_contacts) - self.refresh() @pyqtSlot() def action_open_add_community(self): dialog = ProcessConfigureCommunity(self.app, - self.app.current_account, None, + self.account, None, self.password_asker) if dialog.exec_() == QDialog.Accepted: - self.app.save(self.app.current_account) + self.app.save(self.account) dialog.community.start_coroutines() self.homescreen.refresh() def open_transfer_money_dialog(self): dialog = TransferMoneyDialog(self.app, - self.app.current_account, + self.account, self.password_asker, self.community_view.community, None) @@ -240,11 +245,11 @@ def open_transfer_money_dialog(self): def open_certification_dialog(self): CertificationDialog.open_dialog(self.app, - self.app.current_account, + self.account, self.password_asker) def open_add_contact_dialog(self): - dialog = ConfigureContactDialog(self.app, self.app.current_account, self) + dialog = ConfigureContactDialog(self.app, self.account, self) dialog.exec_() def open_preferences_dialog(self): @@ -331,8 +336,8 @@ def refresh_accounts(self): def refresh_contacts(self): self.ui.menu_contacts_list.clear() - if self.app.current_account: - for index, contact in enumerate(self.app.current_account.contacts): + if self.account: + for index, contact in enumerate(self.account.contacts): contact_menu = self.ui.menu_contacts_list.addMenu(contact['name']) edit_action = contact_menu.addAction(self.tr("Edit")) edit_action.triggered.connect(self.edit_contact) @@ -353,7 +358,7 @@ def refresh(self): self.homescreen.show() self.homescreen.refresh() - if self.app.current_account is None: + if self.account is None: self.widget.setWindowTitle(self.tr("sakia {0}").format(__version__)) self.ui.action_add_a_contact.setEnabled(False) self.ui.actionCertification.setEnabled(False) @@ -363,18 +368,15 @@ def refresh(self): self.ui.menu_contacts_list.setEnabled(False) self.combo_referential.setEnabled(False) self.status_label.setText(self.tr("")) - self.password_asker = None else: - self.password_asker = PasswordAskerDialog(self.app.current_account) - self.combo_referential.blockSignals(True) self.combo_referential.clear() for ref in money.Referentials: self.combo_referential.addItem(ref.translated_name()) + logging.debug(self.app.preferences) self.combo_referential.setEnabled(True) self.combo_referential.blockSignals(False) - logging.debug(self.app.preferences) self.combo_referential.setCurrentIndex(self.app.preferences['ref']) self.ui.action_add_a_contact.setEnabled(True) self.ui.actionCertification.setEnabled(True) @@ -382,7 +384,7 @@ def refresh(self): self.ui.menu_contacts_list.setEnabled(True) self.ui.action_configure_parameters.setEnabled(True) self.widget.setWindowTitle(self.tr("sakia {0} - Account : {1}").format(__version__, - self.app.current_account.name)) + self.account.name)) self.refresh_contacts() @@ -413,7 +415,7 @@ def export_account_accepted(self): path = selected_file[0] else: path = selected_file[0] + ".acc" - self.app.export_account(path, self.app.current_account) + self.app.export_account(path, self.account) def eventFilter(self, target, event): """ @@ -423,8 +425,6 @@ def eventFilter(self, target, event): :return: bool """ if target == self.widget: - if event.type() == QEvent.Close: - self.app.stop() if event.type() == QEvent.LanguageChange: self.ui.retranslateUi(self) self.refresh() diff --git a/src/sakia/gui/process_cfg_account.py b/src/sakia/gui/process_cfg_account.py index 6ae58724..65b5ede6 100644 --- a/src/sakia/gui/process_cfg_account.py +++ b/src/sakia/gui/process_cfg_account.py @@ -9,10 +9,12 @@ from ..gen_resources.account_cfg_uic import Ui_AccountConfigurationDialog from ..gui.process_cfg_community import ProcessConfigureCommunity from ..gui.password_asker import PasswordAskerDialog, detect_non_printable +from ..gui.widgets.dialogs import QAsyncMessageBox from ..models.communities import CommunitiesListModel from ..tools.exceptions import KeyAlreadyUsed, Error, NoPeerAvailable +from ..tools.decorators import asyncify -from PyQt5.QtWidgets import QDialog, QMessageBox +from PyQt5.QtWidgets import QDialog from PyQt5.QtCore import QRegExp from PyQt5.QtGui import QRegExpValidator @@ -216,15 +218,16 @@ def open_process_edit_community(self, index): dialog.accepted.connect(self.action_edit_community) dialog.exec_() - def action_delete_account(self): - reply = QMessageBox.question(self, self.tr("Warning"), + @asyncify + async def action_delete_account(self): + reply = await QAsyncMessageBox.question(self, self.tr("Warning"), self.tr("""This action will delete your account locally. Please note your key parameters (salt and password) if you wish to recover it later. Your account won't be removed from the networks it joined. Are you sure ?""")) if reply == QMessageBox.Yes: account = self.app.current_account - self.app.delete_account(account) + await self.app.delete_account(account) self.app.save(account) self.accept() diff --git a/src/sakia/main.py b/src/sakia/main.py index d185a54f..10909135 100755 --- a/src/sakia/main.py +++ b/src/sakia/main.py @@ -70,4 +70,8 @@ def async_exception_handler(loop, context): app = Application.startup(sys.argv, sakia, loop) window = MainWindow.startup(app) loop.run_forever() + try: + loop.run_until_complete(app.stop()) + except asyncio.CancelledError: + logging.info('CancelledError') sys.exit()