diff --git a/doc/Specifications.md b/doc/Specifications.md deleted file mode 100644 index 6fd2352d2264c193b3e67ff5f33bc27276e2df33..0000000000000000000000000000000000000000 --- a/doc/Specifications.md +++ /dev/null @@ -1,21 +0,0 @@ -# Specifications - -## Client-side concepts - -### Account - * An account is a PGP Key - * An account can be a member of multiple communities - * An account own at least one wallet for each currency he owns - * An account can own multiple wallets of the same currency - * An account must declare the nodes he trusts to receive transactions - * An account must declare the nodes he uses to send transactions for a currency type - -### Community - * A community uses one unique currency - * A community has members - * Each members of the community can issue currency following the rule defined in the community amendment - -### Wallets - * Wallets can own only one type of currency - * Wallets value are the sum of the transactions received and sent that the user listed in this wallet - \ No newline at end of file diff --git a/doc/cutecoint_concept.ui b/doc/cutecoint_concept.ui deleted file mode 100644 index 825a9fa8d565bee64a4c6b5ef09772ff470a91b8..0000000000000000000000000000000000000000 --- a/doc/cutecoint_concept.ui +++ /dev/null @@ -1,324 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>MainWindow</class> - <widget class="QMainWindow" name="MainWindow"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>452</width> - <height>400</height> - </rect> - </property> - <property name="windowTitle"> - <string notr="true">CuteCoin</string> - </property> - <widget class="QWidget" name="centralwidget"> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QFrame" name="actionsFrame"> - <property name="frameShape"> - <enum>QFrame::StyledPanel</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QTabWidget" name="tabWidget"> - <property name="currentIndex"> - <number>0</number> - </property> - <widget class="QWidget" name="walletsTab"> - <attribute name="title"> - <string>Wallets</string> - </attribute> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QListWidget" name="listWidget_2"> - <item> - <property name="text"> - <string>Main wallet (uCoins)</string> - </property> - </item> - <item> - <property name="text"> - <string>Freecoins wallet (freeCoins)</string> - </property> - </item> - </widget> - </item> - <item> - <widget class="QListWidget" name="listWidget_3"> - <item> - <property name="text"> - <string>+10</string> - </property> - </item> - <item> - <property name="text"> - <string>-100</string> - </property> - </item> - <item> - <property name="text"> - <string>-30</string> - </property> - </item> - <item> - <property name="text"> - <string>+300</string> - </property> - </item> - </widget> - </item> - </layout> - </widget> - <widget class="QWidget" name="transactionsTab"> - <attribute name="title"> - <string>Transactions</string> - </attribute> - <layout class="QHBoxLayout" name="horizontalLayout_3"> - <item> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Sent</string> - </property> - </widget> - </item> - <item> - <widget class="QListWidget" name="listWidget"> - <item> - <property name="text"> - <string>100 to Caner Candan (0x5D2662E0F300EBAD)</string> - </property> - </item> - <item> - <property name="text"> - <string>30 to cgeek twicedd (0x399EB3415C237F93)</string> - </property> - </item> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Received</string> - </property> - </widget> - </item> - <item> - <widget class="QListWidget" name="listWidget_4"> - <item> - <property name="text"> - <string>10 from Caner Candan (0x5D2662E0F300EBAD)</string> - </property> - </item> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - <widget class="QWidget" name="communitiesTab"> - <attribute name="title"> - <string>Communities</string> - </attribute> - <layout class="QHBoxLayout" name="horizontalLayout_4"> - <item> - <widget class="QTabWidget" name="tabWidget_2"> - <property name="currentIndex"> - <number>1</number> - </property> - <widget class="QWidget" name="ucoinTab"> - <attribute name="title"> - <string>Ucoin</string> - </attribute> - </widget> - <widget class="QWidget" name="freecoinTab"> - <attribute name="title"> - <string>FreeCoin</string> - </attribute> - <layout class="QHBoxLayout" name="horizontalLayout_5"> - <item> - <layout class="QVBoxLayout" name="verticalLayout_4"> - <item> - <widget class="QLabel" name="label_3"> - <property name="text"> - <string>Members</string> - </property> - </widget> - </item> - <item> - <widget class="QListWidget" name="listWidget_5"> - <item> - <property name="text"> - <string>Mister K</string> - </property> - </item> - <item> - <property name="text"> - <string>Another guy</string> - </property> - </item> - <item> - <property name="text"> - <string>Inso sonI</string> - </property> - </item> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QVBoxLayout" name="verticalLayout_5"> - <item> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>Last issuance</string> - </property> - </widget> - </item> - <item> - <widget class="QListWidget" name="listWidget_6"> - <item> - <property name="text"> - <string>110 (27/12/2014)</string> - </property> - </item> - <item> - <property name="text"> - <string>140 (27/01/2015)</string> - </property> - </item> - <item> - <property name="text"> - <string>190 (27/02/2015)</string> - </property> - </item> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </widget> - </item> - </layout> - </widget> - </widget> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - <widget class="QMenuBar" name="menubar"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>452</width> - <height>20</height> - </rect> - </property> - <widget class="QMenu" name="menuTr_File"> - <property name="title"> - <string>Account</string> - </property> - <addaction name="actionChange_account"/> - <addaction name="actionManage_accounts"/> - <addaction name="actionConfigure_trustable_nodes"/> - </widget> - <widget class="QMenu" name="menuEdit"> - <property name="title"> - <string>Contacts</string> - </property> - <widget class="QMenu" name="menuCaner_Candan"> - <property name="title"> - <string>Caner Candan</string> - </property> - <addaction name="separator"/> - <addaction name="actionSend_a_message"/> - <addaction name="actionSend_money"/> - <addaction name="separator"/> - <addaction name="actionRemove_contact"/> - </widget> - <widget class="QMenu" name="menuCGeek"> - <property name="title"> - <string>cGeek</string> - </property> - <addaction name="actionSend_a_message_2"/> - <addaction name="actionSend_money_2"/> - <addaction name="separator"/> - <addaction name="actionRemove_contact_2"/> - </widget> - <addaction name="menuCaner_Candan"/> - <addaction name="menuCGeek"/> - <addaction name="separator"/> - <addaction name="actionAdd_a_contact"/> - </widget> - <addaction name="menuTr_File"/> - <addaction name="menuEdit"/> - </widget> - <widget class="QStatusBar" name="statusbar"/> - <action name="actionChange_account"> - <property name="text"> - <string>Change account</string> - </property> - </action> - <action name="actionManage_accounts"> - <property name="text"> - <string>Manage accounts</string> - </property> - </action> - <action name="actionConfigure_trustable_nodes"> - <property name="text"> - <string>Configure trustable nodes</string> - </property> - </action> - <action name="actionAdd_a_contact"> - <property name="text"> - <string>Add a contact</string> - </property> - </action> - <action name="actionSend_a_message"> - <property name="text"> - <string>Send a message</string> - </property> - </action> - <action name="actionSend_money"> - <property name="text"> - <string>Send money</string> - </property> - </action> - <action name="actionRemove_contact"> - <property name="text"> - <string>Remove contact</string> - </property> - </action> - <action name="actionSend_a_message_2"> - <property name="text"> - <string>Send a message</string> - </property> - </action> - <action name="actionSend_money_2"> - <property name="text"> - <string>Send money</string> - </property> - </action> - <action name="actionRemove_contact_2"> - <property name="text"> - <string>Remove contact</string> - </property> - </action> - </widget> - <resources/> - <connections/> -</ui> diff --git a/doc/uml/models.png b/doc/uml/models.png deleted file mode 100644 index a4314ae24e7027f32e61959c854ac9f5ceb327ba..0000000000000000000000000000000000000000 Binary files a/doc/uml/models.png and /dev/null differ diff --git a/doc/uml/models.pu b/doc/uml/models.pu deleted file mode 100644 index 82a546ca685ae3669e1d4a6a893d90b8a7bb38f8..0000000000000000000000000000000000000000 --- a/doc/uml/models.pu +++ /dev/null @@ -1,46 +0,0 @@ -@startuml - -#TODO: Rework UML with all informations gathered lately - -class Account { - pgpkey -} - -Account --> "0..*" Community : is a member of -Account --> "0..*" Node : trusts -Account --> "0..*" Node : send transactions with -Account --> "*" Transaction : send -Account --> "*" Transaction : receive -Account --> "0..*" Wallet : owns - -class Community { -} - -Community "1" -- "1" Currency : uses -Community "1" --> "1..*" Node : known nodes - -class Node { - address - port - auth -} - -Node --> "1" Community - -class Transaction { - value - currency - sender - receiver -} - -class Wallet { - currency - coins -} - - - -@enduml - - diff --git a/doc/uml/tx_lifecycle.png b/doc/uml/tx_lifecycle.png new file mode 100644 index 0000000000000000000000000000000000000000..0e7061b3438870f7c6a7c4fae11a191f971e9234 Binary files /dev/null and b/doc/uml/tx_lifecycle.png differ diff --git a/doc/uml/tx_lifecycle.pu b/doc/uml/tx_lifecycle.pu new file mode 100644 index 0000000000000000000000000000000000000000..03d66941a7598cbc1c785fe07a003eca92b4c279 --- /dev/null +++ b/doc/uml/tx_lifecycle.pu @@ -0,0 +1,10 @@ +@startuml + +[*] --> To_send +To_send --> Awaiting : Broadcasted at B +Awaiting --> Validated : Registered in [B; B+15] +Awaiting --> Refused : Not registered in [B; B+15] +Refused --> To_send : Send back order +Refused --> Dropped : Drop order + +@enduml \ No newline at end of file diff --git a/lib/ucoinpy/documents/block.py b/lib/ucoinpy/documents/block.py index 6a023cd6f383908cb087ea57a2abf593e97ea10a..64c3a7c7f87904cba0e4eb4cc593e01cd607adba 100644 --- a/lib/ucoinpy/documents/block.py +++ b/lib/ucoinpy/documents/block.py @@ -224,7 +224,7 @@ BOTTOM_SIGNATURE for i in range(n, tx_max): tx_lines += lines[n] n = n + 1 - transaction = Transaction.from_compact(version, tx_lines) + transaction = Transaction.from_compact(currency, tx_lines) transactions.append(transaction) signature = Block.re_signature.match(lines[n]).group(1) diff --git a/lib/ucoinpy/documents/transaction.py b/lib/ucoinpy/documents/transaction.py index cd42c610e78061bd69da10052e883acbbc030c63..e84313f8c27a002eb5e4fd12fb69b7eafe1c9736 100644 --- a/lib/ucoinpy/documents/transaction.py +++ b/lib/ucoinpy/documents/transaction.py @@ -6,6 +6,7 @@ Created on 2 déc. 2014 from . import Document import re +import logging class Transaction(Document): ''' @@ -40,7 +41,7 @@ SIGNATURE ''' re_type = re.compile("Type: (Transaction)\n") - re_header = re.compile("TX:([0-9])+:([0-9])+:([0-9])+:([0-9])+:(0|1)\n") + re_header = re.compile("TX:([0-9]+):([0-9]+):([0-9]+):([0-9]+):(0|1)\n") re_issuers = re.compile("Issuers:\n") re_inputs = re.compile("Inputs:\n") re_outputs = re.compile("Outputs:\n") diff --git a/res/ui/currency_tab.ui b/res/ui/currency_tab.ui index 8218af48ed277e794265950b98bfee2f45e6fd16..04a102931088059edd6af03e488a454786d270fa 100644 --- a/res/ui/currency_tab.ui +++ b/res/ui/currency_tab.ui @@ -33,7 +33,7 @@ <bool>false</bool> </property> <property name="currentIndex"> - <number>0</number> + <number>1</number> </property> <widget class="QWidget" name="tab_wallets"> <attribute name="icon"> @@ -68,29 +68,48 @@ <item> <layout class="QVBoxLayout" name="verticalLayout_3"> <item> - <widget class="QLabel" name="label"> - <property name="text"> - <string>Sent</string> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="topMargin"> + <number>5</number> </property> - </widget> - </item> - <item> - <widget class="QListView" name="list_transactions_sent"/> + <item> + <widget class="QDateTimeEdit" name="date_from"> + <property name="displayFormat"> + <string>dd/MM/yyyy</string> + </property> + <property name="calendarPopup"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QDateTimeEdit" name="date_to"> + <property name="displayFormat"> + <string>dd/MM/yyyy</string> + </property> + <property name="calendarPopup"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> </item> - </layout> - </item> - <item> - <layout class="QVBoxLayout" name="verticalLayout_2"> <item> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Received</string> + <widget class="QTableView" name="table_history"> + <property name="contextMenuPolicy"> + <enum>Qt::CustomContextMenu</enum> </property> + <attribute name="horizontalHeaderShowSortIndicator" stdset="0"> + <bool>true</bool> + </attribute> + <attribute name="horizontalHeaderStretchLastSection"> + <bool>true</bool> + </attribute> + <attribute name="verticalHeaderVisible"> + <bool>false</bool> + </attribute> </widget> </item> - <item> - <widget class="QListView" name="list_transactions_received"/> - </item> </layout> </item> </layout> @@ -138,9 +157,59 @@ </hint> </hints> </connection> + <connection> + <sender>date_from</sender> + <signal>dateTimeChanged(QDateTime)</signal> + <receiver>CurrencyTabWidget</receiver> + <slot>dates_changed(QDateTime)</slot> + <hints> + <hint type="sourcelabel"> + <x>114</x> + <y>73</y> + </hint> + <hint type="destinationlabel"> + <x>199</x> + <y>149</y> + </hint> + </hints> + </connection> + <connection> + <sender>date_to</sender> + <signal>dateTimeChanged(QDateTime)</signal> + <receiver>CurrencyTabWidget</receiver> + <slot>dates_changed(QDateTime)</slot> + <hints> + <hint type="sourcelabel"> + <x>285</x> + <y>73</y> + </hint> + <hint type="destinationlabel"> + <x>199</x> + <y>149</y> + </hint> + </hints> + </connection> + <connection> + <sender>table_history</sender> + <signal>customContextMenuRequested(QPoint)</signal> + <receiver>CurrencyTabWidget</receiver> + <slot>history_context_menu(QPoint)</slot> + <hints> + <hint type="sourcelabel"> + <x>199</x> + <y>180</y> + </hint> + <hint type="destinationlabel"> + <x>199</x> + <y>149</y> + </hint> + </hints> + </connection> </connections> <slots> <slot>refresh_wallet_content(QModelIndex)</slot> <slot>wallet_context_menu(QPoint)</slot> + <slot>dates_changed(QDateTime)</slot> + <slot>history_context_menu(QPoint)</slot> </slots> </ui> diff --git a/src/cutecoin/__init__.py b/src/cutecoin/__init__.py index 5bdc6645502fb5f55054de8deddbcf778967b770..d939c5ffcc80f629372e682b63181ef35cfb12b5 100644 --- a/src/cutecoin/__init__.py +++ b/src/cutecoin/__init__.py @@ -1,2 +1,2 @@ -__version_info__ = ('0', '7', '5') +__version_info__ = ('0', '8', '0') __version__ = '.'.join(__version_info__) diff --git a/src/cutecoin/core/account.py b/src/cutecoin/core/account.py index 85f70ab27bb256cafe909d33d185a4ea6536f686..aad96327eab228fe8b031b498dc2358d810db7f7 100644 --- a/src/cutecoin/core/account.py +++ b/src/cutecoin/core/account.py @@ -21,6 +21,29 @@ from .person import Person from ..tools.exceptions import NoPeerAvailable +def quantitative(units, community): + return units + + +def relative(units, community): + ud = community.dividend() + relative_value = units / float(ud) + return relative_value + + +def quantitative_zerosum(units, community): + median = community.monetary_mass / community.nb_members + return units - median + + +def relative_zerosum(units, community): + median = community.monetary_mass / community.nb_members + ud = community.dividend() + relative_value = units / float(ud) + relative_median = median / ud + return relative_value - relative_median + + class Account(object): ''' @@ -28,6 +51,11 @@ class Account(object): Each account has only one key, and a key can be locally referenced by only one account. ''' + referentials = {'Units': (quantitative, '{0}'), + 'UD': (relative, 'ud {0}'), + 'Quant Z-sum': (quantitative_zerosum, 'q0 {0}'), + 'Relat Z-sum': (relative_zerosum, 'r0 {0}') + } def __init__(self, salt, pubkey, name, communities, wallets, contacts, dead_communities): @@ -41,6 +69,7 @@ class Account(object): self.dead_communities = dead_communities self.wallets = wallets self.contacts = contacts + self.referential = 'Units' @classmethod def create(cls, name, communities, wallets, confpath): @@ -92,7 +121,6 @@ class Account(object): def add_contact(self, person): same_contact = [contact for contact in self.contacts if person.pubkey == contact.pubkey] if len(same_contact) == 0: - print("add contact") self.contacts.append(person) return True return False @@ -102,6 +130,16 @@ class Account(object): self.communities.append(community) return community + def set_display_referential(self, index): + self.referential = index + + @property + def units_to_ref(self): + return Account.referentials[self.referential][0] + + def ref_name(self, currency): + return Account.referentials[self.referential][1].format(currency) + def set_walletpool_size(self, size, password): logging.debug("Defining wallet pool size") if len(self.wallets) < size: @@ -140,38 +178,13 @@ class Account(object): sources.append(s) return sources - def transactions_received(self, community): - received = [] - for w in self.wallets: - for t in w.transactions_received(community): - # Lets remove transactions from our own wallets - pubkeys = [wallet.pubkey for wallet in self.wallets] - if t.issuers[0] not in pubkeys: - received.append(t) - return received - - def transactions_sent(self, community): + def transfers(self, community): sent = [] for w in self.wallets: - for t in w.transactions_sent(community): - # Lets remove transactions to our own wallets - pubkeys = [wallet.pubkey for wallet in self.wallets] - outputs = [o for o in t.outputs if o.pubkey not in pubkeys] - if len(outputs) > 0: - sent.append(t) + for transfer in w.transfers(community): + sent.append(transfer) return sent - def transactions_awaiting(self, community): - awaiting = [] - for w in self.wallets: - for t in w.transactions_awaiting(community): - # Lets remove transactions to our own wallets - pubkeys = [wallet.pubkey for wallet in self.wallets] - outputs = [o for o in t.outputs if o.pubkey not in pubkeys] - if len(outputs) > 0: - awaiting.append(t) - return awaiting - def member_of(self, community): pubkeys = community.members_pubkeys() if self.pubkey not in pubkeys: diff --git a/src/cutecoin/core/app.py b/src/cutecoin/core/app.py index e351f3ef84b0f9b10acd622ea6823503b55ec517..16aaab65e6cc82c88b6ffea0a61b2388d67158c1 100644 --- a/src/cutecoin/core/app.py +++ b/src/cutecoin/core/app.py @@ -9,9 +9,10 @@ import logging import json import tarfile -from cutecoin.core import config -from cutecoin.tools.exceptions import NameAlreadyExists, BadAccountFile -from cutecoin.core.account import Account +from . import config +from ..tools.exceptions import NameAlreadyExists, BadAccountFile +from .account import Account +from .. import __version__ class Application(object): @@ -90,7 +91,10 @@ class Application(object): if os.path.exists(wallet_path): with open(wallet_path, 'r') as json_data: data = json.load(json_data) - wallet.load_caches(data) + if 'version' in data and data['version'] == __version__: + wallet.load_caches(data) + else: + os.remove(wallet_path) for community in account.communities: wallet.refresh_cache(community) @@ -112,7 +116,9 @@ class Application(object): wallet_path = os.path.join(config.parameters['home'], account.name, '__cache__', wallet.pubkey) with open(wallet_path, 'w') as outfile: - json.dump(wallet.jsonify_caches(), outfile, indent=4, sort_keys=True) + data = wallet.jsonify_caches() + data['version'] = __version__ + json.dump(data, outfile, indent=4, sort_keys=True) def import_account(self, file, name): with tarfile.open(file, "r") as tar: diff --git a/src/cutecoin/core/community.py b/src/cutecoin/core/community.py index fa91e186a333d52ad1cfa0655f70b98714eb42b1..c7b1ab2c57ce4445734fddffaa548a855d873b48 100644 --- a/src/cutecoin/core/community.py +++ b/src/cutecoin/core/community.py @@ -12,6 +12,7 @@ from ..tools.exceptions import NoPeerAvailable import logging import inspect import hashlib +import re from requests.exceptions import RequestException, Timeout @@ -121,6 +122,24 @@ class Community(object): def __eq__(self, other): return (other.currency == self.currency) + @property + def short_currency(self): + words = re.split('[_\W]+', self.currency) + shortened = "" + if len(words) > 1: + shortened = ''.join([w[0] for w in words]) + else: + vowels = ('a', 'e', 'i', 'o', 'u', 'y') + shortened = self.currency + shortened = ''.join([c for c in shortened if c not in vowels]) + return shortened + + @property + def currency_symbol(self): + letter = self.currency[0] + u = ord('\u24B6') + ord(letter) - ord('A') + return chr(u) + def dividend(self): block = self.get_ud_block() if block: @@ -138,6 +157,24 @@ class Community(object): else: return False + @property + def monetary_mass(self): + try: + block = self.request(bma.blockchain.Current) + return block['monetaryMass'] + except ValueError as e: + if '404' in e: + return 0 + + @property + def nb_members(self): + try: + block = self.request(bma.blockchain.Current) + return block['membersCount'] + except ValueError as e: + if '404' in e: + return 0 + def _peering_traversal(self, peer, found_peers, traversed_pubkeys): logging.debug("Read {0} peering".format(peer.pubkey)) traversed_pubkeys.append(peer.pubkey) @@ -157,6 +194,8 @@ class Community(object): self._peering_traversal(next_peer, found_peers, traversed_pubkeys) except Timeout: pass + except ConnectionError: + pass except ValueError: pass except RequestException as e: diff --git a/src/cutecoin/core/person.py b/src/cutecoin/core/person.py index 3b3ad2a02c4b6bb73178a9c6805956f89c025d1f..37cec6fb9bc7a4a950ceb270cd610542b9e444f0 100644 --- a/src/cutecoin/core/person.py +++ b/src/cutecoin/core/person.py @@ -32,8 +32,6 @@ class Person(object): ''' data = community.request(bma.wot.Lookup, req_args={'search': pubkey}, cached=cached) - results = data['results'] - logging.debug(results) timestamp = 0 for result in data['results']: diff --git a/src/cutecoin/core/transfer.py b/src/cutecoin/core/transfer.py new file mode 100644 index 0000000000000000000000000000000000000000..edbb505310976dbc1ac1ecfba75b542ee06e405b --- /dev/null +++ b/src/cutecoin/core/transfer.py @@ -0,0 +1,100 @@ +''' +Created on 31 janv. 2015 + +@author: inso +''' +import logging +from ucoinpy.api import bma +from ucoinpy.documents.transaction import Transaction + + +class Transfer(object): + ''' + A transaction + ''' + TO_SEND = 0 + AWAITING = 1 + VALIDATED = 2 + REFUSED = 3 + SENT = 4 + DROPPED = 5 + + def __init__(self, txdoc, state, metadata): + ''' + Constructor + ''' + self.txdoc = txdoc + self.state = state + self.metadata = metadata + + @classmethod + def initiate(cls, block, time, amount, issuer, receiver, comment): + return cls(None, Transfer.TO_SEND, {'block': block, + 'time': time, + 'amount': amount, + 'issuer': issuer, + 'receiver': receiver, + 'comment': comment}) + + @classmethod + def create_validated(cls, txdoc, metadata): + return cls(txdoc, Transfer.VALIDATED, metadata) + + @classmethod + def load(cls, data): + if data['state'] is Transfer.TO_SEND: + txdoc = None + else: + txdoc = Transaction.from_signed_raw(data['txdoc']) + return cls(txdoc, data['state'], data['metadata']) + + def jsonify(self): + if self.txdoc: + txraw = self.txdoc.signed_raw() + else: + txraw = None + return {'txdoc': txraw, + 'state': self.state, + 'metadata': self.metadata} + + def send(self, txdoc, community): + try: + self.txdoc = txdoc + community.broadcast(bma.tx.Process, + post_args={'transaction': self.txdoc.signed_raw()}) + self.state = Transfer.AWAITING + except ValueError as e: + if '400' in e: + self.state = Transfer.REFUSED + raise + finally: + self.metadata['block'] = community.current_blockid()['number'] + self.metadata['time'] = community.get_block().time + + def check_registered(self, tx, metadata): + + logging.debug("{0} > {1} ?".format(metadata['block'], + self.metadata['block'] + 15)) + if tx.signed_raw() == self.txdoc.signed_raw(): + self.state = Transfer.VALIDATED + self.metadata = metadata + + def check_refused(self, block): + if block > self.metadata['block'] + 15: + self.state = Transfer.REFUSED + + def drop(self): + self.state = Transfer.DROPPED + + +class Received(Transfer): + def __init__(self, txdoc, metadata): + ''' + Constructor + ''' + super().__init__(txdoc, Transfer.VALIDATED, metadata) + + @classmethod + def load(cls, data): + 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 7789640b80a71d4e4ab2c4e706401ca6c0a8f0f7..4a291d2f46b2669e58ca120a939852a9e776dab2 100644 --- a/src/cutecoin/core/wallet.py +++ b/src/cutecoin/core/wallet.py @@ -10,6 +10,7 @@ from ucoinpy.documents.block import Block from ucoinpy.documents.transaction import InputSource, OutputSource, Transaction from ucoinpy.key import SigningKey from ..tools.exceptions import NotEnoughMoneyError, NoPeerAvailable +from cutecoin.core.transfer import Transfer, Received import logging @@ -18,47 +19,29 @@ class Cache(): self.latest_block = 0 self.wallet = wallet - self.tx_sent = [] - self.awaiting_tx = [] - self.tx_received = [] + self._transfers = [] self.available_sources = [] def load_from_json(self, data): - self.tx_received = [] - self.tx_sent = [] - self.awaiting_tx = [] + self._transfers = [] + logging.debug(data) - data_received = data['received'] - for r in data_received: - self.tx_received.append(Transaction.from_signed_raw(r['raw'])) - - data_sent = data['sent'] + data_sent = data['transfers'] for s in data_sent: - self.tx_sent.append(Transaction.from_signed_raw(s['raw'])) - - data_awaiting = data['awaiting'] - for s in data_awaiting: - self.awaiting_tx.append(Transaction.from_signed_raw(s['raw'])) + if s['metadata']['issuer'] == self.wallet.pubkey: + self._transfers.append(Transfer.load(s)) + else: + self._transfers.append(Received.load(s)) - if 'sources' in data: - data_sources = data['sources'] - for s in data_sources: - self.available_sources.append(InputSource.from_inline(s['inline'])) + for s in data['sources']: + self.available_sources.append(InputSource.from_inline(s['inline'])) self.latest_block = data['latest_block'] def jsonify(self): - data_received = [] - for r in self.tx_received: - data_received.append({'raw': r.signed_raw()}) - - data_sent = [] - for s in self.tx_sent: - data_sent.append({'raw': s.signed_raw()}) - - data_awaiting = [] - for s in self.awaiting_tx: - data_awaiting.append({'raw': s.signed_raw()}) + data_transfer = [] + for s in self.transfers: + data_transfer.append(s.jsonify()) data_sources = [] for s in self.available_sources: @@ -66,31 +49,25 @@ class Cache(): data_sources.append({'inline': "{0}\n".format(s.inline())}) return {'latest_block': self.latest_block, - 'received': data_received, - 'sent': data_sent, - 'awaiting': data_awaiting, + 'transfers': data_transfer, 'sources': data_sources} - def latest_sent(self, community): - return self.tx_sent - - def awaiting(self, community): - return self.awaiting_tx - - def latest_received(self, community): - return self.tx_received + @property + def transfers(self): + return [t for t in self._transfers if t.state != Transfer.DROPPED] def refresh(self, community): current_block = 0 try: - try: - block_data = community.request(bma.blockchain.Current) - current_block = block_data['number'] - except ValueError as e: - if '404' in str(e): - current_block = 0 - else: - raise + block_data = community.current_blockid() + current_block = block_data['number'] + + # Lets look if transactions took too long to be validated + awaiting = [t for t in self._transfers + if t.state == Transfer.AWAITING] + for transfer in awaiting: + transfer.check_refused(current_block) + with_tx = community.request(bma.blockchain.TX) # We parse only blocks with transactions @@ -104,21 +81,51 @@ class Cache(): for block_number in parsed_blocks: block = community.request(bma.blockchain.Block, req_args={'number': block_number}) - signed_raw = "{0}{1}\n".format(block['raw'], block['signature']) - block_doc = Block.from_signed_raw(signed_raw) + signed_raw = "{0}{1}\n".format(block['raw'], + block['signature']) + try: + block_doc = Block.from_signed_raw(signed_raw) + except: + logging.debug("Error in {0}".format(block_number)) + raise + metadata = {'block': block_number, + 'time': block_doc.time} for tx in block_doc.transactions: - in_outputs = [o for o in tx.outputs - if o.pubkey == self.wallet.pubkey] - if len(in_outputs) > 0: - self.tx_received.append(tx) - - in_inputs = [i for i in tx.issuers if i == self.wallet.pubkey] - if len(in_inputs) > 0: - # remove from waiting transactions list the one which were - # validated in the blockchain - self.awaiting_tx = [awaiting for awaiting in self.awaiting_tx - if awaiting.compact() != tx.compact()] - self.tx_sent.append(tx) + metadata['issuer'] = tx.issuers[0] + receivers = [o.pubkey for o in tx.outputs + if o.pubkey != metadata['issuer']] + metadata['receiver'] = receivers[0] + + in_issuers = len([i for i in tx.issuers + if i == self.wallet.pubkey]) > 0 + if in_issuers: + outputs = [o for o in tx.outputs + if o.pubkey != self.wallet.pubkey] + amount = 0 + for o in outputs: + amount += o.amount + metadata['amount'] = amount + + awaiting = [t for t in self._transfers + if t.state == Transfer.AWAITING] + awaiting_docs = [t.txdoc.signed_raw() for t in awaiting] + logging.debug(tx.signed_raw()) + logging.debug(awaiting_docs) + if tx.signed_raw() not in awaiting_docs: + transfer = Transfer.create_validated(tx, metadata) + self._transfers.append(transfer) + else: + for transfer in awaiting: + transfer.check_registered(tx, metadata) + else: + outputs = [o for o in tx.outputs + if o.pubkey == self.wallet.pubkey] + if len(outputs) > 0: + amount = 0 + for o in outputs: + amount += o.amount + metadata['amount'] = amount + self._transfers.append(Received(tx, metadata)) if current_block > self.latest_block: self.available_sources = self.wallet.sources(community) @@ -126,9 +133,6 @@ class Cache(): except NoPeerAvailable: return - self.tx_sent = self.tx_sent[:50] - self.tx_received = self.tx_received[:50] - self.latest_block = current_block @@ -167,8 +171,9 @@ class Wallet(object): def load_caches(self, json_data): for currency in json_data: - self.caches[currency] = Cache(self) - self.caches[currency].load_from_json(json_data[currency]) + if currency != 'version': + self.caches[currency] = Cache(self) + self.caches[currency].load_from_json(json_data[currency]) def jsonify_caches(self): data = {} @@ -189,6 +194,9 @@ class Wallet(object): 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): value = self.value(community) ud = community.dividend() @@ -235,6 +243,20 @@ class Wallet(object): def send_money(self, salt, password, community, recipient, amount, message): + time = community.get_block().time + block_number = community.current_blockid()['number'] + key = None + logging.debug("Key : {0} : {1}".format(salt, password)) + if self.walletid == 0: + key = SigningKey(salt, password) + else: + key = SigningKey("{0}{1}".format(salt, self.walletid), password) + logging.debug("Sender pubkey:{0}".format(key.pubkey)) + + transfer = Transfer.initiate(block_number, time, amount, + key.pubkey, recipient, message) + self.caches[community.currency]._transfers.append(transfer) + result = self.tx_inputs(int(amount), community) inputs = result[0] self.caches[community.currency].available_sources = result[1] @@ -246,22 +268,10 @@ class Wallet(object): [self.pubkey], inputs, outputs, message, None) logging.debug("TX : {0}".format(tx.raw())) - key = None - logging.debug("Key : {0} : {1}".format(salt, password)) - if self.walletid == 0: - key = SigningKey(salt, password) - else: - key = SigningKey("{0}{1}".format(salt, self.walletid), password) - logging.debug("Sender pubkey:{0}".format(key.pubkey)) tx.sign([key]) logging.debug("Transaction : {0}".format(tx.signed_raw())) - try: - community.broadcast(bma.tx.Process, - post_args={'transaction': tx.signed_raw()}) - self.caches[community.currency].awaiting_tx.append(tx) - except: - raise + transfer.send(tx, community) def sources(self, community): data = community.request(bma.tx.Sources, @@ -271,20 +281,8 @@ class Wallet(object): tx.append(InputSource.from_bma(s)) return tx - def transactions_awaiting(self, community): - return self.caches[community.currency].awaiting(community) - - def transactions_sent(self, community): - return self.caches[community.currency].latest_sent(community) - - def transactions_received(self, community): - return self.caches[community.currency].latest_received(community) - - def get_text(self, community): - return "%s : \n \ -%d %s \n \ -%.2f UD" % (self.name, self.value(community), community.currency, - self.relative_value(community)) + def transfers(self, community): + return self.caches[community.currency].transfers def jsonify(self): return {'walletid': self.walletid, diff --git a/src/cutecoin/gui/currency_tab.py b/src/cutecoin/gui/currency_tab.py index bd79bf28528c536ae359bbc79f73a939378b3225..9e17d123e63d902777f9460390d3913d3c086539 100644 --- a/src/cutecoin/gui/currency_tab.py +++ b/src/cutecoin/gui/currency_tab.py @@ -9,17 +9,22 @@ import time import requests from ucoinpy.api import bma -from PyQt5.QtWidgets import QWidget, QMenu, QAction, QApplication, QMessageBox -from PyQt5.QtCore import QModelIndex, Qt, pyqtSlot, QObject, QThread, pyqtSignal +from PyQt5.QtWidgets import QWidget, QMenu, QAction, QApplication, \ + QMessageBox, QDialog +from PyQt5.QtCore import QModelIndex, Qt, pyqtSlot, QObject, \ + QThread, pyqtSignal, QDateTime from PyQt5.QtGui import QIcon from ..gen_resources.currency_tab_uic import Ui_CurrencyTabWidget from .community_tab import CommunityTabWidget +from .transfer import TransferMoneyDialog +from ..models.txhistory import HistoryTableModel, TxFilterProxyModel from .informations_tab import InformationsTabWidget -from ..models.sent import SentListModel -from ..models.received import ReceivedListModel from ..models.wallets import WalletsListModel from ..models.wallet import WalletListModel from ..tools.exceptions import NoPeerAvailable +from ..core.wallet import Wallet +from ..core.person import Person +from ..core.transfer import Transfer class BlockchainWatcher(QObject): @@ -92,11 +97,30 @@ class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget): else: self.tabs_account.setEnabled(True) self.refresh_wallets() + blockchain_init = QDateTime() + blockchain_init.setTime_t(self.community.get_block(1).time) + + self.date_from.setMinimumDateTime(blockchain_init) + self.date_from.setDateTime(blockchain_init) + self.date_from.setMaximumDateTime(QDateTime().currentDateTime()) + + self.date_to.setMinimumDateTime(blockchain_init) + tomorrow_datetime = QDateTime().currentDateTime().addDays(1) + self.date_to.setDateTime(tomorrow_datetime) + self.date_to.setMaximumDateTime(tomorrow_datetime) + + ts_from = self.date_from.dateTime().toTime_t() + ts_to = self.date_to.dateTime().toTime_t() + + model = HistoryTableModel(self.app.current_account, self.community) + proxy = TxFilterProxyModel(ts_from, ts_to) + proxy.setSourceModel(model) + proxy.setDynamicSortFilter(True) + proxy.setSortRole(Qt.DisplayRole) + + self.table_history.setModel(proxy) + self.table_history.setSortingEnabled(True) - self.list_transactions_sent.setModel( - SentListModel(self.app.current_account, self.community)) - self.list_transactions_received.setModel( - ReceivedListModel(self.app.current_account, self.community)) self.tab_community = CommunityTabWidget(self.app.current_account, self.community, self.password_asker) @@ -132,18 +156,12 @@ class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget): QModelIndex(), QModelIndex(), []) - if self.list_transactions_sent.model(): - self.list_transactions_sent.model().dataChanged.emit( + if self.tablcommunitye_history.model(): + self.table_history.model().dataChanged.emit( QModelIndex(), QModelIndex(), []) - if self.list_transactions_received.model(): - self.list_transactions_received.model().dataChanged.emit( - QModelIndex(), - QModelIndex(), - []) - if self.tab_community.list_community_members.model(): self.tab_community.list_community_members.model().dataChanged.emit( QModelIndex(), @@ -168,7 +186,7 @@ class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget): def wallet_context_menu(self, point): index = self.list_wallets.indexAt(point) model = self.list_wallets.model() - if index.row() < model.rowCount(None): + if index.row() < model.rowCount(QModelIndex()): wallet = model.wallets[index.row()] menu = QMenu(model.data(index, Qt.DisplayRole), self) @@ -185,14 +203,81 @@ class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget): # Show the context menu. menu.exec_(self.list_wallets.mapToGlobal(point)) + def history_context_menu(self, point): + index = self.table_history.indexAt(point) + model = self.table_history.model() + if index.row() < model.rowCount(QModelIndex()): + menu = QMenu(model.data(index, Qt.DisplayRole), self) + source_index = model.mapToSource(index) + state_col = model.sourceModel().columns.index('State') + state_index = model.sourceModel().index(source_index.row(), + state_col) + state_data = model.sourceModel().data(state_index, Qt.DisplayRole) + + pubkey_col = model.sourceModel().columns.index('UID/Public key') + person_index = model.sourceModel().index(source_index.row(), + pubkey_col) + person = model.sourceModel().data(person_index, Qt.DisplayRole) + transfer = model.sourceModel().transfers[source_index.row()] + if state_data == Transfer.REFUSED or state_data == Transfer.TO_SEND: + send_back = QAction("Send again", self) + send_back.triggered.connect(self.send_again) + send_back.setData(transfer) + menu.addAction(send_back) + + cancel = QAction("Cancel", self) + cancel.triggered.connect(self.cancel_transfer) + cancel.setData(transfer) + menu.addAction(cancel) + + copy_pubkey = QAction("Copy pubkey to clipboard", self) + copy_pubkey.triggered.connect(self.copy_pubkey_to_clipboard) + copy_pubkey.setData(person) + menu.addAction(copy_pubkey) + # Show the context menu. + menu.exec_(self.table_history.mapToGlobal(point)) + def rename_wallet(self): index = self.sender().data() self.list_wallets.edit(index) def copy_pubkey_to_clipboard(self): - wallet = self.sender().data() + data = self.sender().data() clipboard = QApplication.clipboard() - clipboard.setText(wallet.pubkey) + if data.__class__ is Wallet: + clipboard.setText(data.pubkey) + elif data.__class__ is Person: + clipboard.setText(data.pubkey) + elif data.__class__ is str: + clipboard.setText(data) + + def send_again(self): + transfer = self.sender().data() + dialog = TransferMoneyDialog(self.app.current_account, + self.password_asker) + dialog.accepted.connect(self.refresh_wallets) + sender = transfer.metadata['issuer'] + 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.spinbox_amount.setValue(transfer.metadata['amount']) + dialog.radio_pubkey.setChecked(True) + dialog.edit_message.setText(transfer.metadata['comment']) + result = dialog.exec_() + if result == QDialog.Accepted: + transfer.drop() + self.table_history.model().invalidate() + + def cancel_transfer(self): + reply = QMessageBox.warning(self, "Warning", + """Are you sure ? +This money transfer will be removed and not sent.""", +QMessageBox.Ok | QMessageBox.Cancel) + if reply == QMessageBox.Ok: + transfer = self.sender().data() + transfer.drop() + self.table_history.model().invalidate() def wallet_changed(self): self.app.save(self.app.current_account) @@ -206,3 +291,23 @@ class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget): def closeEvent(self, event): self.bc_watcher.deleteLater() self.watcher_thread.deleteLater() + + def dates_changed(self, datetime): + ts_from = self.date_from.dateTime().toTime_t() + ts_to = self.date_to.dateTime().toTime_t() + if self.table_history.model(): + self.table_history.model().set_period(ts_from, ts_to) + self.table_history.model().invalidate() + + def referential_changed(self): + if self.table_history.model(): + self.table_history.model().dataChanged.emit( + QModelIndex(), + QModelIndex(), + []) + + if self.list_wallets.model(): + self.list_wallets.model().dataChanged.emit( + QModelIndex(), + QModelIndex(), + []) diff --git a/src/cutecoin/gui/mainwindow.py b/src/cutecoin/gui/mainwindow.py index 675c4c4c4e21c8f5b0ba869e026e2264cc186b14..f651ed2211966ca14c4f486c4dd61ce2f4f691b6 100644 --- a/src/cutecoin/gui/mainwindow.py +++ b/src/cutecoin/gui/mainwindow.py @@ -4,8 +4,10 @@ Created on 1 févr. 2014 @author: inso ''' from cutecoin.gen_resources.mainwindow_uic import Ui_MainWindow -from PyQt5.QtWidgets import QMainWindow, QAction, QFileDialog, QProgressBar, QMessageBox, QLabel -from PyQt5.QtCore import QSignalMapper, QModelIndex, QObject, QThread, pyqtSlot, pyqtSignal +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 .process_cfg_account import ProcessConfigureAccount from .transfer import TransferMoneyDialog @@ -15,6 +17,7 @@ from .import_account import ImportAccountDialog from .certification import CertificationDialog from .password_asker import PasswordAskerDialog from ..tools.exceptions import NoPeerAvailable +from ..core.account import Account from ..__init__ import __version__ import logging @@ -68,8 +71,18 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.statusbar.addWidget(self.busybar) self.busybar.hide() - self.status_label = QLabel("", self.statusbar) + self.combo_referential = QComboBox(self) + self.combo_referential.setEnabled(False) + self.combo_referential.currentTextChanged.connect(self.referential_changed) + + self.status_label = QLabel("", self) + + self.label_time = QLabel("", self) + self.statusbar.addPermanentWidget(self.status_label) + self.statusbar.addPermanentWidget(self.label_time) + self.statusbar.addPermanentWidget(self.combo_referential) + self.update_time() self.loader_thread = QThread() self.loader = Loader(self.app) @@ -97,6 +110,24 @@ class MainWindow(QMainWindow, Ui_MainWindow): error, QMessageBox.Ok) + @pyqtSlot(str) + def referential_changed(self, text): + if self.app.current_account: + self.app.current_account.set_display_referential(text) + if self.currencies_tabwidget.currentWidget(): + self.currencies_tabwidget.currentWidget().referential_changed() + + @pyqtSlot() + def update_time(self): + date = QDate.currentDate() + self.label_time.setText("- {0} -".format(date.toString("dd/MM/yyyy"))) + next_day = date.addDays(1) + current_time = QDateTime().toMSecsSinceEpoch() + next_time = QDateTime(next_day).toMSecsSinceEpoch() + timer = QTimer() + timer.timeout.connect(self.update_time) + timer.start(next_time - current_time) + def action_change_account(self, account_name): self.busybar.show() self.status_label.setText("Loading account {0}".format(account_name)) @@ -107,11 +138,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): dialog = TransferMoneyDialog(self.app.current_account, self.password_asker) dialog.accepted.connect(self.refresh_wallets) - dialog.exec_() - currency_tab = self.currencies_tabwidget.currentWidget() - currency_tab.list_transactions_sent.model().dataChanged.emit( - QModelIndex(), - QModelIndex(), ()) + if dialog.exec_() == QDialog.Accepted: + currency_tab = self.currencies_tabwidget.currentWidget() + currency_tab.table_history.model().invalidate() def open_certification_dialog(self): dialog = CertificationDialog(self.app.current_account, @@ -171,10 +200,17 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.menu_actions.setEnabled(False) self.action_configure_parameters.setEnabled(False) self.action_set_as_default.setEnabled(False) + self.combo_referential.setEnabled(False) else: self.action_set_as_default.setEnabled(self.app.current_account.name != self.app.default_account) self.password_asker = PasswordAskerDialog(self.app.current_account) + + self.combo_referential.blockSignals(True) + self.combo_referential.addItems(sorted(Account.referentials.keys())) + self.combo_referential.setEnabled(True) + self.combo_referential.blockSignals(False) + self.combo_referential.setCurrentText(self.app.current_account.referential) self.menu_contacts.setEnabled(True) self.action_configure_parameters.setEnabled(True) self.menu_actions.setEnabled(True) diff --git a/src/cutecoin/gui/transfer.py b/src/cutecoin/gui/transfer.py index 344a7d13de1a4005da7a52e856a1c91be7b7b4a9..da15100900e9d0dc1449829e1f39f52d59313f1f 100644 --- a/src/cutecoin/gui/transfer.py +++ b/src/cutecoin/gui/transfer.py @@ -72,10 +72,9 @@ class TransferMoneyDialog(QDialog, Ui_TransferMoneyDialog): QMessageBox.Ok) return except NotEnoughMoneyError as e: - QMessageBox.critical(self, "Money transfer", - "You don't have enough money available in this block : \n{0}" - .format(e.message)) - return + QMessageBox.warning(self, "Money transfer", + """This transaction could not be sent on this block +Please try again later""") except NoPeerAvailable as e: QMessageBox.critical(self, "Money transfer", "Couldn't connect to network : {0}".format(e), @@ -105,7 +104,10 @@ class TransferMoneyDialog(QDialog, Ui_TransferMoneyDialog): def change_current_community(self, index): self.community = self.sender.communities[index] self.dividend = self.community.dividend() - self.label_total.setText(self.wallet.get_text(self.community)) + amount = self.wallet.value(self.community) + ref_amount = self.sender.units_to_ref(amount, self.community) + ref_name = self.sender.ref_name(self.community.currency) + self.label_total.setText("{0} {1}".format(ref_amount, ref_name)) self.spinbox_amount.setSuffix(" " + self.community.currency) self.spinbox_amount.setValue(0) amount = self.wallet.value(self.community) @@ -115,7 +117,10 @@ class TransferMoneyDialog(QDialog, Ui_TransferMoneyDialog): def change_displayed_wallet(self, index): self.wallet = self.sender.wallets[index] - self.label_total.setText(self.wallet.get_text(self.community)) + amount = self.wallet.value(self.community) + ref_amount = self.sender.units_to_ref(amount, self.community) + ref_name = self.sender.ref_name(self.community.currency) + self.label_total.setText("{0} {1}".format(ref_amount, ref_name)) self.spinbox_amount.setValue(0) amount = self.wallet.value(self.community) relative = amount / self.dividend diff --git a/src/cutecoin/models/txhistory.py b/src/cutecoin/models/txhistory.py new file mode 100644 index 0000000000000000000000000000000000000000..24db0bc31181174bb67d5c31ebf8ed089dc5ef3b --- /dev/null +++ b/src/cutecoin/models/txhistory.py @@ -0,0 +1,188 @@ +''' +Created on 5 févr. 2014 + +@author: inso +''' + +import logging +from ..core.transfer import Transfer, Received +from ..core.person import Person +from ..tools.exceptions import PersonNotFoundError +from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, QSortFilterProxyModel, \ + QDateTime +from PyQt5.QtGui import QFont, QColor + + +class TxFilterProxyModel(QSortFilterProxyModel): + def __init__(self, ts_from, ts_to, parent=None): + super().__init__(parent) + self.community = None + self.account = None + self.ts_from = ts_from + self.ts_to = ts_to + + def set_period(self, ts_from, ts_to): + """ + Filter table by given timestamps + """ + logging.debug("Filtering from {0} to {1}".format(ts_from, ts_to)) + self.ts_from = ts_from + self.ts_to = ts_to + + def filterAcceptsRow(self, sourceRow, sourceParent): + def in_period(date_ts): + return (date_ts in range(self.ts_from, self.ts_to)) + date_col = self.sourceModel().columns.index('Date') + source_index = self.sourceModel().index(sourceRow, date_col) + date = self.sourceModel().data(source_index, Qt.DisplayRole) + return in_period(date) + + def columnCount(self, parent): + return self.sourceModel().columnCount(None) - 1 + + def setSourceModel(self, sourceModel): + self.community = sourceModel.community + self.account = sourceModel.account + super().setSourceModel(sourceModel) + + def lessThan(self, left, right): + """ + Sort table by given column number. + """ + left_data = self.sourceModel().data(left, Qt.DisplayRole) + right_data = self.sourceModel().data(right, Qt.DisplayRole) + return (left_data < right_data) + + def data(self, index, role): + source_index = self.mapToSource(index) + source_data = self.sourceModel().data(source_index, role) + state_col = self.sourceModel().columns.index('State') + state_index = self.sourceModel().index(source_index.row(), state_col) + state_data = self.sourceModel().data(state_index, Qt.DisplayRole) + if role == Qt.DisplayRole: + if source_index.column() == self.sourceModel().columns.index('UID/Public key'): + if source_data.__class__ == Person: + tx_person = source_data.name + else: + tx_person = "pub:{0}".format(source_data[:5]) + source_data = tx_person + return source_data + if source_index.column() == self.sourceModel().columns.index('Date'): + date = QDateTime.fromTime_t(source_data) + return date.date() + if source_index.column() == self.sourceModel().columns.index('Payment'): + if source_data is not "": + amount_ref = self.account.units_to_ref(-source_data, self.community) + ref_name = self.account.ref_name(self.community.short_currency) + return "{0:.2f} {1}".format(amount_ref, ref_name) + if source_index.column() == self.sourceModel().columns.index('Deposit'): + if source_data is not "": + amount_ref = self.account.units_to_ref(source_data, self.community) + ref_name = self.account.ref_name(self.community.short_currency) + return "{0:.2f} {1}".format(amount_ref, ref_name) + + if role == Qt.FontRole: + font = QFont() + if state_data == Transfer.AWAITING: + font.setItalic(True) + elif state_data == Transfer.REFUSED: + font.setItalic(True) + elif state_data == Transfer.TO_SEND: + font.setBold(True) + else: + font.setItalic(False) + return font + + if role == Qt.ForegroundRole: + if state_data == Transfer.REFUSED: + return QColor(Qt.red) + elif state_data == Transfer.TO_SEND: + return QColor(Qt.blue) + return source_data + + +class HistoryTableModel(QAbstractTableModel): + + ''' + A Qt abstract item model to display communities in a tree + ''' + + def __init__(self, account, community, parent=None): + ''' + Constructor + ''' + super().__init__(parent) + self.account = account + self.community = community + self.columns = ('Date', 'UID/Public key', 'Payment', + 'Deposit', 'Comment', 'State') + + @property + def transfers(self): + return self.account.transfers(self.community) + + def rowCount(self, parent): + return len(self.transfers) + + def columnCount(self, parent): + return len(self.columns) + + def headerData(self, section, orientation, role): + if role == Qt.DisplayRole: + return self.columns[section] + + def data_received(self, transfer): + amount = transfer.metadata['amount'] + comment = "" + if transfer.txdoc: + comment = transfer.txdoc.comment + pubkey = transfer.metadata['issuer'] + try: + #sender = Person.lookup(pubkey, self.community).name + sender = Person.lookup(pubkey, self.community) + except PersonNotFoundError: + #sender = "pub:{0}".format(pubkey[:5]) + sender = pubkey + + date_ts = transfer.metadata['time'] + + amount_ref = self.account.units_to_ref(amount, self.community) + ref_name = self.account.ref_name(self.community.short_currency) + + return (date_ts, sender, "", amount, "{0:.2f} {1}".format(amount_ref, ref_name), + comment, transfer.state) + + def data_sent(self, transfer): + amount = transfer.metadata['amount'] + comment = "" + if transfer.txdoc: + comment = transfer.txdoc.comment + pubkey = transfer.metadata['receiver'] + try: + #receiver = Person.lookup(pubkey, self.community).name + receiver = Person.lookup(pubkey, self.community) + except PersonNotFoundError: + #receiver = "pub:{0}".format(pubkey[:5]) + receiver = pubkey + + date_ts = transfer.metadata['time'] + + return (date_ts, receiver, amount, + "", comment, transfer.state) + + def data(self, index, role): + row = index.row() + col = index.column() + + if not index.isValid(): + return QVariant() + + transfer = self.transfers[row] + if role == Qt.DisplayRole: + if type(transfer) is Received: + return self.data_received(transfer)[col] + else: + return self.data_sent(transfer)[col] + + def flags(self, index): + return Qt.ItemIsSelectable | Qt.ItemIsEnabled diff --git a/src/cutecoin/models/wallet.py b/src/cutecoin/models/wallet.py index df17811b29b808d607d4d18a5aa7ef24d0570c11..8c67ae6ad6372162d8baecfcc1170fe45ccad792 100644 --- a/src/cutecoin/models/wallet.py +++ b/src/cutecoin/models/wallet.py @@ -26,8 +26,8 @@ class WalletListModel(QAbstractListModel): def data(self, index, role): if role == Qt.DisplayRole: row = index.row() - value = "{0}".format(self.sources[row].amount) - return value + amount = self.sources[row].amount + return amount def flags(self, index): return Qt.ItemIsSelectable | Qt.ItemIsEnabled diff --git a/src/cutecoin/models/wallets.py b/src/cutecoin/models/wallets.py index 745d62f92bab0037e945a2241ac1b49ac8c96d99..3887b36dff3abb30316de793fc8fc2984080db1b 100644 --- a/src/cutecoin/models/wallets.py +++ b/src/cutecoin/models/wallets.py @@ -19,6 +19,7 @@ class WalletsListModel(QAbstractListModel): Constructor ''' super(WalletsListModel, self).__init__(parent) + self.account = account self.wallets = account.wallets self.community = community @@ -29,7 +30,11 @@ class WalletsListModel(QAbstractListModel): row = index.row() w = self.wallets[row] if role == Qt.DisplayRole: - return w.get_text(self.community) + amount = w.value(self.community) + ref_amount = self.account.units_to_ref(amount, self.community) + ref_name = self.account.ref_name(self.community.currency) + return """{0} +{1:.2f} {2}""".format(w.name, ref_amount, ref_name) elif role == Qt.EditRole: return self.wallets[row].name