Batch transaction notifications, add RPC console filtering, macOS autostart

Prevent UI freezes during sync by batching wallet transaction notifications
with a 250ms debounce timer and full-refresh fallback for large batches.
Disable dynamic sorting and view updates on overview/transaction pages while
syncing. Add request/reply/error filter checkboxes to the RPC console with
in-memory message store. Implement macOS LaunchAgents-based autostart.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-26 17:51:13 -07:00
parent 5701545f0d
commit 20151a2248
12 changed files with 488 additions and 26 deletions
+57
View File
@@ -1085,6 +1085,63 @@ QPushButton:!enabled {
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="filterLayout">
<property name="spacing">
<number>12</number>
</property>
<item>
<widget class="QLabel" name="showLabel">
<property name="text">
<string>Show:</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showRequestsCheckBox">
<property name="text">
<string>Requests</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showRepliesCheckBox">
<property name="text">
<string>Replies</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showErrorsCheckBox">
<property name="text">
<string>Errors</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacerFilters">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
+90
View File
@@ -405,6 +405,96 @@ bool SetStartOnSystemStartup(bool fAutoStart)
}
return true;
}
#elif defined(Q_OS_MAC) || defined(MAC_OSX) || defined(__APPLE__)
boost::filesystem::path static GetLaunchAgentsDir()
{
const QString homeDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
if (homeDir.isEmpty())
return boost::filesystem::path();
return boost::filesystem::path(homeDir.toStdString()) / "Library" / "LaunchAgents";
}
boost::filesystem::path static GetAutostartFilePath()
{
return GetLaunchAgentsDir() / "org.triangles.triangles-qt.plist";
}
static std::string PlistEscape(const std::string& value)
{
std::string escaped;
escaped.reserve(value.size());
for (std::string::const_iterator it = value.begin(); it != value.end(); ++it)
{
switch (*it)
{
case '&': escaped += "&amp;"; break;
case '<': escaped += "&lt;"; break;
case '>': escaped += "&gt;"; break;
case '"': escaped += "&quot;"; break;
case '\'': escaped += "&apos;"; break;
default: escaped += *it; break;
}
}
return escaped;
}
bool GetStartOnSystemStartup()
{
boost::filesystem::ifstream optionFile(GetAutostartFilePath());
if (!optionFile.good())
return false;
std::string contents;
std::string line;
while (getline(optionFile, line))
contents += line;
optionFile.close();
return contents.find("<key>RunAtLoad</key>") != std::string::npos &&
contents.find("<true/>") != std::string::npos &&
contents.find("<string>-min</string>") != std::string::npos;
}
bool SetStartOnSystemStartup(bool fAutoStart)
{
if (!fAutoStart)
return !boost::filesystem::exists(GetAutostartFilePath()) || boost::filesystem::remove(GetAutostartFilePath());
const QString exePath = QApplication::applicationFilePath();
if (exePath.isEmpty())
return false;
const QString workingDir = QFileInfo(exePath).absolutePath();
boost::filesystem::create_directories(GetLaunchAgentsDir());
boost::filesystem::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out|std::ios_base::trunc);
if (!optionFile.good())
return false;
optionFile
<< "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
<< "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
<< "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
<< "<plist version=\"1.0\">\n"
<< "<dict>\n"
<< " <key>Label</key>\n"
<< " <string>org.triangles.triangles-qt</string>\n"
<< " <key>ProgramArguments</key>\n"
<< " <array>\n"
<< " <string>" << PlistEscape(exePath.toStdString()) << "</string>\n"
<< " <string>-min</string>\n"
<< " </array>\n"
<< " <key>RunAtLoad</key>\n"
<< " <true/>\n"
<< " <key>WorkingDirectory</key>\n"
<< " <string>" << PlistEscape(workingDir.toStdString()) << "</string>\n"
<< "</dict>\n"
<< "</plist>\n";
optionFile.close();
return optionFile.good();
}
#else
// TODO: OSX startup stuff; see:
+20
View File
@@ -92,6 +92,7 @@ OverviewPage::OverviewPage(QWidget *parent) :
currentStake(0),
currentUnconfirmedBalance(-1),
currentImmatureBalance(-1),
walletTransactionSyncing(false),
txdelegate(new TxViewDelegate()),
filter(0)
{
@@ -166,8 +167,10 @@ void OverviewPage::setModel(WalletModel *model)
// Keep up to date with wallet
setBalance(model->getBalance(), model->getStake(), model->getUnconfirmedBalance(), model->getImmatureBalance());
connect(model, SIGNAL(balanceChanged(qint64, qint64, qint64, qint64)), this, SLOT(setBalance(qint64, qint64, qint64, qint64)));
connect(model, SIGNAL(transactionSyncStateChanged(bool)), this, SLOT(setTransactionSyncState(bool)));
connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
setTransactionSyncState(model->isTransactionSyncing());
}
// update the display unit, to not use the default ("TRI")
@@ -188,6 +191,23 @@ void OverviewPage::updateDisplayUnit()
}
}
void OverviewPage::setTransactionSyncState(bool syncing)
{
walletTransactionSyncing = syncing;
if (!filter)
return;
filter->setDynamicSortFilter(!syncing);
ui->listTransactions->setUpdatesEnabled(!syncing);
if (!syncing)
{
filter->invalidate();
filter->sort(TransactionTableModel::Status, Qt::DescendingOrder);
ui->listTransactions->viewport()->update();
}
}
void OverviewPage::showOutOfSyncWarning(bool fShow)
{
ui->labelWalletStatus->setVisible(fShow);
+2
View File
@@ -30,6 +30,7 @@ public:
public slots:
void setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBalance, qint64 immatureBalance);
void setTransactionSyncState(bool syncing);
signals:
void transactionClicked(const QModelIndex &index);
@@ -42,6 +43,7 @@ private:
qint64 currentStake;
qint64 currentUnconfirmedBalance;
qint64 currentImmatureBalance;
bool walletTransactionSyncing;
TxViewDelegate *txdelegate;
TransactionFilterProxy *filter;
+93 -17
View File
@@ -17,9 +17,6 @@
#include <openssl/crypto.h>
// TODO: make it possible to filter out categories (esp debug messages when implemented)
// TODO: receive errors and debug messages through ClientModel
const int CONSOLE_SCROLLBACK = 50;
const int CONSOLE_HISTORY = 50;
@@ -190,6 +187,7 @@ void RPCExecutor::request(const QString &command)
RPCConsole::RPCConsole(QWidget *parent) :
QDialog(parent),
ui(new Ui::RPCConsole),
clientModel(0),
historyPtr(0)
{
ui->setupUi(this);
@@ -259,12 +257,16 @@ bool RPCConsole::eventFilter(QObject* obj, QEvent *event)
void RPCConsole::setClientModel(ClientModel *model)
{
if (clientModel)
disconnect(clientModel, 0, this, 0);
this->clientModel = model;
if(model)
{
// Subscribe to information, replies, messages, errors
connect(model, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int)));
connect(model, SIGNAL(numBlocksChanged(int,int)), this, SLOT(setNumBlocks(int,int)));
connect(model, SIGNAL(error(QString,QString,bool)), this, SLOT(showClientError(QString,QString,bool)));
// Provide initial values
ui->clientVersion->setText(model->formatFullVersion());
@@ -285,14 +287,16 @@ static QString categoryClass(int category)
{
case RPCConsole::CMD_REQUEST: return "cmd-request"; break;
case RPCConsole::CMD_REPLY: return "cmd-reply"; break;
case RPCConsole::CMD_ERROR: return "cmd-error"; break;
case RPCConsole::CMD_ERROR:
case RPCConsole::MC_ERROR: return "cmd-error"; break;
default: return "misc";
}
}
void RPCConsole::clear()
{
ui->messagesWidget->clear();
consoleEntries.clear();
refreshMessages();
ui->lineEdit->clear();
ui->lineEdit->setFocus();
@@ -323,18 +327,21 @@ void RPCConsole::clear()
void RPCConsole::message(int category, const QString &message, bool html)
{
QTime time = QTime::currentTime();
QString timeString = time.toString();
QString out;
out += "<table><tr><td class=\"time\" width=\"65\">" + timeString + "</td>";
out += "<td class=\"icon\" width=\"32\"><img src=\"" + categoryClass(category) + "\"></td>";
out += "<td class=\"message " + categoryClass(category) + "\" valign=\"middle\">";
if(html)
out += message;
else
out += GUIUtil::HtmlEscape(message, true);
out += "</td></tr></table>";
ui->messagesWidget->append(out);
ConsoleEntry entry;
entry.category = category;
entry.text = message;
entry.time = QTime::currentTime().toString();
entry.html = html;
consoleEntries.append(entry);
while (consoleEntries.size() > CONSOLE_SCROLLBACK)
consoleEntries.removeFirst();
if (categoryVisible(category))
{
ui->messagesWidget->append(formatEntry(entry));
scrollToEnd();
}
}
void RPCConsole::setNumConnections(int count)
@@ -439,3 +446,72 @@ void RPCConsole::on_showCLOptionsButton_clicked()
GUIUtil::HelpMessageBox help;
help.exec();
}
bool RPCConsole::categoryVisible(int category) const
{
switch (category)
{
case CMD_REQUEST:
return ui->showRequestsCheckBox->isChecked();
case CMD_ERROR:
case MC_ERROR:
return ui->showErrorsCheckBox->isChecked();
case CMD_REPLY:
case MC_DEBUG:
default:
return ui->showRepliesCheckBox->isChecked();
}
}
QString RPCConsole::formatEntry(const ConsoleEntry &entry) const
{
QString out;
out += "<table><tr><td class=\"time\" width=\"65\">" + entry.time + "</td>";
out += "<td class=\"icon\" width=\"32\"><img src=\"" + categoryClass(entry.category) + "\"></td>";
out += "<td class=\"message " + categoryClass(entry.category) + "\" valign=\"middle\">";
if (entry.html)
out += entry.text;
else
out += GUIUtil::HtmlEscape(entry.text, true);
out += "</td></tr></table>";
return out;
}
void RPCConsole::refreshMessages()
{
ui->messagesWidget->clear();
for (QList<ConsoleEntry>::const_iterator it = consoleEntries.begin(); it != consoleEntries.end(); ++it)
{
if (categoryVisible(it->category))
ui->messagesWidget->append(formatEntry(*it));
}
scrollToEnd();
}
void RPCConsole::on_showRequestsCheckBox_toggled(bool checked)
{
Q_UNUSED(checked);
refreshMessages();
}
void RPCConsole::on_showRepliesCheckBox_toggled(bool checked)
{
Q_UNUSED(checked);
refreshMessages();
}
void RPCConsole::on_showErrorsCheckBox_toggled(bool checked)
{
Q_UNUSED(checked);
refreshMessages();
}
void RPCConsole::showClientError(const QString &title, const QString &message, bool modal)
{
Q_UNUSED(modal);
if (title.isEmpty())
this->message(MC_ERROR, message);
else
this->message(MC_ERROR, title + ": " + message);
}
+16
View File
@@ -37,6 +37,10 @@ private slots:
void on_openDebugLogfileButton_clicked();
/** display messagebox with program parameters (same as triangles-qt --help) */
void on_showCLOptionsButton_clicked();
void on_showRequestsCheckBox_toggled(bool checked);
void on_showRepliesCheckBox_toggled(bool checked);
void on_showErrorsCheckBox_toggled(bool checked);
void showClientError(const QString &title, const QString &message, bool modal);
public slots:
void clear();
@@ -55,11 +59,23 @@ signals:
void cmdRequest(const QString &command);
private:
struct ConsoleEntry
{
int category;
QString text;
QString time;
bool html;
};
Ui::RPCConsole *ui;
ClientModel *clientModel;
QStringList history;
QList<ConsoleEntry> consoleEntries;
int historyPtr;
bool categoryVisible(int category) const;
QString formatEntry(const ConsoleEntry &entry) const;
void refreshMessages();
void startExecutor();
};
+32 -2
View File
@@ -34,7 +34,10 @@ TransactionView::TransactionView(QWidget *parent) :
QWidget(parent), model(0), transactionProxyModel(0),
ui(new Ui::TransactionsPage),
transactionView(0),
transactionsSortOrderDown(true)
transactionsSortOrderDown(true),
walletTransactionSyncing(false),
transactionSortColumn(TransactionTableModel::Status),
transactionSortOrder(Qt::DescendingOrder)
{
ui->setupUi(this);
setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint | Qt::Window);
@@ -150,7 +153,7 @@ void TransactionView::setModel(WalletModel *model)
transactionView->setSelectionBehavior(QAbstractItemView::SelectRows);
transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
transactionView->setSortingEnabled(true);
transactionView->sortByColumn(TransactionTableModel::Status, Qt::DescendingOrder);
transactionView->sortByColumn(transactionSortColumn, transactionSortOrder);
transactionView->verticalHeader()->hide();
transactionView->horizontalHeader()->resizeSection(TransactionTableModel::Status, 23);
@@ -162,6 +165,9 @@ void TransactionView::setModel(WalletModel *model)
transactionView->horizontalHeader()->setSectionResizeMode(TransactionTableModel::ToAddress, QHeaderView::Stretch);
#endif
transactionView->horizontalHeader()->resizeSection(TransactionTableModel::Amount, 100);
connect(model, SIGNAL(transactionSyncStateChanged(bool)), this, SLOT(setTransactionSyncState(bool)));
setTransactionSyncState(model->isTransactionSyncing());
}
}
@@ -272,6 +278,30 @@ void TransactionView::exportClicked()
}
}
void TransactionView::setTransactionSyncState(bool syncing)
{
walletTransactionSyncing = syncing;
if (!transactionProxyModel || !transactionView)
return;
if (syncing)
{
transactionSortColumn = transactionView->horizontalHeader()->sortIndicatorSection();
transactionSortOrder = transactionView->horizontalHeader()->sortIndicatorOrder();
transactionProxyModel->setDynamicSortFilter(false);
transactionView->setSortingEnabled(false);
transactionView->setUpdatesEnabled(false);
return;
}
transactionProxyModel->setDynamicSortFilter(true);
transactionView->setUpdatesEnabled(true);
transactionView->setSortingEnabled(true);
transactionProxyModel->invalidate();
transactionView->sortByColumn(transactionSortColumn, transactionSortOrder);
transactionView->viewport()->update();
}
void TransactionView::contextualMenu(const QPoint &point)
{
QModelIndex index = transactionView->indexAt(point);
+5 -1
View File
@@ -48,9 +48,12 @@ public:
private:
WalletModel *model;
TransactionFilterProxy *transactionProxyModel;
Ui::TransactionsPage *ui;
Ui::TransactionsPage *ui;
QTableView *transactionView;
bool transactionsSortOrderDown;
bool walletTransactionSyncing;
int transactionSortColumn;
Qt::SortOrder transactionSortOrder;
QComboBox *dateWidget;
QComboBox *typeWidget;
@@ -74,6 +77,7 @@ private slots:
void copyLabel();
void copyAmount();
void copyTxID();
void setTransactionSyncState(bool syncing);
signals:
void doubleClicked(const QModelIndex&);
+11 -1
View File
@@ -106,7 +106,8 @@ TrianglesGUI::TrianglesGUI(bool fIsTestnet, QWidget *parent):
trayIcon(0),
notificator(0),
rpcConsole(0),
prevBlocks(0)
prevBlocks(0),
walletTransactionSyncing(false)
{
ui->setupUi(this);
@@ -592,6 +593,8 @@ void TrianglesGUI::setWalletModel(WalletModel *walletModel)
setEncryptionStatus(walletModel->getEncryptionStatus());
connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int)));
connect(walletModel, SIGNAL(transactionSyncStateChanged(bool)), this, SLOT(setWalletTransactionSyncState(bool)));
setWalletTransactionSyncState(walletModel->isTransactionSyncing());
// Balloon pop-up for new transaction
connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
@@ -1039,6 +1042,8 @@ void TrianglesGUI::incomingTransaction(const QModelIndex & parent, int start, in
{
if(!walletModel || !clientModel)
return;
if(walletTransactionSyncing)
return;
TransactionTableModel *ttm = walletModel->getTransactionTableModel();
qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent)
.data(Qt::EditRole).toULongLong();
@@ -1070,6 +1075,11 @@ void TrianglesGUI::incomingTransaction(const QModelIndex & parent, int start, in
}
}
void TrianglesGUI::setWalletTransactionSyncState(bool syncing)
{
walletTransactionSyncing = syncing;
}
void TrianglesGUI::incomingMessage(const QModelIndex & parent, int start, int end)
{
if(!messageModel)
+2
View File
@@ -143,6 +143,7 @@ private:
QMovie *syncIconMovie;
/** Keep track of previous number of blocks, to detect progress */
int prevBlocks;
bool walletTransactionSyncing;
/** Create the main UI actions. */
void createActions(bool fIsTestnet);
@@ -169,6 +170,7 @@ public slots:
@see WalletModel::EncryptionStatus
*/
void setEncryptionStatus(int status);
void setWalletTransactionSyncState(bool syncing);
/** Notify the user of an error in the network or transaction handling code. */
void error(const QString &title, const QString &message, bool modal);
+141 -5
View File
@@ -8,9 +8,15 @@
#include "wallet.h"
#include "walletdb.h" // for BackupWallet
#include "base58.h"
#include "main.h"
#include <QSet>
#include <QTimer>
#include <QMutexLocker>
static const int MODEL_UPDATE_BATCH_THRESHOLD = 128;
static const int MODEL_UPDATE_BATCH_DELAY_MS = 250;
static const int MODEL_FULL_REFRESH_MIN_INTERVAL_MS = 1500;
WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *parent) :
QObject(parent), wallet(wallet), optionsModel(optionsModel), addressTableModel(0),
@@ -18,7 +24,12 @@ WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *p
cachedBalance(0), cachedStake(0), cachedUnconfirmedBalance(0), cachedImmatureBalance(0),
cachedNumTransactions(0),
cachedEncryptionStatus(Unencrypted),
cachedNumBlocks(0)
cachedNumBlocks(0),
transactionNotificationFlushQueued(false),
fullTransactionRefreshQueued(false),
transactionSyncing(false),
transactionNotificationTimer(0),
lastFullTransactionRefreshTime(0)
{
addressTableModel = new AddressTableModel(wallet, this);
transactionTableModel = new TransactionTableModel(wallet, this);
@@ -28,6 +39,10 @@ WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *p
connect(pollTimer, SIGNAL(timeout()), this, SLOT(pollBalanceChanged()));
pollTimer->start(MODEL_UPDATE_DELAY);
transactionNotificationTimer = new QTimer(this);
transactionNotificationTimer->setSingleShot(true);
connect(transactionNotificationTimer, SIGNAL(timeout()), this, SLOT(flushTransactionNotifications()));
subscribeToCoreSignals();
}
@@ -66,6 +81,11 @@ int WalletModel::getNumTransactions() const
return numTransactions;
}
bool WalletModel::isTransactionSyncing() const
{
return transactionSyncing;
}
void WalletModel::updateStatus()
{
EncryptionStatus newEncryptionStatus = getEncryptionStatus();
@@ -108,8 +128,126 @@ bool WalletModel::checkBalanceChanged()
void WalletModel::updateTransaction(const QString &hash, int status)
{
queueTransactionUpdate(hash, status);
}
void WalletModel::queueTransactionUpdate(const QString &hash, int status)
{
bool shouldScheduleFlush = false;
{
QMutexLocker locker(&transactionNotificationMutex);
int mergedStatus = status;
QMap<QString, int>::iterator it = queuedTransactionNotifications.find(hash);
if (it != queuedTransactionNotifications.end())
{
// Preserve insert/delete semantics when multiple updates arrive
// for the same transaction before the UI thread drains the queue.
if (it.value() == CT_DELETED || status == CT_DELETED)
mergedStatus = CT_DELETED;
else if (it.value() == CT_NEW || status == CT_NEW)
mergedStatus = CT_NEW;
else
mergedStatus = CT_UPDATED;
it.value() = mergedStatus;
}
else
{
queuedTransactionNotifications.insert(hash, mergedStatus);
}
if (IsInitialBlockDownload() || queuedTransactionNotifications.size() >= MODEL_UPDATE_BATCH_THRESHOLD)
fullTransactionRefreshQueued = true;
if (!transactionNotificationFlushQueued)
{
transactionNotificationFlushQueued = true;
shouldScheduleFlush = true;
}
}
if (shouldScheduleFlush)
QMetaObject::invokeMethod(this, "startTransactionNotificationTimer", Qt::QueuedConnection);
}
void WalletModel::startTransactionNotificationTimer()
{
if (transactionNotificationTimer)
transactionNotificationTimer->start(MODEL_UPDATE_BATCH_DELAY_MS);
}
void WalletModel::flushTransactionNotifications()
{
QMap<QString, int> pendingNotifications;
bool refreshAll = false;
bool shouldRescheduleRefresh = false;
{
QMutexLocker locker(&transactionNotificationMutex);
pendingNotifications.swap(queuedTransactionNotifications);
refreshAll = fullTransactionRefreshQueued;
fullTransactionRefreshQueued = false;
transactionNotificationFlushQueued = false;
}
const bool shouldSync = refreshAll ||
IsInitialBlockDownload() ||
pendingNotifications.size() >= MODEL_UPDATE_BATCH_THRESHOLD;
if (transactionSyncing != shouldSync)
{
transactionSyncing = shouldSync;
emit transactionSyncStateChanged(transactionSyncing);
}
if(transactionTableModel)
transactionTableModel->updateTransaction(hash, status);
{
if (refreshAll)
{
const qint64 now = GetTimeMillis();
if (transactionSyncing &&
lastFullTransactionRefreshTime != 0 &&
now - lastFullTransactionRefreshTime < MODEL_FULL_REFRESH_MIN_INTERVAL_MS)
{
QMutexLocker locker(&transactionNotificationMutex);
fullTransactionRefreshQueued = true;
if (!transactionNotificationFlushQueued)
{
transactionNotificationFlushQueued = true;
shouldRescheduleRefresh = true;
}
}
else
{
transactionTableModel->refreshWallet();
lastFullTransactionRefreshTime = now;
}
}
else
{
for (QMap<QString, int>::const_iterator it = pendingNotifications.begin(); it != pendingNotifications.end(); ++it)
transactionTableModel->updateTransaction(it.key(), it.value());
}
}
if (shouldRescheduleRefresh && transactionNotificationTimer)
transactionNotificationTimer->start(MODEL_FULL_REFRESH_MIN_INTERVAL_MS);
bool stillPending = false;
{
QMutexLocker locker(&transactionNotificationMutex);
stillPending = transactionNotificationFlushQueued || !queuedTransactionNotifications.isEmpty();
}
if (!transactionSyncing && pendingNotifications.size() >= MODEL_UPDATE_BATCH_THRESHOLD)
{
transactionSyncing = true;
emit transactionSyncStateChanged(true);
}
if (transactionSyncing && !IsInitialBlockDownload() && !stillPending && pendingNotifications.size() < MODEL_UPDATE_BATCH_THRESHOLD)
{
transactionSyncing = false;
emit transactionSyncStateChanged(false);
}
// Don't call checkBalanceChanged() here - it does LOCK(cs_wallet) + iterates
// all wallet transactions, blocking the UI thread. The pollBalanceChanged()
@@ -392,9 +530,7 @@ static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet,
static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet, const uint256 &hash, ChangeType status)
{
OutputDebugStringF("NotifyTransactionChanged %s status=%i\n", hash.GetHex().c_str(), status);
QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection,
Q_ARG(QString, QString::fromStdString(hash.GetHex())),
Q_ARG(int, status));
walletmodel->queueTransactionUpdate(QString::fromStdString(hash.GetHex()), status);
}
void WalletModel::subscribeToCoreSignals()
+19
View File
@@ -4,6 +4,8 @@
#include <QObject>
#include <vector>
#include <map>
#include <QMap>
#include <QMutex>
#include "allocators.h" /* for SecureString */
@@ -72,6 +74,7 @@ public:
qint64 getImmatureBalance() const;
int getNumTransactions() const;
EncryptionStatus getEncryptionStatus() const;
bool isTransactionSyncing() const;
// Check address for validity
bool validateAddress(const QString &address);
@@ -160,10 +163,16 @@ public slots:
void updateStatus();
/* New transaction, or transaction changed status */
void updateTransaction(const QString &hash, int status);
/* Queue a transaction update from a core thread without touching the UI directly */
void queueTransactionUpdate(const QString &hash, int status);
/* New, updated or removed address book entry */
void updateAddressBook(const QString &address, const QString &label, bool isMine, int status);
/* Current, immature or unconfirmed balance might have changed - emit 'balanceChanged' if so */
void pollBalanceChanged();
/* Start or restart the deferred transaction notification flush timer */
void startTransactionNotificationTimer();
/* Flush queued transaction notifications from core threads */
void flushTransactionNotifications();
signals:
// Signal that balance in wallet changed
@@ -182,6 +191,16 @@ signals:
// Asynchronous error notification
void error(const QString &title, const QString &message, bool modal);
void transactionSyncStateChanged(bool syncing);
private:
QMutex transactionNotificationMutex;
QMap<QString, int> queuedTransactionNotifications;
bool transactionNotificationFlushQueued;
bool fullTransactionRefreshQueued;
bool transactionSyncing;
QTimer *transactionNotificationTimer;
qint64 lastFullTransactionRefreshTime;
};