qt: Move transaction notification to transaction table model
Move transaction new/update notification to TransactionTableModel. This moves the concerns to where they're actually handled. No need to bounce this through wallet model. - Do wallet transaction preprocessing on signal handler side; avoids locking cs_main/cs_wallet on notification in GUI thread (except for new transactions)
This commit is contained in:
parent
cd9114e513
commit
023e63df78
5 changed files with 160 additions and 111 deletions
|
@ -91,15 +91,9 @@ public:
|
||||||
|
|
||||||
Call with transaction that was added, removed or changed.
|
Call with transaction that was added, removed or changed.
|
||||||
*/
|
*/
|
||||||
void updateWallet(const uint256 &hash, int status)
|
void updateWallet(const uint256 &hash, int status, bool showTransaction)
|
||||||
{
|
{
|
||||||
qDebug() << "TransactionTablePriv::updateWallet : " + QString::fromStdString(hash.ToString()) + " " + QString::number(status);
|
qDebug() << "TransactionTablePriv::updateWallet : " + QString::fromStdString(hash.ToString()) + " " + QString::number(status);
|
||||||
{
|
|
||||||
LOCK2(cs_main, wallet->cs_wallet);
|
|
||||||
|
|
||||||
// Find transaction in wallet
|
|
||||||
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
|
|
||||||
bool inWallet = mi != wallet->mapWallet.end();
|
|
||||||
|
|
||||||
// Find bounds of this transaction in model
|
// Find bounds of this transaction in model
|
||||||
QList<TransactionRecord>::iterator lower = qLowerBound(
|
QList<TransactionRecord>::iterator lower = qLowerBound(
|
||||||
|
@ -110,9 +104,6 @@ public:
|
||||||
int upperIndex = (upper - cachedWallet.begin());
|
int upperIndex = (upper - cachedWallet.begin());
|
||||||
bool inModel = (lower != upper);
|
bool inModel = (lower != upper);
|
||||||
|
|
||||||
// Determine whether to show transaction or not
|
|
||||||
bool showTransaction = (inWallet && TransactionRecord::showTransaction(mi->second));
|
|
||||||
|
|
||||||
if(status == CT_UPDATED)
|
if(status == CT_UPDATED)
|
||||||
{
|
{
|
||||||
if(showTransaction && !inModel)
|
if(showTransaction && !inModel)
|
||||||
|
@ -121,7 +112,7 @@ public:
|
||||||
status = CT_DELETED; /* In model, but want to hide, treat as deleted */
|
status = CT_DELETED; /* In model, but want to hide, treat as deleted */
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << " inWallet=" + QString::number(inWallet) + " inModel=" + QString::number(inModel) +
|
qDebug() << " inModel=" + QString::number(inModel) +
|
||||||
" Index=" + QString::number(lowerIndex) + "-" + QString::number(upperIndex) +
|
" Index=" + QString::number(lowerIndex) + "-" + QString::number(upperIndex) +
|
||||||
" showTransaction=" + QString::number(showTransaction) + " derivedStatus=" + QString::number(status);
|
" showTransaction=" + QString::number(showTransaction) + " derivedStatus=" + QString::number(status);
|
||||||
|
|
||||||
|
@ -133,13 +124,16 @@ public:
|
||||||
qWarning() << "TransactionTablePriv::updateWallet : Warning: Got CT_NEW, but transaction is already in model";
|
qWarning() << "TransactionTablePriv::updateWallet : Warning: Got CT_NEW, but transaction is already in model";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(!inWallet)
|
if(showTransaction)
|
||||||
|
{
|
||||||
|
LOCK2(cs_main, wallet->cs_wallet);
|
||||||
|
// Find transaction in wallet
|
||||||
|
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
|
||||||
|
if(mi == wallet->mapWallet.end())
|
||||||
{
|
{
|
||||||
qWarning() << "TransactionTablePriv::updateWallet : Warning: Got CT_NEW, but transaction is not in wallet";
|
qWarning() << "TransactionTablePriv::updateWallet : Warning: Got CT_NEW, but transaction is not in wallet";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(showTransaction)
|
|
||||||
{
|
|
||||||
// Added -- insert at the right position
|
// Added -- insert at the right position
|
||||||
QList<TransactionRecord> toInsert =
|
QList<TransactionRecord> toInsert =
|
||||||
TransactionRecord::decomposeTransaction(wallet, mi->second);
|
TransactionRecord::decomposeTransaction(wallet, mi->second);
|
||||||
|
@ -173,7 +167,6 @@ public:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int size()
|
int size()
|
||||||
{
|
{
|
||||||
|
@ -230,16 +223,20 @@ TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *paren
|
||||||
QAbstractTableModel(parent),
|
QAbstractTableModel(parent),
|
||||||
wallet(wallet),
|
wallet(wallet),
|
||||||
walletModel(parent),
|
walletModel(parent),
|
||||||
priv(new TransactionTablePriv(wallet, this))
|
priv(new TransactionTablePriv(wallet, this)),
|
||||||
|
fProcessingQueuedTransactions(false)
|
||||||
{
|
{
|
||||||
columns << QString() << QString() << tr("Date") << tr("Type") << tr("Address") << BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
|
columns << QString() << QString() << tr("Date") << tr("Type") << tr("Address") << BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
|
||||||
priv->refreshWallet();
|
priv->refreshWallet();
|
||||||
|
|
||||||
connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
|
connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
|
||||||
|
|
||||||
|
subscribeToCoreSignals();
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionTableModel::~TransactionTableModel()
|
TransactionTableModel::~TransactionTableModel()
|
||||||
{
|
{
|
||||||
|
unsubscribeFromCoreSignals();
|
||||||
delete priv;
|
delete priv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,12 +247,12 @@ void TransactionTableModel::updateAmountColumnTitle()
|
||||||
emit headerDataChanged(Qt::Horizontal,Amount,Amount);
|
emit headerDataChanged(Qt::Horizontal,Amount,Amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TransactionTableModel::updateTransaction(const QString &hash, int status)
|
void TransactionTableModel::updateTransaction(const QString &hash, int status, bool showTransaction)
|
||||||
{
|
{
|
||||||
uint256 updated;
|
uint256 updated;
|
||||||
updated.SetHex(hash.toStdString());
|
updated.SetHex(hash.toStdString());
|
||||||
|
|
||||||
priv->updateWallet(updated, status);
|
priv->updateWallet(updated, status, showTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TransactionTableModel::updateConfirmations()
|
void TransactionTableModel::updateConfirmations()
|
||||||
|
@ -649,3 +646,82 @@ void TransactionTableModel::updateDisplayUnit()
|
||||||
updateAmountColumnTitle();
|
updateAmountColumnTitle();
|
||||||
emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));
|
emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// queue notifications to show a non freezing progress dialog e.g. for rescan
|
||||||
|
struct TransactionNotification
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TransactionNotification() {}
|
||||||
|
TransactionNotification(uint256 hash, ChangeType status, bool showTransaction):
|
||||||
|
hash(hash), status(status), showTransaction(showTransaction) {}
|
||||||
|
|
||||||
|
void invoke(QObject *ttm)
|
||||||
|
{
|
||||||
|
QString strHash = QString::fromStdString(hash.GetHex());
|
||||||
|
qDebug() << "NotifyTransactionChanged : " + strHash + " status= " + QString::number(status);
|
||||||
|
QMetaObject::invokeMethod(ttm, "updateTransaction", Qt::QueuedConnection,
|
||||||
|
Q_ARG(QString, strHash),
|
||||||
|
Q_ARG(int, status),
|
||||||
|
Q_ARG(bool, showTransaction));
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
uint256 hash;
|
||||||
|
ChangeType status;
|
||||||
|
bool showTransaction;
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool fQueueNotifications = false;
|
||||||
|
static std::vector< TransactionNotification > vQueueNotifications;
|
||||||
|
|
||||||
|
static void NotifyTransactionChanged(TransactionTableModel *ttm, CWallet *wallet, const uint256 &hash, ChangeType status)
|
||||||
|
{
|
||||||
|
// Find transaction in wallet
|
||||||
|
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
|
||||||
|
// Determine whether to show transaction or not (determine this here so that no relocking is needed in GUI thread)
|
||||||
|
bool inWallet = mi != wallet->mapWallet.end();
|
||||||
|
bool showTransaction = (inWallet && TransactionRecord::showTransaction(mi->second));
|
||||||
|
|
||||||
|
TransactionNotification notification(hash, status, showTransaction);
|
||||||
|
|
||||||
|
if (fQueueNotifications)
|
||||||
|
{
|
||||||
|
vQueueNotifications.push_back(notification);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
notification.invoke(ttm);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ShowProgress(TransactionTableModel *ttm, const std::string &title, int nProgress)
|
||||||
|
{
|
||||||
|
if (nProgress == 0)
|
||||||
|
fQueueNotifications = true;
|
||||||
|
|
||||||
|
if (nProgress == 100)
|
||||||
|
{
|
||||||
|
fQueueNotifications = false;
|
||||||
|
if (vQueueNotifications.size() > 10) // prevent balloon spam, show maximum 10 balloons
|
||||||
|
QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true));
|
||||||
|
for (unsigned int i = 0; i < vQueueNotifications.size(); ++i)
|
||||||
|
{
|
||||||
|
if (vQueueNotifications.size() - i <= 10)
|
||||||
|
QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false));
|
||||||
|
|
||||||
|
vQueueNotifications[i].invoke(ttm);
|
||||||
|
}
|
||||||
|
std::vector<TransactionNotification >().swap(vQueueNotifications); // clear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransactionTableModel::subscribeToCoreSignals()
|
||||||
|
{
|
||||||
|
// Connect signals to wallet
|
||||||
|
wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
|
||||||
|
wallet->ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransactionTableModel::unsubscribeFromCoreSignals()
|
||||||
|
{
|
||||||
|
// Disconnect signals from wallet
|
||||||
|
wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
|
||||||
|
wallet->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2));
|
||||||
|
}
|
||||||
|
|
|
@ -72,12 +72,17 @@ public:
|
||||||
QVariant data(const QModelIndex &index, int role) const;
|
QVariant data(const QModelIndex &index, int role) const;
|
||||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
||||||
QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const;
|
QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const;
|
||||||
|
bool processingQueuedTransactions() { return fProcessingQueuedTransactions; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CWallet* wallet;
|
CWallet* wallet;
|
||||||
WalletModel *walletModel;
|
WalletModel *walletModel;
|
||||||
QStringList columns;
|
QStringList columns;
|
||||||
TransactionTablePriv *priv;
|
TransactionTablePriv *priv;
|
||||||
|
bool fProcessingQueuedTransactions;
|
||||||
|
|
||||||
|
void subscribeToCoreSignals();
|
||||||
|
void unsubscribeFromCoreSignals();
|
||||||
|
|
||||||
QString lookupAddress(const std::string &address, bool tooltip) const;
|
QString lookupAddress(const std::string &address, bool tooltip) const;
|
||||||
QVariant addressColor(const TransactionRecord *wtx) const;
|
QVariant addressColor(const TransactionRecord *wtx) const;
|
||||||
|
@ -92,11 +97,14 @@ private:
|
||||||
QVariant txAddressDecoration(const TransactionRecord *wtx) const;
|
QVariant txAddressDecoration(const TransactionRecord *wtx) const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void updateTransaction(const QString &hash, int status);
|
/* New transaction, or transaction changed status */
|
||||||
|
void updateTransaction(const QString &hash, int status, bool showTransaction);
|
||||||
void updateConfirmations();
|
void updateConfirmations();
|
||||||
void updateDisplayUnit();
|
void updateDisplayUnit();
|
||||||
/** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */
|
/** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */
|
||||||
void updateAmountColumnTitle();
|
void updateAmountColumnTitle();
|
||||||
|
/* Needed to update fProcessingQueuedTransactions through a QueuedConnection */
|
||||||
|
void setProcessingQueuedTransactions(bool value) { fProcessingQueuedTransactions = value; }
|
||||||
|
|
||||||
friend class TransactionTablePriv;
|
friend class TransactionTablePriv;
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,7 +34,6 @@ WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *p
|
||||||
cachedEncryptionStatus(Unencrypted),
|
cachedEncryptionStatus(Unencrypted),
|
||||||
cachedNumBlocks(0)
|
cachedNumBlocks(0)
|
||||||
{
|
{
|
||||||
fProcessingQueuedTransactions = false;
|
|
||||||
fHaveWatchOnly = wallet->HaveWatchOnly();
|
fHaveWatchOnly = wallet->HaveWatchOnly();
|
||||||
fForceCheckBalanceChanged = false;
|
fForceCheckBalanceChanged = false;
|
||||||
|
|
||||||
|
@ -164,11 +163,8 @@ void WalletModel::checkBalanceChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WalletModel::updateTransaction(const QString &hash, int status)
|
void WalletModel::updateTransaction()
|
||||||
{
|
{
|
||||||
if(transactionTableModel)
|
|
||||||
transactionTableModel->updateTransaction(hash, status);
|
|
||||||
|
|
||||||
// Balance and number of transactions might have changed
|
// Balance and number of transactions might have changed
|
||||||
fForceCheckBalanceChanged = true;
|
fForceCheckBalanceChanged = true;
|
||||||
}
|
}
|
||||||
|
@ -455,45 +451,16 @@ static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet,
|
||||||
Q_ARG(int, status));
|
Q_ARG(int, status));
|
||||||
}
|
}
|
||||||
|
|
||||||
// queue notifications to show a non freezing progress dialog e.g. for rescan
|
|
||||||
static bool fQueueNotifications = false;
|
|
||||||
static std::vector<std::pair<uint256, ChangeType> > vQueueNotifications;
|
|
||||||
static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet, const uint256 &hash, ChangeType status)
|
static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet, const uint256 &hash, ChangeType status)
|
||||||
{
|
{
|
||||||
if (fQueueNotifications)
|
Q_UNUSED(wallet);
|
||||||
{
|
Q_UNUSED(hash);
|
||||||
vQueueNotifications.push_back(make_pair(hash, status));
|
Q_UNUSED(status);
|
||||||
return;
|
QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection);
|
||||||
}
|
|
||||||
|
|
||||||
QString strHash = QString::fromStdString(hash.GetHex());
|
|
||||||
|
|
||||||
qDebug() << "NotifyTransactionChanged : " + strHash + " status= " + QString::number(status);
|
|
||||||
QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection,
|
|
||||||
Q_ARG(QString, strHash),
|
|
||||||
Q_ARG(int, status));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ShowProgress(WalletModel *walletmodel, const std::string &title, int nProgress)
|
static void ShowProgress(WalletModel *walletmodel, const std::string &title, int nProgress)
|
||||||
{
|
{
|
||||||
if (nProgress == 0)
|
|
||||||
fQueueNotifications = true;
|
|
||||||
|
|
||||||
if (nProgress == 100)
|
|
||||||
{
|
|
||||||
fQueueNotifications = false;
|
|
||||||
if (vQueueNotifications.size() > 10) // prevent balloon spam, show maximum 10 balloons
|
|
||||||
QMetaObject::invokeMethod(walletmodel, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true));
|
|
||||||
for (unsigned int i = 0; i < vQueueNotifications.size(); ++i)
|
|
||||||
{
|
|
||||||
if (vQueueNotifications.size() - i <= 10)
|
|
||||||
QMetaObject::invokeMethod(walletmodel, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false));
|
|
||||||
|
|
||||||
NotifyTransactionChanged(walletmodel, NULL, vQueueNotifications[i].first, vQueueNotifications[i].second);
|
|
||||||
}
|
|
||||||
std::vector<std::pair<uint256, ChangeType> >().swap(vQueueNotifications); // clear
|
|
||||||
}
|
|
||||||
|
|
||||||
// emits signal "showProgress"
|
// emits signal "showProgress"
|
||||||
QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection,
|
QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection,
|
||||||
Q_ARG(QString, QString::fromStdString(title)),
|
Q_ARG(QString, QString::fromStdString(title)),
|
||||||
|
|
|
@ -133,7 +133,6 @@ public:
|
||||||
CAmount getWatchUnconfirmedBalance() const;
|
CAmount getWatchUnconfirmedBalance() const;
|
||||||
CAmount getWatchImmatureBalance() const;
|
CAmount getWatchImmatureBalance() const;
|
||||||
EncryptionStatus getEncryptionStatus() const;
|
EncryptionStatus getEncryptionStatus() const;
|
||||||
bool processingQueuedTransactions() { return fProcessingQueuedTransactions; }
|
|
||||||
|
|
||||||
// Check address for validity
|
// Check address for validity
|
||||||
bool validateAddress(const QString &address);
|
bool validateAddress(const QString &address);
|
||||||
|
@ -197,7 +196,6 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CWallet *wallet;
|
CWallet *wallet;
|
||||||
bool fProcessingQueuedTransactions;
|
|
||||||
bool fHaveWatchOnly;
|
bool fHaveWatchOnly;
|
||||||
bool fForceCheckBalanceChanged;
|
bool fForceCheckBalanceChanged;
|
||||||
|
|
||||||
|
@ -254,15 +252,13 @@ public slots:
|
||||||
/* Wallet status might have changed */
|
/* Wallet status might have changed */
|
||||||
void updateStatus();
|
void updateStatus();
|
||||||
/* New transaction, or transaction changed status */
|
/* New transaction, or transaction changed status */
|
||||||
void updateTransaction(const QString &hash, int status);
|
void updateTransaction();
|
||||||
/* New, updated or removed address book entry */
|
/* New, updated or removed address book entry */
|
||||||
void updateAddressBook(const QString &address, const QString &label, bool isMine, const QString &purpose, int status);
|
void updateAddressBook(const QString &address, const QString &label, bool isMine, const QString &purpose, int status);
|
||||||
/* Watch-only added */
|
/* Watch-only added */
|
||||||
void updateWatchOnlyFlag(bool fHaveWatchonly);
|
void updateWatchOnlyFlag(bool fHaveWatchonly);
|
||||||
/* Current, immature or unconfirmed balance might have changed - emit 'balanceChanged' if so */
|
/* Current, immature or unconfirmed balance might have changed - emit 'balanceChanged' if so */
|
||||||
void pollBalanceChanged();
|
void pollBalanceChanged();
|
||||||
/* Needed to update fProcessingQueuedTransactions through a QueuedConnection */
|
|
||||||
void setProcessingQueuedTransactions(bool value) { fProcessingQueuedTransactions = value; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // WALLETMODEL_H
|
#endif // WALLETMODEL_H
|
||||||
|
|
|
@ -137,10 +137,12 @@ void WalletView::setWalletModel(WalletModel *walletModel)
|
||||||
void WalletView::processNewTransaction(const QModelIndex& parent, int start, int /*end*/)
|
void WalletView::processNewTransaction(const QModelIndex& parent, int start, int /*end*/)
|
||||||
{
|
{
|
||||||
// Prevent balloon-spam when initial block download is in progress
|
// Prevent balloon-spam when initial block download is in progress
|
||||||
if (!walletModel || walletModel->processingQueuedTransactions() || !clientModel || clientModel->inInitialBlockDownload())
|
if (!walletModel || !clientModel || clientModel->inInitialBlockDownload())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TransactionTableModel *ttm = walletModel->getTransactionTableModel();
|
TransactionTableModel *ttm = walletModel->getTransactionTableModel();
|
||||||
|
if (!ttm || ttm->processingQueuedTransactions())
|
||||||
|
return;
|
||||||
|
|
||||||
QString date = ttm->index(start, TransactionTableModel::Date, parent).data().toString();
|
QString date = ttm->index(start, TransactionTableModel::Date, parent).data().toString();
|
||||||
qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent).data(Qt::EditRole).toULongLong();
|
qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent).data(Qt::EditRole).toULongLong();
|
||||||
|
|
Loading…
Reference in a new issue