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)