diff --git a/res/icons/icons.qrc b/res/icons/icons.qrc index bf77a31ec0b714976425bcd146d69ceeee7b4782..af660cdf8ad598e40f15df78567b8c1cbcc55d33 100644 --- a/res/icons/icons.qrc +++ b/res/icons/icons.qrc @@ -1,6 +1,7 @@ <RCC> <qresource prefix="icons"> <file alias="network_icon">noun_21549_cc.svg</file> + <file alias="member_icon">iconmonstr-user-icon.svg</file> <file alias="informations_icon">iconmonstr-info-2-icon.svg</file> <file alias="community_icon">noun_22441_cc.svg</file> <file alias="wot_icon">noun_2651_cc.svg</file> diff --git a/src/cutecoin/core/account.py b/src/cutecoin/core/account.py index bca78280b64d42b6a99e3f1da2ac55d83d6f59f0..504ae4afdee58460430664b9e27326525be505d8 100644 --- a/src/cutecoin/core/account.py +++ b/src/cutecoin/core/account.py @@ -275,3 +275,6 @@ class Account(QObject): 'wallets': data_wallets, 'contacts': data_contacts} return data + + def get_person(self): + return Person(self.name, self.pubkey) diff --git a/src/cutecoin/core/graph.py b/src/cutecoin/core/graph.py new file mode 100644 index 0000000000000000000000000000000000000000..1dddf104fae5d65a37cf31fd7ffcd98476712949 --- /dev/null +++ b/src/cutecoin/core/graph.py @@ -0,0 +1,251 @@ +import logging +import copy +import time +import datetime +from cutecoin.core.person import Person +from cutecoin.gui.views.wot import NODE_STATUS_HIGHLIGHTED, NODE_STATUS_OUT, ARC_STATUS_STRONG, ARC_STATUS_WEAK + + +class Graph(dict): + def __init__(self, community): + """ + Init Graph instance + :param cutecoin.core.community.Community community: + :return: + """ + self.community = community + self.signature_validity = self.community.get_parameters()['sigValidity'] + #  arc considered strong during 75% of signature validity time + self.ARC_STATUS_STRONG_time = int(self.signature_validity * 0.75) + + def get_shortest_path_between_members(self, from_person, to_person): + """ + Return path list of nodes from from_person to to_person + :param Person from_person: + :param Person to_person: + :return: + """ + path = list() + graph_tmp = copy.deepcopy(self) + + logging.debug("path between %s to %s..." % (from_person.name, to_person.name)) + if from_person.pubkey not in graph_tmp.keys(): + graph_tmp.add_person(from_person) + certifier_list = from_person.certifiers_of(self.community) + graph_tmp.add_certifier_list(certifier_list, from_person, to_person) + certified_list = from_person.certified_by(self.community) + graph_tmp.add_certified_list(certified_list, from_person, to_person) + + if to_person.pubkey not in graph_tmp.keys(): + # recursively feed graph searching for account node... + graph_tmp.explore_to_find_member(to_person, graph_tmp[from_person.pubkey]['connected'], list()) + if len(graph_tmp[from_person.pubkey]['connected']) > 0: + # calculate path of nodes between person and to_person + path = graph_tmp.find_shortest_path(graph_tmp[from_person.pubkey], graph_tmp[to_person.pubkey]) + + if path: + logging.debug([node['text'] for node in path]) + else: + logging.debug('no wot path') + + return path + + def explore_to_find_member(self, person, connected=None, done=None): + """ + Scan graph recursively to find person + :param Person person: Person instance to find + :param list connected: Optional, default=None, Pubkey list of the connected nodes + around the current scanned node + :param list done: Optional, default=None, List of node already scanned + :return: + """ + # functions keywords args are persistent... Need to reset it with None trick + connected = connected or (list() and (connected is None)) + done = done or (list() and (done is None)) + logging.debug("search %s in " % person.name) + logging.debug([self[pubkey]['text'] for pubkey in connected]) + # for each pubkey connected... + for pubkey in tuple(connected): + # capture node connected + node = self[pubkey] + if node['id'] in tuple(done): + continue + person_selected = Person(node['text'], node['id']) + certifier_list = person_selected.certifiers_of(self.community) + self.add_certifier_list(certifier_list, person_selected, person) + if person.pubkey in tuple(self.keys()): + return False + certified_list = person_selected.certified_by(self.community) + self.add_certified_list(certified_list, person_selected, person) + if person.pubkey in tuple(self.keys()): + return False + if node['id'] not in tuple(done): + done.append(node['id']) + if len(done) >= len(self): + return True + result = self.explore_to_find_member(person, self[person_selected.pubkey]['connected'], done) + if not result: + return False + + return True + + def find_shortest_path(self, start, end, path=None): + """ + Find recursively the shortest path between two nodes + :param dict start: Start node + :param dict end: End node + :param list path: Optional, default=None, List of nodes + :return: + """ + path = path or (list() and (path is None)) + path = path + [start] + if start['id'] == end['id']: + return path + if start['id'] not in self.keys(): + return None + shortest = None + for pubkey in tuple(self[start['id']]['connected']): + node = self[pubkey] + if node not in path: + newpath = self.find_shortest_path(node, end, path) + if newpath: + if not shortest or len(newpath) < len(shortest): + shortest = newpath + return shortest + + def add_certifier_list(self, certifier_list, person, person_account): + """ + Add list of certifiers to graph + :param list certifier_list: List of certifiers from api + :param Person person: Person instance which is certified + :param Person person_account: Account person instance + :return: + """ + #  add certifiers of uid + for certifier in tuple(certifier_list): + # add only valid certification... + if (time.time() - certifier['cert_time']['medianTime']) > self.signature_validity: + continue + # new node + if certifier['pubkey'] not in self.keys(): + node_status = 0 + if certifier['pubkey'] == person_account.pubkey: + node_status += NODE_STATUS_HIGHLIGHTED + if certifier['isMember'] is False: + node_status += NODE_STATUS_OUT + self[certifier['pubkey']] = { + 'id': certifier['pubkey'], + 'arcs': list(), + 'text': certifier['uid'], + 'tooltip': certifier['pubkey'], + 'status': node_status, + 'connected': [person.pubkey] + } + + # keep only the latest certification + if self[certifier['pubkey']]['arcs']: + if certifier['cert_time']['medianTime'] < self[certifier['pubkey']]['arcs'][0]['cert_time']: + continue + # display validity status + if (time.time() - certifier['cert_time']['medianTime']) > self.ARC_STATUS_STRONG_time: + arc_status = ARC_STATUS_WEAK + else: + arc_status = ARC_STATUS_STRONG + arc = { + 'id': person.pubkey, + 'status': arc_status, + 'tooltip': datetime.datetime.fromtimestamp( + certifier['cert_time']['medianTime'] + self.signature_validity + ).strftime("%d/%m/%Y"), + 'cert_time': certifier['cert_time']['medianTime'] + } + #  add arc to certifier + self[certifier['pubkey']]['arcs'].append(arc) + # if certifier node not in person nodes + if certifier['pubkey'] not in tuple(self[person.pubkey]['connected']): + # add certifier node to person node + self[person.pubkey]['connected'].append(certifier['pubkey']) + + def add_certified_list(self, certified_list, person, person_account): + """ + Add list of certified from api to graph + :param list certified_list: List of certified from api + :param Person person: Person instance which is certifier + :param Person person_account: Account person instance + :return: + """ + # add certified by uid + for certified in tuple(certified_list): + # add only valid certification... + if (time.time() - certified['cert_time']['medianTime']) > self.signature_validity: + continue + if certified['pubkey'] not in self.keys(): + node_status = 0 + if certified['pubkey'] == person_account.pubkey: + node_status += NODE_STATUS_HIGHLIGHTED + if certified['isMember'] is False: + node_status += NODE_STATUS_OUT + self[certified['pubkey']] = { + 'id': certified['pubkey'], + 'arcs': list(), + 'text': certified['uid'], + 'tooltip': certified['pubkey'], + 'status': node_status, + 'connected': [person.pubkey] + } + # display validity status + if (time.time() - certified['cert_time']['medianTime']) > self.ARC_STATUS_STRONG_time: + arc_status = ARC_STATUS_WEAK + else: + arc_status = ARC_STATUS_STRONG + arc = { + 'id': certified['pubkey'], + 'status': arc_status, + 'tooltip': datetime.datetime.fromtimestamp( + certified['cert_time']['medianTime'] + self.signature_validity + ).strftime("%d/%m/%Y"), + 'cert_time': certified['cert_time']['medianTime'] + } + + # replace old arc if this one is more recent + new_arc = True + index = 0 + for a in self[person.pubkey]['arcs']: + # if same arc already exists... + if a['id'] == arc['id']: + # if arc more recent, dont keep old one... + if arc['cert_time'] >= a['cert_time']: + self[person.pubkey]['arcs'][index] = arc + new_arc = False + index += 1 + + #  if arc not in graph... + if new_arc: + # add arc in graph + self[person.pubkey]['arcs'].append(arc) + # if certified node not in person nodes + if certified['pubkey'] not in tuple(self[person.pubkey]['connected']): + # add certified node to person node + self[person.pubkey]['connected'].append(certified['pubkey']) + + def add_person(self, person, status=None, arcs=None, connected=None): + """ + Add person as a new node in graph + :param Person person: Person instance + :param int status: Optional, default=None, Node status (see cutecoin.gui.views.wot) + :param list arcs: Optional, default=None, List of arcs (certified by person) + :param list connected: Optional, default=None, Public key list of the connected nodes around the person + :return: + """ + # functions keywords args are persistent... Need to reset it with None trick + status = status or (0 and (status is None)) + arcs = arcs or (list() and (arcs is None)) + connected = connected or (list() and (connected is None)) + self[person.pubkey] = { + 'id': person.pubkey, + 'arcs': arcs, + 'text': person.name, + 'tooltip': person.pubkey, + 'status': status, + 'connected': connected + } diff --git a/src/cutecoin/core/person.py b/src/cutecoin/core/person.py index 2b40cb4e931fceae08c9752a69a2d7a3eb87b43d..4548c0794ae49099de0351cb14ce6c706213d52a 100644 --- a/src/cutecoin/core/person.py +++ b/src/cutecoin/core/person.py @@ -6,6 +6,7 @@ Created on 11 févr. 2014 import logging import functools +import datetime from ucoinpy.api import bma from ucoinpy import PROTOCOL_VERSION from ucoinpy.documents.certification import SelfCertification @@ -156,14 +157,28 @@ class Person(object): signature) raise PersonNotFoundError(self.pubkey, community.name()) + def get_join_date(self, community): + try: + search = community.request(bma.blockchain.Membership, {'search': self.pubkey}) + membership_data = None + if len(search['memberships']) > 0: + membership_data = search['memberships'][0] + return datetime.datetime.fromtimestamp(community.get_block(membership_data['blockNumber']).mediantime).strftime("%d/%m/%Y %I:%M") + else: + return None + except ValueError as e: + if '400' in str(e): + raise MembershipNotFoundError(self.pubkey, community.name()) + @cached def membership(self, community): try: search = community.request(bma.blockchain.Membership, {'search': self.pubkey}) - block_number = 0 + block_number = -1 for ms in search['memberships']: - if ms['blockNumber'] >= block_number: + if ms['blockNumber'] > block_number: + block_number = ms['blockNumber'] if 'type' in ms: if ms['type'] is 'IN': membership_data = ms diff --git a/src/cutecoin/gui/community_tab.py b/src/cutecoin/gui/community_tab.py index 69075a40d06bb8663de01e674853b5c9b8cf4be6..5d713e640adff57b8af9354ffc8473ecdd3341ac 100644 --- a/src/cutecoin/gui/community_tab.py +++ b/src/cutecoin/gui/community_tab.py @@ -12,6 +12,7 @@ from PyQt5.QtWidgets import QWidget, QMessageBox, QAction, QMenu, QDialog, \ from ..models.members import MembersFilterProxyModel, MembersTableModel from ..gen_resources.community_tab_uic import Ui_CommunityTabWidget from cutecoin.gui.contact import ConfigureContactDialog +from cutecoin.gui.member import MemberDialog from .wot_tab import WotTabWidget from .transfer import TransferMoneyDialog from .password_asker import PasswordAskerDialog @@ -64,6 +65,10 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): member = Person.lookup(pubkey, self.community) menu = QMenu(self) + informations = QAction("Informations", self) + informations.triggered.connect(self.menu_informations) + informations.setData(member) + add_contact = QAction("Add as contact", self) add_contact.triggered.connect(self.menu_add_as_contact) add_contact.setData(member) @@ -80,6 +85,7 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): view_wot.triggered.connect(self.view_wot) view_wot.setData(member) + menu.addAction(informations) menu.addAction(add_contact) menu.addAction(send_money) menu.addAction(certify) @@ -88,6 +94,10 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): # Show the context menu. menu.exec_(QCursor.pos()) + def menu_informations(self): + person = self.sender().data() + self.member_informations(person) + def menu_add_as_contact(self): person = self.sender().data() self.add_member_as_contact(person) @@ -100,6 +110,10 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): person = self.sender().data() self.certify_member(person) + def member_informations(self, person): + dialog = MemberDialog(self.account, self.community, person) + dialog.exec_() + def add_member_as_contact(self, person): dialog = ConfigureContactDialog(self.account, self.window(), person) result = dialog.exec_() diff --git a/src/cutecoin/gui/member.py b/src/cutecoin/gui/member.py new file mode 100644 index 0000000000000000000000000000000000000000..d961db04146f74fa9a3e22fe1ff2d6520cf36139 --- /dev/null +++ b/src/cutecoin/gui/member.py @@ -0,0 +1,63 @@ +from cutecoin.core.graph import Graph +from PyQt5.QtWidgets import QDialog +from ..gen_resources.member_uic import Ui_DialogMember + + +class MemberDialog(QDialog, Ui_DialogMember): + """ + classdocs + """ + + def __init__(self, account, community, person): + """ + Constructor + """ + super().__init__() + self.setupUi(self) + self.community = community + self.account = account + self.person = person + self.label_uid.setText(person.name) + + join_date = self.person.get_join_date(self.community) + if join_date is None: + join_date = 'not a member' + + # calculate path to account member + graph = Graph(self.community) + path = None + # if selected member is not the account member... + if person.pubkey != self.account.pubkey: + # add path from selected member to account member + path = graph.get_shortest_path_between_members(person, self.account.get_person()) + + text = """ + <table cellpadding="5"> + <tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr> + <tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr> + """.format( + 'Public key', + self.person.pubkey, + 'Join date', + join_date + ) + + if path: + distance = len(path) - 1 + text += """<tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>""".format('Distance', distance) + if distance > 1: + index = 0 + for node in path: + if index == 0: + text += """<tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>""".format('Path', node['text']) + else: + text += """<tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>""".format('', node['text']) + if index == distance and node['id'] != self.account.pubkey: + text += """<tr><td align="right"><b>{:}</b></div></td><td>{:}</td></tr>""".format('', self.account.name) + index += 1 + # close html text + text += "</table>" + + # set text in label + self.label_properties.setText(text) + diff --git a/src/cutecoin/gui/views/wot.py b/src/cutecoin/gui/views/wot.py index ce64aca6aaca8d4dc87ef885a6a51e0f6f5fc020..812c9166fc626ec4d7191e615e10dab8df173a9d 100644 --- a/src/cutecoin/gui/views/wot.py +++ b/src/cutecoin/gui/views/wot.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- import math -from PyQt5.QtGui import QPainter, QBrush, QPen, QPolygonF, QColor, QRadialGradient,\ +from PyQt5.QtGui import QPainter, QBrush, QPen, QPolygonF, QColor, QRadialGradient, \ QPainterPath, QMouseEvent, QWheelEvent, QTransform, QCursor from PyQt5.QtCore import Qt, QRectF, QLineF, QPoint, QPointF, QSizeF, qFuzzyCompare, pyqtSignal -from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsEllipseItem,\ - QGraphicsSimpleTextItem, QGraphicsLineItem, QMenu, QAction, QGraphicsSceneHoverEvent,\ +from PyQt5.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsEllipseItem, \ + QGraphicsSimpleTextItem, QGraphicsLineItem, QMenu, QAction, QGraphicsSceneHoverEvent, \ QGraphicsSceneContextMenuEvent NODE_STATUS_HIGHLIGHTED = 1 @@ -48,19 +48,19 @@ class WotView(QGraphicsView): self.scale(sc, sc) self.centerOn(self.mapToScene(event.pos())) event.accept() - # act normally on scrollbar + #  act normally on scrollbar else: # transmit event to parent class wheelevent super(QGraphicsView, self).wheelEvent(event) class Scene(QGraphicsScene): - # This defines signals taking string arguments node_clicked = pyqtSignal(dict, name='nodeClicked') node_signed = pyqtSignal(dict, name='nodeSigned') node_transaction = pyqtSignal(dict, name='nodeTransaction') node_contact = pyqtSignal(dict, name='nodeContact') + node_member = pyqtSignal(dict, name='nodeMember') def __init__(self, parent=None): """ @@ -73,7 +73,9 @@ class Scene(QGraphicsScene): self.lastDragPos = QPoint() self.setItemIndexMethod(QGraphicsScene.NoIndex) - # axis of the scene for debug purpose + # 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) @@ -88,6 +90,7 @@ class Scene(QGraphicsScene): """ node = Node(metadata, pos) self.addItem(node) + self.nodes[node.id] = node return node def add_arc(self, source_node, destination_node, metadata): @@ -108,9 +111,9 @@ class Scene(QGraphicsScene): """ draw community graph - :param dict graph: graph to draw + :param cutecoin.core.graph.Graph graph: graph to draw """ - # clear scene + #  clear scene self.clear() # capture selected node (to draw it in the center) @@ -140,7 +143,8 @@ class Scene(QGraphicsScene): y = 0 x = -200 # sort by text - nodes = ((k, v) for (k, v) in sorted(graph.items(), key=lambda kv: kv[1]['text'].lower()) if selected_id in (arc['id'] for arc in v['arcs'])) + nodes = ((k, v) for (k, v) in sorted(graph.items(), key=lambda kv: kv[1]['text'].lower()) if + selected_id in (arc['id'] for arc in v['arcs'])) # add nodes and arcs for _id, certifier_node in nodes: node = self.add_node(certifier_node, (x, y)) @@ -151,6 +155,31 @@ class Scene(QGraphicsScene): self.update() + def update_path(self, path): + x = 0 + y = 0 + for json_node in path: + if json_node['status'] & NODE_STATUS_SELECTED: + previous_node = json_node + y -= 100 + continue + node = self.add_node(json_node, (x, y)) + skip_reverse_arc = False + for arc in json_node['arcs']: + if arc['id'] == previous_node['id']: + #print("arc from %s to %s" % (node.id, previous_node['id'])) + self.add_arc(node, self.nodes[previous_node['id']], arc) + skip_reverse_arc = True + break + if not skip_reverse_arc: + for arc in previous_node['arcs']: + if arc['id'] == json_node['id']: + #print("arc from %s to %s" % (previous_node['id'], node.id)) + self.add_arc(self.nodes[previous_node['id']], node, arc) + + previous_node = json_node + y -= 100 + class Node(QGraphicsEllipseItem): def __init__(self, metadata, x_y): @@ -166,6 +195,7 @@ class Node(QGraphicsEllipseItem): super(Node, self).__init__() self.metadata = metadata + self.id = metadata['id'] self.status_wallet = self.metadata['status'] & NODE_STATUS_HIGHLIGHTED self.status_member = not self.metadata['status'] & NODE_STATUS_OUT self.text = self.metadata['text'] @@ -175,6 +205,7 @@ class Node(QGraphicsEllipseItem): self.action_sign = None self.action_transaction = None self.action_contact = None + self.action_show_member = None # color around ellipse outline_color = QColor('grey') @@ -185,7 +216,7 @@ class Node(QGraphicsEllipseItem): outline_width = 2 if not self.status_member: outline_color = QColor('red') - outline_style = Qt.DashLine + outline_style = Qt.SolidLine self.setPen(QPen(outline_color, outline_width, outline_style)) # text inside ellipse @@ -203,10 +234,10 @@ class Node(QGraphicsEllipseItem): self.text_item.boundingRect().height() * 2 ) - # set anchor to the center - self.setTransform(QTransform().translate(-self.boundingRect().width()/2.0, -self.boundingRect().height()/2.0)) + #  set anchor to the center + self.setTransform( + QTransform().translate(-self.boundingRect().width() / 2.0, -self.boundingRect().height() / 2.0)) self.setPos(x, y) - #print(x, y) # center text in ellipse self.text_item.setPos(self.boundingRect().width() / 4.0, self.boundingRect().height() / 4.0) @@ -245,11 +276,15 @@ class Node(QGraphicsEllipseItem): :param event: scene context menu event """ - # no menu on the wallet node + #  no menu on the wallet node if self.status_wallet: return None # create node context menus self.menu = QMenu() + # action show member + self.action_show_member = QAction('Informations', self.scene()) + self.menu.addAction(self.action_show_member) + self.action_show_member.triggered.connect(self.member_action) # action add identity as contact self.action_contact = QAction('Add as contact', self.scene()) self.menu.addAction(self.action_contact) @@ -273,6 +308,20 @@ class Node(QGraphicsEllipseItem): """ self.arcs.append(arc) + def member_action(self): + """ + Transaction action to identity node + """ + # trigger scene signal + self.scene().node_member.emit(self.metadata) + + def contact_action(self): + """ + Transaction action to identity node + """ + # trigger scene signal + self.scene().node_contact.emit(self.metadata) + def sign_action(self): """ Sign identity node @@ -287,12 +336,6 @@ class Node(QGraphicsEllipseItem): # trigger scene signal self.scene().node_transaction.emit(self.metadata) - def contact_action(self): - """ - Transaction action to identity node - """ - # trigger scene signal - self.scene().node_contact.emit(self.metadata) class Arc(QGraphicsLineItem): def __init__(self, source_node, destination_node, metadata): @@ -319,7 +362,7 @@ class Arc(QGraphicsLineItem): self.setAcceptedMouseButtons(Qt.NoButton) - # cursor change on hover + #  cursor change on hover self.setAcceptHoverEvents(True) self.adjust() self.setZValue(0) @@ -405,11 +448,9 @@ class Arc(QGraphicsLineItem): if line.dy() >= 0: angle = (2.0 * math.pi) - angle - # arrow in the middle of the arc - hpx = (line.p2().x() + line.p1().x()) / 2.0 - hpy = (line.p2().y() - line.p1().y()) / 2.0 - if line.dy() < 0: - hpy = -hpy + #  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)) @@ -440,17 +481,17 @@ class Arc(QGraphicsLineItem): # detection mouse hover on arc path path = QPainterPath() path.addPolygon(QPolygonF([self.line().p1(), self.line().p2()])) - # add handles at the start and end of arc + #  add handles at the start and end of arc path.addRect(QRectF( - self.line().p1().x()-5, - self.line().p1().y()-5, - self.line().p1().x()+5, - self.line().p1().y()+5 + self.line().p1().x() - 5, + self.line().p1().y() - 5, + self.line().p1().x() + 5, + self.line().p1().y() + 5 )) path.addRect(QRectF( - self.line().p2().x()-5, - self.line().p2().y()-5, - self.line().p2().x()+5, - self.line().p2().y()+5 + self.line().p2().x() - 5, + self.line().p2().y() - 5, + self.line().p2().x() + 5, + self.line().p2().y() + 5 )) return path diff --git a/src/cutecoin/gui/wot_tab.py b/src/cutecoin/gui/wot_tab.py index 71a96c8b553437556d8060f2d3cef97605646c5d..62efa0201ab96aa053272dbdc91db63e991fd5ab 100644 --- a/src/cutecoin/gui/wot_tab.py +++ b/src/cutecoin/gui/wot_tab.py @@ -1,18 +1,18 @@ # -*- coding: utf-8 -*- -import time -import datetime import logging -from PyQt5.QtWidgets import QWidget, QComboBox, QDialog +from cutecoin.core.graph import Graph +from PyQt5.QtWidgets import QWidget, QComboBox from ..gen_resources.wot_tab_uic import Ui_WotTabWidget from cutecoin.gui.views.wot import NODE_STATUS_HIGHLIGHTED, NODE_STATUS_SELECTED, NODE_STATUS_OUT, ARC_STATUS_STRONG, ARC_STATUS_WEAK from ucoinpy.api import bma -from .certification import CertificationDialog -from cutecoin.gui.contact import ConfigureContactDialog -from .transfer import TransferMoneyDialog from cutecoin.core.person import Person +def get_person_from_metadata(metadata): + return Person(metadata['text'], metadata['id']) + + class WotTabWidget(QWidget, Ui_WotTabWidget): def __init__(self, account, community, password_asker, parent=None): """ @@ -39,6 +39,7 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): self.graphicsView.scene().node_signed.connect(self.sign_node) self.graphicsView.scene().node_transaction.connect(self.send_money_to_node) self.graphicsView.scene().node_contact.connect(self.add_node_as_contact) + self.graphicsView.scene().node_member.connect(self.member_informations) self.account = account self.community = community @@ -46,9 +47,6 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): # nodes list for menu from search self.nodes = list() - self.signature_validity = self.community.get_parameters()['sigValidity'] - # arc considered strong during 75% of signature validity time - self.ARC_STATUS_STRONG_time = int(self.signature_validity * 0.75) # create node metadata from account metadata = {'text': self.account.name, 'id': self.account.pubkey} @@ -60,121 +58,41 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): :param dict metadata: Graph node metadata of the identity """ + logging.debug("Draw graph - " + metadata['text']) + # create Person from node metadata - person = self.get_person_from_metadata(metadata) - certifiers = person.certifiers_of(self.community) + person = get_person_from_metadata(metadata) + person_account = Person(self.account.name, self.account.pubkey) + certifier_list = person.certifiers_of(self.community) + certified_list = person.certified_by(self.community) - # reset graph - graph = dict() + # create empty graph instance + graph = Graph(self.community) # add wallet node node_status = 0 - if person.pubkey == self.account.pubkey: + if person.pubkey == person_account.pubkey: node_status += NODE_STATUS_HIGHLIGHTED if person.is_member(self.community) is False: node_status += NODE_STATUS_OUT node_status += NODE_STATUS_SELECTED + graph.add_person(person, node_status) - # center node - graph[person.pubkey] = { - 'id': person.pubkey, - 'arcs': list(), - 'text': person.name, - 'tooltip': person.pubkey, - 'status': node_status - } - - # add certifiers of uid - for certifier in certifiers: - # new node - if certifier['pubkey'] not in graph.keys(): - node_status = 0 - if certifier['pubkey'] == self.account.pubkey: - node_status += NODE_STATUS_HIGHLIGHTED - if certifier['isMember'] is False: - node_status += NODE_STATUS_OUT - graph[certifier['pubkey']] = { - 'id': certifier['pubkey'], - 'arcs': list(), - 'text': certifier['uid'], - 'tooltip': certifier['pubkey'], - 'status': node_status - } - # add only valid certification... - if (time.time() - certifier['cert_time']['medianTime']) > self.signature_validity: - continue - # keep only the latest certification - if graph[certifier['pubkey']]['arcs']: - if certifier['cert_time']['medianTime'] < graph[certifier['pubkey']]['arcs'][0]['cert_time']: - continue - # display validity status - if (time.time() - certifier['cert_time']['medianTime']) > self.ARC_STATUS_STRONG_time: - arc_status = ARC_STATUS_WEAK - else: - arc_status = ARC_STATUS_STRONG - arc = { - 'id': person.pubkey, - 'status': arc_status, - 'tooltip': datetime.datetime.fromtimestamp( - certifier['cert_time']['medianTime'] + self.signature_validity - ).strftime("%d/%m/%Y"), - 'cert_time': certifier['cert_time']['medianTime'] - } - graph[certifier['pubkey']]['arcs'] = [arc] - - # add certified by uid - for certified in person.certified_by(self.community): - if certified['pubkey'] not in graph.keys(): - node_status = 0 - if certified['pubkey'] == self.account.pubkey: - node_status += NODE_STATUS_HIGHLIGHTED - if certified['isMember'] is False: - node_status += NODE_STATUS_OUT - - graph[certified['pubkey']] = { - 'id': certified['pubkey'], - 'arcs': list(), - 'text': certified['uid'], - 'tooltip': certified['pubkey'], - 'status': node_status - } - # add only valid certification... - if (time.time() - certified['cert_time']['medianTime']) > self.signature_validity: - continue - # display validity status - if (time.time() - certified['cert_time']['medianTime']) > self.ARC_STATUS_STRONG_time: - arc_status = ARC_STATUS_WEAK - else: - arc_status = ARC_STATUS_STRONG - arc = { - 'id': certified['pubkey'], - 'status': arc_status, - 'tooltip': datetime.datetime.fromtimestamp( - certified['cert_time']['medianTime'] + self.signature_validity - ).strftime("%d/%m/%Y"), - 'cert_time': certified['cert_time']['medianTime'] - } - - # replace old arc if this one is more recent - new_arc = True - index = 0 - for a in graph[person.pubkey]['arcs']: - # if same arc already exists... - if a['id'] == arc['id']: - # if arc more recent, dont keep old one... - if arc['cert_time'] >= a['cert_time']: - graph[person.pubkey]['arcs'][index] = arc - new_arc = False - index += 1 - - # if arc not in graph... - if new_arc: - # add arc in graph - graph[person.pubkey]['arcs'].append(arc) + # populate graph with certifiers-of + graph.add_certifier_list(certifier_list, person, person_account) + # populate graph with certified-by + graph.add_certified_list(certified_list, person, person_account) # draw graph in qt scene self.graphicsView.scene().update_wot(graph) + # if selected member is not the account member... + if person.pubkey != person_account.pubkey: + # add path from selected member to account member + path = graph.get_shortest_path_between_members(person, person_account) + if path: + self.graphicsView.scene().update_path(path) + def reset(self): """ Reset graph scene to wallet identity @@ -211,13 +129,6 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): self.comboBoxSearch.addItem(uid) self.comboBoxSearch.showPopup() - if len(nodes) == 1: - node = self.nodes[0] - metadata = {'id': node['pubkey'], 'text': node['uid']} - self.draw_graph( - metadata - ) - def select_node(self, index): """ Select node in graph when item is selected in combobox @@ -230,19 +141,23 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): metadata ) + def member_informations(self, metadata): + person = get_person_from_metadata(metadata) + self.parent.member_informations(person) + def sign_node(self, metadata): - person = self.get_person_from_metadata(metadata) + person = get_person_from_metadata(metadata) self.parent.certify_member(person) def send_money_to_node(self, metadata): - person = self.get_person_from_metadata(metadata) + person = get_person_from_metadata(metadata) self.parent.send_money_to_member(person) def add_node_as_contact(self, metadata): # check if contact already exists... if metadata['id'] == self.account.pubkey or metadata['id'] in [contact.pubkey for contact in self.account.contacts]: return False - person = self.get_person_from_metadata(metadata) + person = get_person_from_metadata(metadata) self.parent.add_member_as_contact(person) def get_block_mediantime(self, number):