diff --git a/src/cutecoin/core/app.py b/src/cutecoin/core/app.py index 33e853dbcb9db345f25d8f97f03735da3a805e43..7ef3a31d77a306ae918f8eb36d4f13df2ef64944 100644 --- a/src/cutecoin/core/app.py +++ b/src/cutecoin/core/app.py @@ -15,6 +15,7 @@ from PyQt5.QtCore import QObject, pyqtSignal from . import config from ..tools.exceptions import NameAlreadyExists, BadAccountFile from .account import Account +from . import person from .. import __version__ @@ -76,8 +77,8 @@ class Application(QObject): self.current_account = account def load(self): - if (os.path.exists(config.parameters['data']) - and os.path.isfile(config.parameters['data'])): + self.load_persons() + try: logging.debug("Loading data...") with open(config.parameters['data'], 'r') as json_data: data = json.load(json_data) @@ -85,6 +86,18 @@ class Application(QObject): self.default_account = data['default_account'] for account_name in data['local_accounts']: self.accounts[account_name] = None + except FileNotFoundError: + pass + + def load_persons(self): + try: + persons_path = os.path.join(config.parameters['home'], + '__persons__') + with open(persons_path, 'r') as persons_path: + data = json.load(persons_path) + person.load_cache(data) + except FileNotFoundError: + pass def load_account(self, account_name): account_path = os.path.join(config.parameters['home'], @@ -100,6 +113,19 @@ class Application(QObject): community_path = os.path.join(config.parameters['home'], account.name, '__cache__', community.currency) + + network_path = os.path.join(config.parameters['home'], + account.name, '__cache__', + community.currency + '_network') + + if os.path.exists(network_path): + 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) + else: + os.remove(network_path) + if os.path.exists(community_path): with open(community_path, 'r') as json_data: data = json.load(json_data) @@ -135,6 +161,14 @@ class Application(QObject): account_path = os.path.join(config.parameters['home'], account.name) shutil.rmtree(account_path) + def save_persons(self): + persons_path = os.path.join(config.parameters['home'], + '__persons__') + with open(persons_path, 'w')as outfile: + data = person.jsonify_cache() + data['version'] = __version__ + json.dump(data, outfile, indent=4, sort_keys=True) + def save_cache(self, account): if not os.path.exists(os.path.join(config.parameters['home'], account.name, '__cache__')): @@ -152,6 +186,16 @@ class Application(QObject): community_path = os.path.join(config.parameters['home'], account.name, '__cache__', community.currency) + + network_path = os.path.join(config.parameters['home'], + account.name, '__cache__', + community.currency + '_network') + + with open(network_path, 'w') as outfile: + data = community.jsonify_network() + data['version'] = __version__ + json.dump(data, outfile, indent=4, sort_keys=True) + with open(community_path, 'w') as outfile: data = community.jsonify_cache() data['version'] = __version__ diff --git a/src/cutecoin/core/community.py b/src/cutecoin/core/community.py index 018f65f1e9ecb87ebee341551657ca7ef4dd4fab..762dd50232bd01eabd5c6a9bb1d889f390a03de4 100644 --- a/src/cutecoin/core/community.py +++ b/src/cutecoin/core/community.py @@ -17,6 +17,9 @@ from requests.exceptions import RequestException class Cache(): + _saved_requests = [hash(bma.blockchain.Block), + hash(bma.wot.Lookup)] + def __init__(self, community): self.latest_block = 0 self.community = community @@ -32,9 +35,8 @@ class Cache(): self.latest_block = data['latest_block'] def jsonify(self): - saved_requests = [hash(bma.blockchain.Block)] data = {k: self.data[k] for k in self.data.keys() - if k[0] in saved_requests} + if k[0] in Cache._saved_requests} entries = [] for d in data: entries.append({'key': d, @@ -44,9 +46,8 @@ class Cache(): def refresh(self): self.latest_block = self.community.current_blockid()['number'] - saved_requests = [hash(bma.blockchain.Block)] self.data = {k: self.data[k] for k in self.data.keys() - if k[0] in saved_requests} + if k[0] in Cache._saved_requests} def request(self, request, req_args={}, get_args={}): cache_key = (hash(request), @@ -85,26 +86,31 @@ class Community(object): self._cache.refresh() @classmethod - def create(cls, currency, peer): - community = cls(currency, [peer]) + def create(cls, node): + network = Network.create(node) + community = cls(node.currency, network) logging.debug("Creating community") return community @classmethod def load(cls, json_data): 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_cache(self, json_data): self._cache.load_from_json(json_data) def jsonify_cache(self): return self._cache.jsonify() + def jsonify_network(self): + return {'network': self._network.jsonify()} + def name(self): return self.currency diff --git a/src/cutecoin/core/net/network.py b/src/cutecoin/core/net/network.py index 665fe9d66a233de79e26cdaed7cd794e01a4c92c..af0c247ab4d1f6c43915f29243f2945b09f85d0b 100644 --- a/src/cutecoin/core/net/network.py +++ b/src/cutecoin/core/net/network.py @@ -31,10 +31,13 @@ class Network(QObject): for n in self._nodes: n.changed.connect(self.nodes_changed) self.must_crawl = False - #TODO: Crawl nodes at startup @classmethod def from_json(cls, currency, json_data): + ''' + We load the nodes which we know for sure since we + used them at the community creation + ''' nodes = [] for data in json_data: node = Node.from_json(currency, data) @@ -42,23 +45,34 @@ class Network(QObject): logging.debug("Loading : {:}".format(data['pubkey'])) block_max = max([n.block for n in nodes]) for node in nodes: - node.check_sync(currency, block_max) + node.check_sync(block_max) return cls(currency, nodes) + def merge_with_json(self, json_data): + ''' + We merge with dynamic nodes detected when we + last stopped cutecoin + ''' + for data in json_data: + node = Node.from_json(self.currency, data) + self._nodes.append(node) + logging.debug("Loading : {:}".format(data['pubkey'])) + self._nodes = self.crawling() + @classmethod - def create(cls, currency, node): + def create(cls, node): nodes = [node] - network = cls(currency, nodes) - nodes = network.crawling + network = cls(node.currency, nodes) + nodes = network.crawling() block_max = max([n.block for n in nodes]) for node in nodes: - node.check_sync(currency, block_max) + node.check_sync(block_max) network._nodes = nodes return network def jsonify(self): data = [] - for node in self.nodes: + for node in self._nodes: data.append(node.jsonify()) return data @@ -105,13 +119,13 @@ class Network(QObject): logging.debug("Peering : next to read : {0} : {1}".format(n.pubkey, (n.pubkey not in traversed_pubkeys))) if n.pubkey not in traversed_pubkeys: - n.peering_traversal(self.currency, nodes, + n.peering_traversal(nodes, traversed_pubkeys, interval) time.sleep(interval) block_max = max([n.block for n in nodes]) for node in [n for n in nodes if n.state == Node.ONLINE]: - node.check_sync(self.currency, block_max) + node.check_sync(block_max) #TODO: Offline nodes for too long have to be removed #TODO: Corrupted nodes should maybe be removed faster ? diff --git a/src/cutecoin/core/net/node.py b/src/cutecoin/core/net/node.py index 448e14a8ca632dcad4021860a75a386c8a267e34..b7e1366e12cb7a5b4f1b6cb28315348ea1561318 100644 --- a/src/cutecoin/core/net/node.py +++ b/src/cutecoin/core/net/node.py @@ -6,7 +6,9 @@ Created on 21 févr. 2015 from ucoinpy.documents.peer import Peer, BMAEndpoint, Endpoint from ucoinpy.api import bma +from ucoinpy.api.bma import ConnectionHandler from requests.exceptions import RequestException +from ...tools.exceptions import InvalidNodeCurrency import logging import time @@ -25,7 +27,7 @@ class Node(QObject): changed = pyqtSignal() - def __init__(self, endpoints, pubkey, block, state): + def __init__(self, currency, endpoints, pubkey, block, state): ''' Constructor ''' @@ -35,11 +37,31 @@ class Node(QObject): self._block = block self._state = state self._neighbours = [] + self._currency = currency + + @classmethod + def from_address(cls, currency, address, port): + peer_data = bma.network.Peering(ConnectionHandler(address, port)).get() + + peer = Peer.from_signed_raw("{0}{1}\n".format(peer_data['raw'], + peer_data['signature'])) + + if currency is not None: + if peer.currency != currency: + raise InvalidNodeCurrency(peer.currency, currency) + + node = cls(peer.currency, peer.endpoints, peer.pubkey, 0, Node.ONLINE, 0) + node.refresh_state() + return node @classmethod def from_peer(cls, currency, peer): - node = cls(peer.endpoints, "", 0, Node.ONLINE) - node.refresh_state(currency) + if currency is not None: + if peer.currency != currency: + raise InvalidNodeCurrency(peer.currency, currency) + + node = cls(peer.currency, peer.endpoints, "", 0, Node.ONLINE) + node.refresh_state() return node @classmethod @@ -48,12 +70,15 @@ class Node(QObject): for endpoint_data in data['endpoints']: endpoints.append(Endpoint.from_inline(endpoint_data)) - node = cls(endpoints, "", 0, Node.ONLINE) - node.refresh_state(currency) + currency = data['currency'] + + node = cls(currency, endpoints, "", 0, Node.ONLINE) + node.refresh_state() return node def jsonify(self): - data = {'pubkey': self._pubkey} + data = {'pubkey': self._pubkey, + 'currency': self._currency} endpoints = [] for e in self._endpoints: endpoints.append(e.inline()) @@ -76,17 +101,21 @@ class Node(QObject): def state(self): return self._state + @property + def currency(self): + return self._currency + @property def neighbours(self): return self._neighbours - def check_sync(self, currency, block): + def check_sync(self, block): if self._block < block: self._state = Node.DESYNCED else: self._state = Node.ONLINE - def refresh_state(self, currency): + def refresh_state(self): emit_change = False try: informations = bma.network.Peering(self.endpoint.conn_handler()).get() @@ -108,51 +137,57 @@ class Node(QObject): self._state = Node.OFFLINE emit_change = True - if node_currency != currency: - self.state = Node.CORRUPTED - emit_change = True - - if block_number != self._block: - self._block = block_number - emit_change = True - - if node_pubkey != self._pubkey: - self._pubkey = node_pubkey - emit_change = True - - logging.debug(neighbours) - new_inlines = [e.inline() for n in neighbours for e in n] - last_inlines = [e.inline() for n in self._neighbours for e in n] - - hash_new_neighbours = hash(tuple(frozenset(sorted(new_inlines)))) - hash_last_neighbours = hash(tuple(frozenset(sorted(last_inlines)))) - if hash_new_neighbours != hash_last_neighbours: - self._neighbours = neighbours - emit_change = True + # If not is offline, do not refresh last data + if self._state != Node.OFFLINE: + # If not changed its currency, consider it corrupted + if node_currency != self._currency: + self.state = Node.CORRUPTED + emit_change = True + else: + if block_number != self._block: + self._block = block_number + emit_change = True + + if node_pubkey != self._pubkey: + self._pubkey = node_pubkey + emit_change = True + + logging.debug(neighbours) + new_inlines = [e.inline() for n in neighbours for e in n] + last_inlines = [e.inline() for n in self._neighbours for e in n] + + hash_new_neighbours = hash(tuple(frozenset(sorted(new_inlines)))) + hash_last_neighbours = hash(tuple(frozenset(sorted(last_inlines)))) + if hash_new_neighbours != hash_last_neighbours: + self._neighbours = neighbours + emit_change = True if emit_change: self.changed.emit() - def peering_traversal(self, currency, found_nodes, + def peering_traversal(self, found_nodes, traversed_pubkeys, interval): logging.debug("Read {0} peering".format(self.pubkey)) traversed_pubkeys.append(self.pubkey) - self.refresh_state(currency) - if self.pubkey not in [n.pubkey for n in found_nodes]: - found_nodes.append(self) + self.refresh_state() + if self.pubkey not in [n.pubkey for n in found_nodes]: + # if node is corrupted remove it + if self._state != Node.CORRUPTED: + found_nodes.append(self) try: + logging.debug(self.neighbours) for n in self.neighbours: - e = next((e for e in self._endpoints if type(e) is BMAEndpoint)) - peering = bma.network.Peering(self.endpoint.conn_handler()).get() + e = next(e for e in n if type(e) is BMAEndpoint) + peering = bma.network.Peering(e.conn_handler()).get() peer = Peer.from_signed_raw("{0}{1}\n".format(peering['raw'], peering['signature'])) - node = Node.from_peer(currency, peer) + node = Node.from_peer(self._currency, peer) logging.debug(traversed_pubkeys) logging.debug("Traversing : next to read : {0} : {1}".format(node.pubkey, (node.pubkey not in traversed_pubkeys))) if node.pubkey not in traversed_pubkeys: - node.peering_traversal(currency, found_nodes, + node.peering_traversal(found_nodes, traversed_pubkeys, interval) time.sleep(interval) except RequestException as e: diff --git a/src/cutecoin/core/person.py b/src/cutecoin/core/person.py index 57c6e7695ecc224cbcca7141eeb9678ae9c534da..2b40cb4e931fceae08c9752a69a2d7a3eb87b43d 100644 --- a/src/cutecoin/core/person.py +++ b/src/cutecoin/core/person.py @@ -5,56 +5,134 @@ Created on 11 févr. 2014 ''' import logging +import functools 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,\ MembershipNotFoundError +from PyQt5.QtCore import QMutex -class Person(object): +def load_cache(json_data): + for person_data in json_data['persons']: + person = Person.from_json(person_data) + Person._instances[person.pubkey] = person + + +def jsonify_cache(): + data = [] + for person in Person._instances.values(): + data.append(person.jsonify()) + return {'persons': data} + +class cached(object): + ''' + Decorator. Caches a function's return value each time it is called. + If called later with the same arguments, the cached value is returned + (not reevaluated). + Delete it to clear it from the cache + ''' + def __init__(self, func): + self.func = func + + def __call__(self, inst, community): + inst._cache_mutex.lock() + try: + inst._cache[community.currency] + except KeyError: + inst._cache[community.currency] = {} + + try: + value = inst._cache[community.currency][self.func.__name__] + except KeyError: + value = self.func(inst, community) + inst._cache[community.currency][self.func.__name__] = value + + inst._cache_mutex.unlock() + return value + + def __repr__(self): + '''Return the function's docstring.''' + return self.func.__repr__ + + def __get__(self, inst, objtype): + if inst is None: + return self.func + return functools.partial(self, inst) + + +class Person(object): ''' A person with a name, a fingerprint and an email - Created by the person.factory ''' + _instances = {} - def __init__(self, name, pubkey): + def __init__(self, name, pubkey, cache): ''' Constructor ''' self.name = name self.pubkey = pubkey + self._cache = cache + self._cache_mutex = QMutex() @classmethod def lookup(cls, pubkey, community, cached=True): ''' Create a person from the pubkey found in a community ''' - data = community.request(bma.wot.Lookup, req_args={'search': pubkey}, - cached=cached) - timestamp = 0 + if pubkey in Person._instances: + return Person._instances[pubkey] + else: + data = community.request(bma.wot.Lookup, req_args={'search': pubkey}, + cached=cached) + timestamp = 0 - for result in data['results']: - if result["pubkey"] == pubkey: - uids = result['uids'] - for uid in uids: - if uid["meta"]["timestamp"] > timestamp: - timestamp = uid["meta"]["timestamp"] - name = uid["uid"] + for result in data['results']: + if result["pubkey"] == pubkey: + uids = result['uids'] + for uid in uids: + if uid["meta"]["timestamp"] > timestamp: + timestamp = uid["meta"]["timestamp"] + name = uid["uid"] - return cls(name, pubkey) + person = cls(name, pubkey, {}) + Person._instances[pubkey] = person + logging.debug("{0}".format(Person._instances.keys())) + return person raise PersonNotFoundError(pubkey, community.name()) @classmethod + def from_metadata(cls, name, pubkey): + if pubkey in Person._instances: + return Person._instances[pubkey] + else: + person = cls(name, pubkey, {}) + Person._instances[pubkey] = person + return person + + @classmethod + #TODO: Remove name from person, contats should not use the person class def from_json(cls, json_person): ''' Create a person from json data ''' - name = json_person['name'] pubkey = json_person['pubkey'] - return cls(name, pubkey) + if pubkey in Person._instances: + return Person._instances[pubkey] + else: + name = json_person['name'] + if 'cache' in json_person: + cache = json_person['cache'] + else: + cache = {} + + person = cls(name, pubkey, cache) + Person._instances[pubkey] = person + return person def selfcert(self, community): data = community.request(bma.wot.Lookup, req_args={'search': self.pubkey}) @@ -78,6 +156,7 @@ class Person(object): signature) raise PersonNotFoundError(self.pubkey, community.name()) + @cached def membership(self, community): try: search = community.request(bma.blockchain.Membership, @@ -97,12 +176,9 @@ class Person(object): if '400' in str(e): raise MembershipNotFoundError(self.pubkey, community.name()) - membership = Membership(PROTOCOL_VERSION, community.currency, self.pubkey, - membership_data['blockNumber'], - membership_data['blockHash'], 'IN', search['uid'], - search['sigDate'], None) - return membership + return membership_data + @cached def is_member(self, community): try: certifiers = community.request(bma.wot.CertifiersOf, {'search': self.pubkey}) @@ -110,6 +186,7 @@ class Person(object): except ValueError: return False + @cached def certifiers_of(self, community): try: certifiers = community.request(bma.wot.CertifiersOf, {'search': self.pubkey}) @@ -141,6 +218,7 @@ class Person(object): return certifiers['certifications'] + @cached def certified_by(self, community): try: certified_list = community.request(bma.wot.CertifiedBy, {'search': self.pubkey}) @@ -151,6 +229,7 @@ class Person(object): except ValueError as e: logging.debug('bma.wot.Lookup request ValueError : ' + str(e)) return list() + certified_list = list() for certified in data['results'][0]['signed']: certified['cert_time'] = dict() @@ -165,7 +244,35 @@ 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] = {} + + change = False + try: + before = self._cache[community.currency][func.__name__] + except KeyError: + change = True + + 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 + + self._cache[community.currency][func.__name__] = value + self._cache_mutex.unlock() + return change + def jsonify(self): data = {'name': self.name, - 'pubkey': self.pubkey} + 'pubkey': self.pubkey, + 'cache': self._cache} return data diff --git a/src/cutecoin/core/watchers/persons.py b/src/cutecoin/core/watchers/persons.py new file mode 100644 index 0000000000000000000000000000000000000000..84e2dc8f7a12733918857358068e053b22375083 --- /dev/null +++ b/src/cutecoin/core/watchers/persons.py @@ -0,0 +1,37 @@ +''' +Created on 27 févr. 2015 + +@author: inso +''' + +from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal +from ..person import Person +import logging + + +class PersonsWatcher(QObject): + ''' + This will crawl the network to always + have up to date informations about the nodes + ''' + + person_changed = pyqtSignal(str) + end_watching = pyqtSignal() + + def __init__(self, community): + super().__init__() + self.community = community + + @pyqtSlot() + def watch(self): + logging.debug("Watching persons") + for p in Person._instances.values(): + for func in [Person.membership, + Person.is_member, + Person.certifiers_of, + Person.certified_by]: + if p.reload(func, self.community): + logging.debug("Change detected on {0} about {1}".format(p.pubkey, + func.__name__)) + self.person_changed.emit(p.pubkey) + self.end_watching.emit() diff --git a/src/cutecoin/gui/community_tab.py b/src/cutecoin/gui/community_tab.py index 3016af98064ef644b08b979416c88a719e659c38..69075a40d06bb8663de01e674853b5c9b8cf4be6 100644 --- a/src/cutecoin/gui/community_tab.py +++ b/src/cutecoin/gui/community_tab.py @@ -57,7 +57,7 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): model = self.table_community_members.model() if index.row() < model.rowCount(): source_index = model.mapToSource(index) - pubkey_col = model.sourceModel().columns.index('Pubkey') + pubkey_col = model.sourceModel().columns_ids.index('pubkey') pubkey_index = model.sourceModel().index(source_index.row(), pubkey_col) pubkey = model.sourceModel().data(pubkey_index, Qt.DisplayRole) @@ -183,3 +183,6 @@ The process to join back the community later will have to be done again.""" "{0}".format(e), QMessageBox.Ok) + def refresh_person(self, pubkey): + index = self.table_community_members.model().sourceModel().person_index(pubkey) + self.table_community_members.model().sourceModel().dataChanged.emit(index[0], index[1]) diff --git a/src/cutecoin/gui/contact.py b/src/cutecoin/gui/contact.py index 469b9fb88839af42fc85ed08fdc1c20c8839a92d..1f2a419cff8775507e54d7e3c1a98b45864916d7 100644 --- a/src/cutecoin/gui/contact.py +++ b/src/cutecoin/gui/contact.py @@ -39,7 +39,7 @@ class ConfigureContactDialog(QDialog, Ui_ConfigureContactDialog): self.contact.pubkey = pubkey else: try: - self.account.add_contact(Person(name, pubkey)) + self.account.add_contact(Person.from_metadata(name, pubkey)) except ContactAlreadyExists as e: QMessageBox.critical(self, "Contact already exists", str(e), diff --git a/src/cutecoin/gui/currency_tab.py b/src/cutecoin/gui/currency_tab.py index fc0da0cd398c7697b794afb6fc9b8dbf1dad065c..f6efa8ac90a4284fbf299a60c28d2fcbf5a96f94 100644 --- a/src/cutecoin/gui/currency_tab.py +++ b/src/cutecoin/gui/currency_tab.py @@ -22,6 +22,7 @@ from ..core.wallet import Wallet from ..core.person import Person from ..core.transfer import Transfer from ..core.watchers.blockchain import BlockchainWatcher +from ..core.watchers.persons import PersonsWatcher class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget): @@ -60,9 +61,17 @@ class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget): self.watcher_thread.start() + self.persons_watcher = PersonsWatcher(self.community) + self.persons_watcher.person_changed.connect(self.tab_community.refresh_person) + self.persons_watcher_thread = QThread() + self.persons_watcher.moveToThread(self.persons_watcher_thread) + self.persons_watcher_thread.started.connect(self.persons_watcher.watch) + self.persons_watcher.end_watching.connect(self.persons_watcher_thread.finished) + self.persons_watcher_thread.start() + person = Person.lookup(self.app.current_account.pubkey, self.community) try: - join_block = person.membership(self.community).block_number + join_block = person.membership(self.community)['blockNumber'] join_date = self.community.get_block(join_block).mediantime parameters = self.community.get_parameters() expiration_date = join_date + parameters['sigValidity'] @@ -160,11 +169,7 @@ class CurrencyTabWidget(QWidget, Ui_CurrencyTabWidget): QModelIndex(), []) - if self.tab_community.table_community_members.model(): - self.tab_community.table_community_members.model().dataChanged.emit( - QModelIndex(), - QModelIndex(), - []) + self.persons_watcher_thread.start() text = "Connected : Block {0}".format(block_number) self.status_label.setText(text) diff --git a/src/cutecoin/gui/mainwindow.py b/src/cutecoin/gui/mainwindow.py index 90d7c29db88cc8dfa782437527ec428aca9dbec9..4e0d982322792770032bf3996ac75cfc3c7eff83 100644 --- a/src/cutecoin/gui/mainwindow.py +++ b/src/cutecoin/gui/mainwindow.py @@ -179,7 +179,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): def open_configure_account_dialog(self): dialog = ProcessConfigureAccount(self.app, self.app.current_account) dialog.accepted.connect(self.refresh) - dialog.exec_() + result = dialog.exec_() + if result == QDialog.Accepted: + self.action_change_account(self.app.current_account.name) def open_about_popup(self): """ @@ -320,6 +322,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): def closeEvent(self, event): if self.app.current_account: self.app.save_cache(self.app.current_account) + self.app.save_persons() self.loader.deleteLater() self.loader_thread.deleteLater() super().closeEvent(event) diff --git a/src/cutecoin/gui/process_cfg_account.py b/src/cutecoin/gui/process_cfg_account.py index d61e3f438717642f7cbb130169fa2d1977b330cb..795d1ec265fd48a1529f9739f5cadbe88b712884 100644 --- a/src/cutecoin/gui/process_cfg_account.py +++ b/src/cutecoin/gui/process_cfg_account.py @@ -121,16 +121,11 @@ class StepPageCommunities(Step): nb_wallets = self.config_dialog.spinbox_wallets.value() self.config_dialog.account.set_walletpool_size(nb_wallets, password) + self.config_dialog.app.add_account(self.config_dialog.account) if len(self.config_dialog.app.accounts) == 1: self.config_dialog.app.default_account = self.config_dialog.account.name - - try: - self.config_dialog.app.add_account(self.config_dialog.account) - except KeyAlreadyUsed as e: - QMessageBox.critical(self, "Error", - str(e), QMessageBox.Ok) - return self.config_dialog.app.save(self.config_dialog.account) + self.config_dialog.app.current_account = self.config_dialog.account def display_page(self): logging.debug("Communities DISPLAY") diff --git a/src/cutecoin/gui/process_cfg_community.py b/src/cutecoin/gui/process_cfg_community.py index 29a6e72b06c92c651ec387862188818c4021fa1e..f0356966c60d04181115f5a54e136b31b0abe2c6 100644 --- a/src/cutecoin/gui/process_cfg_community.py +++ b/src/cutecoin/gui/process_cfg_community.py @@ -17,6 +17,7 @@ from ..gen_resources.community_cfg_uic import Ui_CommunityConfigurationDialog from ..models.peering import PeeringTreeModel from ..core.community import Community from ..core.person import Person +from ..core.net.node import Node from ..tools.exceptions import PersonNotFoundError, NoPeerAvailable @@ -33,6 +34,7 @@ class StepPageInit(Step): ''' def __init__(self, config_dialog): super().__init__(config_dialog) + self.node = None logging.debug("Init") def is_valid(self): @@ -40,38 +42,21 @@ class StepPageInit(Step): port = self.config_dialog.spinbox_port.value() logging.debug("Is valid ? ") try: - peer_data = bma.network.Peering(ConnectionHandler(server, port)) - peer_data.get()['raw'] - except: - QMessageBox.critical(self.config_dialog, "Server error", - "Cannot get node peering") - return False + self.node = Node.from_address(None, server, port) + except Exception as e: + QMessageBox.critical(self.config_dialog, ":(", + str(e), + QMessageBox.Ok) + return True def process_next(self): ''' We create the community ''' - server = self.config_dialog.lineedit_server.text() - port = self.config_dialog.spinbox_port.value() account = self.config_dialog.account logging.debug("Account : {0}".format(account)) - try: - peering = bma.network.Peering(ConnectionHandler(server, port)) - peer_data = peering.get() - peer = Peer.from_signed_raw("{0}{1}\n".format(peer_data['raw'], - peer_data['signature'])) - currency = peer.currency - self.config_dialog.community = Community.create(currency, peer) - except NoPeerAvailable: - QMessageBox.critical(self.config_dialog, "Server Error", - "Cannot join any peer in this community.") - raise - except requests.exceptions.RequestException as e: - QMessageBox.critical(self.config_dialog, ":(", - str(e), - QMessageBox.Ok) - raise + self.config_dialog.community = Community.create(self.node) def display_page(self): self.config_dialog.button_previous.setEnabled(False) @@ -168,19 +153,13 @@ class ProcessConfigureCommunity(QDialog, Ui_CommunityConfigurationDialog): ''' server = self.lineedit_server.text() port = self.spinbox_port.value() - try: - peer_data = bma.network.Peering(ConnectionHandler(server, port)).get() - peer = Peer.from_signed_raw("{0}{1}\n".format(peer_data['raw'], - peer_data['signature'])) - if peer.currency == self.community.currency: - self.community.add_peer(peer) - else: - QMessageBox.critical(self, "Error", - "This peer doesn't use this community currency.") - except requests.exceptions.RequestException as e: - QMessageBox.critical(self, "Server error", - "Cannot get node peering") + try: + node = Node.from_address(self.community.currency, server, port) + self.community.add_node(node) + except Exception as e: + QMessageBox.critical(self, "Error", + str(e)) self.tree_peers.setModel(PeeringTreeModel(self.community)) def showContextMenu(self, point): diff --git a/src/cutecoin/gui/wallets_tab.py b/src/cutecoin/gui/wallets_tab.py index 3af3eb0cc8b68feef347628714c953bca2793cac..a688f162560a5268981231d1a74e3e9f30bfa212 100644 --- a/src/cutecoin/gui/wallets_tab.py +++ b/src/cutecoin/gui/wallets_tab.py @@ -45,7 +45,7 @@ class WalletsTabWidget(QWidget, Ui_WalletsTab): certified = person.certified_by(self.community) certifiers = person.certifiers_of(self.community) - renew_block = membership.block_number + renew_block = membership['blockNumber'] last_renewal = self.community.get_block(renew_block).mediantime expiration = last_renewal + parameters['sigValidity'] except MembershipNotFoundError: diff --git a/src/cutecoin/gui/wot_tab.py b/src/cutecoin/gui/wot_tab.py index 9933e02296d0e8b6977efebab5228977625bb75c..71a96c8b553437556d8060f2d3cef97605646c5d 100644 --- a/src/cutecoin/gui/wot_tab.py +++ b/src/cutecoin/gui/wot_tab.py @@ -254,4 +254,4 @@ class WotTabWidget(QWidget, Ui_WotTabWidget): return block.mediantime def get_person_from_metadata(self, metadata): - return Person(metadata['text'], metadata['id']) + return Person.from_metadata(metadata['text'], metadata['id']) diff --git a/src/cutecoin/models/members.py b/src/cutecoin/models/members.py index f2f22065020bc27a0b4b1eb98c8bd68d2e3cba10..72ce548225455de1adfbdca3728e42f11913f07f 100644 --- a/src/cutecoin/models/members.py +++ b/src/cutecoin/models/members.py @@ -7,7 +7,7 @@ Created on 5 févr. 2014 from ucoinpy.api import bma from ..core.person import Person from PyQt5.QtCore import QAbstractTableModel, QSortFilterProxyModel, Qt, \ - QDateTime + QDateTime, QModelIndex from PyQt5.QtGui import QColor import logging @@ -92,7 +92,7 @@ class MembersTableModel(QAbstractTableModel): def member_data(self, pubkey): person = Person.lookup(pubkey, self.community) - join_block = person.membership(self.community).block_number + join_block = person.membership(self.community)['blockNumber'] join_date = self.community.get_block(join_block).mediantime parameters = self.community.get_parameters() expiration_date = join_date + parameters['sigValidity'] @@ -104,5 +104,14 @@ class MembersTableModel(QAbstractTableModel): col = index.column() return self.member_data(self.pubkeys[row])[col] + def person_index(self, pubkey): + try: + row = self.pubkeys.index(pubkey) + index_start = self.index(row, 0) + index_end = self.index(row, len(self.columns_ids)) + return (index_start, index_end) + except ValueError: + return (QModelIndex(), QModelIndex()) + def flags(self, index): return Qt.ItemIsSelectable | Qt.ItemIsEnabled diff --git a/src/cutecoin/tools/exceptions.py b/src/cutecoin/tools/exceptions.py index d063899b1812883197e96ceec3efaad1cad6d8ed..f394b397109d777550893881fd698817120ec199 100644 --- a/src/cutecoin/tools/exceptions.py +++ b/src/cutecoin/tools/exceptions.py @@ -163,6 +163,19 @@ class NoPeerAvailable(Error): .format(currency, peers)) +class InvalidNodeCurrency(Error): + ''' + Exception raised when a node doesn't use the intended currency + ''' + def __init__(self, currency, node_currency): + ''' + Constructor + ''' + super() .__init__( + "Node is working for {0} currency, but should be {1}" + .format(node_currency, currency)) + + class ContactAlreadyExists(Error): ''' Exception raised when a community doesn't have any