diff --git a/src/sakia/data/graphs/__init__.py b/src/sakia/data/graphs/__init__.py index 21318ddce99b0385dca8da9f9e90ca2314b4df3e..ed8f7ac56cb500a3e4a7cc3dd2ecfdd1d2388629 100644 --- a/src/sakia/data/graphs/__init__.py +++ b/src/sakia/data/graphs/__init__.py @@ -1,3 +1,2 @@ from .base_graph import BaseGraph -from .wot_graph import WoTGraph -from .explorer_graph import ExplorerGraph \ No newline at end of file +from .wot_graph import WoTGraph \ No newline at end of file diff --git a/src/sakia/data/graphs/base_graph.py b/src/sakia/data/graphs/base_graph.py index 58b6b946ecc35cc051440dec05e4e2aa2e12341d..e7903efb83d5c6020e036529f9070799739c1728 100644 --- a/src/sakia/data/graphs/base_graph.py +++ b/src/sakia/data/graphs/base_graph.py @@ -8,32 +8,32 @@ from sakia.constants import MAX_CONFIRMATIONS class BaseGraph(QObject): - def __init__(self, app, community, nx_graph=None): + def __init__(self, app, blockchain_service, identities_service, nx_graph): """ Init Graph instance - :param sakia.core.app.Application app: Application instance - :param sakia.core.community.Community community: Community instance + :param sakia.app.Application app: Application instance + :param sakia.services.BlockchainService blockchain_service: Blockchain service instance + :param sakia.services.IdentitiesService identities_service: Identities service instance :param networkx.Graph nx_graph: The networkx graph :return: """ super().__init__() self.app = app - self.community = community + self.identities_service = identities_service + self.blockchain_service = blockchain_service # graph empty if None parameter self.nx_graph = nx_graph if nx_graph else networkx.DiGraph() - async def arc_status(self, cert_time): + def arc_status(self, cert_time): """ Get arc status of a certification :param int cert_time: the timestamp of the certification :return: the certification time """ - parameters = await self.community.parameters() - signature_validity = parameters['sigValidity'] + parameters = self.blockchain_service.parameters() # arc considered strong during 75% of signature validity time - arc_strong = int(signature_validity * 0.75) + arc_strong = int(parameters.sig_validity * 0.75) # display validity status - ts = time.time() if (time.time() - cert_time) > arc_strong: return EdgeStatus.WEAK else: @@ -49,10 +49,10 @@ class BaseGraph(QObject): """ # new node node_status = NodeStatus.NEUTRAL - is_member = await node_identity.is_member(self.community) + await self.identities_service.refresh_requirements(node_identity) if node_identity.pubkey == account_identity.pubkey: node_status += NodeStatus.HIGHLIGHTED - if is_member is False: + if node_identity.member is False: node_status += NodeStatus.OUT return node_status @@ -64,12 +64,11 @@ class BaseGraph(QObject): :rtype: str """ try: - current_confirmations = self.community.network.confirmations(block_number) + current_confirmations = min(max(block_number - self.blockchain_service.current_buid().number, 0), 6) if MAX_CONFIRMATIONS > current_confirmations: - if self.app.preferences['expert_mode']: - return "{0}/{1}".format(current_confirmations, - MAX_CONFIRMATIONS) + if self.app.parameters.expert_mode: + return "{0}/{1}".format(current_confirmations, MAX_CONFIRMATIONS) else: confirmation = current_confirmations / MAX_CONFIRMATIONS * 100 return "{0} %".format(QLocale().toString(float(confirmation), 'f', 0)) @@ -99,79 +98,78 @@ class BaseGraph(QObject): async def add_certifier_list(self, certifier_list, identity, account_identity): """ Add list of certifiers to graph - :param list certifier_list: List of certifiers from api - :param sakia.core.registry.Identity identity: identity instance which is certified - :param sakia.core.registry.Identity account_identity: Account identity instance + :param List[sakia.data.entities.Certification] certifier_list: List of certified from api + :param sakia.data.entities.Identity identity: identity instance which is certified + :param sakia.data.entities.Identity account_identity: Account identity instance :return: """ - if self.community: - try: - # add certifiers of uid - for certifier in tuple(certifier_list): - node_status = await self.node_status(certifier['identity'], account_identity) - metadata = { - 'text': certifier['identity'].uid, - 'tooltip': certifier['identity'].pubkey, - 'status': node_status - } - self.nx_graph.add_node(certifier['identity'].pubkey, attr_dict=metadata) - - arc_status = await self.arc_status(certifier['cert_time']) - sig_validity = (await self.community.parameters())['sigValidity'] - arc = { - 'status': arc_status, - 'tooltip': QLocale.toString( - QLocale(), - QDateTime.fromTime_t(certifier['cert_time'] + sig_validity).date(), - QLocale.dateFormat(QLocale(), QLocale.ShortFormat) - ), - 'cert_time': certifier['cert_time'], - 'confirmation_text': self.confirmation_text(certifier['block_number']) - } - - self.nx_graph.add_edge(certifier['identity'].pubkey, identity.pubkey, attr_dict=arc, weight=len(certifier_list)) - except NoPeerAvailable as e: - logging.debug(str(e)) + try: + # add certifiers of uid + for certification in tuple(certifier_list): + certifier = self.identities_service.get_identity(certification.certifier) + node_status = await self.node_status(certifier, account_identity) + metadata = { + 'text': certifier.uid, + 'tooltip': certifier.pubkey, + 'status': node_status + } + self.nx_graph.add_node(certifier.pubkey, attr_dict=metadata) + + arc_status = self.arc_status(certification.timestamp) + sig_validity = self.blockchain_service.parameters().sig_validity + arc = { + 'status': arc_status, + 'tooltip': QLocale.toString( + QLocale(), + QDateTime.fromTime_t(certification.timestamp + sig_validity).date(), + QLocale.dateFormat(QLocale(), QLocale.ShortFormat) + ), + 'cert_time': certification.timestamp, + 'confirmation_text': self.confirmation_text(certification.block) + } + + self.nx_graph.add_edge(certifier.pubkey, identity.pubkey, attr_dict=arc, weight=len(certifier_list)) + except NoPeerAvailable as e: + logging.debug(str(e)) async def add_certified_list(self, certified_list, identity, account_identity): """ Add list of certified from api to graph - :param list certified_list: List of certified from api + :param List[sakia.data.entities.Certification] certified_list: List of certified from api :param identity identity: identity instance which is certifier :param identity account_identity: Account identity instance :return: """ - - if self.community: - try: - # add certified by uid - for certified in tuple(certified_list): - node_status = await self.node_status(certified['identity'], account_identity) - metadata = { - 'text': certified['identity'].uid, - 'tooltip': certified['identity'].pubkey, - 'status': node_status - } - self.nx_graph.add_node(certified['identity'].pubkey, attr_dict=metadata) - - arc_status = await self.arc_status(certified['cert_time']) - sig_validity = (await self.community.parameters())['sigValidity'] - arc = { - 'status': arc_status, - 'tooltip': QLocale.toString( - QLocale(), - QDateTime.fromTime_t(certified['cert_time'] + sig_validity).date(), - QLocale.dateFormat(QLocale(), QLocale.ShortFormat) - ), - 'cert_time': certified['cert_time'], - 'confirmation_text': self.confirmation_text(certified['block_number']) - } - - self.nx_graph.add_edge(identity.pubkey, certified['identity'].pubkey, attr_dict=arc, - weight=len(certified_list)) - - except NoPeerAvailable as e: - logging.debug(str(e)) + try: + # add certified by uid + for certification in tuple(certified_list): + certified = self.identities_service.get_identity(certification.certified) + node_status = await self.node_status(certified, account_identity) + metadata = { + 'text': certified.uid, + 'tooltip': certified.pubkey, + 'status': node_status + } + self.nx_graph.add_node(certified.pubkey, attr_dict=metadata) + + arc_status = self.arc_status(certification.timestamp) + sig_validity = self.blockchain_service.parameters().sig_validity + arc = { + 'status': arc_status, + 'tooltip': QLocale.toString( + QLocale(), + QDateTime.fromTime_t(certification.timestamp + sig_validity).date(), + QLocale.dateFormat(QLocale(), QLocale.ShortFormat) + ), + 'cert_time': certification.timestamp, + 'confirmation_text': self.confirmation_text(certification.block) + } + + self.nx_graph.add_edge(identity.pubkey, certified.pubkey, attr_dict=arc, + weight=len(certified_list)) + + except NoPeerAvailable as e: + logging.debug(str(e)) def add_identity(self, identity, status): """ diff --git a/src/sakia/data/graphs/explorer_graph.py b/src/sakia/data/graphs/explorer_graph.py deleted file mode 100644 index 43a99c4e01fb2fed3605453c837b0dc1fe85f1e5..0000000000000000000000000000000000000000 --- a/src/sakia/data/graphs/explorer_graph.py +++ /dev/null @@ -1,106 +0,0 @@ -import logging -import networkx -import asyncio -from PyQt5.QtCore import pyqtSignal -from .base_graph import BaseGraph -from ..graphs.constants import EdgeStatus, NodeStatus - - -class ExplorerGraph(BaseGraph): - - graph_changed = pyqtSignal() - current_identity_changed = pyqtSignal(str) - - def __init__(self, app, community, nx_graph=None): - """ - Init ExplorerGraph instance - :param sakia.core.app.Application app: Application instance - :param sakia.core.community.Community community: Community instance - :param networkx.Graph nx_graph: The networkx graph - :return: - """ - super().__init__(app, community, nx_graph) - self.exploration_task = None - self.explored_identity = None - self.steps = 0 - - def start_exploration(self, identity, steps): - """ - Start exploration of the wot from given identity - :param sakia.core.registry.Identity identity: The identity source of exploration - :param int steps: The number of steps from identity to explore - """ - if self.exploration_task: - if self.explored_identity is not identity or steps != self.steps: - self.exploration_task.cancel() - else: - return - self.nx_graph.clear() - self.explored_identity = identity - self.steps = steps - self.exploration_task = asyncio.ensure_future(self._explore(identity, steps)) - - def stop_exploration(self): - """ - Stop current exploration task, if present. - """ - if self.exploration_task: - self.exploration_task.cancel() - self.exploration_task = None - - async def _explore(self, identity, steps): - """ - Scan graph recursively - :param sakia.core.registry.Identity identity: identity instance from where we start - :param int steps: The number of steps from given identity to explore - :return: False when the identity is added in the graph - """ - # functions keywords args are persistent... Need to reset it with None trick - logging.debug("search %s in " % identity.uid) - - explored = [] - explorable = {0: [identity]} - current_identity = identity - self.nx_graph.clear() - self.add_identity(current_identity, NodeStatus.HIGHLIGHTED) - self.nx_graph.node[current_identity.pubkey]['is_sentry'] = False - self.graph_changed.emit() - for step in range(1, steps + 1): - explorable[step] = [] - - for step in range(0, steps): - while len(explorable[step]) > 0: - current_identity = explorable[step].pop() - # for each pubkey connected... - if current_identity not in explored: - self.current_identity_changed.emit(current_identity.pubkey) - node = self.add_identity(current_identity, NodeStatus.NEUTRAL) - self.nx_graph.node[current_identity.pubkey]['is_sentry'] = False - logging.debug("New identity explored : {pubkey}".format(pubkey=current_identity.pubkey[:5])) - self.graph_changed.emit() - - certifier_list = await current_identity.unique_valid_certifiers_of(self.app.identities_registry, - self.community) - await self.add_certifier_list(certifier_list, current_identity, identity) - logging.debug("New identity certifiers : {pubkey}".format(pubkey=current_identity.pubkey[:5])) - - is_sentry = self.is_sentry(len(certifier_list), await self.community.nb_members()) - self.nx_graph.node[current_identity.pubkey]['is_sentry'] = is_sentry - - self.graph_changed.emit() - - - certified_list = await current_identity.unique_valid_certified_by(self.app.identities_registry, - self.community) - await self.add_certified_list(certified_list, current_identity, identity) - logging.debug("New identity certified : {pubkey}".format(pubkey=current_identity.pubkey[:5])) - self.graph_changed.emit() - - for cert in certified_list + certifier_list: - if cert['identity'] not in explorable[step + 1]: - explorable[step + 1].append(cert['identity']) - - explored.append(current_identity) - logging.debug("New identity explored : {pubkey}".format(pubkey=current_identity.pubkey[:5])) - self.graph_changed.emit() - self.current_identity_changed.emit("") diff --git a/src/sakia/data/graphs/wot_graph.py b/src/sakia/data/graphs/wot_graph.py index 60ab1cd28b9ece3c61eab094f059ddda35be54ab..abe9cfb35c5b1bebd42c429c2ae3568911a3ab1d 100644 --- a/src/sakia/data/graphs/wot_graph.py +++ b/src/sakia/data/graphs/wot_graph.py @@ -6,35 +6,37 @@ from .constants import NodeStatus class WoTGraph(BaseGraph): - def __init__(self, app, community, nx_graph=None): + def __init__(self, app, blockchain_service, identities_service, nx_graph=None): """ Init WoTGraph instance - :param sakia.core.app.Application app: Application instance - :param sakia.core.community.Community community: Community instance + :param sakia.app.Application app: the app + :param sakia.data.entities.Connection connection: the connection + :param sakia.services.BlockchainService blockchain_service: the blockchain service + :param sakia.services.IdentitiesService identities_service: the identities service :param networkx.Graph nx_graph: The networkx graph :return: """ - super().__init__(app, community, nx_graph) + super().__init__(app, blockchain_service, identities_service, nx_graph) - async def initialize(self, center_identity, account_identity): - node_status = await self.node_status(center_identity, account_identity) + async def initialize(self, center_identity, connection_identity): + node_status = await self.node_status(center_identity, connection_identity) self.add_identity(center_identity, node_status) # create Identity from node metadata - certifier_coro = asyncio.ensure_future(center_identity.unique_valid_certifiers_of(self.app.identities_registry, - self.community)) - certified_coro = asyncio.ensure_future(center_identity.unique_valid_certified_by(self.app.identities_registry, - self.community)) + certifier_coro = asyncio.ensure_future(self.identities_service.load_certifiers_of(center_identity)) + certified_coro = asyncio.ensure_future(self.identities_service.load_certified_by(center_identity)) - certifier_list, certified_list = await asyncio.gather(certifier_coro, certified_coro) + await asyncio.gather(certifier_coro, certified_coro) # populate graph with certifiers-of + certifier_list = self.identities_service.certifications_received(center_identity.pubkey) certifier_coro = asyncio.ensure_future(self.add_certifier_list(certifier_list, - center_identity, account_identity)) + center_identity, connection_identity)) # populate graph with certified-by + certified_list = self.identities_service.certifications_sent(center_identity.pubkey) certified_coro = asyncio.ensure_future(self.add_certified_list(certified_list, - center_identity, account_identity)) + center_identity, connection_identity)) await asyncio.gather(certifier_coro, certified_coro) @@ -61,21 +63,20 @@ class WoTGraph(BaseGraph): return path - async def explore_to_find_member(self, account_identity, to_identity): + async def explore_to_find_member(self, from_identity, to_identity): """ Scan graph to find identity - :param sakia.core.registry.Identity from_identity: Scan starting point - :param sakia.core.registry.Identity to_identity: Scan goal + :param sakia.data.entities.Identity from_identity: Scan starting point + :param sakia.data.entities.Identity to_identity: Scan goal """ explored = [] - explorable = [account_identity] + explorable = [from_identity] while len(explorable) > 0: current = explorable.pop() - certified_list = await current.unique_valid_certified_by(self.app.identities_registry, - self.community) + certified_list = await self.identities_service.certifications_sent(current.pubkey) - await self.add_certified_list(certified_list, current, account_identity) + await self.add_certified_list(certified_list, current, from_identity) if to_identity.pubkey in [data['identity'].pubkey for data in certified_list]: return True diff --git a/src/sakia/data/processors/certifications.py b/src/sakia/data/processors/certifications.py index c49d9253c9f10fe8b2b7e121b2ae0f39f393b8bf..17455bb5f8fce020aaab4067d13b27f9a1593ae4 100644 --- a/src/sakia/data/processors/certifications.py +++ b/src/sakia/data/processors/certifications.py @@ -26,6 +26,24 @@ class CertificationsProcessor: return cls(app.db.certifications_repo, app.db.identities_repo, BmaConnector(NodesProcessor(app.db.nodes_repo))) + def certifications_sent(self, currency, pubkey): + """ + Get the list of certifications sent for a given pubkey + :param str currency: + :param str pubkey: + :rtype: List[sakia.data.entities.Certification] + """ + return self._certifications_repo.get_all(currency=currency, certifier=pubkey) + + def certifications_received(self, currency, pubkey): + """ + Get the list of certifications sent for a given pubkey + :param str currency: + :param str pubkey: + :rtype: List[sakia.data.entities.Certification] + """ + return self._certifications_repo.get_all(currency=currency, certified=pubkey) + def create_certification(self, currency, cert, blockstamp): """ Creates a certification and insert it in the db diff --git a/src/sakia/gui/navigation/controller.py b/src/sakia/gui/navigation/controller.py index f45fabc385ee161def41b07757020215f30a5e38..cccc645ab84d4abc33686bbef14d05ef9407355a 100644 --- a/src/sakia/gui/navigation/controller.py +++ b/src/sakia/gui/navigation/controller.py @@ -7,7 +7,6 @@ from .network.controller import NetworkController from .identities.controller import IdentitiesController from .informations.controller import InformationsController from .graphs.wot.controller import WotController -from .graphs.explorer.controller import ExplorerController from sakia.data.repositories import ConnectionsRepo from sakia.data.entities import Connection from PyQt5.QtCore import pyqtSignal @@ -34,8 +33,7 @@ class NavigationController(ComponentController): 'Network': NetworkController, 'Identities': IdentitiesController, 'Informations': InformationsController, - 'Wot': WotController, - 'Explorer': ExplorerController + 'Wot': WotController } self.view.current_view_changed.connect(self.handle_view_change) diff --git a/src/sakia/gui/navigation/graphs/base/controller.py b/src/sakia/gui/navigation/graphs/base/controller.py index 9417e02b891ab038083abf4dc85c3eb10f273887..a8e57dc263f850c9c65df9cda7bf1760da3e750e 100644 --- a/src/sakia/gui/navigation/graphs/base/controller.py +++ b/src/sakia/gui/navigation/graphs/base/controller.py @@ -62,13 +62,12 @@ class BaseGraphController(ComponentController): """ raise NotImplementedError("refresh not implemented") - @asyncify - async def node_context_menu(self, pubkey): + def node_context_menu(self, pubkey): """ Open the node context menu :param str pubkey: the pubkey of the node to open """ - identity = await self.model.get_identity(pubkey) + identity = self.model.get_identity(pubkey) menu = ContextMenu.from_data(self.view, self.model.app, self.model.account, self.model.community, self.password_asker, (identity,)) menu.view_identity_in_wot.connect(self.draw_graph) diff --git a/src/sakia/gui/navigation/graphs/base/graph_tab.py b/src/sakia/gui/navigation/graphs/base/graph_tab.py deleted file mode 100644 index 9879cfb65c114babe56a74ae85b93902e29e77b8..0000000000000000000000000000000000000000 --- a/src/sakia/gui/navigation/graphs/base/graph_tab.py +++ /dev/null @@ -1,114 +0,0 @@ -from PyQt5.QtWidgets import QWidget -from PyQt5.QtCore import pyqtSlot, QEvent, QLocale, QDateTime, pyqtSignal, QObject -from PyQt5.QtGui import QCursor - -from ...tools.exceptions import MembershipNotFoundError -from ...tools.decorators import asyncify, once_at_a_time -from ...core.registry import BlockchainState -from ..widgets.context_menu import ContextMenu - - -class GraphTabWidget(QObject): - - money_sent = pyqtSignal() - - def __init__(self, app, account=None, community=None, password_asker=None, widget=QWidget): - """ - :param sakia.core.app.Application app: Application instance - :param sakia.core.app.Application app: Application instance - :param sakia.core.Account account: The account displayed in the widget - :param sakia.core.Community community: The community displayed in the widget - :param sakia.gui.Password_Asker: password_asker: The widget to ask for passwords - :param class widget: The class of the graph tab - """ - super().__init__() - - self.widget = widget() - self.account = account - self.community = community - self.password_asker = password_asker - - self.app = app - - - @once_at_a_time - @asyncify - async def refresh_informations_frame(self): - parameters = self.community.parameters - try: - identity = await self.account.identity(self.community) - membership = identity.membership(self.community) - renew_block = membership['blockNumber'] - last_renewal = self.community.get_block(renew_block)['medianTime'] - expiration = last_renewal + parameters['sigValidity'] - except MembershipNotFoundError: - last_renewal = None - expiration = None - - certified = await identity.unique_valid_certified_by(self.app.identities_registry, self.community) - certifiers = await identity.unique_valid_certifiers_of(self.app.identities_registry, self.community) - if last_renewal and expiration: - date_renewal = QLocale.toString( - QLocale(), - QDateTime.fromTime_t(last_renewal).date(), QLocale.dateFormat(QLocale(), QLocale.LongFormat) - ) - date_expiration = QLocale.toString( - QLocale(), - QDateTime.fromTime_t(expiration).date(), QLocale.dateFormat(QLocale(), QLocale.LongFormat) - ) - - if self.account.pubkey in self.community.members_pubkeys(): - # set infos in label - self.label_general.setText( - self.tr(""" - <table cellpadding="5"> - <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr> - <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr> - <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr> - </table> - """).format( - self.account.name, self.account.pubkey, - self.tr("Membership"), - self.tr("Last renewal on {:}, expiration on {:}").format(date_renewal, date_expiration), - self.tr("Your web of trust"), - self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers), - len(certified)) - ) - ) - else: - # set infos in label - self.label_general.setText( - self.tr(""" - <table cellpadding="5"> - <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr> - <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr> - <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr> - </table> - """).format( - self.account.name, self.account.pubkey, - self.tr("Not a member"), - self.tr("Last renewal on {:}, expiration on {:}").format(date_renewal, date_expiration), - self.tr("Your web of trust"), - self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers), - len(certified)) - ) - ) - else: - # set infos in label - self.label_general.setText( - self.tr(""" - <table cellpadding="5"> - <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr> - <tr><td align="right"><b>{:}</b></td></tr> - <tr><td align="right"><b>{:}</b></td><td>{:}</td></tr> - </table> - """).format( - self.account.name, self.account.pubkey, - self.tr("Not a member"), - self.tr("Your web of trust"), - self.tr("Certified by {:} members; Certifier of {:} members").format(len(certifiers), - len(certified)) - ) - ) - - diff --git a/src/sakia/gui/navigation/graphs/base/model.py b/src/sakia/gui/navigation/graphs/base/model.py index 8e3abc814303391e21240d8c9e43174735fecd42..c52f269303b920d62e235f4bd8b23db00174f62f 100644 --- a/src/sakia/gui/navigation/graphs/base/model.py +++ b/src/sakia/gui/navigation/graphs/base/model.py @@ -6,16 +6,26 @@ class BaseGraphModel(ComponentModel): The model of Navigation component """ - def __init__(self, parent, app, account, community): + def __init__(self, parent, app, connection, blockchain_service, identities_service): + """ + Constructor of a model of WoT component + + :param sakia.gui.identities.controller.IdentitiesController parent: the controller + :param sakia.app.Application app: the app + :param sakia.data.entities.Connection connection: the connection + :param sakia.services.BlockchainService blockchain_service: the blockchain service + :param sakia.services.IdentitiesService identities_service: the identities service + """ super().__init__(parent) self.app = app - self.account = account - self.community = community + self.connection = connection + self.blockchain_service = blockchain_service + self.identities_service = identities_service - async def get_identity(self, pubkey): + def get_identity(self, pubkey): """ Get identity from pubkey :param str pubkey: Identity pubkey :rtype: sakia.core.registry.Identity """ - return await self.app.identities_registry.future_find(pubkey, self.community) + return self.identities_service.get_identity(pubkey, self.community) diff --git a/src/sakia/gui/navigation/graphs/explorer/__init__.py b/src/sakia/gui/navigation/graphs/explorer/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/src/sakia/gui/navigation/graphs/explorer/controller.py b/src/sakia/gui/navigation/graphs/explorer/controller.py deleted file mode 100644 index 5124dfaf450d06c4f1344820c9229e37d8d70b99..0000000000000000000000000000000000000000 --- a/src/sakia/gui/navigation/graphs/explorer/controller.py +++ /dev/null @@ -1,93 +0,0 @@ -import asyncio - -from sakia.decorators import asyncify, once_at_a_time -from sakia.gui.sub.search_user.controller import SearchUserController -from .model import ExplorerModel -from .view import ExplorerView -from ..base.controller import BaseGraphController - - -class ExplorerController(BaseGraphController): - """ - The homescreen view - """ - - def __init__(self, parent, view, model, password_asker=None): - """ - Constructor of the explorer component - - :param sakia.gui.graphs.explorer.view.ExplorerView: the view - :param sakia.gui.graphs.explorer.model.ExplorerModel model: the model - """ - super().__init__(parent, view, model, password_asker) - self.set_scene(view.scene()) - self.reset() - self.view.button_go.clicked.connect(self.start_exploration) - self.model.graph.graph_changed.connect(self.refresh) - self.model.graph.current_identity_changed.connect(self.view.update_current_identity) - - def center_on_identity(self, identity): - """ - Draw community graph centered on the identity - - :param sakia.core.registry.Identity identity: Center identity - """ - asyncio.ensure_future(self.draw_graph(identity)) - - def start_exploration(self): - self.model.graph.stop_exploration() - self.model.graph.start_exploration(self.model.identity, self.view.steps()) - - @property - def view(self) -> ExplorerView: - return self._view - - @property - def model(self) -> ExplorerModel: - return self._model - - @classmethod - def create(cls, parent, app, **kwargs): - account = kwargs['account'] - community = kwargs['community'] - - view = ExplorerView(parent.view) - model = ExplorerModel(None, app, account, community) - explorer = cls(parent, view, model) - model.setParent(explorer) - search_user = SearchUserController.create(explorer, app, **{'account': account, - 'community': community}) - explorer.view.set_search_user(search_user.view) - search_user.identity_selected.connect(explorer.center_on_identity) - return explorer - - async def draw_graph(self, identity): - """ - Draw community graph centered on the identity - - :param sakia.core.registry.Identity identity: Center identity - """ - self.view.update_wot(self.model.graph.nx_graph, identity) - - @once_at_a_time - @asyncify - async def refresh(self): - """ - Refresh graph scene to current metadata - """ - await self.draw_graph(self.model.identity) - self.view.update_wot(self.model.graph.nx_graph, self.model.identity) - - @once_at_a_time - @asyncify - async def reset(self, checked=False): - """ - Reset graph scene to wallet identity - """ - # draw graph in qt scene - self.view.scene().clear() - self.view.reset_steps() - maximum_steps = await self.model.maximum_steps() - self.view.set_steps_max(maximum_steps) - await self.model.set_identity(None) - await self.draw_graph(self.model.identity) diff --git a/src/sakia/gui/navigation/graphs/explorer/edge.py b/src/sakia/gui/navigation/graphs/explorer/edge.py deleted file mode 100644 index 085538aac0961cce51fb0b0922e4077f6a850fc2..0000000000000000000000000000000000000000 --- a/src/sakia/gui/navigation/graphs/explorer/edge.py +++ /dev/null @@ -1,156 +0,0 @@ -import math - -from PyQt5.QtCore import Qt, QRectF, QLineF, QPointF, QSizeF, \ - qFuzzyCompare -from PyQt5.QtGui import QColor, QPen, QPolygonF - -from sakia.data.graphs.constants import EdgeStatus -from ..base.edge import BaseEdge - - -class ExplorerEdge(BaseEdge): - def __init__(self, source_node, destination_node, metadata, nx_pos, steps, steps_max): - """ - Create an arc between two nodes - - :param Node source_node: Source node of the arc - :param Node destination_node: Destination node of the arc - :param dict metadata: Arc metadata - :param dict nx_pos: The position generated by nx_graph - :param int steps: The steps from the center identity - :param int steps_max: The steps max of the graph - """ - super().__init__(source_node, destination_node, metadata, nx_pos) - - self.source_point = self.destination_point - self.steps = steps - self.steps_max = steps_max - self.highlighted = False - - self.arrow_size = 5 - # cursor change on hover - self.setAcceptHoverEvents(True) - self.setZValue(0) - self._line_styles = { - EdgeStatus.STRONG: Qt.SolidLine, - EdgeStatus.WEAK: Qt.DashLine - } - self.timeline = None - - @property - def line_style(self): - return self._line_styles[self.status] - - # virtual function require subclassing - def boundingRect(self): - """ - Return the bounding rectangle size - - :return: QRectF - """ - if not self.source or not self.destination: - return QRectF() - pen_width = 1.0 - extra = (pen_width + self.arrow_size) / 2.0 - - return QRectF( - self.source_point, QSizeF( - self.destination_point.x() - self.source_point.x(), - self.destination_point.y() - self.source_point.y() - ) - ).normalized().adjusted( - -extra, - -extra, - extra, - extra - ) - - def paint(self, painter, option, widget): - """ - Customize line adding an arrow head - - :param QPainter painter: Painter instance of the item - :param option: Painter option of the item - :param widget: Widget instance - """ - if not self.source or not self.destination: - return - line = QLineF(self.source_point, self.destination_point) - if qFuzzyCompare(line.length(), 0): - return - - # Draw the line itself - color = QColor() - color.setHsv(120 - 60 / self.steps_max * self.steps, - 180 + 50 / self.steps_max * self.steps, - 150 + 80 / self.steps_max * self.steps) - if self.highlighted: - color.setHsv(0, 0, 0) - - style = self.line_style - - painter.setPen(QPen(color, 1, style, Qt.RoundCap, Qt.RoundJoin)) - painter.drawLine(line) - painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) - - # Draw the arrows - angle = math.acos(line.dx() / line.length()) - if line.dy() >= 0: - angle = (2.0 * math.pi) - angle - - # arrow in the middle of the arc - hpx = line.p1().x() + (line.dx() / 2.0) - hpy = line.p1().y() + (line.dy() / 2.0) - head_point = QPointF(hpx, hpy) - - painter.setPen(QPen(color, 1, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) - destination_arrow_p1 = head_point + QPointF( - math.sin(angle - math.pi / 3) * self.arrow_size, - math.cos(angle - math.pi / 3) * self.arrow_size) - destination_arrow_p2 = head_point + QPointF( - math.sin(angle - math.pi + math.pi / 3) * self.arrow_size, - math.cos(angle - math.pi + math.pi / 3) * self.arrow_size) - - painter.setBrush(color) - painter.drawPolygon(QPolygonF([head_point, destination_arrow_p1, destination_arrow_p2])) - - if self.metadata["confirmation_text"]: - painter.drawText(head_point, self.metadata["confirmation_text"]) - - def move_source_point(self, node_id, x, y): - """ - Move to corresponding position - :param str node_id: the node id - :param float x: x coordinates - :param float y: y coordinates - :return: - """ - if node_id == self.source: - self.source_point = QPointF(x, y) - self.update(self.boundingRect()) - - def move_destination_point(self, node_id, x, y): - """ - Move to corresponding position - :param str node_id: the node id - :param float x: x coordinates - :param float y: y coordinates - :return: - """ - if node_id == self.destination: - self.destination_point = QPointF(x, y) - self.update(self.boundingRect()) - - def highlight(self): - """ - Highlight the edge in the scene - """ - self.highlighted = True - self.update(self.boundingRect()) - - def neutralize(self): - """ - Neutralize the edge in the scene - """ - self.highlighted = False - self.update(self.boundingRect()) diff --git a/src/sakia/gui/navigation/graphs/explorer/explorer.ui b/src/sakia/gui/navigation/graphs/explorer/explorer.ui deleted file mode 100644 index c8ccca83a0ab54c652b6643b9ed6e32bf124b207..0000000000000000000000000000000000000000 --- a/src/sakia/gui/navigation/graphs/explorer/explorer.ui +++ /dev/null @@ -1,86 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>ExplorerWidget</class> - <widget class="QWidget" name="ExplorerWidget"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>522</width> - <height>442</height> - </rect> - </property> - <property name="windowTitle"> - <string>Form</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="ExplorerGraphicsView" name="graphics_view"> - <property name="viewportUpdateMode"> - <enum>QGraphicsView::BoundingRectViewportUpdate</enum> - </property> - </widget> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <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="QLabel" name="label"> - <property name="text"> - <string>Steps</string> - </property> - </widget> - </item> - <item> - <widget class="QSlider" name="steps_slider"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="tickPosition"> - <enum>QSlider::TicksBothSides</enum> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="button_go"> - <property name="text"> - <string>Go</string> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - <customwidgets> - <customwidget> - <class>ExplorerGraphicsView</class> - <extends>QGraphicsView</extends> - <header>sakia.gui.navigation.graphs.explorer.graphics_view</header> - </customwidget> - </customwidgets> - <resources> - <include location="../../../../../../res/icons/icons.qrc"/> - </resources> - <connections/> - <slots> - <slot>reset()</slot> - <slot>search()</slot> - <slot>select_node()</slot> - </slots> -</ui> diff --git a/src/sakia/gui/navigation/graphs/explorer/graphics_view.py b/src/sakia/gui/navigation/graphs/explorer/graphics_view.py deleted file mode 100644 index 3229282c5f63c2cd16d79cf7ffa0d40df6b8ba7b..0000000000000000000000000000000000000000 --- a/src/sakia/gui/navigation/graphs/explorer/graphics_view.py +++ /dev/null @@ -1,43 +0,0 @@ -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPainter, QWheelEvent -from PyQt5.QtWidgets import QGraphicsView -from .scene import ExplorerScene - - -class ExplorerGraphicsView(QGraphicsView): - def __init__(self, parent=None): - """ - Create View to display scene - - :param parent: [Optional, default=None] Parent widget - """ - super().__init__(parent) - - self.setScene(ExplorerScene(self)) - - self.setCacheMode(QGraphicsView.CacheBackground) - self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate) - self.setRenderHint(QPainter.Antialiasing) - self.setRenderHint(QPainter.SmoothPixmapTransform) - - def wheelEvent(self, event: QWheelEvent): - """ - Zoom in/out on the mouse cursor - """ - # zoom only when CTRL key pressed - if (event.modifiers() & Qt.ControlModifier) == Qt.ControlModifier: - steps = event.angleDelta().y() / 15 / 8 - - if steps == 0: - event.ignore() - return - - # scale factor 1.25 - sc = pow(1.25, steps) - self.scale(sc, sc) - self.centerOn(self.mapToScene(event.pos())) - event.accept() - # act normally on scrollbar - else: - # transmit event to parent class wheelevent - super().wheelEvent(event) \ No newline at end of file diff --git a/src/sakia/gui/navigation/graphs/explorer/model.py b/src/sakia/gui/navigation/graphs/explorer/model.py deleted file mode 100644 index 110b2410dd13bfd3b4bd12a93b6fe3228e5c136b..0000000000000000000000000000000000000000 --- a/src/sakia/gui/navigation/graphs/explorer/model.py +++ /dev/null @@ -1,55 +0,0 @@ -from sakia.errors import NoPeerAvailable - -from sakia.data.graphs import ExplorerGraph -from ..base.model import BaseGraphModel - - -class ExplorerModel(BaseGraphModel): - """ - The model of Navigation component - """ - - def __init__(self, parent, app, account, community): - super().__init__(parent, app, account, community) - self.app = app - self.account = account - self.community = community - self.explorer_graph = ExplorerGraph(self.app, self.community) - self.identity = None - - @property - def graph(self): - """ - Return underlying graph object - """ - return self.explorer_graph - - async def set_identity(self, identity): - """ - Change current identity - If identity is None, it defaults to account identity - :param sakia.core.registry.Identity identity: the new identity to show - :return: - """ - if identity: - self.identity = identity - else: - identity_account = await self.account.identity(self.community) - self.identity = identity_account - - async def start_exploration(self, steps): - """ - Get nx graph of current identity wot graph - :rtype: sakia.core.registry.Identity - """ - return self.explorer_graph.start_exploration(self.identity, steps) - - async def maximum_steps(self): - """ - Get the maximum steps to display in the view - """ - try: - parameters = await self.community.parameters() - return parameters['stepMax'] - except NoPeerAvailable: - return 0 diff --git a/src/sakia/gui/navigation/graphs/explorer/node.py b/src/sakia/gui/navigation/graphs/explorer/node.py deleted file mode 100644 index 19dda987cbf73bd95549b6818dbe5a7fd923f90a..0000000000000000000000000000000000000000 --- a/src/sakia/gui/navigation/graphs/explorer/node.py +++ /dev/null @@ -1,191 +0,0 @@ -import math - -from PyQt5.QtCore import Qt, QPointF, QTimeLine, QTimer -from PyQt5.QtGui import QTransform, QColor, QPen, QBrush, QRadialGradient -from PyQt5.QtWidgets import QGraphicsSimpleTextItem - -from sakia.data.graphs.constants import NodeStatus -from ..base.node import BaseNode - - -class ExplorerNode(BaseNode): - def __init__(self, nx_node, center_pos, nx_pos, steps, steps_max, small): - """ - Create node in the graph scene - - :param tuple nx_node: Node info - :param center_pos: The position of the center node - :param nx_pos: Position of the nodes in the graph - :param int steps: The steps from the center identity - :param int steps_max: The steps max of the graph - :param bool small: Small dots for big networks - """ - super().__init__(nx_node, nx_pos) - - self.steps = steps - self.steps_max = steps_max - self.highlighted = False - self.status_sentry = False - - if small: - self.setRect( - 0, - 0, - 10, - 10 - ) - self.text_item = None - else: - # text inside ellipse - self.text_item = QGraphicsSimpleTextItem(self) - self.text_item.setText(self.text) - # center ellipse around text - self.setRect( - 0, - 0, - self.text_item.boundingRect().width() * 2, - self.text_item.boundingRect().height() * 2 - ) - # center text in ellipse - self.text_item.setPos(self.boundingRect().width() / 4.0, self.boundingRect().height() / 4.0) - - - # set anchor to the center - self.setTransform( - QTransform().translate(-self.boundingRect().width() / 2.0, -self.boundingRect().height() / 2.0)) - - # cursor change on hover - self.setAcceptHoverEvents(True) - self.setZValue(1) - - # animation and moves - self.timeline = None - self.loading_timer = QTimer() - self.loading_timer.timeout.connect(self.next_tick) - self.loading_counter = 0 - self._refresh_colors() - self.setPos(center_pos) - self.move_to(nx_pos) - - def update_metadata(self, metadata): - super().update_metadata(metadata) - self.status_sentry = self.metadata['is_sentry'] if 'is_sentry' in self.metadata else False - self._refresh_colors() - - def _refresh_colors(self): - """ - Refresh elements in the node - """ - # color around ellipse - outline_color = QColor('grey') - outline_style = Qt.SolidLine - outline_width = 1 - if self.status_wallet: - outline_width = 2 - if not self.status_member: - outline_color = QColor('red') - - if self.status_sentry: - outline_color = QColor('black') - outline_width = 3 - - self.setPen(QPen(outline_color, outline_width, outline_style)) - - if self.highlighted: - text_color = QColor('grey') - else: - text_color = QColor('black') - - if self.status_wallet == NodeStatus.HIGHLIGHTED: - text_color = QColor('grey') - - if self.text_item: - self.text_item.setBrush(QBrush(text_color)) - - # create gradient inside the ellipse - gradient = QRadialGradient(QPointF(0, self.boundingRect().height() / 4), self.boundingRect().width()) - color = QColor() - color.setHsv(120 - 60 / self.steps_max * self.steps, - 180 + 50 / self.steps_max * self.steps, - 60 + 170 / self.steps_max * self.steps) - if self.highlighted: - color = color.darker(200) - color = color.lighter(math.fabs(math.sin(self.loading_counter / 100 * math.pi) * 100) + 100) - gradient.setColorAt(0, color) - gradient.setColorAt(1, color.darker(150)) - self.setBrush(QBrush(gradient)) - - def move_to(self, nx_pos): - """ - Move to corresponding position - :param nx_pos: - :return: - """ - origin_x = self.x() - origin_y = self.y() - final_x = nx_pos[self.id][0] - final_y = nx_pos[self.id][1] - - def frame_move(frame): - value = self.timeline.valueForTime(self.timeline.currentTime()) - x = origin_x + (final_x - origin_x) * value - y = origin_y + (final_y - origin_y) * value - self.setPos(x, y) - if self.scene(): - self.scene().node_moved.emit(self.id, x, y) - - def timeline_ends(): - self.setPos(final_x, final_y) - self.timeline = None - - # Remember to hold the references to QTimeLine and QGraphicsItemAnimation instances. - # They are not kept anywhere, even if you invoke QTimeLine.start(). - self.timeline = QTimeLine(1000) - self.timeline.setFrameRange(0, 100) - self.timeline.frameChanged.connect(frame_move) - self.timeline.finished.connect(timeline_ends) - - self.timeline.start() - - def highlight(self): - """ - Highlight the edge in the scene - """ - self.highlighted = True - self._refresh_colors() - self.update(self.boundingRect()) - - def neutralize(self): - """ - Neutralize the edge in the scene - """ - self.highlighted = False - self._refresh_colors() - self.update(self.boundingRect()) - - def start_loading_animation(self): - """ - Neutralize the edge in the scene - """ - if not self.loading_timer.isActive(): - self.loading_timer.start(10) - - def stop_loading_animation(self): - """ - Neutralize the edge in the scene - """ - self.loading_timer.stop() - self.loading_counter = 100 - self._refresh_colors() - self.update(self.boundingRect()) - - def next_tick(self): - """ - Next tick - :return: - """ - self.loading_counter += 1 - self.loading_counter %= 100 - self._refresh_colors() - self.update(self.boundingRect()) - diff --git a/src/sakia/gui/navigation/graphs/explorer/scene.py b/src/sakia/gui/navigation/graphs/explorer/scene.py deleted file mode 100644 index 1036bc3809e94c3789e0eb5339355f24bba60b54..0000000000000000000000000000000000000000 --- a/src/sakia/gui/navigation/graphs/explorer/scene.py +++ /dev/null @@ -1,309 +0,0 @@ -import networkx -import logging -import math -from PyQt5.QtCore import QPoint, pyqtSignal -from PyQt5.QtWidgets import QGraphicsScene - -from .edge import ExplorerEdge -from .node import ExplorerNode - -from ..base.scene import BaseScene - - -class ExplorerScene(BaseScene): - - node_moved = pyqtSignal(str, float, float) - - def __init__(self, parent=None): - """ - Create scene of the graph - - :param parent: [Optional, default=None] Parent view - """ - super().__init__(parent) - - self.lastDragPos = QPoint() - self.setItemIndexMethod(QGraphicsScene.NoIndex) - - # list of nodes in scene - self.nodes = dict() - # axis of the scene for debug purpose - # self.addLine(-100, 0, 100, 0) - # self.addLine(0, -100, 0, 100) - self.node_hovered.connect(self.display_path_to) - - # list of nodes in scene - self.nodes = dict() - self.edges = dict() - self.busy = None - self.nx_graph = None - self.identity = None - # axis of the scene for debug purpose - # self.addLine(-100, 0, 100, 0) - # self.addLine(0, -100, 0, 100) - - @staticmethod - def _init_layout(nx_graph): - """ - Init the data of the layout - :param MultiGraph nx_graph: - """ - data = {} - INF = len(nx_graph.nodes()) * len(nx_graph.nodes()) - - for node in nx_graph.nodes(): - data[node] = { - 'theta': None, - 'scenter': INF, - 'nchild': 0, - 'sparent': None, - 'stsize': 0.0, - 'span': 0.0 - } - return data - - @staticmethod - def _set_nstep_to_center(nx_graph, data, current): - """ - Set the number of steps to the center - :param networkx.MultiGraph nx_graph: the graph - :param dict data: the data of the layout - """ - queue = [current] - while queue: - n = queue.pop() - nsteps = data[n]['scenter'] + 1 - for edge in networkx.edges(nx_graph.to_undirected(), n): - next_node = edge[0] if edge[0] is not n else edge[1] - if data[next_node]['sparent']: - continue - if nsteps < data[next_node]['scenter']: - data[next_node]['scenter'] = nsteps - data[next_node]['sparent'] = n - data[n]['nchild'] += 1 - queue.append(next_node) - - @staticmethod - def _set_parent_nodes(nx_graph, data, center): - """ - Set the parent of each node - :param networkx.MultiGraph nx_graph: the graph - :param dict data: the data of the layout - :param str center: the id of the node at the center - """ - unset = data[center]['scenter'] - data[center]['scenter'] = 0 - data[center]['sparent'] = None - - logging.debug("Parent node of {0}".format(center)) - ExplorerScene._set_nstep_to_center(nx_graph, data, center) - for node in nx_graph.nodes(): - if data[node]['scenter'] == unset: - return -1 - return max([n['scenter'] for n in data.values()]) - - @staticmethod - def _set_subtree_size(nx_graph, data): - """ - Compute the subtree size of each node, which is the - number of leaves in subtree rooted to the node - :param networkx.MultiGraph nx_graph: the graph - :param dict data: - """ - for node in nx_graph.nodes(): - if data[node]['nchild'] > 0: - continue - data[node]['stsize'] += 1 - parent = data[node]['sparent'] - while parent: - data[parent]['stsize'] += 1 - parent = data[parent]['sparent'] - - @staticmethod - def _set_subtree_spans(nx_graph, data, current): - """ - Compute the subtree spans of each node - :param networkx.MultiGraph nx_graph: the graph - :param dict data: the data of the layout - :param str current: the current node which we compute the subtree - """ - ratio = data[current]['span'] / data[current]['stsize'] - for edge in nx_graph.to_undirected().edges(current): - next_node = edge[0] if edge[0] != current else edge[1] - if data[next_node]['sparent'] != current: - continue - if data[next_node]['span'] != 0.0: - continue - - data[next_node]['span'] = ratio * data[next_node]['stsize'] - if data[next_node]['nchild'] > 0: - ExplorerScene._set_subtree_spans(nx_graph, data, next_node) - - @staticmethod - def _set_positions(nx_graph, data, current): - """ - Compute the polar positions of each node - :param networkx.MultiDiGraph nx_graph: the graph - :param dict data: the data of the layout - :param str current: the current node which we compute the subtree - """ - if not data[current]['sparent']: - theta = 0 - else: - theta = data[current]['theta'] - data[current]['span'] / 2 - - for edge in nx_graph.to_undirected().edges(current): - next_node = edge[0] if edge[0] != current else edge[1] - if data[next_node]['sparent'] != current: - continue - if data[next_node]['theta']: - continue - - data[next_node]['theta'] = theta + data[next_node]['span'] / 2.0 - theta += data[next_node]['span'] - if data[next_node]['nchild'] > 0: - ExplorerScene._set_positions(nx_graph, data, next_node) - - @staticmethod - def twopi_layout(nx_graph, center=None): - """ - Render the twopi layout. Ported from C code available at - https://github.com/ellson/graphviz/blob/master/lib/twopigen/circle.c - - :param networkx.MultiDiGraph nx_graph: the networkx graph - :param str center: the centered node - :return: - """ - if len(nx_graph.nodes()) == 0: - return {} - - if len(nx_graph.nodes()) == 1: - return {nx_graph.nodes()[0]: (0, 0)} - #nx_graph = nx_graph.to_undirected() - - data = ExplorerScene._init_layout(nx_graph) - if not center: - center = networkx.center(nx_graph)[0] - ExplorerScene._set_parent_nodes(nx_graph, data, center) - ExplorerScene._set_subtree_size(nx_graph, data) - data[center]['span'] = 2 * math.pi - ExplorerScene._set_subtree_spans(nx_graph, data, center) - data[center]['theta'] = 0.0 - ExplorerScene._set_positions(nx_graph, data, center) - - distances = networkx.shortest_path_length(nx_graph.to_undirected(), center) - nx_pos = {} - for node in nx_graph.nodes(): - hyp = distances[node] + 1 - theta = data[node]['theta'] - nx_pos[node] = (hyp * math.cos(theta) * 100, hyp * math.sin(theta) * 100) - return nx_pos - - def clear(self): - """ - clear the scene - """ - for node in self.nodes.values(): - if node.timeline: - node.timeline.stop() - - self.nodes.clear() - self.edges.clear() - super().clear() - - def update_current_identity(self, identity_pubkey): - """ - Update the current identity loaded - - :param str identity_pubkey: - """ - for node in self.nodes.values(): - node.stop_loading_animation() - - if identity_pubkey in self.nodes: - self.nodes[identity_pubkey].start_loading_animation() - - def update_wot(self, nx_graph, identity, dist_max): - """ - draw community graph - - :param networkx.Graph nx_graph: graph to draw - :param sakia.core.registry.Identity identity: the wot of the identity - :param dist_max: the dist_max to display - """ - # clear scene - self.identity = identity - self.nx_graph = nx_graph.copy() - - graph_pos = ExplorerScene.twopi_layout(nx_graph, center=identity.pubkey) - if len(nx_graph.nodes()) > 0: - distances = networkx.shortest_path_length(nx_graph.to_undirected(), identity.pubkey) - else: - distances = {} - - # create networkx graph - for nx_node in nx_graph.nodes(data=True): - if nx_node[0] in self.nodes: - v = self.nodes[nx_node[0]] - v.move_to(graph_pos) - v.update_metadata(nx_node[1]) - else: - center_pos = None - if len(nx_graph.edges(nx_node[0])) > 0: - for edge in nx_graph.edges(nx_node[0]): - neighbour = edge[0] if edge[0] != nx_node[0] else edge[1] - if neighbour in self.nodes: - center_pos = self.nodes[neighbour].pos() - break - if not center_pos: - if identity.pubkey in self.nodes: - center_pos = self.nodes[identity.pubkey].pos() - else: - center_pos = QPoint(0, 0) - - small = distances[nx_node[0]] > 1 - - v = ExplorerNode(nx_node, center_pos, graph_pos, distances[nx_node[0]], dist_max, small) - self.addItem(v) - self.nodes[nx_node[0]] = v - - for edge in nx_graph.edges(data=True): - edge[2]["confirmation_text"] = "" - if (edge[0], edge[1]) not in self.edges: - distance = max(self.nodes[edge[0]].steps, self.nodes[edge[1]].steps) - explorer_edge = ExplorerEdge(edge[0], edge[1], edge[2], graph_pos, distance, dist_max) - self.node_moved.connect(explorer_edge.move_source_point) - self.node_moved.connect(explorer_edge.move_destination_point) - self.addItem(explorer_edge) - self.edges[(edge[0], edge[1])] = explorer_edge - - self.update() - - def display_path_to(self, node_id): - if node_id != self.identity.pubkey: - for edge in self.edges.values(): - edge.neutralize() - - for node in self.nodes.values(): - node.neutralize() - - path = [] - try: - path = networkx.shortest_path(self.nx_graph, node_id, self.identity.pubkey) - except (networkx.exception.NetworkXError, networkx.exception.NetworkXNoPath) as e: - logging.debug(str(e)) - try: - path = networkx.shortest_path(self.nx_graph, self.identity.pubkey, node_id) - except (networkx.exception.NetworkXError, networkx.exception.NetworkXNoPath) as e: - logging.debug(str(e)) - - for node, next_node in zip(path[:-1], path[1:]): - if (node, next_node) in self.edges: - edge = self.edges[(node, next_node)] - elif (next_node, node) in self.edges: - edge = self.edges[(next_node, node)] - if edge: - edge.highlight() - self.nodes[node].highlight() - self.nodes[next_node].highlight() - logging.debug("Update edge between {0} and {1}".format(node, next_node)) diff --git a/src/sakia/gui/navigation/graphs/explorer/view.py b/src/sakia/gui/navigation/graphs/explorer/view.py deleted file mode 100644 index 0d2ccb88f7e3f7c5d2f690f30352fae626009ce1..0000000000000000000000000000000000000000 --- a/src/sakia/gui/navigation/graphs/explorer/view.py +++ /dev/null @@ -1,77 +0,0 @@ -from PyQt5.QtCore import QEvent -from ..base.view import BaseGraphView -from .explorer_uic import Ui_ExplorerWidget - - -class ExplorerView(BaseGraphView, Ui_ExplorerWidget): - """ - Wot graph view - """ - - def __init__(self, parent): - """ - Constructor - """ - super().__init__(parent) - self.setupUi(self) - - def set_search_user(self, search_user): - """ - Set the search user view in the gui - :param sakia.gui.search_user.view.SearchUserView search_user: the view - :return: - """ - self.layout().insertWidget(0, search_user) - - def set_steps_max(self, maximum): - """ - Set the steps slider max value - :param int maximum: the max value - """ - self.steps_slider.setMaximum(maximum) - - def scene(self): - """ - Get the scene of the underlying graphics view - :rtype: sakia.gui.graphs.explorer.scene.ExplorerScene - """ - return self.graphics_view.scene() - - def reset_steps(self): - """ - Reset the steps slider - """ - self.steps_slider.setValue(0) - - def steps(self): - """ - Get the number of steps selected - :return: - """ - return self.steps_slider.value() - - def update_wot(self, nx_graph, identity): - """ - Display given wot around given identity - :param nx_graph: - :param identity: - :param steps: - :return: - """ - # draw graph in qt scene - self.scene().update_wot(nx_graph, identity, self.steps_slider.maximum()) - - async def draw_graph(self): - """ - Draw community graph centered on the identity - - :param sakia.core.registry.Identity identity: Center identity - """ - self.view.update_wot(self.model.graph.nx_graph, self.model.identity) - - def update_current_identity(self, pubkey): - """ - Change currently blinking identity - :param str pubkey: - """ - self.scene().update_current_identity(pubkey) diff --git a/src/sakia/gui/navigation/graphs/wot/controller.py b/src/sakia/gui/navigation/graphs/wot/controller.py index 3dae41d1185eadc574f85f48b480b7f9723dc151..9df22776ab9118385379548ee860c25f4c4ef3ad 100644 --- a/src/sakia/gui/navigation/graphs/wot/controller.py +++ b/src/sakia/gui/navigation/graphs/wot/controller.py @@ -25,17 +25,18 @@ class WotController(BaseGraphController): @classmethod def create(cls, parent, app, **kwargs): - account = kwargs['account'] - community = kwargs['community'] + connection = kwargs['connection'] + blockchain_service = kwargs['blockchain_service'] + identities_service = kwargs['identities_service'] view = WotView(parent.view) - model = WotModel(None, app, account, community) + model = WotModel(None, app, connection, blockchain_service, identities_service) wot = cls(parent, view, model) model.setParent(wot) - search_user = SearchUserController.create(wot, app, **{'account': account, - 'community': community}) - wot.view.set_search_user(search_user.view) - search_user.identity_selected.connect(wot.center_on_identity) + #search_user = SearchUserController.create(wot, app, **{'account': account, + # 'community': community}) + #wot.view.set_search_user(search_user.view) + #search_user.identity_selected.connect(wot.center_on_identity) return wot @property @@ -69,7 +70,7 @@ class WotController(BaseGraphController): """ Refresh graph scene to current metadata """ - nx_graph = await self.model.get_nx_graph() + nx_graph = self.model.get_nx_graph() self.view.display_wot(nx_graph, self.model.identity) path = await self.model.get_shortest_path() if path: diff --git a/src/sakia/gui/navigation/graphs/wot/model.py b/src/sakia/gui/navigation/graphs/wot/model.py index d6c0440e14985e86e053867bc7b3fdb08ee01ae8..d1c85b38443c99f7114cbcbc4cab9f96ccc3c68e 100644 --- a/src/sakia/gui/navigation/graphs/wot/model.py +++ b/src/sakia/gui/navigation/graphs/wot/model.py @@ -7,12 +7,22 @@ class WotModel(BaseGraphModel): The model of Navigation component """ - def __init__(self, parent, app, account, community): - super().__init__(parent, app, account, community) + def __init__(self, parent, app, connection, blockchain_service, identities_service): + """ + Constructor of a model of WoT component + + :param sakia.gui.identities.controller.IdentitiesController parent: the controller + :param sakia.app.Application app: the app + :param sakia.data.entities.Connection connection: the connection + :param sakia.services.BlockchainService blockchain_service: the blockchain service + :param sakia.services.IdentitiesService identities_service: the identities service + """ + super().__init__(parent, app, connection, blockchain_service, identities_service) self.app = app - self.account = account - self.community = community - self.wot_graph = WoTGraph(self.app, self.community) + self.connection = connection + self.blockchain_service = blockchain_service + self.identities_service = identities_service + self.wot_graph = WoTGraph(self.app, self.blockchain_service, self.identities_service) self.identity = None async def set_identity(self, identity=None): @@ -22,16 +32,16 @@ class WotModel(BaseGraphModel): :param sakia.core.registry.Identity identity: the new identity to show :return: """ - identity_account = await self.account.identity(self.community) + connection_identity = self.identities_service.get_identity(self.connection.pubkey) if identity: self.identity = identity else: - self.identity = identity_account + self.identity = connection_identity # create empty graph instance - await self.wot_graph.initialize(self.identity, identity_account) + await self.wot_graph.initialize(self.identity, connection_identity) - async def get_nx_graph(self): + def get_nx_graph(self): """ Get nx graph of current identity wot graph :rtype: sakia.core.registry.Identity @@ -43,7 +53,7 @@ class WotModel(BaseGraphModel): Get shortest path between current identity and account :rtype: list[str] """ - identity_account = await self.account.identity(self.community) + identity_account = self.identities_service.get_identity(self.connection.pubkey) # if selected member is not the account member... if self.identity.pubkey != identity_account.pubkey: path = await self.wot_graph.get_shortest_path_to_identity(self.identity, identity_account) diff --git a/src/sakia/gui/navigation/graphs/wot/view.py b/src/sakia/gui/navigation/graphs/wot/view.py index e4e404604c6c184641d6aa4440548be63e3cd1e2..f735745feb9d7715da9d62241192d8a19bd083f1 100644 --- a/src/sakia/gui/navigation/graphs/wot/view.py +++ b/src/sakia/gui/navigation/graphs/wot/view.py @@ -46,4 +46,4 @@ class WotView(BaseGraphView, Ui_WotWidget): :param path: :return: """ - self.graphics_view.scene().update_path(nx_graph, path) + #self.graphics_view.scene().update_path(nx_graph, path) diff --git a/src/sakia/gui/navigation/identities/model.py b/src/sakia/gui/navigation/identities/model.py index 218985c6aac2f568c136012e8e570b25efeb6a65..b27282067e6b7fa5490cdf4c63a45402421c0972 100644 --- a/src/sakia/gui/navigation/identities/model.py +++ b/src/sakia/gui/navigation/identities/model.py @@ -44,13 +44,9 @@ class IdentitiesModel(ComponentModel): """ if index.isValid() and index.row() < self.table_model.rowCount(): source_index = self.table_model.mapToSource(index) - pubkey_col = self.table_model.sourceModel().columns_ids.index('pubkey') - pubkey_index = self.table_model.sourceModel().index(source_index.row(), - pubkey_col) - pubkey = self.table_model.sourceModel().data(pubkey_index, Qt.DisplayRole) - identity_col = self.table_model.sourceModel().columns_ids.index('pubkey') + identity_col = self.table_model.sourceModel().columns_ids.index('identity') identity_index = self.table_model.sourceModel().index(source_index.row(), - pubkey_col) + identity_col) identity = self.table_model.sourceModel().data(identity_index, Qt.DisplayRole) return True, identity return False, None diff --git a/src/sakia/gui/navigation/model.py b/src/sakia/gui/navigation/model.py index 296b3a841e1e89ce618858d55f4ba53ae339e8fd..49f97c0350d93ca4be4f32c2903bdfd60410a641 100644 --- a/src/sakia/gui/navigation/model.py +++ b/src/sakia/gui/navigation/model.py @@ -73,13 +73,16 @@ class NavigationModel(ComponentModel): 'identities_service': self.app.identities_services[connection.currency], } }, - # { - # 'node': { - # 'title': self.tr('Web of Trust'), - # 'icon': ':/icons/wot_icon', - # 'component': "Wot", - # } - # } + { + 'node': { + 'title': self.tr('Web of Trust'), + 'icon': ':/icons/wot_icon', + 'component': "Wot", + 'connection': connection, + 'blockchain_service': self.app.blockchain_services[connection.currency], + 'identities_service': self.app.identities_services[connection.currency], + } + } ] }) return self.navigation diff --git a/src/sakia/gui/sub/user_information/controller.py b/src/sakia/gui/sub/user_information/controller.py index 9572ab0d89527b9d1aa5872e3d394e3ffbd2864b..db3cfab194a5ff2fb1e7c2c757c6d5f12760878f 100644 --- a/src/sakia/gui/sub/user_information/controller.py +++ b/src/sakia/gui/sub/user_information/controller.py @@ -54,7 +54,7 @@ class UserInformationController(ComponentController): if self.model.identity: self.view.show_busy() self.view.display_uid(self.model.identity.uid) - await self.model.update_identity() + await self.model.load_identity() self.view.display_identity_timestamps(self.model.identity.pubkey, self.model.identity.timestamp, self.model.identity.membership_timestamp) self.view.hide_busy() diff --git a/src/sakia/gui/sub/user_information/model.py b/src/sakia/gui/sub/user_information/model.py index 5dd61b03e4caaa278affe0c5e05eb5a18ac8fd68..a14e74d318c3ea3d5fb07fe11dc10eb9b82d8a94 100644 --- a/src/sakia/gui/sub/user_information/model.py +++ b/src/sakia/gui/sub/user_information/model.py @@ -25,8 +25,10 @@ class UserInformationModel(ComponentModel): self.identity = identity self.identities_service = identities_service - async def update_identity(self): + async def load_identity(self): """ Ask network service to request identity informations """ - await self.network_service.update_memberships(self.identity) + await self.identities_service.load_memberships(self.identity) + await self.identities_service.load_certifiers_of(self.identity) + await self.identities_service.load_certified_by(self.identity) diff --git a/src/sakia/services/identities.py b/src/sakia/services/identities.py index 4d59cc156ebdf43b965a9805260067aa0f40e2fb..d07451b6f0e3ca24efb546b8f44d6357641c3179 100644 --- a/src/sakia/services/identities.py +++ b/src/sakia/services/identities.py @@ -66,9 +66,10 @@ class IdentitiesService(QObject): return sig_period - (current_time - latest_time) return 0 - async def update_memberships(self, identity): + async def load_memberships(self, identity): """ Request the identity data and save it to written identities + It does nothing if the identity is already written and updated with blockchain lookups :param sakia.data.entities.Identity identity: the identity """ if not identity.written_on: @@ -94,27 +95,53 @@ class IdentitiesService(QObject): except NoPeerAvailable as e: logging.debug(str(e)) - async def update_certified_by(self, identity): + async def load_certifiers_of(self, identity): """ Request the identity data and save it to written certifications + It does nothing if the identity is already written and updated with blockchain lookups :param sakia.data.entities.Identity identity: the identity """ - try: - data = await self._bma_connector.get(self.currency, bma.wot.CertifiedBy, {'search': self.pubkey}) - for certified_data in data['certifications']: - cert = Certification(self.currency, data["pubkey"], - certified_data["pubkey"], certified_data["sigDate"]) - cert.block = certified_data["cert_time"]["number"] - cert.timestamp = certified_data["cert_time"]["medianTime"] - if certified_data['written']: - cert.written_on = BlockUID(certified_data['written']['number'], - certified_data['written']['hash']) - self._certs_processor.commit_certification(cert) - except errors.DuniterError as e: - if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID): - logging.debug("Certified by error : {0}".format(str(e))) - except NoPeerAvailable as e: - logging.debug(str(e)) + if not identity.written_on: + try: + data = await self._bma_connector.get(self.currency, bma.wot.certifiers_of, {'search': identity.pubkey}) + for certified_data in data['certifications']: + cert = Certification(self.currency, data["pubkey"], + certified_data["pubkey"], certified_data["sigDate"]) + cert.block = certified_data["cert_time"]["number"] + cert.timestamp = certified_data["cert_time"]["medianTime"] + if certified_data['written']: + cert.written_on = BlockUID(certified_data['written']['number'], + certified_data['written']['hash']) + self._certs_processor.commit_certification(cert) + except errors.DuniterError as e: + if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID): + logging.debug("Certified by error : {0}".format(str(e))) + except NoPeerAvailable as e: + logging.debug(str(e)) + + async def load_certified_by(self, identity): + """ + Request the identity data and save it to written certifications + It does nothing if the identity is already written and updated with blockchain lookups + :param sakia.data.entities.Identity identity: the identity + """ + if not identity.written_on: + try: + data = await self._bma_connector.get(self.currency, bma.wot.certified_by, {'search': identity.pubkey}) + for certified_data in data['certifications']: + cert = Certification(self.currency, data["pubkey"], + certified_data["pubkey"], certified_data["sigDate"]) + cert.block = certified_data["cert_time"]["number"] + cert.timestamp = certified_data["cert_time"]["medianTime"] + if certified_data['written']: + cert.written_on = BlockUID(certified_data['written']['number'], + certified_data['written']['hash']) + self._certs_processor.commit_certification(cert) + except errors.DuniterError as e: + if e.ucode in (errors.NO_MATCHING_IDENTITY, errors.NO_MEMBER_MATCHING_PUB_OR_UID): + logging.debug("Certified by error : {0}".format(str(e))) + except NoPeerAvailable as e: + logging.debug(str(e)) def _parse_revocations(self, block): """ @@ -201,16 +228,19 @@ class IdentitiesService(QObject): :param sakia.data.entities.Identity identity: :return: """ - requirements = await self._bma_connector.get(self.currency, bma.wot.Requirements, - get_args={'search': identity.pubkey}) - identity_data = requirements['identities'][0] - identity.uid = identity_data["uid"] - identity.blockstamp = identity["meta"]["timestamp"] - identity.member = identity["membershipExpiresIn"] > 0 and identity["outdistanced"] is False - median_time = self._blockchain_processor.time(self.currency) - expiration_time = self._blockchain_processor.parameters(self.currency).ms_validity - identity.membership_timestamp = median_time - (expiration_time - identity["membershipExpiresIn"]) - self._identities_processor.commit_identity(identity) + try: + requirements = await self._bma_connector.get(self.currency, bma.wot.requirements, + req_args={'search': identity.pubkey}) + identity_data = requirements['identities'][0] + identity.uid = identity_data["uid"] + identity.blockstamp = identity["meta"]["timestamp"] + identity.member = identity["membershipExpiresIn"] > 0 and identity["outdistanced"] is False + median_time = self._blockchain_processor.time(self.currency) + expiration_time = self._blockchain_processor.parameters(self.currency).ms_validity + identity.membership_timestamp = median_time - (expiration_time - identity["membershipExpiresIn"]) + self._identities_processor.commit_identity(identity) + except NoPeerAvailable as e: + self._logger.debug(str(e)) def parse_block(self, block): """ @@ -277,3 +307,19 @@ class IdentitiesService(QObject): """ validity = self._blockchain_processor.parameters(self.currency).ms_validity return identity.membership_timestamp + validity + + def certifications_received(self, pubkey): + """ + Get the list of certifications received by a given identity + :param str pubkey: the pubkey + :rtype: List[sakia.data.entities.Certifications] + """ + return self._certs_processor.certifications_received(self.currency, pubkey) + + def certifications_sent(self, pubkey): + """ + Get the list of certifications received by a given identity + :param str pubkey: the pubkey + :rtype: List[sakia.data.entities.Certifications] + """ + return self._certs_processor.certifications_sent(self.currency, pubkey)