diff --git a/src/connection.cpp b/src/connection.cpp new file mode 100644 index 0000000..089a384 --- /dev/null +++ b/src/connection.cpp @@ -0,0 +1,243 @@ +#include "connection.h" +#include "mainwindow.h" +#include "settings.h" +#include "ui_connection.h" +#include "rpc.h" + +#include "precompiled.h" + +using json = nlohmann::json; + +class LoadingDialog : public QDialog { + //Q_OBJECT +public: + LoadingDialog(QWidget* parent); + ~LoadingDialog(); +public slots: + void reject(); +}; + +LoadingDialog::LoadingDialog(QWidget* parent) : QDialog(parent) {} +LoadingDialog::~LoadingDialog() {} +void LoadingDialog::reject() { + //event->ignore(); +} + +ConnectionLoader::ConnectionLoader(MainWindow* main) { + this->main = main; + + d = new LoadingDialog(main); + connD = new Ui_ConnectionDialog(); + connD->setupUi(d); + + // Center on screen + QRect screenGeometry = QApplication::desktop()->screenGeometry(d); + int x = (screenGeometry.width() - d->width()) / 2; + int y = (screenGeometry.height() - d->height()) / 2; + d->move(x, y); + connD->buttonBox->setEnabled(false); + d->show(); +} + +ConnectionLoader::~ConnectionLoader() { + delete d; + delete connD; +} + +void ConnectionLoader::getConnection(std::function cb) { + + // Priority 1: Try to connect to detect zcash.conf and connect to it. + bool isZcashConfPresent = false; + auto conn = autoDetectZcashConf(); + + // If not autodetected, go and read the UI Settings + if (conn.get() != nullptr) { + isZcashConfPresent = true; + } else { + conn = loadFromSettings(); + + if (conn.get() == nullptr) { + // Nothing configured, show an error + auto explanation = QString() + % "A zcash.conf was not found on this machine.\n\n" + % "If you are connecting to a remote/non-standard node " + % "please set the host/port and user/password in the File->Settings menu."; + + QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical); + connD->icon->setPixmap(icon.pixmap(64, 64)); + connD->status->setText(explanation); + connD->progressBar->setValue(0); + + connD->buttonBox->setEnabled(true); + cb(nullptr); + } + } + + QNetworkAccessManager* client = new QNetworkAccessManager(main); + + QUrl myurl; + myurl.setScheme("http"); + myurl.setHost(Settings::getInstance()->getHost()); + myurl.setPort(Settings::getInstance()->getPort().toInt()); + + QNetworkRequest* request = new QNetworkRequest(); + request->setUrl(myurl); + request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); + + QString headerData = "Basic " + Settings::getInstance()->getUsernamePassword().toLocal8Bit().toBase64(); + request->setRawHeader("Authorization", headerData.toLocal8Bit()); + + auto connection = new Connection(client, request); + json payload = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", "getinfo"} + }; + connection->doRPC(payload, + [=] (auto result) { + // Success + d->close(); + cb(new RPC(connection, main)); + }, + [=] (auto err, auto res) { + // Failed + auto explanation = QString() + % (isZcashConfPresent ? "A zcash.conf file was found, but a" : "A") + % " connection to zcashd could not be established.\n\n" + % "If you are connecting to a remote/non-standard node " + % "please set the host/port and user/password in the File->Settings menu."; + + QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical); + connD->icon->setPixmap(icon.pixmap(64, 64)); + connD->status->setText(explanation); + connD->progressBar->setValue(0); + + connD->buttonBox->setEnabled(true); + } + ); +} + + +/** + * Try to automatically detect a zcash.conf file in the correct location and load parameters + */ +std::shared_ptr ConnectionLoader::autoDetectZcashConf() { + +#ifdef Q_OS_LINUX + auto confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, ".zcash/zcash.conf"); +#elif defined(Q_OS_DARWIN) + auto confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, "/Library/Application Support/Zcash/zcash.conf"); +#else + auto confLocation = QStandardPaths::locate(QStandardPaths::AppDataLocation, "../../Zcash/zcash.conf"); +#endif + + confLocation = QDir::cleanPath(confLocation); + + if (confLocation.isNull()) { + // No zcash file, just return with nothing + return nullptr; + } + + QFile file(confLocation); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << file.errorString(); + return nullptr; + } + + QTextStream in(&file); + + auto zcashconf = new ConnectionConfig(); + zcashconf->host = "127.0.0.1"; + zcashconf->connType = ConnectionType::DetectedConfExternalZcashD; + + while (!in.atEnd()) { + QString line = in.readLine(); + auto s = line.indexOf("="); + QString name = line.left(s).trimmed().toLower(); + QString value = line.right(line.length() - s - 1).trimmed(); + + if (name == "rpcuser") { + zcashconf->rpcuser = value; + } + if (name == "rpcpassword") { + zcashconf->rpcpassword = value; + } + if (name == "rpcport") { + zcashconf->port = value; + } + if (name == "testnet" && + value == "1" && + zcashconf->port.isEmpty()) { + zcashconf->port = "18232"; + } + } + + // If rpcport is not in the file, and it was not set by the testnet=1 flag, then go to default + if (zcashconf->port.isEmpty()) zcashconf->port = "8232"; + + file.close(); + + return std::make_shared(zcashconf); +} + +/** + * Load connection settings from the UI, which indicates an unknown, external zcashd + */ +std::shared_ptr ConnectionLoader::loadFromSettings() { + // Load from the QT Settings. + QSettings s; + + auto host = s.value("connection/host").toString(); + auto port = s.value("connection/port").toString(); + auto username = s.value("connection/rpcuser").toString(); + auto password = s.value("connection/rpcpassword").toString(); + + if (username.isEmpty() || password.isEmpty()) + return nullptr; + + auto uiConfig = new ConnectionConfig{ host, port, username, password, ConnectionType::UISettingsZCashD }; + + return std::make_shared(uiConfig); +} + + + + + + + + +Connection::Connection(QNetworkAccessManager* c, QNetworkRequest* r) { + this->restclient = c; + this->request = r; +} + +Connection::~Connection() { + delete restclient; + delete request; +} + + + +void Connection::doRPC(const json& payload, const std::function& cb, + const std::function& ne) { + QNetworkReply *reply = restclient->post(*request, QByteArray::fromStdString(payload.dump())); + + QObject::connect(reply, &QNetworkReply::finished, [=] { + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + auto parsed = json::parse(reply->readAll(), nullptr, false); + ne(reply->error(), parsed); + + return; + } + + auto parsed = json::parse(reply->readAll(), nullptr, false); + if (parsed.is_discarded()) { + ne(reply->error(), "Unknown error"); + } + + cb(parsed["result"]); + }); +} diff --git a/src/connection.h b/src/connection.h new file mode 100644 index 0000000..3c329fd --- /dev/null +++ b/src/connection.h @@ -0,0 +1,64 @@ +#ifndef CONNECTION_H +#define CONNECTION_H + +#include "ui_connection.h" +#include "precompiled.h" + +using json = nlohmann::json; + +class MainWindow; +class RPC; + +enum ConnectionType { + DetectedConfExternalZcashD = 1, + UISettingsZCashD, + InternalZcashD +}; + +struct ConnectionConfig { + QString host; + QString port; + QString rpcuser; + QString rpcpassword; + + ConnectionType connType; +}; + +class LoadingDialog; + +class ConnectionLoader { + +public: + ConnectionLoader(MainWindow* main); + ~ConnectionLoader(); + + void getConnection(std::function cb); + +private: + std::shared_ptr autoDetectZcashConf(); + std::shared_ptr loadFromSettings(); + + LoadingDialog* d; + Ui_ConnectionDialog* connD; + MainWindow* main; +}; + +/** + * Represents a connection to a zcashd. It may even start a new zcashd if needed. + * This is also a UI class, so it may show a dialog waiting for the connection. +*/ +class Connection { +public: + Connection(QNetworkAccessManager* c, QNetworkRequest* r); + ~Connection(); + + + QNetworkAccessManager* restclient; + QNetworkRequest* request; + + void doRPC(const json& payload, const std::function& cb, + const std::function& ne); + +}; + +#endif \ No newline at end of file diff --git a/src/connection.ui b/src/connection.ui index 7450240..7c83748 100644 --- a/src/connection.ui +++ b/src/connection.ui @@ -11,10 +11,13 @@ - Connecting to zcashd + zec-qt-wallet + + + true - + Qt::Horizontal @@ -24,17 +27,27 @@ - - - - 24 + + + + Connection Status + + + true - + - Connection Status + TextLabel + + + + + + + 24 diff --git a/src/main.cpp b/src/main.cpp index 6f1c5c1..36febd2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,12 +19,11 @@ int main(int argc, char *argv[]) #endif std::srand(std::time(nullptr)); + Settings::init(); QCoreApplication::setOrganizationName("zec-qt-wallet-org"); QCoreApplication::setApplicationName("zec-qt-wallet"); - Settings::init(); - MainWindow w; w.setWindowTitle("zec-qt-wallet v" + QString(APP_VERSION)); w.show(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d5bd7e8..c5ac6c2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -11,6 +11,7 @@ #include "utils.h" #include "turnstile.h" #include "senttxstore.h" +#include "connection.h" #include "precompiled.h" @@ -62,13 +63,17 @@ MainWindow::MainWindow(QWidget *parent) : setupBalancesTab(); setupTurnstileDialog(); - rpc = new RPC(new QNetworkAccessManager(this), this); - rpc->refreshZECPrice(); - - rpc->refresh(true); // Force refresh first time - restoreSavedStates(); + + new ConnectionLoader(this).getConnection([=] (RPC* rpc) { + if (rpc == nullptr) + return; + this->rpc = rpc; + this->rpc->refreshZECPrice(); + this->rpc->refresh(true); // Force refresh first time + }); } + void MainWindow::restoreSavedStates() { QSettings s; @@ -385,11 +390,14 @@ void MainWindow::setupSettingsModal() { settings.rpcuser->text(), settings.rpcpassword->text()); - this->rpc->reloadConnectionInfo(); + auto me = this; + ConnectionLoader(this).getConnection([&me] (auto newrpc) { + delete me->rpc; + me->rpc = newrpc; + // Then refresh everything. + me->rpc->refresh(true); + }); } - - // Then refresh everything. - this->rpc->refresh(true); }; }); diff --git a/src/mainwindow.h b/src/mainwindow.h index 614cbf6..b7c50bd 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -86,7 +86,7 @@ private: void restoreSavedStates(); - RPC* rpc; + RPC* rpc = nullptr; QMovie* loadingMovie; }; diff --git a/src/precompiled.h b/src/precompiled.h index 8c6b605..9c045fd 100644 --- a/src/precompiled.h +++ b/src/precompiled.h @@ -7,7 +7,6 @@ #include #include -#include #include #include #include @@ -23,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +44,8 @@ #include #include #include +#include +#include #include "3rdparty/json/json.hpp" #include "3rdparty/qrcode/QrCode.hpp" diff --git a/src/rpc.cpp b/src/rpc.cpp index 87d0f89..77a2fd2 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -6,8 +6,8 @@ using json = nlohmann::json; -RPC::RPC(QNetworkAccessManager* client, MainWindow* main) { - this->restclient = client; +RPC::RPC(Connection* conn, MainWindow* main) { + this->conn = conn; this->main = main; this->ui = main->ui; @@ -26,8 +26,6 @@ RPC::RPC(QNetworkAccessManager* client, MainWindow* main) { main->ui->transactionsTable->setColumnWidth(2, 200); main->ui->transactionsTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); - reloadConnectionInfo(); - // Set up timer to refresh Price priceTimer = new QTimer(main); QObject::connect(priceTimer, &QTimer::timeout, [=]() { @@ -49,7 +47,6 @@ RPC::RPC(QNetworkAccessManager* client, MainWindow* main) { }); // Start at every 10s. When an operation is pending, this will change to every second txTimer->start(Utils::updateSpeed); - } RPC::~RPC() { @@ -64,27 +61,12 @@ RPC::~RPC() { delete allBalances; delete zaddresses; - delete restclient; + delete conn; } -void RPC::reloadConnectionInfo() { - // Reset for any errors caused. - firstTime = true; - - QUrl myurl; - myurl.setScheme("http"); //https also applicable - myurl.setHost(Settings::getInstance()->getHost()); - myurl.setPort(Settings::getInstance()->getPort().toInt()); - - request.setUrl(myurl); - request.setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); - - QString headerData = "Basic " + Settings::getInstance()->getUsernamePassword().toLocal8Bit().toBase64(); - request.setRawHeader("Authorization", headerData.toLocal8Bit()); -} void RPC::doRPC(const json& payload, const std::function& cb) { - QNetworkReply *reply = restclient->post(request, QByteArray::fromStdString(payload.dump())); + QNetworkReply *reply = conn->restclient->post(*conn->request, QByteArray::fromStdString(payload.dump())); QObject::connect(reply, &QNetworkReply::finished, [=] { reply->deleteLater(); @@ -230,7 +212,7 @@ void RPC::getTransactions(const std::function& cb) { } void RPC::doSendRPC(const json& payload, const std::function& cb, const std::function& err) { - QNetworkReply *reply = restclient->post(request, QByteArray::fromStdString(payload.dump())); + QNetworkReply *reply = conn->restclient->post(*conn->request, QByteArray::fromStdString(payload.dump())); QObject::connect(reply, &QNetworkReply::finished, [=] { reply->deleteLater(); @@ -790,7 +772,7 @@ void RPC::refreshZECPrice() { QNetworkRequest req; req.setUrl(cmcURL); - QNetworkReply *reply = restclient->get(req); + QNetworkReply *reply = conn->restclient->get(req); QObject::connect(reply, &QNetworkReply::finished, [=] { reply->deleteLater(); diff --git a/src/rpc.h b/src/rpc.h index c789bcd..887451e 100644 --- a/src/rpc.h +++ b/src/rpc.h @@ -8,6 +8,7 @@ #include "txtablemodel.h" #include "ui_mainwindow.h" #include "mainwindow.h" +#include "connection.h" using json = nlohmann::json; @@ -27,7 +28,7 @@ struct TransactionItem { class RPC { public: - RPC(QNetworkAccessManager* restclient, MainWindow* main); + RPC(Connection* conn, MainWindow* main); ~RPC(); void refresh(bool force = false); @@ -45,8 +46,6 @@ public: const QList* getUTXOs() { return utxos; } const QMap* getAllBalances() { return allBalances; } - void reloadConnectionInfo(); - void newZaddr(bool sapling, const std::function& cb); void newTaddr(const std::function& cb); @@ -69,7 +68,7 @@ public: for (auto item: payloads) { json payload = payloadGenerator(item); - QNetworkReply *reply = restclient->post(request, QByteArray::fromStdString(payload.dump())); + QNetworkReply *reply = conn->restclient->post(*conn->request, QByteArray::fromStdString(payload.dump())); QObject::connect(reply, &QNetworkReply::finished, [=] { reply->deleteLater(); @@ -133,8 +132,7 @@ private: void handleConnectionError (const QString& error); void handleTxError (const QString& error); - QNetworkAccessManager* restclient; - QNetworkRequest request; + Connection* conn = nullptr; QList* utxos = nullptr; QMap* allBalances = nullptr; diff --git a/zec-qt-wallet.pro b/zec-qt-wallet.pro index efd6b04..51f74b7 100644 --- a/zec-qt-wallet.pro +++ b/zec-qt-wallet.pro @@ -53,7 +53,8 @@ SOURCES += \ src/txtablemodel.cpp \ src/turnstile.cpp \ src/utils.cpp \ - src/qrcodelabel.cpp + src/qrcodelabel.cpp \ + src/connection.cpp HEADERS += \ src/mainwindow.h \ @@ -70,7 +71,8 @@ HEADERS += \ src/senttxstore.h \ src/turnstile.h \ src/utils.h \ - src/qrcodelabel.h + src/qrcodelabel.h \ + src/connection.h FORMS += \ src/mainwindow.ui \