diff --git a/src/cutecoin/core/registry/identities.py b/src/cutecoin/core/registry/identities.py index 65935c537732ea212d618557649d3c2ad89c3498..eea2f3d778697ee7dde857170215c25b1dee3072 100644 --- a/src/cutecoin/core/registry/identities.py +++ b/src/cutecoin/core/registry/identities.py @@ -60,7 +60,7 @@ class IdentitiesRegistry: identity.blockchain_state = BlockchainState.VALIDATED logging.debug("Lookup : found {0}".format(identity)) if not future_identity.cancelled(): - future_identity.set_result(True) + future_identity.set_result(identity) else: reply = community.bma_access.simple_request(qtbma.wot.Lookup, req_args={'search': pubkey}) @@ -69,7 +69,7 @@ class IdentitiesRegistry: reply = community.bma_access.simple_request(qtbma.wot.CertifiersOf, req_args={'search': pubkey}) reply.finished.connect(lambda: handle_certifiersof_reply(reply, tries=tries+1)) elif not future_identity.cancelled(): - future_identity.set_result(True) + future_identity.set_result(identity) def handle_lookup_reply(reply, tries=0): status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) diff --git a/src/cutecoin/gui/community_view.py b/src/cutecoin/gui/community_view.py index 1296420062882761a753b2b8262c4c337a10bb08..39b822482af2c7cf635916942b928a286c1250ed 100644 --- a/src/cutecoin/gui/community_view.py +++ b/src/cutecoin/gui/community_view.py @@ -15,11 +15,10 @@ from .wot_tab import WotTabWidget from .identities_tab import IdentitiesTabWidget from .transactions_tab import TransactionsTabWidget from .network_tab import NetworkTabWidget -from .password_asker import PasswordAskerDialog from . import toast import asyncio from ..tools.exceptions import MembershipNotFoundError, LookupFailureError, NoPeerAvailable -from ..tools.decorators import asyncify +from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task from ..gen_resources.community_view_uic import Ui_CommunityWidget @@ -79,7 +78,13 @@ class CommunityWidget(QWidget, Ui_CommunityWidget): self.button_membership.clicked.connect(self.send_membership_demand) + def cancel_once_tasks(self): + cancel_once_task(self, self.refresh_block) + cancel_once_task(self, self.refresh_status) + cancel_once_task(self, self.refresh_quality_buttons) + def change_account(self, account, password_asker): + self.cancel_once_tasks() if self.account: self.account.broadcast_error.disconnect(self.handle_broadcast_error) self.account.membership_broadcasted.disconnect(self.handle_membership_broadcasted) @@ -98,6 +103,8 @@ class CommunityWidget(QWidget, Ui_CommunityWidget): self.tab_history.change_account(account, self.password_asker) def change_community(self, community): + self.cancel_once_tasks() + self.tab_network.change_community(community) self.tab_wot.change_community(community) self.tab_history.change_community(community) @@ -119,6 +126,7 @@ class CommunityWidget(QWidget, Ui_CommunityWidget): error, QMessageBox.Ok) + @once_at_a_time @asyncify @asyncio.coroutine def refresh_block(self, block_number): @@ -169,6 +177,7 @@ class CommunityWidget(QWidget, Ui_CommunityWidget): self.tab_history.refresh_balance() self.refresh_status() + @once_at_a_time @asyncify @asyncio.coroutine def refresh_status(self): @@ -203,6 +212,7 @@ class CommunityWidget(QWidget, Ui_CommunityWidget): self.status_label.setText(label_text) + @once_at_a_time @asyncify @asyncio.coroutine def refresh_quality_buttons(self): diff --git a/src/cutecoin/gui/identities_tab.py b/src/cutecoin/gui/identities_tab.py index 6af8313eeec80b1c0a33400df2aa176f3bcd3b42..d8b40b4a70b6483e7457b0d95228f770b9b84d14 100644 --- a/src/cutecoin/gui/identities_tab.py +++ b/src/cutecoin/gui/identities_tab.py @@ -18,7 +18,7 @@ from .certification import CertificationDialog import asyncio from ..core.net.api import bma as qtbma from ..core.registry import Identity -from ..tools.decorators import asyncify +from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task class IdentitiesTabWidget(QWidget, Ui_IdentitiesTab): @@ -61,17 +61,26 @@ class IdentitiesTabWidget(QWidget, Ui_IdentitiesTab): self.button_search.addAction(direct_connections) self.button_search.clicked.connect(self._async_execute_search_text) + def cancel_once_tasks(self): + cancel_once_task(self, self.identity_context_menu) + cancel_once_task(self, self._async_execute_search_text) + cancel_once_task(self, self._async_search_members) + cancel_once_task(self, self._async_search_direct_connections) + def change_account(self, account, password_asker): + self.cancel_once_tasks() self.account = account self.password_asker = password_asker if self.account is None: self.community = None def change_community(self, community): + self.cancel_once_tasks() self.community = community self.table_identities.model().change_community(community) self._async_search_direct_connections() + @once_at_a_time @asyncify @asyncio.coroutine def identity_context_menu(self, point): @@ -160,6 +169,7 @@ class IdentitiesTabWidget(QWidget, Ui_IdentitiesTab): identity = self.sender().data() self.view_in_wot.emit(identity) + @once_at_a_time @asyncify @asyncio.coroutine def _async_execute_search_text(self, checked): @@ -175,6 +185,7 @@ class IdentitiesTabWidget(QWidget, Ui_IdentitiesTab): self.edit_textsearch.clear() self.refresh_identities(identities) + @once_at_a_time @asyncify @asyncio.coroutine def _async_search_members(self, checked=False): @@ -191,6 +202,7 @@ class IdentitiesTabWidget(QWidget, Ui_IdentitiesTab): self.edit_textsearch.clear() self.refresh_identities(identities) + @once_at_a_time @asyncify @asyncio.coroutine def _async_search_direct_connections(self, checked=False): diff --git a/src/cutecoin/gui/transactions_tab.py b/src/cutecoin/gui/transactions_tab.py index e474e944e86b011044231b705a62c8d307086fee..ac380a11539cc3e33b599c10ac7891138f3d39cc 100644 --- a/src/cutecoin/gui/transactions_tab.py +++ b/src/cutecoin/gui/transactions_tab.py @@ -11,7 +11,7 @@ from .transfer import TransferMoneyDialog from .certification import CertificationDialog from ..core.wallet import Wallet from ..core.registry import Identity -from ..tools.decorators import asyncify +from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task from .transfer import TransferMoneyDialog from . import toast @@ -39,18 +39,42 @@ class TransactionsTabWidget(QWidget, Ui_transactionsTabWidget): self.account = None self.community = None self.password_asker = None + + ts_from = self.date_from.dateTime().toTime_t() + ts_to = self.date_to.dateTime().toTime_t() + model = HistoryTableModel(self.app, self.account, self.community) + proxy = TxFilterProxyModel(ts_from, ts_to) + proxy.setSourceModel(model) + proxy.setDynamicSortFilter(True) + proxy.setSortRole(Qt.DisplayRole) + + self.table_history.setModel(proxy) + self.table_history.setSelectionBehavior(QAbstractItemView.SelectRows) + self.table_history.setSortingEnabled(True) + self.table_history.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive) + self.table_history.resizeColumnsToContents() self.progressbar.hide() self.refresh() + def cancel_once_tasks(self): + cancel_once_task(self, self.refresh_minimum_maximum) + cancel_once_task(self, self.refresh_balance) + cancel_once_task(self, self.history_context_menu) + def change_account(self, account, password_asker): + self.cancel_once_tasks() self.account = account self.password_asker = password_asker + self.table_history.model().sourceModel().change_account(account) def change_community(self, community): + self.cancel_once_tasks() self.community = community + self.table_history.model().sourceModel().change_community(self.community) self.refresh() self.stop_progress([]) + @once_at_a_time @asyncify @asyncio.coroutine def refresh_minimum_maximum(self): @@ -72,20 +96,6 @@ class TransactionsTabWidget(QWidget, Ui_transactionsTabWidget): #TODO: Use resetmodel instead of destroy/create if self.community: self.refresh_minimum_maximum() - ts_from = self.date_from.dateTime().toTime_t() - ts_to = self.date_to.dateTime().toTime_t() - - model = HistoryTableModel(self.app, self.community) - proxy = TxFilterProxyModel(ts_from, ts_to) - proxy.setSourceModel(model) - proxy.setDynamicSortFilter(True) - proxy.setSortRole(Qt.DisplayRole) - - self.table_history.setModel(proxy) - self.table_history.setSelectionBehavior(QAbstractItemView.SelectRows) - self.table_history.setSortingEnabled(True) - self.table_history.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive) - self.table_history.resizeColumnsToContents() self.refresh_balance() @@ -114,6 +124,7 @@ class TransactionsTabWidget(QWidget, Ui_transactionsTabWidget): self.table_history.model().sourceModel().refresh_transfers() self.table_history.resizeColumnsToContents() + @once_at_a_time @asyncify @asyncio.coroutine def refresh_balance(self): @@ -130,6 +141,7 @@ class TransactionsTabWidget(QWidget, Ui_transactionsTabWidget): ) ) + @once_at_a_time @asyncify @asyncio.coroutine def history_context_menu(self, point): diff --git a/src/cutecoin/gui/wot_tab.py b/src/cutecoin/gui/wot_tab.py index 89fbaf5d4e4bcdf59de3140d7ebfdca3611ff0c1..b3457d2fe1e7272882702f10e926909d66cd1b0c 100644 --- a/src/cutecoin/gui/wot_tab.py +++ b/src/cutecoin/gui/wot_tab.py @@ -6,7 +6,7 @@ from PyQt5.QtWidgets import QWidget, QComboBox, QDialog from PyQt5.QtCore import pyqtSlot, QEvent, QLocale, QDateTime from ..tools.exceptions import MembershipNotFoundError -from ..tools.decorators import asyncify +from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task from ..core.net.api import bma from ..core.graph import Graph from ..core.registry import BlockchainState @@ -56,14 +56,16 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): # create node metadata from account self._current_identity = None + def cancel_once_tasks(self): + cancel_once_task(self, self.draw_graph) + cancel_once_task(self, self.refresh_informations_frame) + cancel_once_task(self, self.reset) + def change_account(self, account, password_asker): self.account = account self.password_asker = password_asker def change_community(self, community): - if self.draw_task and not self.draw_task.done: - self.draw_task.cancel() - if self.community: self.community.network.new_block_mined.disconnect(self.refresh) if community: @@ -71,6 +73,7 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): self.community = community self.reset() + @once_at_a_time @asyncify @asyncio.coroutine def refresh_informations_frame(self): @@ -161,17 +164,10 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): ) ) - def draw_graph(self, identity): - if self.draw_task and not self.draw_task.done(): - self.draw_task.cancel() - - try: - self.draw_task = asyncio.async(self.cor_draw_graph(identity)) - except asyncio.CancelledError: - logging.debug("Cancelled drawing task") - + @once_at_a_time + @asyncify @asyncio.coroutine - def cor_draw_graph(self, identity): + def draw_graph(self, identity): """ Draw community graph centered on the identity @@ -219,6 +215,7 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): if path: self.graphicsView.scene().update_path(path) + @once_at_a_time @asyncify @asyncio.coroutine def reset(self, checked=False): diff --git a/src/cutecoin/models/identities.py b/src/cutecoin/models/identities.py index a90824ffb4d46d320dddeaae33f1b0fc44952173..431416c03d8edc0b199c0ea6264618c0e26d7350 100644 --- a/src/cutecoin/models/identities.py +++ b/src/cutecoin/models/identities.py @@ -6,7 +6,7 @@ Created on 5 févr. 2014 from ..core.net.api import bma as qtbma from ..tools.exceptions import NoPeerAvailable, MembershipNotFoundError -from ..tools.decorators import asyncify +from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task from PyQt5.QtCore import QAbstractTableModel, QSortFilterProxyModel, Qt, \ QDateTime, QModelIndex, QLocale from PyQt5.QtGui import QColor @@ -95,6 +95,7 @@ class IdentitiesTableModel(QAbstractTableModel): self._sig_validity = 0 def change_community(self, community): + cancel_once_task(self, self.refresh_identities) self.community = community def sig_validity(self): @@ -118,6 +119,7 @@ class IdentitiesTableModel(QAbstractTableModel): return (identity.uid, identity.pubkey, join_date, expiration_date) + @once_at_a_time @asyncify @asyncio.coroutine def refresh_identities(self, identities): diff --git a/src/cutecoin/models/txhistory.py b/src/cutecoin/models/txhistory.py index 27e0e71d4c4ba9b6a29bb0b8d49565a1bf1aeef1..bd22eccb338102552f416e9b0f4f278962fc3c2d 100644 --- a/src/cutecoin/models/txhistory.py +++ b/src/cutecoin/models/txhistory.py @@ -8,7 +8,7 @@ import datetime import logging import asyncio from ..core.transfer import Transfer -from ..tools.decorators import asyncify +from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, QSortFilterProxyModel, \ QDateTime, QLocale, QModelIndex @@ -171,14 +171,14 @@ class HistoryTableModel(QAbstractTableModel): A Qt abstract item model to display communities in a tree """ - def __init__(self, app, community, parent=None): + def __init__(self, app, account, community, parent=None): """ Constructor """ super().__init__(parent) self.app = app + self.account = account self.community = community - self.account._current_ref self.transfers_data = [] self.refresh_transfers() self._max_validations = 0 @@ -208,12 +208,19 @@ class HistoryTableModel(QAbstractTableModel): 'Block Number' ) - @property - def account(self): - return self.app.current_account + def change_account(self, account): + cancel_once_task(self, self.refresh_transfers) + self.account = account + + def change_community(self, community): + cancel_once_task(self, self.refresh_transfers) + self.community = community def transfers(self): - return self.account.transfers(self.community) + self.account.dividends(self.community) + if self.account: + return self.account.transfers(self.community) + self.account.dividends(self.community) + else: + return [] @asyncio.coroutine def data_received(self, transfer): @@ -273,24 +280,26 @@ class HistoryTableModel(QAbstractTableModel): deposit, "", state, id, self.account.pubkey, block_number, amount) + @once_at_a_time @asyncify @asyncio.coroutine def refresh_transfers(self): self.beginResetModel() self.transfers_data = [] - for transfer in self.transfers(): - data = None - if type(transfer) is Transfer: - if transfer.metadata['issuer'] == self.account.pubkey: - data = yield from self.data_sent(transfer) - else: - data = yield from self.data_received(transfer) - elif type(transfer) is dict: - data = yield from self.data_dividend(transfer) - if data: - self.transfers_data.append(data) - members_pubkeys = yield from self.community.members_pubkeys() - self._max_validations = self.community.network.fork_window(members_pubkeys) + 1 + if self.community: + for transfer in self.transfers(): + data = None + if type(transfer) is Transfer: + if transfer.metadata['issuer'] == self.account.pubkey: + data = yield from self.data_sent(transfer) + else: + data = yield from self.data_received(transfer) + elif type(transfer) is dict: + data = yield from self.data_dividend(transfer) + if data: + self.transfers_data.append(data) + members_pubkeys = yield from self.community.members_pubkeys() + self._max_validations = self.community.network.fork_window(members_pubkeys) + 1 self.endResetModel() def max_validations(self): @@ -303,14 +312,15 @@ class HistoryTableModel(QAbstractTableModel): return len(self.columns_types) def headerData(self, section, orientation, role): - if role == Qt.DisplayRole: - if self.columns_types[section] == 'payment' or self.columns_types[section] == 'deposit': - return '{:}\n({:})'.format( - self.column_headers[section], - self.account.current_ref.diff_units(self.community.short_currency) - ) - - return self.column_headers[section] + if self.account and self.community: + if role == Qt.DisplayRole: + if self.columns_types[section] == 'payment' or self.columns_types[section] == 'deposit': + return '{:}\n({:})'.format( + self.column_headers[section], + self.account.current_ref.diff_units(self.community.short_currency) + ) + + return self.column_headers[section] def data(self, index, role): row = index.row() diff --git a/src/cutecoin/tools/decorators.py b/src/cutecoin/tools/decorators.py index 1324e59f78cd01b90e6c2a5255ebc9a545096a8b..3842dbd419aa04b4275fe5f303ac48a5e2d8b443 100644 --- a/src/cutecoin/tools/decorators.py +++ b/src/cutecoin/tools/decorators.py @@ -1,10 +1,34 @@ import asyncio import functools +import logging + + +def cancel_once_task(object, fn): + if getattr(object, "__tasks", None): + tasks = getattr(object, "__tasks") + if fn.__name__ in tasks and not tasks[fn.__name__].done(): + getattr(object, "__tasks")[fn.__name__].cancel() + + +def once_at_a_time(fn): + @functools.wraps(fn) + def wrapper(*args, **kwargs): + if getattr(args[0], "__tasks", None) is None: + setattr(args[0], "__tasks", {}) + if fn.__name__ in args[0].__tasks: + if not args[0].__tasks[fn.__name__].done(): + args[0].__tasks[fn.__name__].cancel() + try: + args[0].__tasks[fn.__name__] = fn(*args, **kwargs) + except asyncio.CancelledError: + logging.debug("Cancelled asyncified : {0}".format(fn.__name__)) + return args[0].__tasks[fn.__name__] + return wrapper def asyncify(fn): @functools.wraps(fn) def wrapper(*args, **kwargs): - asyncio.async(asyncio.coroutine(fn)(*args, **kwargs)) + return asyncio.async(asyncio.coroutine(fn)(*args, **kwargs)) return wrapper