diff --git a/src/sakia/data/graphs/base_graph.py b/src/sakia/data/graphs/base_graph.py index c0dc01ffa8340d0c706b21b6b8fdf41d2fcb472c..08960463416652d0204aad19c9425c6e13e0961d 100644 --- a/src/sakia/data/graphs/base_graph.py +++ b/src/sakia/data/graphs/base_graph.py @@ -80,7 +80,7 @@ class BaseGraph(QObject): :rtype: str """ try: - current_confirmations = min(max(block_number - self.blockchain_service.current_buid().number, 0), 6) + current_confirmations = min(max(self.blockchain_service.current_buid().number - block_number, 0), 6) if MAX_CONFIRMATIONS > current_confirmations: if self.app.parameters.expert_mode: diff --git a/src/sakia/data/processors/identities.py b/src/sakia/data/processors/identities.py index f697c363f1c1fff54d0b51b23833e5ded16c2699..6e5b2a055e50532de88d3e8d9fed10bce02c68f3 100644 --- a/src/sakia/data/processors/identities.py +++ b/src/sakia/data/processors/identities.py @@ -97,7 +97,7 @@ class IdentitiesProcessor: :rtype: sakia.data.entities.Identity """ - return self._identities_repo.get_written(currency=currency, pubkey=pubkey) + return self._identities_repo.get_all(currency=currency, pubkey=pubkey, ms_written_on=0) def get_identity(self, currency, pubkey, uid=""): """ diff --git a/src/sakia/data/repositories/identities.py b/src/sakia/data/repositories/identities.py index b27021566d6f8f2dc5ed3ff45d5b9293fbe6c38f..567982b9b8eb9f6bc284c4bace67f98f6cef6640 100644 --- a/src/sakia/data/repositories/identities.py +++ b/src/sakia/data/repositories/identities.py @@ -64,39 +64,6 @@ class IdentitiesRepo: if data: return Identity(*data) - def get_written(self, offset=0, limit=1000, sort_by="currency", sort_order="ASC", **search): - """ - Get an identity in the database written in the blockchain - and corresponding to the search - - :param dict search: the criterions of the lookup - :rtype: List[sakia.data.entities.Identity] - """ - filters = [] - values = [] - for k, v in search.items(): - if isinstance(v, bool): - v = int(v) - filters.append("{k}=?".format(k=k)) - values.append(v) - - request = """SELECT * FROM identities WHERE {filters} - AND ms_written_on!="{empty_buid}" - ORDER BY {sort_by} {sort_order} - LIMIT {limit} OFFSET {offset}""" \ - .format(filters=" AND ".join(filters), - empty_buid=str(BlockUID.empty()), - offset=offset, - limit=limit, - sort_by=sort_by, - sort_order=sort_order - ) - c = self._conn.execute(request, tuple(values)) - datas = c.fetchall() - if datas: - return [Identity(*data) for data in datas] - return [] - def get_all(self, **search): """ Get all existing identity in the database corresponding to the search diff --git a/src/sakia/gui/dialogs/revocation/model.py b/src/sakia/gui/dialogs/revocation/model.py index 3030926e57cb034f68afdbfb11b95222edf67a0e..968f6f93a5c8a64f64a46768ff1abfef3aeeae95 100644 --- a/src/sakia/gui/dialogs/revocation/model.py +++ b/src/sakia/gui/dialogs/revocation/model.py @@ -1,7 +1,6 @@ -from sakia.gui.component.model import ComponentModel from duniterpy.documents.certification import Revocation from duniterpy.api import bma, errors -from sakia.core.net import Node +from PyQt5.QtCore import QObject import aiohttp diff --git a/src/sakia/gui/main_window/toolbar/controller.py b/src/sakia/gui/main_window/toolbar/controller.py index 6ec8802c354fe7cd5b718cb231555d69fe4b7869..5b48edbfebed6cf8483b3ebdd8343c90b033c5e8 100644 --- a/src/sakia/gui/main_window/toolbar/controller.py +++ b/src/sakia/gui/main_window/toolbar/controller.py @@ -4,10 +4,12 @@ from PyQt5.QtWidgets import QDialog, QMessageBox from sakia.decorators import asyncify from sakia.gui.dialogs.connection_cfg.controller import ConnectionConfigController from sakia.gui.dialogs.certification.controller import CertificationController +from sakia.gui.dialogs.revocation.controller import RevocationController from sakia.gui.dialogs.transfer.controller import TransferController from sakia.gui.widgets import toast from sakia.gui.widgets.dialogs import QAsyncMessageBox, QAsyncFileDialog, dialog_async_exec from sakia.gui.password_asker import PasswordAskerDialog +from sakia.gui.preferences import PreferencesDialog from .model import ToolbarModel from .view import ToolbarView @@ -28,9 +30,9 @@ class ToolbarController(QObject): self.model = model self.view.button_certification.clicked.connect(self.open_certification_dialog) self.view.button_send_money.clicked.connect(self.open_transfer_money_dialog) - self.view.action_publish_uid.triggered.connect(self.publish_uid) - self.view.button_membership.clicked.connect(self.send_membership_demand) + self.view.button_membership.clicked.connect(self.send_join_demand) self.view.action_add_connection.triggered.connect(self.open_add_connection_dialog) + self.view.action_parameters.triggered.connect(self.open_settings_dialog) @classmethod def create(cls, app, navigation): @@ -52,11 +54,12 @@ class ToolbarController(QObject): self.view.button_membership.setEnabled(enabled) @asyncify - async def send_membership_demand(self, checked=False): - password = await self.password_asker.async_exec() - if self.password_asker.result() == QDialog.Rejected: + async def send_join_demand(self, checked=False): + connection = self.model.navigation_model.navigation.current_connection() + password = PasswordAskerDialog(connection).async_exec() + if not password: return - result = await self.account.send_membership(password, self.community, 'IN') + result = await self.model.send_join(connection, password) if result[0]: if self.app.preferences['notifications']: toast.display(self.tr("Membership"), self.tr("Success sending Membership demand")) @@ -70,61 +73,19 @@ class ToolbarController(QObject): await QAsyncMessageBox.critical(self, self.tr("Membership"), result[1]) - @asyncify - async def send_membership_leaving(self): - reply = await QAsyncMessageBox.warning(self, self.tr("Warning"), - self.tr("""Are you sure ? -Sending a leaving demand cannot be canceled. -The process to join back the community later will have to be done again.""") -.format(self.account.pubkey), QMessageBox.Ok | QMessageBox.Cancel) - if reply == QMessageBox.Ok: - password = self.password_asker.exec_() - if self.password_asker.result() == QDialog.Rejected: - return - result = await self.account.send_membership(password, self.community, 'OUT') - if result[0]: - if self.app.preferences['notifications']: - toast.display(self.tr("Revoke"), self.tr("Success sending Revoke demand")) - else: - await QAsyncMessageBox.information(self, self.tr("Revoke"), - self.tr("Success sending Revoke demand")) - else: - if self.app.preferences['notifications']: - toast.display(self.tr("Revoke"), result[1]) - else: - await QAsyncMessageBox.critical(self, self.tr("Revoke"), - result[1]) - - @asyncify - async def publish_uid(self, checked=False): - password = await self.password_asker.async_exec() - if self.password_asker.result() == QDialog.Rejected: - return - result = await self.account.send_selfcert(password, self.community) - if result[0]: - if self.app.preferences['notifications']: - toast.display(self.tr("UID"), self.tr("Success publishing your UID")) - else: - await QAsyncMessageBox.information(self, self.tr("Membership"), - self.tr("Success publishing your UID")) - else: - if self.app.preferences['notifications']: - toast.display(self.tr("UID"), result[1]) - else: - await QAsyncMessageBox.critical(self, self.tr("UID"), - result[1]) - def open_certification_dialog(self): CertificationController.open_dialog(self, self.model.app, self.model.navigation_model.current_connection()) def open_revocation_dialog(self): - RevocationDialog.open_dialog(self.app, - self.account) + RevocationController.open_dialog(self.app, self.model.navigation_model.current_connection()) def open_transfer_money_dialog(self): TransferController.open_dialog(self, self.model.app, self.model.navigation_model.current_connection()) + def open_settings_dialog(self): + PreferencesDialog(self.model.app).exec() + def open_add_connection_dialog(self): connection_config = ConnectionConfigController.create_connection(self, self.model.app) connection_config.exec() diff --git a/src/sakia/gui/main_window/toolbar/model.py b/src/sakia/gui/main_window/toolbar/model.py index 56a49cb5118ac724b1cc0992d9d7ea2d43eefd2e..909264b7301003c667bb3409e6768a3860d4ad5f 100644 --- a/src/sakia/gui/main_window/toolbar/model.py +++ b/src/sakia/gui/main_window/toolbar/model.py @@ -6,6 +6,9 @@ import attr class ToolbarModel(QObject): """ The model of Navigation component + + :param sakia.app.Application app: the application + :param sakia.gui.navigation.model.NavigationModel navigation_model: The navigation model """ app = attr.ib() @@ -13,3 +16,6 @@ class ToolbarModel(QObject): def __attrs_post_init__(self): super().__init__() + + async def send_join(self, connection, password): + return await self.app.documents_service.send_membership(connection, password, "IN") diff --git a/src/sakia/gui/main_window/toolbar/toolbar.ui b/src/sakia/gui/main_window/toolbar/toolbar.ui index 63f494287f7f137dc243514a1975b9520cb4d43c..39850025c90911ad23c57e76d28c2c2c7c1480a8 100644 --- a/src/sakia/gui/main_window/toolbar/toolbar.ui +++ b/src/sakia/gui/main_window/toolbar/toolbar.ui @@ -32,7 +32,7 @@ <string>Send money</string> </property> <property name="icon"> - <iconset resource="../../../../res/icons/icons.qrc"> + <iconset resource="../../../../../res/icons/icons.qrc"> <normaloff>:/icons/payment_icon</normaloff>:/icons/payment_icon</iconset> </property> <property name="iconSize"> @@ -49,7 +49,7 @@ <string>Certification</string> </property> <property name="icon"> - <iconset resource="../../../../res/icons/icons.qrc"> + <iconset resource="../../../../../res/icons/icons.qrc"> <normaloff>:/icons/certification_icon</normaloff>:/icons/certification_icon</iconset> </property> <property name="iconSize"> @@ -66,7 +66,7 @@ <string>Renew membership</string> </property> <property name="icon"> - <iconset resource="../../../../res/icons/icons.qrc"> + <iconset resource="../../../../../res/icons/icons.qrc"> <normaloff>:/icons/renew_membership</normaloff>:/icons/renew_membership</iconset> </property> <property name="iconSize"> @@ -96,7 +96,7 @@ <string/> </property> <property name="icon"> - <iconset resource="../../../../res/icons/icons.qrc"> + <iconset resource="../../../../../res/icons/icons.qrc"> <normaloff>:/icons/menu_icon</normaloff>:/icons/menu_icon</iconset> </property> <property name="iconSize"> @@ -119,7 +119,7 @@ </layout> </widget> <resources> - <include location="../../../../res/icons/icons.qrc"/> + <include location="../../../../../res/icons/icons.qrc"/> </resources> <connections/> </ui> diff --git a/src/sakia/gui/main_window/toolbar/view.py b/src/sakia/gui/main_window/toolbar/view.py index 3ab97e60650df9028820fe926718275ebed0f11a..6d50549cce52df4084dc064e5fec4d296d2ad3ce 100644 --- a/src/sakia/gui/main_window/toolbar/view.py +++ b/src/sakia/gui/main_window/toolbar/view.py @@ -18,15 +18,14 @@ class ToolbarView(QFrame, Ui_SakiaToolbar): tool_menu = QMenu(self.tr("Tools"), self.toolbutton_menu) self.toolbutton_menu.setMenu(tool_menu) - self.action_publish_uid = QAction(self.tr(ToolbarView._action_publish_uid_text), self) self.action_revoke_uid = QAction(self.tr(ToolbarView._action_revoke_uid_text), self) - tool_menu.addAction(self.action_publish_uid) tool_menu.addAction(self.action_revoke_uid) - menu_options = QMenu(self.tr("Options"), self.toolbutton_menu) - self.action_add_connection = QAction(self.tr("Add a connection"), menu_options) - menu_options.addAction(self.action_add_connection) - tool_menu.addMenu(menu_options) + self.action_add_connection = QAction(self.tr("Add a connection"), tool_menu) + tool_menu.addAction(self.action_add_connection) + + self.action_parameters = QAction(self.tr("Settings"), tool_menu) + tool_menu.addAction(self.action_parameters) self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Minimum) self.setMaximumHeight(60) diff --git a/src/sakia/gui/navigation/controller.py b/src/sakia/gui/navigation/controller.py index d40cac944b4e2b3a1c85670cb61bbcf3b1824950..c4c7952ba99e81242af75d331d33a491f0e664cd 100644 --- a/src/sakia/gui/navigation/controller.py +++ b/src/sakia/gui/navigation/controller.py @@ -13,7 +13,8 @@ from PyQt5.QtWidgets import QMenu, QAction, QMessageBox, QDialog from PyQt5.QtGui import QCursor from sakia.decorators import asyncify from sakia.gui.password_asker import PasswordAskerDialog -from sakia.gui.widgets.dialogs import QAsyncFileDialog, dialog_async_exec +from sakia.gui.widgets.dialogs import QAsyncFileDialog, QAsyncMessageBox +from sakia.gui.widgets import toast class NavigationController(QObject): @@ -109,9 +110,66 @@ class NavigationController(QObject): action_gen_revokation.triggered.connect(lambda c: self.action_save_revokation(raw_data['misc']['connection'])) + action_publish_uid = QAction(self.tr("Publish UID"), menu) + menu.addAction(action_publish_uid) + action_publish_uid.triggered.connect(lambda c: + self.publish_uid(raw_data['misc']['connection'])) + action_publish_uid.setEnabled(self.model.identity_published(raw_data['misc']['connection'])) + + # Show the context menu. + action_publish_uid = QAction(self.tr("Leave the currency"), menu) + menu.addAction(action_publish_uid) + action_publish_uid.triggered.connect(lambda c: + self.leave_currency(raw_data['misc']['connection'])) + action_publish_uid.setEnabled(self.model.identity_is_member(raw_data['misc']['connection'])) # Show the context menu. + menu.popup(QCursor.pos()) + @asyncify + async def publish_uid(self, connection): + password = await self.password_asker.async_exec() + if self.password_asker.result() == QDialog.Rejected: + return + result = await self.account.send_selfcert(password, self.community) + if result[0]: + if self.app.preferences['notifications']: + toast.display(self.tr("UID"), self.tr("Success publishing your UID")) + else: + await QAsyncMessageBox.information(self, self.tr("Membership"), + self.tr("Success publishing your UID")) + else: + if self.app.preferences['notifications']: + toast.display(self.tr("UID"), result[1]) + else: + await QAsyncMessageBox.critical(self, self.tr("UID"), + result[1]) + + @asyncify + async def send_leave(self): + reply = await QAsyncMessageBox.warning(self, self.tr("Warning"), + self.tr("""Are you sure ? +Sending a leaving demand cannot be canceled. +The process to join back the community later will have to be done again.""") + .format(self.account.pubkey), QMessageBox.Ok | QMessageBox.Cancel) + if reply == QMessageBox.Ok: + password = PasswordAskerDialog(self.model.navigation_model.navigation.current_connection()).async_exec() + if not password: + return + result = await self.model.send_leave(password) + if result[0]: + if self.app.preferences['notifications']: + toast.display(self.tr("Revoke"), self.tr("Success sending Revoke demand")) + else: + await QAsyncMessageBox.information(self, self.tr("Revoke"), + self.tr("Success sending Revoke demand")) + else: + if self.app.preferences['notifications']: + toast.display(self.tr("Revoke"), result[1]) + else: + await QAsyncMessageBox.critical(self, self.tr("Revoke"), + result[1]) + def action_save_revokation(self, connection): password = PasswordAskerDialog(connection).exec() if not password: diff --git a/src/sakia/gui/navigation/model.py b/src/sakia/gui/navigation/model.py index b6424d1215fd6dea06f6c944e231adc8c8255f2c..0fbbc117009270641641cb0652be3407db221eaf 100644 --- a/src/sakia/gui/navigation/model.py +++ b/src/sakia/gui/navigation/model.py @@ -126,4 +126,15 @@ class NavigationModel(QObject): return None def generate_revokation(self, connection, password): - return self.app.documents_service.generate_revokation(connection, password) \ No newline at end of file + return self.app.documents_service.generate_revokation(connection, password) + + def identity_published(self, connection): + identities_services = self.app.identities_services[connection.currency] + return identities_services.get_identity(connection.pubkey, connection.uid).written_on != 0 + + def identity_is_member(self, connection): + identities_services = self.app.identities_services[connection.currency] + return identities_services.get_identity(connection.pubkey, connection.uid).member + + async def send_leave(self, connection, password): + return await self.app.documents_service.send_membership(connection, password, "OUT") diff --git a/src/sakia/services/documents.py b/src/sakia/services/documents.py index 428860d732f4217fd12abfea77c096227c384a67..dbfb4855deee719379a2d1d158f90c13b3c92adc 100644 --- a/src/sakia/services/documents.py +++ b/src/sakia/services/documents.py @@ -93,7 +93,7 @@ class DocumentsService: return result, identity - async def send_membership(self, currency, identity, salt, password, mstype): + async def send_membership(self, connection, password, mstype): """ Send a membership document to a target community. Signal "document_broadcasted" is emitted at the end.