Browse Source

Fetch balances + fix gui thread issues

pull/19/head
Aditya Kulkarni 5 years ago
parent
commit
53cbea1fda
  1. 6
      src/balancestablemodel.cpp
  2. 4
      src/balancestablemodel.h
  3. 60
      src/connection.cpp
  4. 38
      src/connection.h
  5. 55
      src/controller.cpp
  6. 2
      src/controller.h
  7. 4
      src/datamodel.cpp
  8. 8
      src/datamodel.h
  9. 20
      src/liteinterface.cpp
  10. 5
      src/liteinterface.h

6
src/balancestablemodel.cpp

@ -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;

4
src/balancestablemodel.h

@ -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;

60
src/connection.cpp

@ -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
});
}

38
src/connection.h

@ -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) ;

55
src/controller.cpp

@ -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->fetchZUnspent([=] (json reply) {
auto anyZUnconfirmed = processUnspent(reply, newBalances, newUtxos);
zrpc->fetchUnspent([=] (json reply) {
auto anyUnconfirmed = 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(anyTUnconfirmed || anyZUnconfirmed);
updateUI(anyUnconfirmed);
main->balancesReady();
});
main->balancesReady();
});
}

2
src/controller.h

@ -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);

4
src/datamodel.cpp

@ -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
src/datamodel.h

@ -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;

20
src/liteinterface.cpp

@ -47,28 +47,16 @@ 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;
// json payload = {
// {"jsonrpc", "1.0"},
// {"id", "someid"},
// {"method", "listunspent"},
// {"params", {0}} // Get UTXOs with 0 confirmations as well.
// };
// conn->doRPCWithDefaultErrorHandling(payload, cb);
}
void LiteInterface::fetchZUnspent(const std::function<void(json)>& cb) {
if (conn == nullptr)
return;
conn->doRPCWithDefaultErrorHandling("notes", "", cb);
// json payload = {
// {"jsonrpc", "1.0"},
// {"id", "someid"},
// {"method", "z_listunspent"},
// {"method", "listunspent"},
// {"params", {0}} // Get UTXOs with 0 confirmations as well.
// };
@ -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;

5
src/liteinterface.h

@ -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…
Cancel
Save