diff --git a/res/icons/.directory b/res/icons/.directory new file mode 100644 index 0000000000000000000000000000000000000000..3be210363988903713c784f95f6e80c82e8d46cc --- /dev/null +++ b/res/icons/.directory @@ -0,0 +1,6 @@ +[Dolphin] +PreviewsShown=true +SortRole=date +Timestamp=2015,3,14,11,40,36 +Version=3 +ViewMode=1 diff --git a/res/icons/AUTHORS b/res/icons/AUTHORS index 0a9bc15fbcb18d091c2fa41a848706895c03c62d..172dbd597bbb10b31963335274cc56696dc94ec5 100644 --- a/res/icons/AUTHORS +++ b/res/icons/AUTHORS @@ -8,3 +8,6 @@ noun_29542_cc.svg : Created by Chris Kerr noun_43022_cc.svg : Created by Jon Prepeluh noun_63271_cc.svg : Created by Mark Shorter noun_43022_cc.svg : Created by Piotrek Chuchla +noun_7440_cc.svg : Created by Yuri Mamae +noun_62479_cc.svg : Created by Bridget Gahagan +noun_76373_cc.svg : Created by João Paulo \ No newline at end of file diff --git a/res/icons/icons.qrc b/res/icons/icons.qrc index af660cdf8ad598e40f15df78567b8c1cbcc55d33..804d8a04aeb678fc22dbf7a87813a923d114dbdd 100644 --- a/res/icons/icons.qrc +++ b/res/icons/icons.qrc @@ -1,5 +1,8 @@ <RCC> <qresource prefix="icons"> + <file alias="add_account_icon">noun_7440_cc.svg</file> + <file alias="ucoin_info_icon">noun_76373_cc.svg</file> + <file alias="import_icon">noun_62479_cc.svg</file> <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> diff --git a/res/icons/noun_62479_cc.svg b/res/icons/noun_62479_cc.svg new file mode 100644 index 0000000000000000000000000000000000000000..a22a87c97d0baa8c01c9ab0f60069e6d37faecfb --- /dev/null +++ b/res/icons/noun_62479_cc.svg @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="Layer_1" + x="0px" + y="0px" + width="90px" + height="90px" + viewBox="4.5 -9.0 90.0 121.5" + enable-background="new 0 0 90 90" + xml:space="preserve" + inkscape:version="0.91 r13725" + sodipodi:docname="noun_62479_cc.svg"><metadata + id="metadata15"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs13" /><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="640" + inkscape:window-height="480" + id="namedview11" + showgrid="false" + inkscape:zoom="2.6222222" + inkscape:cx="45" + inkscape:cy="31.338977" + inkscape:window-x="0" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="Layer_1" /><polygon + points="54.686,56.338 54.686,35.182 49.943,35.182 49.943,48.187 9.812,8.054 6.458,11.407 46.646,51.596 33.531,51.596 33.531,56.338 " + id="polygon3" + transform="matrix(1.4579929,0,0,1.4579929,-16.457719,-17.188675)" /><path + d="m 59.193161,15.343521 c -3.971573,0 -7.908154,0.503008 -11.700393,1.494443 l 1.749591,6.689272 c 3.220707,-0.84272 6.569717,-1.269912 9.950802,-1.269912 21.635157,0 39.237509,17.600891 39.237509,39.236048 0,21.633699 -17.602352,39.233128 -39.237509,39.233128 -21.633699,0 -39.233132,-17.599429 -39.233132,-39.233128 0,-3.470023 0.451978,-6.907971 1.34427,-10.219073 l -6.67615,-1.799163 c -1.049755,3.897215 -1.581922,7.94023 -1.581922,12.018236 0,25.444888 20.702041,46.146938 46.146934,46.146938 25.447809,0 46.151309,-20.70205 46.151309,-46.146938 0.001,-25.446351 -20.7035,-46.149851 -46.151309,-46.149851 z" + id="path5" + inkscape:connector-curvature="0" /></svg> \ No newline at end of file diff --git a/res/icons/noun_7440_cc.svg b/res/icons/noun_7440_cc.svg new file mode 100644 index 0000000000000000000000000000000000000000..19712a466f729184de572f9ac2f4189eb46abe9b --- /dev/null +++ b/res/icons/noun_7440_cc.svg @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:ns="http://ns.adobe.com/SaveForWeb/1.0/" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="Layer_1" + x="0px" + y="0px" + width="100px" + height="100px" + viewBox="5.0 -10.0 100.0 135.0" + enable-background="new 0 0 100 100" + xml:space="preserve" + inkscape:version="0.91 r13725" + sodipodi:docname="noun_7440_cc.svg"><defs + id="defs21" /><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1366" + inkscape:window-height="725" + id="namedview19" + showgrid="false" + inkscape:zoom="2.36" + inkscape:cx="50" + inkscape:cy="50" + inkscape:window-x="-7" + inkscape:window-y="17" + inkscape:window-maximized="1" + inkscape:current-layer="Layer_1" /><metadata + id="metadata3"><ns:sfw><ns:slices /><ns:sliceSourceBounds + x="0.083" + y="-99.397" + width="99.612" + height="99.046" + bottomLeftOrigin="true" /></ns:sfw><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><path + d="m 76.049064,20.333886 c 0,15.511169 -12.573022,39.463143 -28.082885,39.463143 -15.509864,0 -28.084192,-23.951974 -28.084192,-39.463143 0,-15.5098639 12.574328,-28.0828859 28.084192,-28.0828859 15.509863,0 28.082885,12.573022 28.082885,28.0828859 z" + id="path5" + inkscape:connector-curvature="0" + style="fill:#000000" /><g + id="g7" + transform="matrix(1.3064237,0,0,1.3064237,-8.125127,-8.2075549)"><rect + x="84.732002" + y="17.913" + width="7.165" + height="22.761" + id="rect9" + style="fill:#000000" /><rect + x="76.935997" + y="25.711" + width="22.76" + height="7.1659999" + id="rect11" + style="fill:#000000" /></g><path + d="m 92.93328,56.840592 c 0,0 -12.412327,-2.906793 -27.440119,-4.496711 -4.89909,5.946841 -10.963509,10.038561 -17.528289,10.038561 -6.566086,0 -12.629198,-4.09172 -17.530901,-10.038561 -15.026486,1.589918 -27.4388183,4.496711 -27.4388183,4.496711 -6.0814032,0 -11.0131526,4.930444 -11.0131526,11.013153 l 0,42.781455 c 0,6.08271 4.9317494,11.01316 11.0131526,11.01316 l 89.9381273,0 c 6.0801,0 11.01316,-4.92914 11.01316,-11.01316 l 0,-42.781455 c 0,-6.082709 -4.93175,-11.013153 -11.01316,-11.013153 z" + id="path13" + inkscape:connector-curvature="0" + style="fill:#000000" /></svg> \ No newline at end of file diff --git a/res/icons/noun_76373_cc.svg b/res/icons/noun_76373_cc.svg new file mode 100644 index 0000000000000000000000000000000000000000..2679707d282061d5c6a2cf9256c27cc310d54bde --- /dev/null +++ b/res/icons/noun_76373_cc.svg @@ -0,0 +1,139 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="Your_Icon" + x="0px" + y="0px" + width="100px" + height="100px" + viewBox="5.0 -10.0 100.0 135.0" + enable-background="new 0 0 100 100" + xml:space="preserve" + inkscape:version="0.91 r13725" + sodipodi:docname="noun_76373_cc.svg"><metadata + id="metadata71"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs69" /><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="640" + inkscape:window-height="480" + id="namedview67" + showgrid="false" + inkscape:zoom="2.36" + inkscape:cx="39.201211" + inkscape:cy="50" + inkscape:window-x="0" + inkscape:window-y="27" + inkscape:window-maximized="0" + inkscape:current-layer="Your_Icon" /><g + id="g3" + transform="matrix(1.3295413,0,0,1.3295413,-12.784498,-9.4181654)"><path + d="m 79.978,96 -55.52,0 c -7.72,0 -14,-6.279 -14,-14 l 0,-64 c 0,-7.72 6.28,-14 14,-14 l 55.52,0 c 7.72,0 14,6.28 14,14 l 0,64 c 0,7.721 -6.281,14 -14,14 z M 24.458,8 c -5.514,0 -10,4.486 -10,10 l 0,64 c 0,5.514 4.486,10 10,10 l 55.52,0 c 5.514,0 10,-4.486 10,-10 l 0,-64 c 0,-5.514 -4.486,-10 -10,-10 l -55.52,0 z" + id="path5" + inkscape:connector-curvature="0" /></g><g + id="g7" + transform="matrix(1.3295413,0,0,1.3295413,-12.784498,-9.4181654)"><path + d="m 52.218,77.5 c -15.164,0 -27.5,-12.336 -27.5,-27.5 0,-15.163 12.336,-27.5 27.5,-27.5 15.163,0 27.5,12.337 27.5,27.5 0,15.164 -12.337,27.5 -27.5,27.5 z m 0,-51 c -12.958,0 -23.5,10.542 -23.5,23.5 0,12.959 10.542,23.5 23.5,23.5 12.958,0 23.5,-10.541 23.5,-23.5 0,-12.958 -10.542,-23.5 -23.5,-23.5 z" + id="path9" + inkscape:connector-curvature="0" /></g><g + id="g11" + transform="matrix(1.3295413,0,0,1.3295413,-12.784498,-9.4181654)"><rect + x="6.0219998" + y="48" + width="13" + height="4" + id="rect13" /></g><g + id="g15" + transform="matrix(1.3295413,0,0,1.3295413,-12.784498,-9.4181654)"><rect + x="6.0219998" + y="58" + width="13" + height="4" + id="rect17" /></g><g + id="g19" + transform="matrix(1.3295413,0,0,1.3295413,-12.784498,-9.4181654)"><rect + x="6.0219998" + y="68" + width="13" + height="4" + id="rect21" /></g><g + id="g23" + transform="matrix(1.3295413,0,0,1.3295413,-12.784498,-9.4181654)"><rect + x="6.0219998" + y="18" + width="13" + height="4" + id="rect25" /></g><g + id="g27" + transform="matrix(1.3295413,0,0,1.3295413,-12.784498,-9.4181654)"><rect + x="6.0949998" + y="28" + width="12.855" + height="4" + id="rect29" /></g><g + id="g31" + transform="matrix(1.3295413,0,0,1.3295413,-12.784498,-9.4181654)"><rect + x="6.0219998" + y="38" + width="13" + height="4" + id="rect33" /></g><g + id="g35" + transform="matrix(1.3295413,0,0,1.3295413,-12.784498,-9.4181654)"><rect + x="6.0219998" + y="78" + width="13" + height="4" + id="rect37" /></g><g + id="g39" + transform="matrix(1.3295413,0,0,1.3295413,-12.784498,-9.4181654)"><g + id="g41"><circle + cx="52.219002" + cy="37.063" + r="1" + id="circle43" /><circle + cx="52.219002" + cy="37.063" + r="2" + id="circle45" /></g><g + id="g47"><path + d="m 52.218,39.063 -0.082,-0.002 c -1.102,-0.046 -1.961,-0.979 -1.916,-2.08 0.045,-1.076 0.922,-1.919 1.998,-1.919 1.182,0.047 2.041,0.979 1.998,2.081 -0.045,1.079 -0.924,1.92 -1.998,1.92 z m 0,-2 c 0,0 0,0 -0.002,0 l 0.002,1 0,-1 z" + id="path49" + inkscape:connector-curvature="0" /><path + d="M 52.218,40.063 52.113,40.06 c -1.67,-0.069 -2.959,-1.469 -2.892,-3.12 0.067,-1.614 1.384,-2.878 2.997,-2.878 1.764,0.069 3.063,1.439 2.997,3.12 -0.068,1.617 -1.384,2.881 -2.997,2.881 z m -0.04,-2 0.04,1 -0.04,-1 z m 1.038,-1.001 0,0.056 c 0,-0.005 10e-4,-0.011 10e-4,-0.016 0.022,-0.567 -0.425,-1.016 -1.039,-1.04 -0.498,0.001 -0.937,0.423 -0.959,0.961 0,0.014 -10e-4,0.027 -10e-4,0.041 l 1.998,-0.002 z" + id="path51" + inkscape:connector-curvature="0" /></g></g><g + id="g53" + transform="matrix(1.3295413,0,0,1.3295413,-12.784498,-9.4181654)"><rect + x="46.995998" + y="42.063" + width="7.2509999" + height="3" + id="rect55" /></g><g + id="g57" + transform="matrix(1.3295413,0,0,1.3295413,-12.784498,-9.4181654)"><rect + x="46.997002" + y="61.938" + width="10.442" + height="3" + id="rect59" /></g><rect + x="53.943848" + y="50.328758" + width="5.3952785" + height="22.600872" + id="rect61" /></svg> \ No newline at end of file diff --git a/res/ui/homescreen.ui b/res/ui/homescreen.ui new file mode 100644 index 0000000000000000000000000000000000000000..5175ab847228391bdad450b76f7c4b4ea4e08438 --- /dev/null +++ b/res/ui/homescreen.ui @@ -0,0 +1,158 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>HomeScreenWidget</class> + <widget class="QWidget" name="HomeScreenWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>301</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <property name="styleSheet"> + <string notr="true">QToolButton { + font-size: 14pt; + font-weight: bold; +}</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_welcome"> + <property name="text"> + <string><html><head/><body><p align="center"><span style=" font-size:xx-large; font-weight:600;">Welcome to Cutecoin !</span></p><p>Would you like to...</p></body></html></string> + </property> + <property name="textFormat"> + <enum>Qt::RichText</enum> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="topMargin"> + <number>6</number> + </property> + <item> + <widget class="QToolButton" name="button_new"> + <property name="text"> + <string>Create a new account</string> + </property> + <property name="icon"> + <iconset resource="../icons/icons.qrc"> + <normaloff>:/icons/add_account_icon</normaloff>:/icons/add_account_icon</iconset> + </property> + <property name="iconSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonFollowStyle</enum> + </property> + <property name="autoRaise"> + <bool>false</bool> + </property> + <property name="arrowType"> + <enum>Qt::NoArrow</enum> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="topMargin"> + <number>6</number> + </property> + <item> + <widget class="QToolButton" name="button_import"> + <property name="text"> + <string>Import an existing account</string> + </property> + <property name="icon"> + <iconset resource="../icons/icons.qrc"> + <normaloff>:/icons/import_icon</normaloff>:/icons/import_icon</iconset> + </property> + <property name="iconSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonFollowStyle</enum> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <property name="topMargin"> + <number>6</number> + </property> + <item> + <widget class="QToolButton" name="button_info"> + <property name="text"> + <string>Get to know more about ucoin</string> + </property> + <property name="icon"> + <iconset resource="../icons/icons.qrc"> + <normaloff>:/icons/ucoin_info_icon</normaloff>:/icons/ucoin_info_icon</iconset> + </property> + <property name="iconSize"> + <size> + <width>32</width> + <height>32</height> + </size> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonFollowStyle</enum> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources> + <include location="../icons/icons.qrc"/> + </resources> + <connections/> +</ui> diff --git a/src/cutecoin/core/account.py b/src/cutecoin/core/account.py index 1c678750a459f9f49fa5dbce98cbbfadc80587c2..38622cffb4c465cbb3a57d75da8c1527d3f5133c 100644 --- a/src/cutecoin/core/account.py +++ b/src/cutecoin/core/account.py @@ -6,8 +6,6 @@ Created on 1 févr. 2014 from ucoinpy import PROTOCOL_VERSION from ucoinpy.api import bma -from ucoinpy.api.bma import ConnectionHandler -from ucoinpy.documents.peer import Peer from ucoinpy.documents.certification import SelfCertification, Certification from ucoinpy.documents.membership import Membership from ucoinpy.key import SigningKey @@ -20,7 +18,7 @@ from PyQt5.QtCore import QObject, pyqtSignal, Qt from .wallet import Wallet from .community import Community from .person import Person -from ..tools.exceptions import NoPeerAvailable, ContactAlreadyExists +from ..tools.exceptions import ContactAlreadyExists def quantitative(units, community): @@ -63,31 +61,53 @@ class Account(QObject): loading_progressed = pyqtSignal(int, int) - def __init__(self, salt, pubkey, name, communities, wallets, contacts, - dead_communities): + def __init__(self, salt, pubkey, name, communities, wallets, contacts): ''' - Constructor + Create an account + + :param str salt: The root key salt + :param str pubkey: Known account pubkey. Used to check that password \ + is OK by comparing (salt, given_passwd) = (pubkey, privkey) \ + with known pubkey + :param str name: The account name, same as network identity uid + :param array communities: Community objects referenced by this account + :param array wallets: Wallet objects owned by this account + :param array contacts: Contacts of this account + + .. warnings:: The class methods create and load should be used to create an account ''' super().__init__() self.salt = salt self.pubkey = pubkey self.name = name self.communities = communities - self.dead_communities = dead_communities self.wallets = wallets self.contacts = contacts self.referential = 'Units' @classmethod - def create(cls, name, communities, wallets, confpath): + def create(cls, name): ''' - Constructor + Factory method to create an empty account object + This new account doesn't have any key and it should be given + one later + It doesn't have any community nor does it have wallets. + Communities could be added later, wallets will be managed + by its wallet pool size. + + :param str name: The account name, same as network identity uid + :return: A new empty account object ''' - account = cls(None, None, name, communities, wallets, [], []) + account = cls(None, None, name, [], [], []) return account @classmethod def load(cls, json_data): + ''' + Factory method to create an Account object from its json view. + :param dict json_data: The account view as a json dict + :return: A new account object created from the json datas + ''' salt = json_data['salt'] pubkey = json_data['pubkey'] @@ -102,25 +122,35 @@ class Account(QObject): wallets.append(Wallet.load(data)) communities = [] - dead_communities = [] for data in json_data['communities']: community = Community.load(data) communities.append(community) account = cls(salt, pubkey, name, communities, wallets, - contacts, dead_communities) + contacts) return account def __eq__(self, other): + ''' + :return: True if account.pubkey == other.pubkey + ''' if other is not None: return other.pubkey == self.pubkey else: return False def check_password(self, password): + ''' + Method to verify the key password validity + + :param str password: The key password + :return: True if the generated pubkey is the same as the account + .. warnings:: Generates a new temporary SigningKey + ''' key = SigningKey(self.salt, password) return (key.pubkey == self.pubkey) +#TODO: Contacts should be pure json, not Person objects def add_contact(self, person): same_contact = [contact for contact in self.contacts if person.pubkey == contact.pubkey] @@ -130,11 +160,21 @@ class Account(QObject): self.contacts.append(person) def add_community(self, community): - logging.debug("Adding a community") + ''' + Add a community to the account + + :param community: A community object to add + ''' self.communities.append(community) return community def refresh_cache(self): + ''' + Refresh the local account cache + This needs n_wallets * n_communities cache refreshing to end + + .. note:: emit the Account pyqtSignal loading_progressed during refresh + ''' loaded_wallets = 0 def progressing(value, maximum): @@ -166,15 +206,29 @@ class Account(QObject): return Account.referentials[self.referential][3].format(currency) def set_walletpool_size(self, size, password): + ''' + Change the size of the wallet pool + + :param int size: The new size of the wallet pool + :param str password: The password of the account, same for all wallets + ''' logging.debug("Defining wallet pool size") if len(self.wallets) < size: for i in range(len(self.wallets), size): - wallet = Wallet.create(i, self.salt, password, "Wallet {0}".format(i)) + wallet = Wallet.create(i, self.salt, password, + "Wallet {0}".format(i)) self.wallets.append(wallet) else: self.wallets = self.wallets[:size] def certify(self, password, community, pubkey): + ''' + Certify an other identity + + :param str password: The account SigningKey password + :param community: The community target of the certification + :param str pubkey: The certified identity pubkey + ''' certified = Person.lookup(pubkey, community) blockid = community.current_blockid() @@ -196,14 +250,14 @@ class Account(QObject): logging.debug("Posted data : {0}".format(data)) community.broadcast(bma.wot.Add, {}, data) - def sources(self, community): - sources = [] - for w in self.wallets: - for s in w.sources(community): - sources.append(s) - return sources - def transfers(self, community): + ''' + Get all transfers done in a community by all the wallets + owned by this account + + :param community: The target community of this request + :return: All account wallets transfers + ''' sent = [] for w in self.wallets: for transfer in w.transfers(community): @@ -211,20 +265,35 @@ class Account(QObject): return sent def amount(self, community): + ''' + Get amount of money owned in a community by all the wallets + owned by this account + + :param community: The target community of this request + :return: The value of all wallets values accumulated + ''' value = 0 for w in self.wallets: value += w.value(community) return value def member_of(self, community): - pubkeys = community.members_pubkeys() - if self.pubkey not in pubkeys: - logging.debug("{0} not found in members : {1}".format(self.pubkey, - pubkeys)) - return False - return True + ''' + Check if this account identity is a member of a community + + :param community: The target community of this request + :return: True if the account is a member of the target community + ''' + self_person = Person.lookup(self.pubkey, community) + return self_person.is_member(community) + + def send_selfcert(self, password, community): + ''' + Send our self certification to a target community - def send_pubkey(self, password, community): + :param str password: The account SigningKey password + :param community: The community target of the self certification + ''' selfcert = SelfCertification(PROTOCOL_VERSION, community.currency, self.pubkey, @@ -238,7 +307,14 @@ class Account(QObject): 'self_': selfcert.signed_raw(), 'other': []}) - def send_membership(self, password, community, type): + def send_membership(self, password, community, mstype): + ''' + Send a membership document to a target community + + :param str password: The account SigningKey password + :param community: The community target of the membership document + :param str mstype: The type of membership demand. "IN" to join, "OUT" to leave + ''' self_ = Person.lookup(self.pubkey, community) selfcert = self_.selfcert(community) @@ -246,7 +322,7 @@ class Account(QObject): membership = Membership(PROTOCOL_VERSION, community.currency, selfcert.pubkey, blockid['number'], - blockid['hash'], type, selfcert.uid, + blockid['hash'], mstype, selfcert.uid, selfcert.timestamp, None) key = SigningKey(self.salt, password) membership.sign([key]) @@ -255,9 +331,13 @@ class Account(QObject): {'membership': membership.signed_raw()}) def jsonify(self): + ''' + Get the account in a json format. + + :return: A dict view of the account to be saved as json + ''' data_communities = [] - communities = self.communities + self.dead_communities - for c in communities: + for c in self.communities: data_communities.append(c.jsonify()) data_wallets = [] diff --git a/src/cutecoin/core/app.py b/src/cutecoin/core/app.py index 7ef3a31d77a306ae918f8eb36d4f13df2ef64944..8f0d043269fc14bf58402b48bfcb80d8cb5a60ee 100644 --- a/src/cutecoin/core/app.py +++ b/src/cutecoin/core/app.py @@ -24,13 +24,16 @@ class Application(QObject): ''' Managing core application datas : Accounts list and general configuration + Saving and loading the application state ''' loading_progressed = pyqtSignal(int, int) def __init__(self, argv): ''' - Constructor + Create a new "cutecoin" application + + :param argv: The argv parameters of the call ''' super().__init__() self.accounts = {} @@ -40,6 +43,12 @@ class Application(QObject): self.load() def get_account(self, name): + ''' + Load an account then return it + + :param str name: The account name + :return: The loaded account if it's a success, else return None + ''' self.load_account(name) if name in self.accounts.keys(): return self.accounts[name] @@ -47,14 +56,18 @@ class Application(QObject): return None def create_account(self, name): + ''' + Create a new account from its name + + :param str name: The account name + :return: The new account + :raise: NameAlreadyExists if the account name is already used locally + ''' for a in self.accounts: if a == name: raise NameAlreadyExists(a) - account = Account.create(name, - [], - [], - config.parameters) + account = Account.create(name) return account @@ -62,11 +75,22 @@ class Application(QObject): self.accounts[account.name] = account def delete_account(self, account): + ''' + Delete an account. + Current account changes to None if it is deleted. + ''' self.accounts.pop(account.name) if self.current_account == account: self.current_account = None def change_current_account(self, account): + ''' + Change current account displayed and refresh its cache. + + :param account: The account object to display + .. note:: Emits the application pyqtSignal loading_progressed + during cache refresh + ''' def progressing(value, maximum): self.loading_progressed.emit(value, maximum) @@ -77,6 +101,13 @@ class Application(QObject): self.current_account = account def load(self): + ''' + Load a saved application state from the data file. + Loads only jsonified objects but not their cache. + + If the standard application state file can't be found, + no error is raised. + ''' self.load_persons() try: logging.debug("Loading data...") @@ -90,6 +121,10 @@ class Application(QObject): pass def load_persons(self): + ''' + Load the Person instances of the person module. + Each instance is unique, and can be find by its public key. + ''' try: persons_path = os.path.join(config.parameters['home'], '__persons__') @@ -100,6 +135,11 @@ class Application(QObject): pass def load_account(self, account_name): + ''' + Load an account from its name + + :param str account_name: The account name + ''' account_path = os.path.join(config.parameters['home'], account_name, 'properties') with open(account_path, 'r') as json_data: @@ -109,6 +149,11 @@ class Application(QObject): self.accounts[account_name] = account def load_cache(self, account): + ''' + Load an account cache + + :param account: The account object to load the cache + ''' for community in account.communities: community_path = os.path.join(config.parameters['home'], account.name, '__cache__', @@ -122,7 +167,7 @@ class Application(QObject): with open(network_path, 'r') as json_data: data = json.load(json_data) if 'version' in data and data['version'] == __version__: - community.load_network(data) + community.load_merge_network(data['network']) else: os.remove(network_path) @@ -146,6 +191,11 @@ class Application(QObject): os.remove(wallet_path) def save(self, account): + ''' + Save an account + + :param account: The account object to save + ''' with open(config.parameters['data'], 'w') as outfile: json.dump(self.jsonify(), outfile, indent=4, sort_keys=True) account_path = os.path.join(config.parameters['home'], @@ -162,6 +212,9 @@ class Application(QObject): shutil.rmtree(account_path) def save_persons(self): + ''' + Save the person module cache + ''' persons_path = os.path.join(config.parameters['home'], '__persons__') with open(persons_path, 'w')as outfile: @@ -170,6 +223,11 @@ class Application(QObject): json.dump(data, outfile, indent=4, sort_keys=True) def save_cache(self, account): + ''' + Save the cache of an account + + :param account: The account object to save the cache + ''' if not os.path.exists(os.path.join(config.parameters['home'], account.name, '__cache__')): os.makedirs(os.path.join(config.parameters['home'], @@ -192,7 +250,7 @@ class Application(QObject): community.currency + '_network') with open(network_path, 'w') as outfile: - data = community.jsonify_network() + data['network'] = community.jsonify_network() data['version'] = __version__ json.dump(data, outfile, indent=4, sort_keys=True) @@ -202,6 +260,12 @@ class Application(QObject): json.dump(data, outfile, indent=4, sort_keys=True) def import_account(self, file, name): + ''' + Import an account from a tar file + + :param str file: The file path of the tar file + :param str name: The account name + ''' with tarfile.open(file, "r") as tar: path = os.path.join(config.parameters['home'], name) @@ -222,6 +286,12 @@ class Application(QObject): self.save(account) def export_account(self, file, account): + ''' + Export an account to a tar file + + :param str file: The filepath of the tar file + :param account: The account object to export + ''' with tarfile.open(file, "w") as tar: for file in ["properties"]: path = os.path.join(config.parameters['home'], @@ -229,6 +299,11 @@ class Application(QObject): tar.add(path, file) def jsonify_accounts(self): + ''' + Jsonify an account + + :return: The account as a dict to format as json + ''' data = [] logging.debug("{0}".format(self.accounts)) for account in self.accounts: @@ -236,6 +311,11 @@ class Application(QObject): return data def jsonify(self): + ''' + Jsonify the app datas + + :return: The accounts of the app to format as json + ''' data = {'default_account': self.default_account, 'local_accounts': self.jsonify_accounts()} return data diff --git a/src/cutecoin/core/community.py b/src/cutecoin/core/community.py index 762dd50232bd01eabd5c6a9bb1d889f390a03de4..171fc81389386cfce46577012a0a0bc6758c1437 100644 --- a/src/cutecoin/core/community.py +++ b/src/cutecoin/core/community.py @@ -21,11 +21,19 @@ class Cache(): hash(bma.wot.Lookup)] def __init__(self, community): + ''' + Init an empty cache + ''' self.latest_block = 0 self.community = community self.data = {} def load_from_json(self, data): + ''' + Put data in the cache from json datas. + + :param dict data: The cache in json format + ''' self.data = {} for entry in data['cache']: key = entry['key'] @@ -35,6 +43,11 @@ class Cache(): self.latest_block = data['latest_block'] def jsonify(self): + ''' + Get the cache in json format + + :return: The cache as a dict in json format + ''' data = {k: self.data[k] for k in self.data.keys() if k[0] in Cache._saved_requests} entries = [] @@ -45,11 +58,23 @@ class Cache(): 'cache': entries} def refresh(self): + ''' + Refreshing the cache just clears every last requests which + cannot be saved because they can change from one block to another. + ''' self.latest_block = self.community.current_blockid()['number'] self.data = {k: self.data[k] for k in self.data.keys() if k[0] in Cache._saved_requests} def request(self, request, req_args={}, get_args={}): + ''' + Send a cached request to a community. + If the request was already sent in current block, return last value get. + + :param request: The request bma class + :param req_args: The arguments passed to the request constructor + :param get_args: The arguments passed to the requests __get__ method + ''' cache_key = (hash(request), hash(tuple(frozenset(sorted(req_args.keys())))), hash(tuple(frozenset(sorted(req_args.items())))), @@ -72,12 +97,21 @@ class Cache(): class Community(object): ''' - classdocs + A community is a group of nodes using the same currency. + + .. warning:: The currency name is supposed to be unique in cutecoin + but nothing exists in ucoin to assert that a currency name is unique. ''' def __init__(self, currency, network): ''' - A community is a group of nodes using the same currency. + Initialize community attributes with a currency and a network. + + :param str currency: The currency name of the community. + :param network: The network of the community + + .. warning:: The community object should be created using its factory + class methods. ''' self.currency = currency self._network = network @@ -87,6 +121,11 @@ class Community(object): @classmethod def create(cls, node): + ''' + Create a community from its first node. + + :param node: The first Node of the community + ''' network = Network.create(node) community = cls(node.currency, network) logging.debug("Creating community") @@ -94,31 +133,70 @@ class Community(object): @classmethod def load(cls, json_data): + ''' + Load a community from json + + :param dict json_data: The community as a dict in json format + ''' currency = json_data['currency'] network = Network.from_json(currency, json_data['peers']) community = cls(currency, network) return community - def load_network(self, json_data): - self._network.merge_with_json(json_data['network']) + def load_merge_network(self, json_data): + ''' + Load the last state of the network. + This network is merged with the network currently + known network. + + :param dict json_data: + ''' + self._network.merge_with_json(json_data) def load_cache(self, json_data): + ''' + Load the community cache. + + :param dict json_data: The community as a dict in json format. + ''' self._cache.load_from_json(json_data) def jsonify_cache(self): + ''' + Get the cache jsonified. + + :return: The cache as a dict in json format. + ''' return self._cache.jsonify() def jsonify_network(self): - return {'network': self._network.jsonify()} + ''' + Get the network jsonified. + ''' + return self._network.jsonify() + @property def name(self): + ''' + The community name is its currency name. + + :return: The community name + ''' return self.currency def __eq__(self, other): + ''' + :return: True if this community has the same currency name as the other one. + ''' return (other.currency == self.currency) @property def short_currency(self): + ''' + Format the currency name to a short one + + :return: The currency name in a shot format. + ''' words = re.split('[_\W]+', self.currency) shortened = "" if len(words) > 1: @@ -131,12 +209,22 @@ class Community(object): @property def currency_symbol(self): + ''' + Format the currency name to a symbol one. + + :return: The currency name as a utf-8 circled symbol. + ''' letter = self.currency[0] u = ord('\u24B6') + ord(letter) - ord('A') return chr(u) @property def dividend(self): + ''' + Get the last generated community universal dividend. + + :return: The last UD or 1 if no UD was generated. + ''' block = self.get_ud_block() if block: return block['dividend'] @@ -144,6 +232,12 @@ class Community(object): return 1 def get_ud_block(self, x=0): + ''' + Get a block with universal dividend + + :param int x: Get the 'x' older block with UD in it + :return: The last block with universal dividend. + ''' blocks = self.request(bma.blockchain.UD)['result']['blocks'] if len(blocks) > 0: block_number = blocks[len(blocks)-(1+x)] @@ -155,6 +249,11 @@ class Community(object): @property def monetary_mass(self): + ''' + Get the community monetary mass + + :return: The monetary mass value + ''' try: block = self.request(bma.blockchain.Current) return block['monetaryMass'] @@ -164,6 +263,11 @@ class Community(object): @property def nb_members(self): + ''' + Get the community members number + + :return: The community members number + ''' try: block = self.request(bma.blockchain.Current) return block['membersCount'] @@ -173,16 +277,44 @@ class Community(object): @property def nodes(self): + ''' + Get the known community nodes + + :return: All community known nodes + ''' return self._network.all_nodes @property def network(self): + ''' + Get the community network instance. + + :return: The community network instance. + ''' return self._network + @property + def parameters(self): + ''' + + ''' + return self.request(bma.blockchain.Parameters) + + @property def add_peer(self, peer): + ''' + Add a peer to the community. + + :param peer: The new peer as a ucoinpy Peer object. + ''' self._network.add_node(Node.from_peer(peer)) def get_block(self, number=None): + ''' + Get a block + + :param int number: The block number. If none, returns current block. + ''' if number is None: data = self.request(bma.blockchain.Current) else: @@ -193,6 +325,11 @@ class Community(object): data['signature'])) def current_blockid(self): + ''' + Get the current block id. + + :return: The current block ID as [NUMBER-HASH] format. + ''' try: block = self.request(bma.blockchain.Current, cached=False) signed_raw = "{0}{1}\n".format(block['raw'], block['signature']) @@ -209,14 +346,27 @@ class Community(object): def members_pubkeys(self): ''' Listing members pubkeys of a community + + :return: All members pubkeys. ''' memberships = self.request(bma.wot.Members) return [m['pubkey'] for m in memberships["results"]] def refresh_cache(self): + ''' + Start the refresh processing of the cache + ''' self._cache.refresh() def request(self, request, req_args={}, get_args={}, cached=True): + ''' + Start a request to the community. + + :param request: A ucoinpy bma request class + :param req_args: Arguments to pass to the request constructor + :param get_args: Arguments to pass to the request __get__ method + :return: The returned data + ''' if cached: return self._cache.request(request, req_args, get_args) else: @@ -244,6 +394,15 @@ class Community(object): raise NoPeerAvailable(self.currency, len(nodes)) def post(self, request, req_args={}, post_args={}): + ''' + Post data to a community. + Only sends the data to one node. + + :param request: A ucoinpy bma request class + :param req_args: Arguments to pass to the request constructor + :param post_args: Arguments to pass to the request __post__ method + :return: The returned data + ''' nodes = self._network.online_nodes for node in nodes: req = request(node.endpoint.conn_handler(), **req_args) @@ -259,6 +418,18 @@ class Community(object): raise NoPeerAvailable(self.currency, len(nodes)) def broadcast(self, request, req_args={}, post_args={}): + ''' + Broadcast data to a community. + Sends the data to all knew nodes. + + :param request: A ucoinpy bma request class + :param req_args: Arguments to pass to the request constructor + :param post_args: Arguments to pass to the request __post__ method + :return: The returned data + + .. note:: If one node accept the requests (returns 200), + the broadcast is considered accepted by the network. + ''' tries = 0 ok = False value_error = None @@ -283,9 +454,11 @@ class Community(object): raise NoPeerAvailable(self.currency, len(nodes)) def jsonify(self): + ''' + Jsonify the community datas. + + :return: The community as a dict in json format. + ''' data = {'currency': self.currency, 'peers': self._network.jsonify()} - return data - - def get_parameters(self): - return self.request(bma.blockchain.Parameters) + return data \ No newline at end of file diff --git a/src/cutecoin/core/graph.py b/src/cutecoin/core/graph.py index e768f3083dd604cd3ec85ac72d9b31cee749d90a..ee11732d443b4c5e4dde79ee6ffbb62acbd72a27 100644 --- a/src/cutecoin/core/graph.py +++ b/src/cutecoin/core/graph.py @@ -15,7 +15,7 @@ class Graph(object): """ self.community = community - self.signature_validity = self.community.get_parameters()['sigValidity'] + self.signature_validity = self.community.parameters['sigValidity'] #  arc considered strong during 75% of signature validity time self.ARC_STATUS_STRONG_time = int(self.signature_validity * 0.75) # graph empty if None parameter diff --git a/src/cutecoin/core/net/network.py b/src/cutecoin/core/net/network.py index af0c247ab4d1f6c43915f29243f2945b09f85d0b..bab907d6ff7544dd4026eb3802010b65f585a281 100644 --- a/src/cutecoin/core/net/network.py +++ b/src/cutecoin/core/net/network.py @@ -17,13 +17,17 @@ from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot class Network(QObject): ''' - classdocs + A network is managing nodes polling and crawling of a + given community. ''' nodes_changed = pyqtSignal() def __init__(self, currency, nodes): ''' - Constructor + Constructor of a network + + :param str currency: The currency name of the community + :param list nodes: The nodes of the network ''' super().__init__() self.currency = currency @@ -33,25 +37,29 @@ class Network(QObject): self.must_crawl = False @classmethod - def from_json(cls, currency, json_data): + def create(cls, node): ''' - We load the nodes which we know for sure since we - used them at the community creation + Create a new network with one knew node + Crawls the nodes from the first node to build the + community network + + :param node: The first knew node of the network ''' - nodes = [] - for data in json_data: - node = Node.from_json(currency, data) - nodes.append(node) - logging.debug("Loading : {:}".format(data['pubkey'])) + nodes = [node] + network = cls(node.currency, nodes) + nodes = network.crawling() block_max = max([n.block for n in nodes]) for node in nodes: node.check_sync(block_max) - return cls(currency, nodes) + network._nodes = nodes + return network def merge_with_json(self, json_data): ''' - We merge with dynamic nodes detected when we + We merge with knew nodes when we last stopped cutecoin + + :param dict json_data: Nodes in json format ''' for data in json_data: node = Node.from_json(self.currency, data) @@ -60,41 +68,66 @@ class Network(QObject): self._nodes = self.crawling() @classmethod - def create(cls, node): - nodes = [node] - network = cls(node.currency, nodes) - nodes = network.crawling() + def from_json(cls, currency, json_data): + ''' + Load a network from a configured community + + :param str currency: The currency name of a community + :param dict json_data: A json_data view of a network + ''' + nodes = [] + for data in json_data: + node = Node.from_json(currency, data) + nodes.append(node) + logging.debug("Loading : {:}".format(data['pubkey'])) block_max = max([n.block for n in nodes]) for node in nodes: node.check_sync(block_max) - network._nodes = nodes - return network + return cls(currency, nodes) def jsonify(self): + ''' + Get the network in json format. + + :return: The network as a dict in json format. + ''' data = [] for node in self._nodes: data.append(node.jsonify()) return data - def __del__(self): - self.must_crawl = False - def stop_crawling(self): + ''' + Stop network nodes crawling. + ''' self.must_crawl = False @property def online_nodes(self): + ''' + Get nodes which are in the ONLINE state. + ''' return [n for n in self._nodes if n.state == Node.ONLINE] @property def all_nodes(self): + ''' + Get all knew nodes. + ''' return self._nodes.copy() def add_nodes(self, node): + ''' + Add a node to the network. + ''' self._nodes.append(node) node.changed.connect(self.nodes_changed) def start_perpetual_crawling(self): + ''' + Start crawling which never stops. + To stop this crawling, call "stop_crawling" method. + ''' self.must_crawl = True while self.must_crawl: nodes = self.crawling(interval=10) @@ -112,6 +145,11 @@ class Network(QObject): n.changed.connect(self.nodes_changed) def crawling(self, interval=0): + ''' + One network crawling. + + :param int interval: The interval between two nodes request. + ''' nodes = [] traversed_pubkeys = [] for n in self._nodes.copy(): diff --git a/src/cutecoin/core/net/node.py b/src/cutecoin/core/net/node.py index 60cb7235b88edd6f4a674f8c85120d3ed9e75949..6ed23a0e504717eef72053c9058a81b674d72692 100644 --- a/src/cutecoin/core/net/node.py +++ b/src/cutecoin/core/net/node.py @@ -16,9 +16,14 @@ from PyQt5.QtCore import QObject, pyqtSignal class Node(QObject): - """ - classdocs - """ + ''' + A node is a peer seend from the client point of view. + This node can have multiple states : + - ONLINE : The node is available for requests + - OFFLINE: The node is disconnected + - DESYNCED : The node is online but is desynced from the network + - CORRUPTED : The node is corrupted, some weird behaviour is going on + ''' ONLINE = 1 OFFLINE = 2 @@ -27,17 +32,10 @@ class Node(QObject): changed = pyqtSignal() - def __init__(self, currency: str, endpoints: list, pubkey: str, block: int, state: int): - """ + def __init__(self, currency, endpoints, pubkey, block, state): + ''' Constructor - :param str currency: Name of the currency - :param list endpoints: List of BMAEndpoint - :param str pubkey: Public key of the node owner - :param int block: Last block number - :param int state: State of the node - :return: - """ - + ''' super().__init__() self._endpoints = endpoints self._pubkey = pubkey @@ -48,6 +46,14 @@ class Node(QObject): @classmethod def from_address(cls, currency, address, port): + ''' + Factory method to get a node from a given address + + :param str currency: The node currency. None if we don't know\ + the currency it should have, for example if its the first one we add + :param str address: The node address + :param int port: The node port + ''' peer_data = bma.network.Peering(ConnectionHandler(address, port)).get() peer = Peer.from_signed_raw("{0}{1}\n".format(peer_data['raw'], @@ -57,12 +63,19 @@ class Node(QObject): if peer.currency != currency: raise InvalidNodeCurrency(peer.currency, currency) - node = cls(peer.currency, peer.endpoints, peer.pubkey, 0, Node.ONLINE, 0) + node = cls(peer.currency, peer.endpoints, peer.pubkey, 0, Node.ONLINE) node.refresh_state() return node @classmethod def from_peer(cls, currency, peer): + ''' + Factory method to get a node from a peer document. + + :param str currency: The node currency. None if we don't know\ + the currency it should have, for example if its the first one we add + :param peer: The peer document + ''' if currency is not None: if peer.currency != currency: raise InvalidNodeCurrency(peer.currency, currency) diff --git a/src/cutecoin/core/person.py b/src/cutecoin/core/person.py index bcaa2d8ab04d25f9a743e744ed65110bb0327939..af9bb55b1cd177933e7f652befe0ad506a65c9b6 100644 --- a/src/cutecoin/core/person.py +++ b/src/cutecoin/core/person.py @@ -11,7 +11,7 @@ from ucoinpy.api import bma from ucoinpy import PROTOCOL_VERSION from ucoinpy.documents.certification import SelfCertification from ucoinpy.documents.membership import Membership -from cutecoin.tools.exceptions import PersonNotFoundError,\ +from ..tools.exceptions import Error, PersonNotFoundError,\ MembershipNotFoundError from PyQt5.QtCore import QMutex @@ -51,6 +51,8 @@ class cached(object): except KeyError: value = self.func(inst, community) inst._cache[community.currency][self.func.__name__] = value + finally: + inst._cache_mutex.unlock() inst._cache_mutex.unlock() return value @@ -65,15 +67,20 @@ class cached(object): return functools.partial(self, inst) +#TODO: Change Person to Identity ? class Person(object): ''' - A person with a name, a fingerprint and an email + A person with a name and a pubkey ''' _instances = {} def __init__(self, name, pubkey, cache): ''' - Constructor + Initializing a person object. + + :param str name: The person name, also known as its uid on the network + :param str pubkey: The person pubkey + :param cache: The last returned values of the person properties. ''' self.name = name self.pubkey = pubkey @@ -83,13 +90,26 @@ class Person(object): @classmethod def lookup(cls, pubkey, community, cached=True): ''' - Create a person from the pubkey found in a community + Get a person from the pubkey found in a community + + :param str pubkey: The person pubkey + :param community: The community in which to look for the pubkey + :param bool cached: True if the person should be searched in the + cache before requesting the community. + + :return: A new person if the pubkey was unknown or\ + the known instance if pubkey was already known. ''' - if pubkey in Person._instances: + if cached and pubkey in Person._instances: return Person._instances[pubkey] else: - data = community.request(bma.wot.Lookup, req_args={'search': pubkey}, - cached=cached) + try: + data = community.request(bma.wot.Lookup, req_args={'search': pubkey}, + cached=cached) + except ValueError as e: + if '404' in str(e): + raise PersonNotFoundError(pubkey, community.name) + timestamp = 0 for result in data['results']: @@ -104,10 +124,18 @@ class Person(object): Person._instances[pubkey] = person logging.debug("{0}".format(Person._instances.keys())) return person - raise PersonNotFoundError(pubkey, community.name()) + raise PersonNotFoundError(pubkey, community.name) @classmethod def from_metadata(cls, metadata): + ''' + Get a person from a metadata dict. + A metadata dict has a 'text' key corresponding to the person name, + and a 'id' key corresponding to the person pubkey. + + :param dict metadata: The person metadata + :return: A new person if pubkey wasn't knwon, else the existing instance. + ''' name = metadata['text'] pubkey = metadata['id'] if pubkey in Person._instances: @@ -119,17 +147,20 @@ class Person(object): @classmethod #TODO: Remove name from person, contats should not use the person class - def from_json(cls, json_person): + def from_json(cls, json_data): ''' Create a person from json data + + :param dict json_data: The person as a dict in json format + :return: A new person if pubkey wasn't known, else a new person instance. ''' - pubkey = json_person['pubkey'] + pubkey = json_data['pubkey'] if pubkey in Person._instances: return Person._instances[pubkey] else: - name = json_person['name'] - if 'cache' in json_person: - cache = json_person['cache'] + name = json_data['name'] + if 'cache' in json_data: + cache = json_data['cache'] else: cache = {} @@ -138,6 +169,13 @@ class Person(object): return person def selfcert(self, community): + ''' + Get the person self certification. + This request is not cached in the person object. + + :param community: The community target to request the self certification + :return: A SelfCertification ucoinpy object + ''' data = community.request(bma.wot.Lookup, req_args={'search': self.pubkey}) logging.debug(data) timestamp = 0 @@ -157,9 +195,17 @@ class Person(object): timestamp, name, signature) - raise PersonNotFoundError(self.pubkey, community.name()) + raise PersonNotFoundError(self.pubkey, community.name) +#TODO: Cache this data by returning only the timestamp instead of a datetime object def get_join_date(self, community): + ''' + Get the person join date. + This request is not cached in the person object. + + :param community: The community target to request the join date + :return: A datetime object + ''' try: search = community.request(bma.blockchain.Membership, {'search': self.pubkey}) membership_data = None @@ -170,10 +216,17 @@ class Person(object): return None except ValueError as e: if '400' in str(e): - raise MembershipNotFoundError(self.pubkey, community.name()) + raise MembershipNotFoundError(self.pubkey, community.name) +#TODO: Manage 'OUT' memberships @cached def membership(self, community): + ''' + Get the person last membership document. + + :param community: The community target to request the join date + :return: The membership data in BMA json format + ''' try: search = community.request(bma.blockchain.Membership, {'search': self.pubkey}) @@ -188,15 +241,21 @@ class Person(object): membership_data = ms if membership_data is None: - raise MembershipNotFoundError(self.pubkey, community.name()) + raise MembershipNotFoundError(self.pubkey, community.name) except ValueError as e: if '400' in str(e): - raise MembershipNotFoundError(self.pubkey, community.name()) + raise MembershipNotFoundError(self.pubkey, community.name) return membership_data @cached def is_member(self, community): + ''' + Check if the person is a member of a community + + :param community: The community target to request the join date + :return: True if the person is a member of a community + ''' try: certifiers = community.request(bma.wot.CertifiersOf, {'search': self.pubkey}) return certifiers['isMember'] @@ -205,6 +264,12 @@ class Person(object): @cached def certifiers_of(self, community): + ''' + Get the list of this person certifiers + + :param community: The community target to request the join date + :return: The list of the certifiers of this community in BMA json format + ''' try: certifiers = community.request(bma.wot.CertifiersOf, {'search': self.pubkey}) except ValueError as e: @@ -237,6 +302,12 @@ class Person(object): @cached def certified_by(self, community): + ''' + Get the list of persons certified by this person + + :param community: The community target to request the join date + :return: The list of the certified persons of this community in BMA json format + ''' try: certified_list = community.request(bma.wot.CertifiedBy, {'search': self.pubkey}) except ValueError as e: @@ -262,33 +333,50 @@ class Person(object): return certified_list['certifications'] def reload(self, func, community): - self._cache_mutex.lock() - if community.currency not in self._cache: - self._cache[community.currency] = {} + ''' + Reload a cached property of this person in a community. + This method is thread safe. + This method clears the cache entry for this community and get it back. - change = False + :param func: The cached property to reload + :param community: The community to request for data + :return: True if a changed was made by the reload. + ''' + self._cache_mutex.lock() try: + if community.currency not in self._cache: + self._cache[community.currency] = {} + + change = False before = self._cache[community.currency][func.__name__] - except KeyError: - change = True - value = func(self, community) + value = func(self, community) + + if not change: + if type(value) is dict: + hash_before = (hash(tuple(frozenset(sorted(before.keys())))), + hash(tuple(frozenset(sorted(before.items()))))) + hash_after = (hash(tuple(frozenset(sorted(value.keys())))), + hash(tuple(frozenset(sorted(value.items()))))) + change = hash_before != hash_after + elif type(value) is bool: + change = before != value - if not change: - if type(value) is dict: - hash_before = (hash(tuple(frozenset(sorted(before.keys())))), - hash(tuple(frozenset(sorted(before.items()))))) - hash_after = (hash(tuple(frozenset(sorted(value.keys())))), - hash(tuple(frozenset(sorted(value.items()))))) - change = hash_before != hash_after - elif type(value) is bool: - change = before != value + self._cache[community.currency][func.__name__] = value - self._cache[community.currency][func.__name__] = value - self._cache_mutex.unlock() + except KeyError: + change = True + except Error: + return False + finally: + self._cache_mutex.unlock() return change def jsonify(self): + ''' + Get the community as dict in json format. + :return: The community as a dict in json format + ''' data = {'name': self.name, 'pubkey': self.pubkey, 'cache': self._cache} diff --git a/src/cutecoin/core/transfer.py b/src/cutecoin/core/transfer.py index 5979b6615ec2bcf14347ffba2623e28e6ba2f0a8..ddf144c10fccdd7f9b704cbc8c0c3676dc57dae6 100644 --- a/src/cutecoin/core/transfer.py +++ b/src/cutecoin/core/transfer.py @@ -10,18 +10,35 @@ from ucoinpy.documents.transaction import Transaction class Transfer(object): ''' - A transaction + A transfer is the lifecycle of a transaction. + TO_SEND means the transaction wasn't sent yet + AWAITING means the transaction is waiting for a blockchain validation + VALIDATED means the transaction was registered in the blockchain + REFUSED means the transaction took too long to be registered in the blockchain, + therefore it is considered as refused + DROPPED means the transaction was canceled locally. It can still be validated + in the blockchain if it was sent, if the guy is unlucky ;) ''' TO_SEND = 0 AWAITING = 1 VALIDATED = 2 REFUSED = 3 - SENT = 4 DROPPED = 5 def __init__(self, txdoc, state, metadata): ''' - Constructor + The constructor of a transfer. + Check for metadata keys which must be present : + - receiver + - block + - time + - issuer + - amount + - comment + + :param txdoc: The Transaction ucoinpy object + :param state: The state of the Transfer (TO_SEND, AWAITING, VALIDATED, REFUSED or DROPPED) + :param metadata: The transfer metadata ''' assert('receiver' in metadata) assert('block' in metadata) @@ -36,25 +53,40 @@ class Transfer(object): @classmethod def initiate(cls, metadata): + ''' + Create a new transfer in a "TO_SEND" state. + ''' return cls(None, Transfer.TO_SEND, metadata) @classmethod def create_validated(cls, txdoc, metadata): + ''' + Create a new transfer in a "VALIDATED" state. + ''' return cls(txdoc, Transfer.VALIDATED, metadata) - @property - def metadata(self): - return self._metadata - @classmethod def load(cls, data): + ''' + Create a new transfer from a dict in json format. + ''' if data['state'] is Transfer.TO_SEND: txdoc = None else: txdoc = Transaction.from_signed_raw(data['txdoc']) return cls(txdoc, data['state'], data['metadata']) + @property + def metadata(self): + ''' + :return: this transfer metadata + ''' + return self._metadata + def jsonify(self): + ''' + :return: The transfer as a dict in json format + ''' if self.txdoc: txraw = self.txdoc.signed_raw() else: @@ -64,6 +96,14 @@ class Transfer(object): 'metadata': self._metadata} def send(self, txdoc, community): + ''' + Send a transaction and update the transfer state to AWAITING if accepted. + If the transaction was refused (return code != 200), state becomes REFUSED + The txdoc is saved as the transfer txdoc. + + :param txdoc: A transaction ucoinpy object + :param community: The community target of the transaction + ''' try: self.txdoc = txdoc community.broadcast(bma.tx.Process, @@ -78,27 +118,54 @@ class Transfer(object): self._metadata['time'] = community.get_block().mediantime def check_registered(self, tx, block, time): + ''' + Check if the transfer was registered in a block. + Update the transfer state to VALIDATED if it was registered. + + :param tx: A transaction ucoinpy object found in the block + :param int block: The block number checked + :param int time: The time of the block + ''' if tx.signed_raw() == self.txdoc.signed_raw(): self.state = Transfer.VALIDATED self._metadata['block'] = block self._metadata['time'] = time def check_refused(self, block): + ''' + Check if the transfer was refused + If more than 15 blocks were mined since the transaction + transfer, it is considered as refused. + + :param int block: The current block number + ''' if block > self._metadata['block'] + 15: self.state = Transfer.REFUSED def drop(self): + ''' + Cancel the transfer locally. + The transfer state becomes "DROPPED". + ''' self.state = Transfer.DROPPED class Received(Transfer): def __init__(self, txdoc, metadata): ''' - Constructor + A transfer were the receiver is the local user. + + :param txdoc: The transaction document of the received transfer + :param metadata: The metadata of the transfer ''' super().__init__(txdoc, Transfer.VALIDATED, metadata) @classmethod def load(cls, data): + ''' + Create a transfer from a dict in json format. + + :param data: The transfer as a dict in json format + ''' txdoc = Transaction.from_signed_raw(data['txdoc']) return cls(txdoc, data['metadata']) diff --git a/src/cutecoin/core/wallet.py b/src/cutecoin/core/wallet.py index 037b72cc9680eecc85fbe360c48cb738f2f0fe4d..e7ef3d75cd1e417705747b06ebd91941c2d807d0 100644 --- a/src/cutecoin/core/wallet.py +++ b/src/cutecoin/core/wallet.py @@ -60,6 +60,7 @@ class Cache(): def transfers(self): return [t for t in self._transfers if t.state != Transfer.DROPPED] +#TODO: Refactor to reduce this method size and split it to more methods def refresh(self, community): current_block = 0 try: @@ -153,7 +154,11 @@ class Wallet(QObject): def __init__(self, walletid, pubkey, name): ''' - Constructor + Constructor of a wallet object + + :param int walletid: The wallet number, unique between all wallets + :param str pubkey: The wallet pubkey + :param str name: The wallet name ''' super().__init__() self.coins = [] @@ -164,6 +169,14 @@ class Wallet(QObject): @classmethod def create(cls, walletid, salt, password, name): + ''' + Factory method to create a new wallet + + :param int walletid: The wallet number, unique between all wallets + :param str salt: The account salt + :param str password: The account password + :param str name: The account name + ''' if walletid == 0: key = SigningKey(salt, password) else: @@ -172,32 +185,58 @@ class Wallet(QObject): @classmethod def load(cls, json_data): + ''' + Factory method to load a saved wallet. + + :param dict json_data: The wallet as a dict in json format + ''' walletid = json_data['walletid'] pubkey = json_data['pubkey'] name = json_data['name'] return cls(walletid, pubkey, name) - def __eq__(self, other): - return (self.keyid == other.keyid) - def load_caches(self, json_data): + ''' + Load this wallet caches. + Each cache correspond to one different community. + + :param dict json_data: The caches as a dict in json format + ''' for currency in json_data: if currency != 'version': self.caches[currency] = Cache(self) self.caches[currency].load_from_json(json_data[currency]) def jsonify_caches(self): + ''' + Get this wallet caches as json. + + :return: The wallet caches as a dict in json format + ''' data = {} for currency in self.caches: data[currency] = self.caches[currency].jsonify() return data def refresh_cache(self, community): + ''' + Refresh the cache of this wallet for the specified community. + + :param community: The community to refresh its cache + ''' if community.currency not in self.caches: self.caches[community.currency] = Cache(self) self.caches[community.currency].refresh(community) def check_password(self, salt, password): + ''' + Check if wallet password is ok. + + :param salt: The account salt + :param password: The given password + :return: True if (salt, password) generates the good public key + .. warning:: Generates a new temporary SigningKey from salt and password + ''' key = None if self.walletid == 0: key = SigningKey(salt, password) @@ -205,22 +244,39 @@ class Wallet(QObject): key = SigningKey("{0}{1}".format(salt, self.walletid), password) return (key.pubkey == self.pubkey) - def show_value(self, community): - return self.referential(community) - def relative_value(self, community): + ''' + Get wallet value relative to last generated UD + + :param community: The community to get value + :return: The wallet relative value + ''' value = self.value(community) ud = community.dividend relative_value = value / float(ud) return relative_value def value(self, community): + ''' + Get wallet absolute value + + :param community: The community to get value + :return: The wallet absolute value + ''' value = 0 for s in self.sources(community): value += s.amount return value def tx_inputs(self, amount, community): + ''' + Get inputs to generate a transaction with a given amount of money + + :param int amount: The amount target value + :param community: The community target of the transaction + + :return: The list of inputs to use in the transaction document + ''' value = 0 inputs = [] cache = self.caches[community.currency] @@ -239,6 +295,15 @@ class Wallet(QObject): len(inputs), amount) def tx_outputs(self, pubkey, amount, inputs): + ''' + Get outputs to generate a transaction with a given amount of money + + :param str pubkey: The target pubkey of the transaction + :param int amount: The amount to send + :param list inputs: The inputs used to send the given amount of money + + :return: The list of outputs to use in the transaction document + ''' outputs = [] inputs_value = 0 for i in inputs: @@ -253,7 +318,16 @@ class Wallet(QObject): def send_money(self, salt, password, community, recipient, amount, message): - + ''' + Send money to a given recipient in a specified community + + :param str salt: The account salt + :param str password: The account password + :param community: The community target of the transfer + :param str recipient: The pubkey of the recipient + :param int amount: The amount of money to transfer + :param str message: The message to send with the transfer + ''' time = community.get_block().mediantime block_number = community.current_blockid()['number'] key = None @@ -292,6 +366,12 @@ class Wallet(QObject): transfer.send(tx, community) def sources(self, community): + ''' + Get available sources in a given community + + :param community: The community where we want available sources + :return: List of InputSource ucoinpy objects + ''' data = community.request(bma.tx.Sources, req_args={'pubkey': self.pubkey}) tx = [] @@ -300,9 +380,20 @@ class Wallet(QObject): return tx def transfers(self, community): + ''' + Get all transfers objects of this wallet + + :param community: The community we want to get the executed transfers + :return: A list of Transfer objects + ''' return self.caches[community.currency].transfers def jsonify(self): + ''' + Get the wallet as json format. + + :return: The wallet as a dict in json format. + ''' return {'walletid': self.walletid, 'pubkey': self.pubkey, 'name': self.name} diff --git a/src/cutecoin/core/watchers/blockchain.py b/src/cutecoin/core/watchers/blockchain.py index b2f6861f9d262072a522360b4e35a59ad914a1c8..73ca32a30fe44d620d56ac4a39a2c374488cab82 100644 --- a/src/cutecoin/core/watchers/blockchain.py +++ b/src/cutecoin/core/watchers/blockchain.py @@ -16,7 +16,7 @@ class BlockchainWatcher(QObject): super().__init__() self.account = account self.community = community - self.time_to_wait = int(self.community.get_parameters()['avgGenTime'] / 10) + self.time_to_wait = int(self.community.parameters['avgGenTime'] / 10) self.exiting = False blockid = self.community.current_blockid() self.last_block = blockid['number'] diff --git a/src/cutecoin/core/watchers/network.py b/src/cutecoin/core/watchers/network.py index 39d55edccb40f829cbd24ba01bbfc607fae672fb..77e5b18d41c84a019ab3cc34a61d2b7c975d2950 100644 --- a/src/cutecoin/core/watchers/network.py +++ b/src/cutecoin/core/watchers/network.py @@ -19,7 +19,7 @@ class NetworkWatcher(QObject): @pyqtSlot() def watch(self): - self.community.network.moveToThread(self.thread()) + #self.community.network.moveToThread(self.thread()) self.community.network.start_perpetual_crawling() @pyqtSlot() diff --git a/src/cutecoin/gui/community_tab.py b/src/cutecoin/gui/community_tab.py index 9588ec9dbfc920de98c34ae4c5fc74f565244fb6..f54c835c8145828617b8af8b218e5c7adb942150 100644 --- a/src/cutecoin/gui/community_tab.py +++ b/src/cutecoin/gui/community_tab.py @@ -123,7 +123,7 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): def send_money_to_member(self, person): dialog = TransferMoneyDialog(self.account, self.password_asker) dialog.edit_pubkey.setText(person.pubkey) - dialog.combo_community.setCurrentText(self.community.name()) + dialog.combo_community.setCurrentText(self.community.name) dialog.radio_pubkey.setChecked(True) if dialog.exec_() == QDialog.Accepted: currency_tab = self.window().currencies_tabwidget.currentWidget() @@ -131,7 +131,7 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): def certify_member(self, person): dialog = CertificationDialog(self.account, self.password_asker) - dialog.combo_community.setCurrentText(self.community.name()) + dialog.combo_community.setCurrentText(self.community.name) dialog.edit_pubkey.setText(person.pubkey) dialog.radio_pubkey.setChecked(True) dialog.exec_() diff --git a/src/cutecoin/gui/currency_tab.py b/src/cutecoin/gui/currency_tab.py index f6efa8ac90a4284fbf299a60c28d2fcbf5a96f94..bb0d3cab2304482f830ab5bcd091d3ee25b06940 100644 --- a/src/cutecoin/gui/currency_tab.py +++ b/src/cutecoin/gui/currency_tab.py @@ -73,10 +73,10 @@ class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget): try: join_block = person.membership(self.community)['blockNumber'] join_date = self.community.get_block(join_block).mediantime - parameters = self.community.get_parameters() + parameters = self.community.parameters expiration_date = join_date + parameters['sigValidity'] current_time = time.time() - sig_validity = self.community.get_parameters()['sigValidity'] + sig_validity = self.community.parameters['sigValidity'] warning_expiration_time = int(sig_validity / 3) will_expire_soon = (current_time > expiration_date - warning_expiration_time) @@ -251,7 +251,7 @@ class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget): wallet_index = [w.pubkey for w in self.app.current_account.wallets].index(sender) dialog.combo_wallets.setCurrentIndex(wallet_index) dialog.edit_pubkey.setText(transfer.metadata['receiver']) - dialog.combo_community.setCurrentText(self.community.name()) + dialog.combo_community.setCurrentText(self.community.name) dialog.spinbox_amount.setValue(transfer.metadata['amount']) dialog.radio_pubkey.setChecked(True) dialog.edit_message.setText(transfer.metadata['comment']) diff --git a/src/cutecoin/gui/homescreen.py b/src/cutecoin/gui/homescreen.py new file mode 100644 index 0000000000000000000000000000000000000000..e31275ec98e598355ba3bae05ae00ae116bca919 --- /dev/null +++ b/src/cutecoin/gui/homescreen.py @@ -0,0 +1,21 @@ +""" +Created on 31 janv. 2015 + +@author: vit +""" + +from PyQt5.QtWidgets import QWidget +from ..gen_resources.homescreen_uic import Ui_HomeScreenWidget + + +class HomeScreenWidget(QWidget, Ui_HomeScreenWidget): + """ + classdocs + """ + + def __init__(self): + """ + Constructor + """ + super().__init__() + self.setupUi(self) diff --git a/src/cutecoin/gui/informations_tab.py b/src/cutecoin/gui/informations_tab.py index e96b6f1a617f8cb727f7fa17d2f4b91f52db72b6..e5c6bebf1691706ccc1bfdc21420fb35fe759d3a 100644 --- a/src/cutecoin/gui/informations_tab.py +++ b/src/cutecoin/gui/informations_tab.py @@ -30,9 +30,9 @@ class InformationsTabWidget(QWidget, Ui_InformationsTabWidget): def refresh(self): #  try to request money parameters try: - params = self.community.get_parameters() + params = self.community.parameters except Exception as e: - logging.debug('community get_parameters error : ' + str(e)) + logging.debug('community parameters error : ' + str(e)) return False #  try to request money variables from last ud block diff --git a/src/cutecoin/gui/mainwindow.py b/src/cutecoin/gui/mainwindow.py index 3b5a204c7eb9360351c4c75c03aed8a45e37109d..40d0c4a09220223167a0ccbaf62c71616b7add32 100644 --- a/src/cutecoin/gui/mainwindow.py +++ b/src/cutecoin/gui/mainwindow.py @@ -3,23 +3,27 @@ Created on 1 févr. 2014 @author: inso ''' -from cutecoin.gen_resources.mainwindow_uic import Ui_MainWindow +from ..gen_resources.mainwindow_uic import Ui_MainWindow +from ..gen_resources.about_uic import Ui_AboutPopup +from ..gen_resources.homescreen_uic import Ui_HomeScreenWidget + from PyQt5.QtWidgets import QMainWindow, QAction, QFileDialog, QProgressBar, \ QMessageBox, QLabel, QComboBox, QDialog -from PyQt5.QtCore import QSignalMapper, QModelIndex, QObject, QThread, \ - pyqtSlot, pyqtSignal, QDate, QDateTime, QTimer -from PyQt5.QtGui import QIcon +from PyQt5.QtCore import QSignalMapper, QObject, QThread, \ + pyqtSlot, pyqtSignal, QDate, QDateTime, QTimer, QUrl +from PyQt5.QtGui import QIcon, QDesktopServices + from .process_cfg_account import ProcessConfigureAccount from .transfer import TransferMoneyDialog from .currency_tab import CurrencyTabWidget -from cutecoin.gui.contact import ConfigureContactDialog +from .contact import ConfigureContactDialog from .import_account import ImportAccountDialog from .certification import CertificationDialog from .password_asker import PasswordAskerDialog from ..tools.exceptions import NoPeerAvailable +from .homescreen import HomeScreenWidget from ..core.account import Account from ..__init__ import __version__ -from cutecoin.gen_resources.about_uic import Ui_AboutPopup import logging import requests @@ -97,6 +101,15 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.loader.loaded.connect(self.loader_thread.quit) self.loader.connection_error.connect(self.display_error) self.loader_thread.started.connect(self.loader.load) + + self.homescreen = HomeScreenWidget() + self.centralWidget().layout().addWidget(self.homescreen) + self.homescreen.button_new.clicked.connect(self.open_add_account_dialog) + self.homescreen.button_import.clicked.connect(self.import_account) + self.open_ucoin_info = lambda: QDesktopServices.openUrl(QUrl("http://ucoin.io/theoretical/")) + self.homescreen.button_info.clicked.connect(self.open_ucoin_info) + + #TODO: There are too much refresh() calls on startup self.refresh() def open_add_account_dialog(self): @@ -159,6 +172,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.status_label.setText("Loading account {0}".format(account_name)) self.loader.set_account_name(account_name) self.loader_thread.start(QThread.LowPriority) + self.homescreen.setEnabled(False) def open_transfer_money_dialog(self): dialog = TransferMoneyDialog(self.app.current_account, @@ -227,7 +241,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): tab_currency.refresh() self.currencies_tabwidget.addTab(tab_currency, QIcon(":/icons/currency_icon"), - community.name()) + community.name) except NoPeerAvailable as e: QMessageBox.critical(self, "Could not join {0}".format(community.currency), str(e), @@ -275,6 +289,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): signal_mapper.mapped[str].connect(self.action_change_account) if self.app.current_account is None: + self.currencies_tabwidget.hide() + self.homescreen.show() self.setWindowTitle("CuteCoin {0}".format(__version__)) self.menu_contacts.setEnabled(False) self.menu_actions.setEnabled(False) @@ -284,6 +300,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.status_label.setText("") self.password_asker = None else: + self.currencies_tabwidget.show() + self.homescreen.hide() self.action_set_as_default.setEnabled(self.app.current_account.name != self.app.default_account) self.password_asker = PasswordAskerDialog(self.app.current_account) diff --git a/src/cutecoin/gui/network_tab.py b/src/cutecoin/gui/network_tab.py index 0fc50f1397e1bf6038d87521a8aacfdd33a77d4d..aad3e04a0059fe6121a93eda45f1d3512e8305f1 100644 --- a/src/cutecoin/gui/network_tab.py +++ b/src/cutecoin/gui/network_tab.py @@ -31,7 +31,6 @@ class NetworkTabWidget(QWidget, Ui_NetworkTabWidget): self.watcher_thread = QThread() self.network_watcher.moveToThread(self.watcher_thread) self.watcher_thread.started.connect(self.network_watcher.watch) - self.watcher_thread.start() community.network.nodes_changed.connect(self.refresh_nodes) @@ -43,3 +42,7 @@ class NetworkTabWidget(QWidget, Ui_NetworkTabWidget): self.network_watcher.deleteLater() self.watcher_thread.deleteLater() + def showEvent(self, event): + super().showEvent(event) + self.watcher_thread.start() + diff --git a/src/cutecoin/gui/process_cfg_community.py b/src/cutecoin/gui/process_cfg_community.py index f0356966c60d04181115f5a54e136b31b0abe2c6..4f115ec673180d88b60d7ac8f21edfa9cfedd762 100644 --- a/src/cutecoin/gui/process_cfg_community.py +++ b/src/cutecoin/gui/process_cfg_community.py @@ -184,7 +184,7 @@ Would you like to publish the key ?""".format(self.account.pubkey)) if self.password_asker.result() == QDialog.Rejected: return try: - self.account.send_pubkey(password, self.community) + self.account.send_selfcert(password, self.community) except ValueError as e: QMessageBox.critical(self, "Pubkey publishing error", e.message) diff --git a/src/cutecoin/gui/wallets_tab.py b/src/cutecoin/gui/wallets_tab.py index a688f162560a5268981231d1a74e3e9f30bfa212..b75b69173ef8e9de31d179bc358b7bbba069e0d6 100644 --- a/src/cutecoin/gui/wallets_tab.py +++ b/src/cutecoin/gui/wallets_tab.py @@ -33,7 +33,7 @@ class WalletsTabWidget(QWidget, Ui_WalletsTab): self.refresh() def refresh(self): - parameters = self.community.get_parameters() + parameters = self.community.parameters last_renewal = "" expiration = "" certifiers = 0 diff --git a/src/cutecoin/models/communities.py b/src/cutecoin/models/communities.py index b039663929c33ae04230126b1eb0f2f5fd8e20c8..2e1479b5de3da5939c4829066f25f8c7ff4ad911 100644 --- a/src/cutecoin/models/communities.py +++ b/src/cutecoin/models/communities.py @@ -27,7 +27,7 @@ class CommunitiesListModel(QAbstractListModel): if role == Qt.DisplayRole: row = index.row() - value = self.communities[row].name() + value = self.communities[row].name return value def flags(self, index): diff --git a/src/cutecoin/models/community.py b/src/cutecoin/models/community.py index 1987a5a44e590af0928a684cba79fae8a04b2a81..81b8fbd05c43e93acbf239984ebe09c319f91fbf 100644 --- a/src/cutecoin/models/community.py +++ b/src/cutecoin/models/community.py @@ -9,7 +9,7 @@ class CommunityItemModel(object): def __init__(self, community, communities_item=None): self.communities_item = communities_item - self.community_text = community.name() + self.community_text = community.name self.main_node_items = [] def appendChild(self, item): diff --git a/src/cutecoin/models/members.py b/src/cutecoin/models/members.py index 72ce548225455de1adfbdca3728e42f11913f07f..fe48c7c2e78e10a6964001fb5b124c6983462702 100644 --- a/src/cutecoin/models/members.py +++ b/src/cutecoin/models/members.py @@ -36,7 +36,7 @@ class MembersFilterProxyModel(QSortFilterProxyModel): expiration_index = self.sourceModel().index(source_index.row(), expiration_col) expiration_data = self.sourceModel().data(expiration_index, Qt.DisplayRole) current_time = QDateTime().currentDateTime().toMSecsSinceEpoch() - sig_validity = self.community.get_parameters()['sigValidity'] + sig_validity = self.community.parameters['sigValidity'] warning_expiration_time = int(sig_validity / 3) #logging.debug("{0} > {1}".format(current_time, expiration_data)) will_expire_soon = (current_time > expiration_data*1000 - warning_expiration_time*1000) @@ -94,7 +94,7 @@ class MembersTableModel(QAbstractTableModel): person = Person.lookup(pubkey, self.community) join_block = person.membership(self.community)['blockNumber'] join_date = self.community.get_block(join_block).mediantime - parameters = self.community.get_parameters() + parameters = self.community.parameters expiration_date = join_date + parameters['sigValidity'] return (person.name, pubkey, join_date, expiration_date) diff --git a/src/cutecoin/models/network.py b/src/cutecoin/models/network.py index 63d725fa89131a2f451f2a1e23f4169889a6988e..f44a7735e3ea146d5d279a30d5412758979dcd59 100644 --- a/src/cutecoin/models/network.py +++ b/src/cutecoin/models/network.py @@ -136,7 +136,6 @@ class NetworkTableModel(QAbstractTableModel): Node.CORRUPTED: QColor(Qt.darkRed) } return colors[node.state] - #TODO: Display colors depending on node state def flags(self, index): return Qt.ItemIsSelectable | Qt.ItemIsEnabled