diff --git a/src/sakia/gui/navigation/txhistory/controller.py b/src/sakia/gui/navigation/txhistory/controller.py index e064f5023b18e76c98bc2cd833c408c81d92a589..e52965be7245aa29e143f3c0a275214c1e2e2613 100644 --- a/src/sakia/gui/navigation/txhistory/controller.py +++ b/src/sakia/gui/navigation/txhistory/controller.py @@ -1,3 +1,4 @@ +import asyncio import logging from PyQt5.QtCore import QTime, pyqtSignal, QObject, QDateTime @@ -39,6 +40,7 @@ class TxHistoryController(QObject): self.view.table_history.customContextMenuRequested["QPoint"].connect( self.history_context_menu ) + self.view.button_refresh.clicked.connect(self.refresh_from_network) self.refresh() @classmethod @@ -172,3 +174,36 @@ class TxHistoryController(QObject): # refresh self.refresh_balance() self.refresh_pages() + + @asyncify + async def refresh_from_network(self, _): + """ + Update tx history from network for the selected date period + :return: + """ + self._logger.debug("Manually refresh tx history...") + + pubkey = self.model.connection.pubkey + + ( + changed_tx, + new_tx + ) = await self.model.transactions_service.update_transactions_history(pubkey, + self.view.table_history.model().ts_from, + self.view.table_history.model().ts_to) + for tx in changed_tx: + self.model.app.transaction_state_changed.emit(tx) + + for tx in new_tx: + self.model.app.new_transfer.emit(self.model.connection, tx) + + new_dividends = await self.model.transactions_service.update_dividends_history( + pubkey, + self.view.table_history.model().ts_from, + self.view.table_history.model().ts_to, + new_tx + ) + self._logger.debug("Found {} new dividends".format(len(new_dividends))) + + for ud in new_dividends: + self.app.new_dividend.emit(self.model.connection, ud) diff --git a/src/sakia/gui/navigation/txhistory/txhistory.ui b/src/sakia/gui/navigation/txhistory/txhistory.ui index 85bf1bb5513d49be8e83ef3099f05b4e7bd4dbea..9d00b07f0f32b31c5b60af82682acc725a7c3297 100644 --- a/src/sakia/gui/navigation/txhistory/txhistory.ui +++ b/src/sakia/gui/navigation/txhistory/txhistory.ui @@ -120,6 +120,9 @@ <layout class="QVBoxLayout" name="verticalLayout_3"> <item> <layout class="QHBoxLayout" name="horizontalLayout_2"> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> <property name="topMargin"> <number>5</number> </property> @@ -143,6 +146,29 @@ </property> </widget> </item> + <item> + <widget class="QPushButton" name="button_refresh"> + <property name="maximumSize"> + <size> + <width>30</width> + <height>30</height> + </size> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../../../../../res/icons/sakia.icons.qrc"> + <normaloff>:/icons/refresh_icon</normaloff>:/icons/refresh_icon</iconset> + </property> + <property name="iconSize"> + <size> + <width>16</width> + <height>16</height> + </size> + </property> + </widget> + </item> </layout> </item> <item> diff --git a/src/sakia/services/transactions.py b/src/sakia/services/transactions.py index ca2c91f59c47762a0b03b94053c1a25bc5fab22f..e32b6707861abd6016b9764ad902adb88965a28c 100644 --- a/src/sakia/services/transactions.py +++ b/src/sakia/services/transactions.py @@ -1,3 +1,5 @@ +from datetime import datetime + from PyQt5.QtCore import QObject from sakia.data.entities.transaction import ( parse_transaction_doc, @@ -9,7 +11,6 @@ from duniterpy.documents import SimpleTransaction, Block from sakia.data.entities import Dividend from duniterpy.api import bma import logging -import sqlite3 class TransactionsService(QObject): @@ -91,7 +92,8 @@ class TransactionsService(QObject): async def parse_transactions_history(self, connections, start, end): """ - Request transactions from the network to initialize data for a given pubkey + Request transactions from the network to add data for given connections + :param List[sakia.data.entities.Connection] connections: the list of connections found by tx parsing :param int start: the first block :param int end: the last block @@ -140,7 +142,8 @@ class TransactionsService(QObject): async def parse_dividends_history(self, connections, start, end, transactions): """ - Request transactions from the network to initialize data for a given pubkey + Request transactions from the network to add data for a given connections + :param List[sakia.data.entities.Connection] connections: the list of connections found by tx parsing :param List[duniterpy.documents.Block] blocks: the list of transactions found by tx parsing :param List[sakia.data.entities.Transaction] transactions: the list of transactions found by tx parsing @@ -201,6 +204,126 @@ class TransactionsService(QObject): dividends[connection].append(dividend) return dividends + async def update_transactions_history(self, pubkey, start, end): + """ + Request transactions from the network to update data for a given pubkey + + :param str pubkey: pubkey for tx history + :param int start: start timestamp + :param int end: end timestamp + """ + self._logger.debug("Manually refresh transactions...") + + transfers_changed = [] + new_transfers = [] + txid = 0 + history_data = await self._bma_connector.get( + self.currency, + bma.tx.times, + req_args={"pubkey": pubkey, "start": start, "end": end}, + ) + for tx_data in history_data["history"]["sent"]: + for tx in [ + t for t in self._transactions_processor.awaiting(self.currency) + ]: + if self._transactions_processor.run_state_transitions( + tx, tx_data["hash"], tx_data["block_number"] + ): + transfers_changed.append(tx) + self._logger.debug( + "New transaction validated : {0}".format(tx.sha_hash) + ) + for tx_data in history_data["history"]["received"]: + tx_doc = TransactionDoc.from_bma_history( + history_data["currency"], tx_data + ) + if not self._transactions_processor.find_by_hash( + pubkey, tx_doc.sha_hash + ) and SimpleTransaction.is_simple(tx_doc): + tx = parse_transaction_doc( + tx_doc, + pubkey, + tx_data["block_number"], + tx_data["time"], + txid, + ) + if tx: + new_transfers.append(tx) + self._transactions_processor.commit(tx) + else: + logging.debug("Error during transfer parsing") + return transfers_changed, new_transfers + + async def update_dividends_history(self, pubkey, start, end, transactions): + """ + Request transactions from the network to update data for a given pubkey + + :param str pubkey: pubkey for dividend history + :param int start: start timestamp + :param int end: end timestamp + :param List[sakia.data.entities.Transaction] transactions: the list of transactions found by tx parsing + """ + self._logger.debug("Manually refresh dividends...") + dividends = [] + # fixme: this request only returns non consumed UD. Should returns all UD created by pubkey in a time period. + # missing UD will result... Use another request asap ! + history_data = await self._bma_connector.get( + self.currency, bma.ud.history, req_args={"pubkey": pubkey} + ) + block_timestamps = [] + block_numbers = [] + for ud_data in history_data["history"]["history"]: + dividend = Dividend( + currency=self.currency, + pubkey=pubkey, + block_number=ud_data["block_number"], + timestamp=ud_data["time"], + amount=ud_data["amount"], + base=ud_data["base"], + ) + if start <= dividend.timestamp <= end: + self._logger.debug( + "Dividend from transaction input of block {0} ({1})".format(dividend.block_number, + datetime.fromtimestamp( + dividend.timestamp)) + ) + block_timestamps.append(dividend.timestamp) + block_numbers.append(dividend.block_number) + if self._dividends_processor.commit(dividend): + dividends.append(dividend) + + for tx in transactions: + txdoc = TransactionDoc.from_signed_raw(tx.raw) + for input in txdoc.inputs: + # For each dividends inputs, if it is consumed (not present in ud history) + if ( + input.source == "D" + and input.origin_id == pubkey + and input.index not in block_numbers + ): + block_data = await self._bma_connector.get( + self.currency, + bma.blockchain.block, + req_args={"number": input.index}, + ) + block = Block.from_signed_raw( + block_data["raw"] + block_data["signature"] + "\n" + ) + dividend = Dividend( + currency=self.currency, + pubkey=pubkey, + block_number=input.index, + timestamp=block.mediantime, + amount=block.ud, + base=block.unit_base, + ) + self._logger.debug( + "Dividend from transaction input of block {0} ({1})".format(dividend.block_number, datetime.fromtimestamp(dividend.timestamp)) + ) + if self._dividends_processor.commit(dividend): + dividends.append(dividend) + return dividends + def transfers(self, pubkey): """ Get all transfers from or to a given pubkey