Fetch balances + fix gui thread issues
This commit is contained in:
parent
28fdac1b72
commit
53cbea1fda
|
@ -7,7 +7,7 @@ BalancesTableModel::BalancesTableModel(QObject *parent)
|
|||
: QAbstractTableModel(parent) {
|
||||
}
|
||||
|
||||
void BalancesTableModel::setNewData(const QMap<QString, double> balances,
|
||||
void BalancesTableModel::setNewData(const QMap<QString, qint64> balances,
|
||||
const QList<UnspentOutput> outputs)
|
||||
{
|
||||
loading = false;
|
||||
|
@ -22,7 +22,7 @@ void BalancesTableModel::setNewData(const QMap<QString, double> balances,
|
|||
|
||||
// Process the address balances into a list
|
||||
delete modeldata;
|
||||
modeldata = new QList<std::tuple<QString, double>>();
|
||||
modeldata = new QList<std::tuple<QString, qint64>>();
|
||||
std::for_each(balances.keyBegin(), balances.keyEnd(), [=] (auto keyIt) {
|
||||
if (balances.value(keyIt) > 0)
|
||||
modeldata->push_back(std::make_tuple(keyIt, balances.value(keyIt)));
|
||||
|
@ -72,7 +72,7 @@ QVariant BalancesTableModel::data(const QModelIndex &index, int role) const
|
|||
// If any of the UTXOs for this address has zero confirmations, paint it in red
|
||||
const auto& addr = std::get<0>(modeldata->at(index.row()));
|
||||
for (auto utxo : *utxos) {
|
||||
if (utxo.address == addr && utxo.confirmations == 0) {
|
||||
if (utxo.address == addr && !utxo.spendable) {
|
||||
QBrush b;
|
||||
b.setColor(Qt::red);
|
||||
return b;
|
||||
|
|
|
@ -10,7 +10,7 @@ public:
|
|||
BalancesTableModel(QObject* parent);
|
||||
~BalancesTableModel();
|
||||
|
||||
void setNewData(const QMap<QString, double> balances, const QList<UnspentOutput> outputs);
|
||||
void setNewData(const QMap<QString, qint64> balances, const QList<UnspentOutput> outputs);
|
||||
|
||||
int rowCount(const QModelIndex &parent) const;
|
||||
int columnCount(const QModelIndex &parent) const;
|
||||
|
@ -18,7 +18,7 @@ public:
|
|||
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
||||
|
||||
private:
|
||||
QList<std::tuple<QString, double>>* modeldata = nullptr;
|
||||
QList<std::tuple<QString, qint64>>* modeldata = nullptr;
|
||||
QList<UnspentOutput>* utxos = nullptr;
|
||||
|
||||
bool loading = true;
|
||||
|
|
|
@ -33,7 +33,7 @@ void ConnectionLoader::loadConnection() {
|
|||
d->exec();
|
||||
}
|
||||
|
||||
void ConnectionLoader::doAutoConnect(bool tryEzcashdStart) {
|
||||
void ConnectionLoader::doAutoConnect() {
|
||||
qDebug() << "Doing autoconnect";
|
||||
|
||||
auto config = std::shared_ptr<ConnectionConfig>(new ConnectionConfig());
|
||||
|
@ -51,7 +51,7 @@ void ConnectionLoader::doAutoConnect(bool tryEzcashdStart) {
|
|||
// If success, set the connection
|
||||
main->logger->write("Connection is online.");
|
||||
this->doRPCSetConnection(connection);
|
||||
}, [=](auto err, auto errJson) {});
|
||||
}, [=](auto err) {});
|
||||
}
|
||||
|
||||
void ConnectionLoader::doRPCSetConnection(Connection* conn) {
|
||||
|
@ -96,6 +96,9 @@ void ConnectionLoader::showError(QString explanation) {
|
|||
|
||||
|
||||
|
||||
/***********************************************************************************
|
||||
* Connection, Executor and Callback Class
|
||||
************************************************************************************/
|
||||
void Executor::run() {
|
||||
char* resp = litelib_execute(this->cmd.toStdString().c_str());
|
||||
|
||||
|
@ -111,50 +114,71 @@ void Executor::run() {
|
|||
qDebug() << "Reply=" << reply;
|
||||
auto parsed = json::parse(reply.toStdString().c_str(), nullptr, false);
|
||||
|
||||
const bool isGuiThread =
|
||||
QThread::currentThread() == QCoreApplication::instance()->thread();
|
||||
qDebug() << "executing RPC: isGUI=" << isGuiThread;
|
||||
|
||||
emit responseReady(parsed);
|
||||
}
|
||||
|
||||
|
||||
void Callback::processRPCCallback(json resp) {
|
||||
const bool isGuiThread = QThread::currentThread() == QCoreApplication::instance()->thread();
|
||||
qDebug() << "Doing RPC callback: isGUI=" << isGuiThread;
|
||||
this->cb(resp);
|
||||
|
||||
// Destroy self
|
||||
delete this;
|
||||
}
|
||||
|
||||
void Callback::processError(QString resp) {
|
||||
const bool isGuiThread = QThread::currentThread() == QCoreApplication::instance()->thread();
|
||||
qDebug() << "Doing RPC callback: isGUI=" << isGuiThread;
|
||||
this->errCb(resp);
|
||||
|
||||
// Destroy self
|
||||
delete this;
|
||||
}
|
||||
|
||||
/***********************************************************************************
|
||||
* Connection Class
|
||||
************************************************************************************/
|
||||
Connection::Connection(MainWindow* m, std::shared_ptr<ConnectionConfig> conf) {
|
||||
this->config = conf;
|
||||
this->main = m;
|
||||
}
|
||||
|
||||
Connection::~Connection() {
|
||||
// Register the JSON type as a type that can be passed between signals and slots.
|
||||
qRegisterMetaType<json>("json");
|
||||
}
|
||||
|
||||
void Connection::doRPC(const QString cmd, const QString args, const std::function<void(json)>& cb,
|
||||
const std::function<void(QNetworkReply*, const json&)>& ne) {
|
||||
const std::function<void(QString)>& errCb) {
|
||||
if (shutdownInProgress) {
|
||||
// Ignoring RPC because shutdown in progress
|
||||
return;
|
||||
}
|
||||
|
||||
const bool isGuiThread =
|
||||
QThread::currentThread() == QCoreApplication::instance()->thread();
|
||||
qDebug() << "Doing RPC: isGUI=" << isGuiThread;
|
||||
|
||||
// Create a runner.
|
||||
auto runner = new Executor(cmd, args);
|
||||
QObject::connect(runner, &Executor::responseReady, [=] (json resp) {
|
||||
cb(resp);
|
||||
});
|
||||
|
||||
// Callback object. Will delete itself
|
||||
auto c = new Callback(cb, errCb);
|
||||
|
||||
QObject::connect(runner, &Executor::responseReady, c, &Callback::processRPCCallback);
|
||||
QObject::connect(runner, &Executor::handleError, c, &Callback::processError);
|
||||
|
||||
QThreadPool::globalInstance()->start(runner);
|
||||
}
|
||||
|
||||
void Connection::doRPCWithDefaultErrorHandling(const QString cmd, const QString args, const std::function<void(json)>& cb) {
|
||||
doRPC(cmd, args, cb, [=] (auto reply, auto parsed) {
|
||||
if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) {
|
||||
this->showTxError(QString::fromStdString(parsed["error"]["message"]));
|
||||
} else {
|
||||
this->showTxError(reply->errorString());
|
||||
}
|
||||
doRPC(cmd, args, cb, [=] (QString err) {
|
||||
this->showTxError(err);
|
||||
});
|
||||
}
|
||||
|
||||
void Connection::doRPCIgnoreError(const QString cmd, const QString args, const std::function<void(json)>& cb) {
|
||||
doRPC(cmd, args, cb, [=] (auto, auto) {
|
||||
doRPC(cmd, args, cb, [=] (auto) {
|
||||
// Ignored error handling
|
||||
});
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ private:
|
|||
|
||||
Connection* makeConnection(std::shared_ptr<ConnectionConfig> config);
|
||||
|
||||
void doAutoConnect(bool tryEzcashdStart = true);
|
||||
void doAutoConnect();
|
||||
|
||||
void showError(QString explanation);
|
||||
void showInformation(QString info, QString detail = "");
|
||||
|
@ -45,6 +45,32 @@ private:
|
|||
Controller* rpc;
|
||||
};
|
||||
|
||||
/**
|
||||
* An object that will call the callback function in the GUI thread, and destroy itself after the callback is finished
|
||||
*/
|
||||
class Callback: public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Callback(const std::function<void(json)> cb, const std::function<void(QString)> errCb) { this->cb = cb; this->errCb = errCb;}
|
||||
~Callback() = default;
|
||||
|
||||
public slots:
|
||||
void processRPCCallback(json resp);
|
||||
void processError(QString error);
|
||||
|
||||
private:
|
||||
std::function<void(json)> cb;
|
||||
std::function<void(QString)> errCb;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* A runnable that runs some lightclient Command in a non-UI thread.
|
||||
* It emits the "responseReady" signal, which should be processed in a GUI thread.
|
||||
*
|
||||
* Since the autoDelete flag is ON, the runnable should be destroyed automatically
|
||||
* by the threadpool.
|
||||
*/
|
||||
class Executor : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -60,7 +86,8 @@ public:
|
|||
virtual void run();
|
||||
|
||||
signals:
|
||||
void responseReady(json);
|
||||
void responseReady(json);
|
||||
void handleError(QString);
|
||||
|
||||
private:
|
||||
QString cmd;
|
||||
|
@ -72,17 +99,20 @@ private:
|
|||
* This is also a UI class, so it may show a dialog waiting for the connection.
|
||||
*/
|
||||
class Connection : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Connection(MainWindow* m, std::shared_ptr<ConnectionConfig> conf);
|
||||
~Connection();
|
||||
~Connection() = default;
|
||||
|
||||
std::shared_ptr<ConnectionConfig> config;
|
||||
MainWindow* main;
|
||||
|
||||
void shutdown();
|
||||
|
||||
|
||||
void doRPC(const QString cmd, const QString args, const std::function<void(json)>& cb,
|
||||
const std::function<void(QNetworkReply*, const json&)>& ne);
|
||||
const std::function<void(QString)>& errCb);
|
||||
void doRPCWithDefaultErrorHandling(const QString cmd, const QString args, const std::function<void(json)>& cb);
|
||||
void doRPCIgnoreError(const QString cmd, const QString args, const std::function<void(json)>& cb) ;
|
||||
|
||||
|
|
|
@ -141,7 +141,7 @@ void Controller::noConnection() {
|
|||
main->ui->statusBar->showMessage(QObject::tr("No Connection"), 1000);
|
||||
|
||||
// Clear balances table.
|
||||
QMap<QString, double> emptyBalances;
|
||||
QMap<QString, qint64> emptyBalances;
|
||||
QList<UnspentOutput> emptyOutputs;
|
||||
balancesTableModel->setNewData(emptyBalances, emptyOutputs);
|
||||
|
||||
|
@ -295,7 +295,7 @@ void Controller::getInfoThenRefresh(bool force) {
|
|||
main->statusIcon->setToolTip(tooltip);
|
||||
});
|
||||
|
||||
}, [=](QNetworkReply* reply, const json&) {
|
||||
}, [=](QString err) {
|
||||
// zcashd has probably disappeared.
|
||||
this->noConnection();
|
||||
|
||||
|
@ -304,7 +304,7 @@ void Controller::getInfoThenRefresh(bool force) {
|
|||
if (!shown && prevCallSucceeded) { // show error only first time
|
||||
shown = true;
|
||||
QMessageBox::critical(main, QObject::tr("Connection Error"), QObject::tr("There was an error connecting to zcashd. The error was") + ": \n\n"
|
||||
+ reply->errorString(), QMessageBox::StandardButton::Ok);
|
||||
+ err, QMessageBox::StandardButton::Ok);
|
||||
shown = false;
|
||||
}
|
||||
|
||||
|
@ -356,22 +356,25 @@ void Controller::updateUI(bool anyUnconfirmed) {
|
|||
};
|
||||
|
||||
// Function to process reply of the listunspent and z_listunspent API calls, used below.
|
||||
bool Controller::processUnspent(const json& reply, QMap<QString, double>* balancesMap, QList<UnspentOutput>* newUtxos) {
|
||||
bool Controller::processUnspent(const json& reply, QMap<QString, qint64>* balancesMap, QList<UnspentOutput>* newUtxos) {
|
||||
bool anyUnconfirmed = false;
|
||||
for (auto& it : reply.get<json::array_t>()) {
|
||||
QString qsAddr = QString::fromStdString(it["address"]);
|
||||
auto confirmations = it["confirmations"].get<json::number_unsigned_t>();
|
||||
if (confirmations == 0) {
|
||||
anyUnconfirmed = true;
|
||||
}
|
||||
|
||||
newUtxos->push_back(
|
||||
UnspentOutput{ qsAddr, QString::fromStdString(it["txid"]),
|
||||
Settings::getDecimalString(it["amount"].get<json::number_float_t>()),
|
||||
(int)confirmations, it["spendable"].get<json::boolean_t>() });
|
||||
auto processFn = [=](const json& array) {
|
||||
for (auto& it : array) {
|
||||
QString qsAddr = QString::fromStdString(it["address"]);
|
||||
int block = it["created_in_block"].get<json::number_unsigned_t>();
|
||||
QString txid = QString::fromStdString(it["created_in_txid"]);
|
||||
QString amount = Settings::getDecimalString(it["value"].get<json::number_unsigned_t>());
|
||||
|
||||
newUtxos->push_back(UnspentOutput{ qsAddr, txid, amount, block, true });
|
||||
|
||||
(*balancesMap)[qsAddr] = (*balancesMap)[qsAddr] + it["value"].get<json::number_unsigned_t>();
|
||||
}
|
||||
};
|
||||
|
||||
processFn(reply["unspent_notes"].get<json::array_t>());
|
||||
processFn(reply["utxos"].get<json::array_t>());
|
||||
|
||||
(*balancesMap)[qsAddr] = (*balancesMap)[qsAddr] + it["amount"].get<json::number_float_t>();
|
||||
}
|
||||
return anyUnconfirmed;
|
||||
};
|
||||
|
||||
|
@ -423,23 +426,19 @@ void Controller::refreshBalances() {
|
|||
// 2. Get the UTXOs
|
||||
// First, create a new UTXO list. It will be replacing the existing list when everything is processed.
|
||||
auto newUtxos = new QList<UnspentOutput>();
|
||||
auto newBalances = new QMap<QString, double>();
|
||||
auto newBalances = new QMap<QString, qint64>();
|
||||
|
||||
// Call the Transparent and Z unspent APIs serially and then, once they're done, update the UI
|
||||
zrpc->fetchTransparentUnspent([=] (json reply) {
|
||||
auto anyTUnconfirmed = processUnspent(reply, newBalances, newUtxos);
|
||||
zrpc->fetchUnspent([=] (json reply) {
|
||||
auto anyUnconfirmed = processUnspent(reply, newBalances, newUtxos);
|
||||
|
||||
zrpc->fetchZUnspent([=] (json reply) {
|
||||
auto anyZUnconfirmed = processUnspent(reply, newBalances, newUtxos);
|
||||
// Swap out the balances and UTXOs
|
||||
model->replaceBalances(newBalances);
|
||||
model->replaceUTXOs(newUtxos);
|
||||
|
||||
// Swap out the balances and UTXOs
|
||||
model->replaceBalances(newBalances);
|
||||
model->replaceUTXOs(newUtxos);
|
||||
updateUI(anyUnconfirmed);
|
||||
|
||||
updateUI(anyTUnconfirmed || anyZUnconfirmed);
|
||||
|
||||
main->balancesReady();
|
||||
});
|
||||
main->balancesReady();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ private:
|
|||
void refreshSentZTrans();
|
||||
void refreshReceivedZTrans(QList<QString> zaddresses);
|
||||
|
||||
bool processUnspent (const json& reply, QMap<QString, double>* newBalances, QList<UnspentOutput>* newUtxos);
|
||||
bool processUnspent (const json& reply, QMap<QString, qint64>* newBalances, QList<UnspentOutput>* newUtxos);
|
||||
void updateUI (bool anyUnconfirmed);
|
||||
|
||||
void getInfoThenRefresh(bool force);
|
||||
|
|
|
@ -7,7 +7,7 @@ DataModel::DataModel() {
|
|||
QWriteLocker locker(lock);
|
||||
|
||||
utxos = new QList<UnspentOutput>();
|
||||
balances = new QMap<QString, double>();
|
||||
balances = new QMap<QString, qint64>();
|
||||
usedAddresses = new QMap<QString, bool>();
|
||||
zaddresses = new QList<QString>();
|
||||
taddresses = new QList<QString>();
|
||||
|
@ -40,7 +40,7 @@ void DataModel::replaceTaddresses(QList<QString>* newT) {
|
|||
taddresses = newT;
|
||||
}
|
||||
|
||||
void DataModel::replaceBalances(QMap<QString, double>* newBalances) {
|
||||
void DataModel::replaceBalances(QMap<QString, qint64>* newBalances) {
|
||||
QWriteLocker locker(lock);
|
||||
Q_ASSERT(newBalances);
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ struct UnspentOutput {
|
|||
QString address;
|
||||
QString txid;
|
||||
QString amount;
|
||||
int confirmations;
|
||||
int blockCreated;
|
||||
bool spendable;
|
||||
};
|
||||
|
||||
|
@ -18,7 +18,7 @@ class DataModel {
|
|||
public:
|
||||
void replaceZaddresses(QList<QString>* newZ);
|
||||
void replaceTaddresses(QList<QString>* newZ);
|
||||
void replaceBalances(QMap<QString, double>* newBalances);
|
||||
void replaceBalances(QMap<QString, qint64>* newBalances);
|
||||
void replaceUTXOs(QList<UnspentOutput>* utxos);
|
||||
|
||||
void markAddressUsed(QString address);
|
||||
|
@ -26,7 +26,7 @@ public:
|
|||
const QList<QString> getAllZAddresses() { QReadLocker locker(lock); return *zaddresses; }
|
||||
const QList<QString> getAllTAddresses() { QReadLocker locker(lock); return *taddresses; }
|
||||
const QList<UnspentOutput> getUTXOs() { QReadLocker locker(lock); return *utxos; }
|
||||
const QMap<QString, double> getAllBalances() { QReadLocker locker(lock); return *balances; }
|
||||
const QMap<QString, qint64> getAllBalances() { QReadLocker locker(lock); return *balances; }
|
||||
const QMap<QString, bool> getUsedAddresses() { QReadLocker locker(lock); return *usedAddresses; }
|
||||
|
||||
|
||||
|
@ -36,7 +36,7 @@ private:
|
|||
|
||||
|
||||
QList<UnspentOutput>* utxos = nullptr;
|
||||
QMap<QString, double>* balances = nullptr;
|
||||
QMap<QString, qint64>* balances = nullptr;
|
||||
QMap<QString, bool>* usedAddresses = nullptr;
|
||||
QList<QString>* zaddresses = nullptr;
|
||||
QList<QString>* taddresses = nullptr;
|
||||
|
|
|
@ -47,10 +47,12 @@ void LiteInterface::fetchZAddresses(const std::function<void(json)>& cb) {
|
|||
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||
}
|
||||
|
||||
void LiteInterface::fetchTransparentUnspent(const std::function<void(json)>& cb) {
|
||||
void LiteInterface::fetchUnspent(const std::function<void(json)>& cb) {
|
||||
if (conn == nullptr)
|
||||
return;
|
||||
|
||||
conn->doRPCWithDefaultErrorHandling("notes", "", cb);
|
||||
|
||||
// json payload = {
|
||||
// {"jsonrpc", "1.0"},
|
||||
// {"id", "someid"},
|
||||
|
@ -61,20 +63,6 @@ void LiteInterface::fetchTransparentUnspent(const std::function<void(json)>& cb)
|
|||
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||
}
|
||||
|
||||
void LiteInterface::fetchZUnspent(const std::function<void(json)>& cb) {
|
||||
if (conn == nullptr)
|
||||
return;
|
||||
|
||||
// json payload = {
|
||||
// {"jsonrpc", "1.0"},
|
||||
// {"id", "someid"},
|
||||
// {"method", "z_listunspent"},
|
||||
// {"params", {0}} // Get UTXOs with 0 confirmations as well.
|
||||
// };
|
||||
|
||||
// conn->doRPCWithDefaultErrorHandling(payload, cb);
|
||||
}
|
||||
|
||||
void LiteInterface::createNewZaddr(bool sapling, const std::function<void(json)>& cb) {
|
||||
if (conn == nullptr)
|
||||
return;
|
||||
|
@ -217,7 +205,7 @@ void LiteInterface::sendZTransaction(json params, const std::function<void(json)
|
|||
}
|
||||
|
||||
void LiteInterface::fetchInfo(const std::function<void(json)>& cb,
|
||||
const std::function<void(QNetworkReply*, const json&)>& err) {
|
||||
const std::function<void(QString)>& err) {
|
||||
if (conn == nullptr)
|
||||
return;
|
||||
|
||||
|
|
|
@ -28,8 +28,7 @@ public:
|
|||
void setConnection(Connection* c);
|
||||
Connection* getConnection() { return conn; }
|
||||
|
||||
void fetchTransparentUnspent (const std::function<void(json)>& cb);
|
||||
void fetchZUnspent (const std::function<void(json)>& cb);
|
||||
void fetchUnspent (const std::function<void(json)>& cb);
|
||||
void fetchTransactions (const std::function<void(json)>& cb);
|
||||
void fetchZAddresses (const std::function<void(json)>& cb);
|
||||
void fetchTAddresses (const std::function<void(json)>& cb);
|
||||
|
@ -40,7 +39,7 @@ public:
|
|||
const std::function<void(QList<TransactionItem>)> txdataFn);
|
||||
|
||||
void fetchInfo(const std::function<void(json)>& cb,
|
||||
const std::function<void(QNetworkReply*, const json&)>& err);
|
||||
const std::function<void(QString)>& err);
|
||||
void fetchBlockchainInfo(const std::function<void(json)>& cb);
|
||||
void fetchNetSolOps(const std::function<void(qint64)> cb);
|
||||
void fetchOpStatus(const std::function<void(json)>& cb);
|
||||
|
|
Loading…
Reference in New Issue