diff --git a/application.qrc b/application.qrc index faab39d..0f7de37 100644 --- a/application.qrc +++ b/application.qrc @@ -6,5 +6,8 @@ res/connected.gif res/loading.gif res/icon.ico - + + + res/zcashdlogo.gif + diff --git a/res/zcashdlogo.gif b/res/zcashdlogo.gif new file mode 100644 index 0000000..bde1631 Binary files /dev/null and b/res/zcashdlogo.gif differ diff --git a/src/connection.cpp b/src/connection.cpp index 3919584..f1f1f26 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -15,48 +15,287 @@ ConnectionLoader::ConnectionLoader(MainWindow* main, RPC* rpc) { d = new QDialog(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); + connD->topIcon->setBasePixmap(QIcon(":/icons/res/icon.ico").pixmap(256, 256)); } -ConnectionLoader::~ConnectionLoader() { +ConnectionLoader::~ConnectionLoader() { delete d; delete connD; } -void ConnectionLoader::loadConnection() { +void ConnectionLoader::loadConnection() { + QTimer::singleShot(1, [=]() { this->doAutoConnect(); }); + d->exec(); +} + +void ConnectionLoader::doAutoConnect() { // Priority 1: Try to connect to detect zcash.conf and connect to it. auto config = autoDetectZcashConf(); - // If not autodetected, go and read the UI Settings - if (config.get() == nullptr) { - config = loadFromSettings(); + if (config.get() != nullptr) { + auto connection = makeConnection(config); + + refreshZcashdState(connection, [=] () { + // Refused connection. So try and start embedded zcashd + if (Settings::getInstance()->useEmbedded()) { + this->showInformation("Starting Embedded zcashd"); + if (this->startEmbeddedZcashd()) { + // Embedded zcashd started up. Wait a second and then refresh the connection + QTimer::singleShot(1000, [=]() { doAutoConnect(); } ); + } else { + // Errored out, show error and exit + QString explanation = QString() % "Couldn't start the embedded zcashd.\n\n" % + "Maybe the zcash-params are corrupt? Please delete your zcash-params directory and restart.\n\n" % + (ezcashd ? "The process returned:\n\n" % ezcashd->errorString() : QString("")); + this->showError(explanation); + } + } else { + // zcash.conf exists, there's no connection, and the user asked us not to start zcashd. Error! + QString explanation = QString() % "Couldn't connect to zcashd configured in zcash.conf.\n\n" % + "Not starting embedded zcashd because --no-embedded was passed"; + this->showError(explanation); + } + }); + } else { + if (Settings::getInstance()->useEmbedded()) { + // zcash.conf was not found, so create one + createZcashConf(); + } else { + // Fall back to manual connect + doManualConnect(); + } + } +} - if (config.get() == nullptr) { - // Nothing configured, show an error - QString 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 Edit->Settings menu."; +QString randomPassword() { + static const char alphanum[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; - showError(explanation); - doRPCSetConnection(nullptr); + const int passwordLength = 10; + char* s = new char[passwordLength + 1]; + for (int i = 0; i < passwordLength; ++i) { + s[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; + } + + s[passwordLength] = 0; + return QString::fromStdString(s); +} + +/** + * This will create a new zcash.conf, download zcash parameters. + */ +void ConnectionLoader::createZcashConf() { + // Fetch params. After params are fetched, create the zcash.conf file and + // try loading the connection again + downloadParams([=] () { + auto confLocation = zcashConfWritableLocation(); + qDebug() << "Creating file " << confLocation; + + QFileInfo fi(confLocation); + QDir().mkdir(fi.dir().absolutePath()); + + QFile file(confLocation); + if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) { + qDebug() << "Could not create zcash.conf, returning"; return; } + + QTextStream out(&file); + + out << "server=1\n"; + out << "rpcuser=zec-qt-wallet\n"; + out << "rpcpassword=" % randomPassword() << "\n"; + file.close(); + + this->doAutoConnect(); + }); +} + + +void ConnectionLoader::downloadParams(std::function cb) { + // Add all the files to the download queue + downloadQueue = new QQueue(); + client = new QNetworkAccessManager(main); + + downloadQueue->enqueue(QUrl("https://z.cash/downloads/sapling-output.params")); + downloadQueue->enqueue(QUrl("https://z.cash/downloads/sapling-spend.params")); + downloadQueue->enqueue(QUrl("https://z.cash/downloads/sprout-proving.key")); + downloadQueue->enqueue(QUrl("https://z.cash/downloads/sprout-verifying.key")); + downloadQueue->enqueue(QUrl("https://z.cash/downloads/sprout-groth16.params")); + + doNextDownload(cb); +} + +void ConnectionLoader::doNextDownload(std::function cb) { + auto fnSaveFileName = [&] (QUrl url) { + QString path = url.path(); + QString basename = QFileInfo(path).fileName(); + + return basename; + }; + + if (downloadQueue->isEmpty()) { + delete downloadQueue; + client->deleteLater(); + + this->showInformation("All Downloads Finished Successfully!"); + cb(); + return; + } + + QUrl url = downloadQueue->dequeue(); + int filesRemaining = downloadQueue->size(); + + QString filename = fnSaveFileName(url); + QString paramsDir = zcashParamsDir(); + + if (QFile(QDir(paramsDir).filePath(filename)).exists()) { + qDebug() << filename << " already exists, skipping "; + doNextDownload(cb); + + return; + } + + // The downloaded file is written to a new name, and then renamed when the operation completes. + currentOutput = new QFile(QDir(paramsDir).filePath(filename + ".part")); + + if (!currentOutput->open(QIODevice::WriteOnly)) { + this->showError("Couldn't download params. Please check the help site for more info."); + } + qDebug() << "Downloading " << url << " to " << filename; + + QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + currentDownload = client->get(request); + downloadTime.start(); + + // Download Progress + QObject::connect(currentDownload, &QNetworkReply::downloadProgress, [=] (auto done, auto total) { + // calculate the download speed + double speed = done * 1000.0 / downloadTime.elapsed(); + QString unit; + if (speed < 1024) { + unit = "bytes/sec"; + } else if (speed < 1024*1024) { + speed /= 1024; + unit = "kB/s"; + } else { + speed /= 1024*1024; + unit = "MB/s"; + } + + this->showInformation( + "Downloading " % filename % (filesRemaining > 1 ? " ( +" % QString::number(filesRemaining) % " more remaining )" : QString("")), + QString::number(done/1024/1024, 'f', 0) % "MB of " % QString::number(total/1024/1024, 'f', 0) + "MB at " % QString::number(speed, 'f', 2) % unit); + }); + + // Download Finished + QObject::connect(currentDownload, &QNetworkReply::finished, [=] () { + // Rename file + currentOutput->rename(QDir(paramsDir).filePath(filename)); + + currentOutput->close(); + currentDownload->deleteLater(); + currentOutput->deleteLater(); + + if (currentDownload->error()) { + this->showError("Downloading " + filename + " failed/ Please check the help site for more info"); + } else { + doNextDownload(cb); + } + }); + + // Download new data available. + QObject::connect(currentDownload, &QNetworkReply::readyRead, [=] () { + currentOutput->write(currentDownload->readAll()); + }); +} + +bool ConnectionLoader::startEmbeddedZcashd() { + if (!Settings::getInstance()->useEmbedded()) + return false; + + if (ezcashd != nullptr) { + if (ezcashd->state() == QProcess::NotRunning) { + qDebug() << "Process started and then crashed"; + return false; + } else { + return true; + } + } + + // Finally, start zcashd + qDebug() << "Starting zcashd"; + QFileInfo fi(Settings::getInstance()->getExecName()); +#ifdef Q_OS_LINUX + auto zcashdProgram = fi.dir().absoluteFilePath("zcashd"); +#elif defined(Q_OS_DARWIN) + auto zcashdProgram = fi.dir().absoluteFilePath("zcashd"); +#else + auto zcashdProgram = fi.dir().absoluteFilePath("zcashd.exe"); +#endif + + if (!QFile(zcashdProgram).exists()) { + qDebug() << "Can't find zcashd at " << zcashdProgram; + return false; + } + + ezcashd = new QProcess(main); + QObject::connect(ezcashd, &QProcess::started, [=] () { + qDebug() << "zcashd started"; + }); + + QObject::connect(ezcashd, QOverload::of(&QProcess::finished), + [=](int exitCode, QProcess::ExitStatus exitStatus) { + qDebug() << "zcashd finished with code " << exitCode << "," << exitStatus; + }); + + QObject::connect(ezcashd, &QProcess::errorOccurred, [&] (auto error) mutable { + qDebug() << "Couldn't start zcashd: " << error; + }); + + ezcashd->start(zcashdProgram); + + return true; +} + +void ConnectionLoader::doManualConnect() { + auto config = loadFromSettings(); + + if (!config) { + // Nothing configured, show an error + QString explanation = QString() + % "A manual connection was requested, but the settings are not configured.\n\n" + % "Please set the host/port and user/password in the Edit->Settings menu."; + + showError(explanation); + doRPCSetConnection(nullptr); + + return; } auto connection = makeConnection(config); - refreshZcashdState(connection); + refreshZcashdState(connection, [=] () { + QString explanation = QString() + % "Could not connect to zcashd configured in settings.\n\n" + % "Please set the host/port and user/password in the Edit->Settings menu."; + + showError(explanation); + doRPCSetConnection(nullptr); + + return; + }); } void ConnectionLoader::doRPCSetConnection(Connection* conn) { + rpc->setEZcashd(ezcashd); rpc->setConnection(conn); + + d->accept(); + delete this; } @@ -79,7 +318,7 @@ Connection* ConnectionLoader::makeConnection(std::shared_ptr c return new Connection(main, client, request, config); } -void ConnectionLoader::refreshZcashdState(Connection* connection) { +void ConnectionLoader::refreshZcashdState(Connection* connection, std::function refused) { json payload = { {"jsonrpc", "1.0"}, {"id", "someid"}, @@ -91,53 +330,49 @@ void ConnectionLoader::refreshZcashdState(Connection* connection) { d->hide(); this->doRPCSetConnection(connection); }, - [=] (auto reply, auto res) { - auto err = reply->error(); + [=] (auto reply, auto res) { // Failed, see what it is. + auto err = reply->error(); //qDebug() << err << ":" << QString::fromStdString(res.dump()); - if (err == QNetworkReply::NetworkError::ConnectionRefusedError) { - auto isZcashConfFound = connection->config.get()->usingZcashConf; - QString explanation = QString() - % (isZcashConfFound ? "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 Edit->Settings menu"; - - this->showError(explanation); + if (err == QNetworkReply::NetworkError::ConnectionRefusedError) { + refused(); } else if (err == QNetworkReply::NetworkError::AuthenticationRequiredError) { QString explanation = QString() % "Authentication failed. The username / password you specified was " % "not accepted by zcashd. Try changing it in the Edit->Settings menu"; this->showError(explanation); - } else if (err == QNetworkReply::NetworkError::InternalServerError && !res.is_discarded()) { - d->show(); - + } else if (err == QNetworkReply::NetworkError::InternalServerError && + !res.is_discarded()) { // The server is loading, so just poll until it succeeds - QString status = QString::fromStdString(res["error"]["message"]); - - QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); - connD->icon->setPixmap(icon.pixmap(128, 128)); - connD->status->setText("Your zcashd is starting up. Please wait.\n\n" % status); + QString status = QString::fromStdString(res["error"]["message"]); + this->showInformation("Your zcashd is starting up. Please wait.", status); // Refresh after one second - QTimer::singleShot(1000, [=]() { this->refreshZcashdState(connection); }); + QTimer::singleShot(1000, [=]() { this->refreshZcashdState(connection, refused); }); } } ); } -void ConnectionLoader::showError(QString explanation) { - QMessageBox::critical(main, "Connection Error", explanation, QMessageBox::Ok); +void ConnectionLoader::showInformation(QString info, QString detail) { + connD->status->setText(info); + connD->statusDetail->setText(detail); } - /** - * Try to automatically detect a zcash.conf file in the correct location and load parameters - */ -std::shared_ptr ConnectionLoader::autoDetectZcashConf() { + * Show error will close the loading dialog and show an error. +*/ +void ConnectionLoader::showError(QString explanation) { + rpc->setEZcashd(nullptr); + rpc->noConnection(); + + QMessageBox::critical(main, "Connection Error", explanation, QMessageBox::Ok); + d->close(); +} +QString ConnectionLoader::locateZcashConfFile() { #ifdef Q_OS_LINUX auto confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, ".zcash/zcash.conf"); #elif defined(Q_OS_DARWIN) @@ -145,8 +380,42 @@ std::shared_ptr ConnectionLoader::autoDetectZcashConf() { #else auto confLocation = QStandardPaths::locate(QStandardPaths::AppDataLocation, "../../Zcash/zcash.conf"); #endif + return QDir::cleanPath(confLocation); +} + +QString ConnectionLoader::zcashConfWritableLocation() { +#ifdef Q_OS_LINUX + auto confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath(".zcash/zcash.conf"); +#elif defined(Q_OS_DARWIN) + auto confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath("/Library/Application Support/Zcash/zcash.conf"); +#else + auto confLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("../../Zcash/zcash.conf"); +#endif - confLocation = QDir::cleanPath(confLocation); + return confLocation; +} + +QString ConnectionLoader::zcashParamsDir() { + #ifdef Q_OS_LINUX + auto paramsLocation = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath(".zcash-params")); +#elif defined(Q_OS_DARWIN) + //auto paramsLocation = QDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).filePath("/Library/Application Support/Zcash/zcash.conf"); +#else + auto paramsLocation = QDir(QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("../../ZcashParams")); +#endif + + if (!paramsLocation.exists()) { + QDir().mkpath(paramsLocation.absolutePath()); + } + + return paramsLocation.absolutePath(); +} + +/** + * Try to automatically detect a zcash.conf file in the correct location and load parameters + */ +std::shared_ptr ConnectionLoader::autoDetectZcashConf() { + auto confLocation = locateZcashConfFile(); if (confLocation.isNull()) { // No zcash file, just return with nothing @@ -240,10 +509,19 @@ Connection::~Connection() { void Connection::doRPC(const json& payload, const std::function& cb, const std::function& ne) { + if (shutdownInProgress) { + qDebug() << "Ignoring RPC because shutdown in progress"; + return; + } + QNetworkReply *reply = restclient->post(*request, QByteArray::fromStdString(payload.dump())); QObject::connect(reply, &QNetworkReply::finished, [=] { reply->deleteLater(); + if (shutdownInProgress) { + qDebug() << "Ignoring callback because shutdown in progress"; + return; + } if (reply->error() != QNetworkReply::NoError) { auto parsed = json::parse(reply->readAll(), nullptr, false); @@ -283,3 +561,10 @@ void Connection::showTxError(const QString& error) { QMessageBox::critical(main, "Transaction Error", "There was an error sending the transaction. The error was: \n\n" + error, QMessageBox::StandardButton::Ok); } + +/** + * Prevent all future calls from going through + */ +void Connection::shutdown() { + shutdownInProgress = true; +} diff --git a/src/connection.h b/src/connection.h index d6c26a9..f416653 100644 --- a/src/connection.h +++ b/src/connection.h @@ -41,18 +41,39 @@ private: Connection* makeConnection(std::shared_ptr config); - void refreshZcashdState(Connection* connection); - int getProgressFromStatus(QString status); + void doAutoConnect(); + void doManualConnect(); + + void createZcashConf(); + QString locateZcashConfFile(); + QString zcashConfWritableLocation(); + QString zcashParamsDir(); + + void downloadParams(std::function cb); + void doNextDownload(std::function cb); + bool startEmbeddedZcashd(); + + void refreshZcashdState(Connection* connection, std::function refused); void showError(QString explanation); + void showInformation(QString info, QString detail = ""); void doRPCSetConnection(Connection* conn); + QProcess* ezcashd = nullptr; + QDialog* d; Ui_ConnectionDialog* connD; MainWindow* main; RPC* rpc; + + QNetworkReply* currentDownload = nullptr; + QFile* currentOutput = nullptr; + QQueue* downloadQueue = nullptr; + + QNetworkAccessManager* client = nullptr; + QTime downloadTime; }; /** @@ -69,6 +90,8 @@ public: std::shared_ptr config; MainWindow* main; + void shutdown(); + void doRPC(const json& payload, const std::function& cb, const std::function& ne); void doRPCWithDefaultErrorHandling(const json& payload, const std::function& cb); @@ -122,6 +145,9 @@ public: }); waitTimer->start(100); } + +private: + bool shutdownInProgress = false; }; #endif diff --git a/src/connection.ui b/src/connection.ui index a4d98af..9a33ed6 100644 --- a/src/connection.ui +++ b/src/connection.ui @@ -2,6 +2,9 @@ ConnectionDialog + + Qt::ApplicationModal + 0 @@ -16,98 +19,75 @@ true - - - + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + Qt::Horizontal - - QDialogButtonBox::Close - - - + + - TextLabel + Starting Up Qt::AlignCenter - - 20 - - - - - - - Qt::Horizontal + + true - - - - Qt::Vertical - - - - 20 - 40 - - - - - - + + - Connection Status + - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignCenter - - true + + + + + + Qt::Horizontal + + + FilledIconLabel + QLabel +
fillediconlabel.h
+
+
- - - buttonBox - accepted() - ConnectionDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - ConnectionDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - +
diff --git a/src/fillediconlabel.cpp b/src/fillediconlabel.cpp new file mode 100644 index 0000000..0982cab --- /dev/null +++ b/src/fillediconlabel.cpp @@ -0,0 +1,27 @@ +#include "fillediconlabel.h" + +FilledIconLabel::FilledIconLabel(QWidget* parent) : + QLabel(parent) { + this->setMinimumSize(1, 1); + setScaledContents(false); +} + +void FilledIconLabel::setBasePixmap(QPixmap pm) { + basePm = pm; +} + +/** + * When resized, we re-draw the whole pixmap, resizing it as needed. + */ +void FilledIconLabel::resizeEvent(QResizeEvent*) { + QSize sz = size(); + + QPixmap scaled = basePm.scaled(sz, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + QPixmap p(sz); + p.fill(Qt::white); + QPainter painter(&p); + painter.drawPixmap((sz.width() - scaled.width()) / 2, (sz.height() - scaled.height()) / 2, scaled); + + QLabel::setPixmap(p); +} \ No newline at end of file diff --git a/src/fillediconlabel.h b/src/fillediconlabel.h new file mode 100644 index 0000000..d5de800 --- /dev/null +++ b/src/fillediconlabel.h @@ -0,0 +1,21 @@ +#ifndef FILLEDICONLABEL_H +#define FILLEDICONLABEL_H + +#include "precompiled.h" + +class FilledIconLabel : public QLabel +{ + Q_OBJECT +public: + explicit FilledIconLabel(QWidget *parent = 0); + void setBasePixmap(QPixmap pm); + +public slots: + void resizeEvent(QResizeEvent *); + +private: + QPixmap basePm; +}; + + +#endif // FILLEDICONLABEL_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index eb518f3..76fcd14 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,13 @@ int main(int argc, char *argv[]) std::srand(static_cast(std::time(nullptr))); Settings::init(); + Settings::getInstance()->setExecName(argv[0]); + + if (argc >= 2 && QString::fromStdString(argv[1]) == "--no-embedded") { + Settings::getInstance()->setUseEmbedded(false); + } else { + Settings::getInstance()->setUseEmbedded(true); + } QCoreApplication::setOrganizationName("zec-qt-wallet-org"); QCoreApplication::setApplicationName("zec-qt-wallet"); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 52f51dc..cd97d07 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -75,6 +75,7 @@ MainWindow::MainWindow(QWidget *parent) : setupRecieveTab(); setupBalancesTab(); setupTurnstileDialog(); + setupZcashdTab(); rpc = new RPC(this); @@ -97,6 +98,10 @@ void MainWindow::closeEvent(QCloseEvent* event) { s.setValue("baltablegeometry", ui->balancesTable->horizontalHeader()->saveState()); s.setValue("tratablegeometry", ui->transactionsTable->horizontalHeader()->saveState()); + // Let the RPC know to shutdown any running service. + rpc->shutdownZcashd(); + + // Bubble up QMainWindow::closeEvent(event); } @@ -369,13 +374,6 @@ void MainWindow::setupSettingsModal() { QIntValidator validator(0, 65535); settings.port->setValidator(&validator); - // Load current values into the dialog - auto conf = Settings::getInstance()->getSettings(); - settings.hostname->setText(conf.host); - settings.port->setText(conf.port); - settings.rpcuser->setText(conf.rpcuser); - settings.rpcpassword->setText(conf.rpcpassword); - // If values are coming from zcash.conf, then disable all the fields auto zcashConfLocation = Settings::getInstance()->getZcashdConfLocation(); if (!zcashConfLocation.isEmpty()) { @@ -386,6 +384,13 @@ void MainWindow::setupSettingsModal() { settings.rpcpassword->setEnabled(false); } else { + // Load current values into the dialog + auto conf = Settings::getInstance()->getSettings(); + settings.hostname->setText(conf.host); + settings.port->setText(conf.port); + settings.rpcuser->setText(conf.rpcuser); + settings.rpcpassword->setText(conf.rpcpassword); + settings.confMsg->setText("No local zcash.conf found. Please configure connection manually."); settings.hostname->setEnabled(true); settings.port->setEnabled(true); @@ -739,6 +744,10 @@ void MainWindow::setupBalancesTab() { }); } +void MainWindow::setupZcashdTab() { + ui->zcashdlogo->setBasePixmap(QPixmap(":/img/res/zcashdlogo.gif")); +} + void MainWindow::setupTransactionsTab() { // Double click opens up memo if one exists QObject::connect(ui->transactionsTable, &QTableView::doubleClicked, [=] (auto index) { diff --git a/src/mainwindow.h b/src/mainwindow.h index a8521ed..33d8f53 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -51,6 +51,7 @@ private: void setupTransactionsTab(); void setupRecieveTab(); void setupBalancesTab(); + void setupZcashdTab(); void setupTurnstileDialog(); void setupSettingsModal(); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 838cd52..0a4aacd 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 889 - 603 + 968 + 616 @@ -22,7 +22,7 @@ - 1 + 4 @@ -316,8 +316,8 @@ 0 0 - 841 - 321 + 922 + 376 @@ -715,6 +715,145 @@ + + + zcashd + + + + + + + + + 0 + 0 + + + + + + + false + + + + + + + + + + + + + You are currently not mining + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Loading... + + + + + + + Block height + + + + + + + Loading... + + + + + + + Network solution rate + + + + + + + Connections + + + + + + + Loading... + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + | + + + + + + + | + + + + + + + | + + + + + + + + + + @@ -724,8 +863,8 @@ 0 0 - 889 - 22 + 968 + 21 @@ -835,6 +974,11 @@ QLabel
qrcodelabel.h
+ + FilledIconLabel + QLabel +
fillediconlabel.h
+
tabWidget diff --git a/src/precompiled.h b/src/precompiled.h index 9c8f162..916fb43 100644 --- a/src/precompiled.h +++ b/src/precompiled.h @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/src/rpc.cpp b/src/rpc.cpp index 9dac2c7..a0aa2c5 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -8,7 +8,9 @@ using json = nlohmann::json; RPC::RPC(MainWindow* main) { auto cl = new ConnectionLoader(main, this); - cl->loadConnection(); + + // Execute the load connection async, so we can set up the rest of RPC properly. + QTimer::singleShot(1, [=]() {cl->loadConnection(); }); this->main = main; this->ui = main->ui; @@ -62,12 +64,21 @@ RPC::~RPC() { delete conn; } +void RPC::setEZcashd(QProcess* p) { + ezcashd = p; + + if (ezcashd == nullptr) + ui->tabWidget->removeTab(4); +} + void RPC::setConnection(Connection* c) { if (c == nullptr) return; delete conn; this->conn = c; + ui->statusBar->showMessage("Ready!"); + refreshZECPrice(); refresh(); } @@ -209,6 +220,10 @@ void RPC::sendZTransaction(json params, const std::function& cb) { * private keys */ void RPC::getAllPrivKeys(const std::function>)> cb) { + if (conn == nullptr) { + // No connection, just return + return; + } // A special function that will call the callback when two lists have been added auto holder = new QPair>>(); @@ -314,8 +329,10 @@ void RPC::fillTxJsonParams(json& params, Tx tx) { } -void RPC::noConnection() { - ui->statusBar->showMessage("No Connection to zcashd"); +void RPC::noConnection() { + QIcon i = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical); + main->statusIcon->setPixmap(i.pixmap(16, 16)); + main->statusLabel->setText("No Connection"); } // Refresh received z txs by calling z_listreceivedbyaddress/gettransaction @@ -427,7 +444,6 @@ void RPC::refreshReceivedZTrans(QList zaddrs) { ); } - /// This will refresh all the balance data from zcashd void RPC::refresh(bool force) { if (conn == nullptr) @@ -469,6 +485,25 @@ void RPC::getInfoThenRefresh(bool force) { refreshTransactions(); } + // Get network sol/s + if (ezcashd) { + int conns = reply["connections"].get(); + + json payload = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", "getnetworksolps"} + }; + + conn->doRPCIgnoreError(payload, [=](const json& reply) { + qint64 solrate = reply.get(); + + ui->blockheight->setText(QString::number(curBlock)); + ui->numconnections->setText(QString::number(conns)); + ui->solrate->setText(QString::number(solrate) % " Sol/s"); + }); + } + // Call to see if the blockchain is syncing. json payload = { {"jsonrpc", "1.0"}, @@ -478,12 +513,24 @@ void RPC::getInfoThenRefresh(bool force) { conn->doRPCIgnoreError(payload, [=](const json& reply) { auto progress = reply["verificationprogress"].get(); - bool isSyncing = progress < 0.999; // 99.9% + bool isSyncing = progress < 0.995; // 99.59% int blockNumber = reply["blocks"].get(); Settings::getInstance()->setSyncing(isSyncing); Settings::getInstance()->setBlockNumber(blockNumber); + // Update zcashd tab if it exists + if (ezcashd && isSyncing) { + // 895 / ~426530 (0 % ) + const qint64 genisisTimeMSec = 1477638000000; + qint64 estBlocks = (QDateTime::currentMSecsSinceEpoch() - genisisTimeMSec) / 2.5 / 60 / 1000; + // Round to nearest 10 + estBlocks = ((estBlocks + 5) / 10) * 10; + ui->blockheight->setText(ui->blockheight->text() % /*" / ~" % QString::number(estBlocks) % */ + " ( " % QString::number(progress * 100, 'f', 0) % "% )"); + } + + // Update the status bar QString statusText = QString() % (isSyncing ? "Syncing" : "Connected") % " (" % @@ -790,7 +837,7 @@ void RPC::refreshZECPrice() { } for (const json& item : parsed.get()) { - if (item["symbol"].get().compare("ZEC") == 0) { + if (item["symbol"].get() == "ZEC") { QString price = QString::fromStdString(item["price_usd"].get()); qDebug() << "ZEC Price=" << price; Settings::getInstance()->setZECPrice(price.toDouble()); @@ -808,6 +855,52 @@ void RPC::refreshZECPrice() { }); } +void RPC::shutdownZcashd() { + // Shutdown embedded zcashd if it was started + if (ezcashd == nullptr || conn == nullptr) { + // No zcashd running internally, just return + return; + } + + json payload = { + {"jsonrpc", "1.0"}, + {"id", "someid"}, + {"method", "stop"} + }; + + conn->doRPCWithDefaultErrorHandling(payload, [=](auto) {}); + conn->shutdown(); + + QDialog d(main); + Ui_ConnectionDialog connD; + connD.setupUi(&d); + connD.topIcon->setBasePixmap(QIcon(":/icons/res/icon.ico").pixmap(256, 256)); + connD.status->setText("Please wait for zec-qt-wallet to exit"); + connD.statusDetail->setText("Waiting for zcashd to exit"); + + QTimer waiter(main); + + // We capture by reference all the local variables because of the d.exec() + // below, which blocks this function until we exit. + int waitCount = 0; + QObject::connect(&waiter, &QTimer::timeout, [&] () { + waitCount++; + if ((ezcashd->atEnd() && ezcashd->processId() == 0) || + waitCount > 30) { + qDebug() << "Ended"; + waiter.stop(); + QTimer::singleShot(1000, [&]() { d.accept(); }); + } else { + qDebug() << "Not ended, continuing to wait..."; + } + }); + waiter.start(1000); + + // Wait for the zcash process to exit. + d.exec(); +} + + // Fetch the Z-board topics list void RPC::getZboardTopics(std::function)> cb) { if (conn == nullptr) diff --git a/src/rpc.h b/src/rpc.h index 6ce2fc8..ae2b1ef 100644 --- a/src/rpc.h +++ b/src/rpc.h @@ -32,6 +32,8 @@ public: ~RPC(); void setConnection(Connection* c); + void setEZcashd(QProcess* p); + const QProcess* getEZcashD() { return ezcashd; } void refresh(bool force = false); @@ -58,14 +60,15 @@ public: void importZPrivKey(QString addr, bool rescan, const std::function& cb); void importTPrivKey(QString addr, bool rescan, const std::function& cb); + void shutdownZcashd(); + void noConnection(); + void getAllPrivKeys(const std::function>)>); Turnstile* getTurnstile() { return turnstile; } Connection* getConnection() { return conn; } private: - void noConnection(); - void refreshBalances(); void refreshTransactions(); @@ -88,6 +91,7 @@ private: void handleTxError (const QString& error); Connection* conn = nullptr; + QProcess* ezcashd = nullptr; QList* utxos = nullptr; QMap* allBalances = nullptr; @@ -108,6 +112,7 @@ private: // Current balance in the UI. If this number updates, then refresh the UI QString currentBalance; + // First time warning flag for no connection bool firstTime = true; }; diff --git a/src/scripts/mkrelease.sh b/src/scripts/mkrelease.sh index ff234a3..211dfd8 100755 --- a/src/scripts/mkrelease.sh +++ b/src/scripts/mkrelease.sh @@ -9,7 +9,17 @@ if [ -z $MXE_PATH ]; then echo "MXE_PATH is not set. Set it to ~/github/mxe/usr/ if [ -z $APP_VERSION ]; then echo "APP_VERSION is not set"; exit 1; fi if [ -z $PREV_VERSION ]; then echo "PREV_VERSION is not set"; exit 1; fi -echo -n "Version files." +if [ ! -f ../zcash/artifacts/zcashd ]; then + echo "Couldn't find zcashd in ../zcash/artifacts/. Please build zcashd." + exit 1; +fi + +if [ ! -f ../zcash/artifacts/zcashd.exe ]; then + echo "Couldn't find zcashd.exe in ../zcash/artifacts/. Please build zcashd.exe" + exit 1; +fi + +echo -n "Version files....." # Replace the version number in the .pro file so it gets picked up everywhere sed -i "s/${PREV_VERSION}/${APP_VERSION}/g" zec-qt-wallet.pro > /dev/null @@ -17,29 +27,29 @@ sed -i "s/${PREV_VERSION}/${APP_VERSION}/g" zec-qt-wallet.pro > /dev/null sed -i "s/${PREV_VERSION}/${APP_VERSION}/g" README.md > /dev/null echo "[OK]" -echo -n "Cleaning......" -rm -f bin/linux-zec-qt-wallet* -rm -rf release/ +echo -n "Cleaning.........." +rm -rf bin/* rm -rf artifacts/* make distclean > /dev/null echo "[OK]" -echo "Linux" +echo "" +echo "[Linux]" -echo -n "Configuring..." +echo -n "Configuring......." $QT_STATIC/bin/qmake zec-qt-wallet.pro -spec linux-clang CONFIG+=release > /dev/null #Mingw seems to have trouble with precompiled headers, so strip that option from the .pro file echo "[OK]" -echo -n "Building......" +echo -n "Building.........." rm -rf bin/zec-qt-wallet* > /dev/null make -j$(nproc) > /dev/null echo "[OK]" # Test for Qt -echo -n "Static link..." +echo -n "Static link......." if [[ $(ldd zec-qt-wallet | grep -i "Qt") ]]; then echo "FOUND QT; ABORT"; exit 1 @@ -47,33 +57,38 @@ fi echo "[OK]" -echo -n "Packaging....." -strip zec-qt-wallet +echo -n "Packaging........." mkdir bin/zec-qt-wallet-v$APP_VERSION > /dev/null +strip zec-qt-wallet cp zec-qt-wallet bin/zec-qt-wallet-v$APP_VERSION > /dev/null +cp ../zcash/artifacts/zcashd bin/zec-qt-wallet-v$APP_VERSION > /dev/null cp README.md bin/zec-qt-wallet-v$APP_VERSION > /dev/null cp LICENSE bin/zec-qt-wallet-v$APP_VERSION > /dev/null cd bin && tar cvf linux-zec-qt-wallet-v$APP_VERSION.tar.gz zec-qt-wallet-v$APP_VERSION/ > /dev/null cd .. mkdir artifacts >/dev/null 2>&1 cp bin/linux-zec-qt-wallet-v$APP_VERSION.tar.gz ./artifacts +echo "[OK]" -if [ -f artifacts/linux-zec-qt-wallet-v$APP_VERSION.tar.gz ] ; then - echo "[OK]" - echo "Done. Build is artifacts/linux-zec-qt-wallet-v$APP_VERSION.tar.gz" - echo "Package contents:" - tar tf "artifacts/linux-zec-qt-wallet-v$APP_VERSION.tar.gz" +if [ -f artifacts/linux-zec-qt-wallet-v$APP_VERSION.tar.gz ] ; then + echo -n "Package contents.." + # Test if the package is built OK + if tar tf "artifacts/linux-zec-qt-wallet-v$APP_VERSION.tar.gz" | wc -l | grep -q "5"; then + echo "[OK]" + else + exit 1 + fi else echo "[ERROR]" exit 1 fi -echo "Windows" - +echo "" +echo "[Windows]" export PATH=$MXE_PATH:$PATH -echo -n "Configuring..." +echo -n "Configuring......." make clean > /dev/null rm -f zec-qt-wallet-mingw.pro rm -rf release/ @@ -82,30 +97,38 @@ cat zec-qt-wallet.pro | sed "s/precompile_header/release/g" | sed "s/PRECOMPILED echo "[OK]" -echo -n "Building......" +echo -n "Building.........." x86_64-w64-mingw32.static-qmake-qt5 zec-qt-wallet-mingw.pro CONFIG+=release > /dev/null make -j32 > /dev/null echo "[OK]" -echo -n "Packaging....." +echo -n "Packaging........." mkdir release/zec-qt-wallet-v$APP_VERSION cp release/zec-qt-wallet.exe release/zec-qt-wallet-v$APP_VERSION +cp ../zcash/artifacts/zcashd.exe release/zec-qt-wallet-v$APP_VERSION > /dev/null cp README.md release/zec-qt-wallet-v$APP_VERSION cp LICENSE release/zec-qt-wallet-v$APP_VERSION cd release && zip -r Windows-zec-qt-wallet-v$APP_VERSION.zip zec-qt-wallet-v$APP_VERSION/ > /dev/null cd .. mkdir artifacts >/dev/null 2>&1 cp release/Windows-zec-qt-wallet-v$APP_VERSION.zip ./artifacts - +echo "[OK]" if [ -f artifacts/Windows-zec-qt-wallet-v$APP_VERSION.zip ] ; then - echo "[OK]" - - echo "Done. Build is artifacts/Windows-zec-qt-wallet-v$APP_VERSION.zip" - echo "Package contents:" - unzip -l "artifacts/Windows-zec-qt-wallet-v$APP_VERSION.zip" + echo -n "Package contents.." + if unzip -l "artifacts/Windows-zec-qt-wallet-v$APP_VERSION.zip" | wc -l | grep -q "10"; then + echo "[OK]" + else + echo "[ERROR]" + exit 1 + fi + else echo "[ERROR]" exit 1 fi + +echo "" +echo "Build is artifacts/Windows-zec-qt-wallet-v$APP_VERSION.zip" +echo "Build is artifacts/linux-zec-qt-wallet-v$APP_VERSION.tar.gz" \ No newline at end of file diff --git a/src/senttxstore.cpp b/src/senttxstore.cpp index f5d587d..bb77a8b 100644 --- a/src/senttxstore.cpp +++ b/src/senttxstore.cpp @@ -31,7 +31,7 @@ QList SentTxStore::readSentTxFile() { QJsonDocument jsonDoc; data.open(QFile::ReadOnly); - jsonDoc = QJsonDocument().fromJson(data.readAll()); + jsonDoc = QJsonDocument::fromJson(data.readAll()); data.close(); QList items; @@ -87,7 +87,7 @@ void SentTxStore::addToSentTx(Tx tx, QString txid) { QJsonObject txItem; txItem["type"] = "sent"; txItem["from"] = tx.fromAddr; - txItem["datetime"] = QDateTime().currentMSecsSinceEpoch() / (qint64)1000; + txItem["datetime"] = QDateTime::currentMSecsSinceEpoch() / (qint64)1000; txItem["address"] = QString(); // The sent address is blank, to be consistent with t-Addr sent behaviour txItem["txid"] = txid; txItem["amount"] = -totalAmount; diff --git a/src/settings.cpp b/src/settings.cpp index a28033e..8ae7502 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -5,9 +5,6 @@ Settings* Settings::instance = nullptr; -Settings::~Settings() { -} - bool Settings::getSaveZtxs() { // Load from the QT Settings. return QSettings().value("options/savesenttx", true).toBool(); @@ -124,11 +121,10 @@ QString Settings::getZECUSDDisplayFormat(double bal) { return getZECDisplayFormat(bal); } - void Settings::saveRestore(QDialog* d) { d->restoreGeometry(QSettings().value(d->objectName() % "geometry").toByteArray()); QObject::connect(d, &QDialog::finished, [=](auto) { QSettings().setValue(d->objectName() % "geometry", d->saveGeometry()); }); -} \ No newline at end of file +} diff --git a/src/settings.h b/src/settings.h index 2ad1dc6..b13586f 100644 --- a/src/settings.h +++ b/src/settings.h @@ -31,6 +31,12 @@ public: bool isSyncing(); void setSyncing(bool syncing); + QString getExecName() { return _executable; } + void setExecName(QString name) { _executable = name; } + + void setUseEmbedded(bool r) { _useEmbedded = r; } + bool useEmbedded() { return _useEmbedded; } + int getBlockNumber(); void setBlockNumber(int number); @@ -57,9 +63,11 @@ private: static Settings* instance; QString _confLocation; - bool _isTestnet = false; - bool _isSyncing = false; - int _blockNumber = 0; + QString _executable; + bool _isTestnet = false; + bool _isSyncing = false; + int _blockNumber = 0; + bool _useEmbedded = false; double zecPrice = 0.0; }; diff --git a/src/turnstile.cpp b/src/turnstile.cpp index fb2211f..64ec6e0 100644 --- a/src/turnstile.cpp +++ b/src/turnstile.cpp @@ -14,8 +14,6 @@ Turnstile::Turnstile(RPC* _rpc, MainWindow* mainwindow) { this->mainwindow = mainwindow; } -Turnstile::~Turnstile() { -} void printPlan(QList plan) { for (auto item : plan) { @@ -118,7 +116,7 @@ void Turnstile::planMigration(QString zaddr, QString destAddr, int numsplits, in // The first migration is shifted to the current block, so the user sees something // happening immediately - if (migItems.size() == 0) { + if (migItems.empty()) { // Show error and abort QMessageBox::warning(mainwindow, "Locked funds", @@ -220,9 +218,7 @@ Turnstile::getNextStep(QList& plan) { bool Turnstile::isMigrationPresent() { auto plan = readMigrationPlan(); - if (plan.isEmpty()) return false; - - return true; + return !plan.isEmpty(); } ProgressReport Turnstile::getPlanProgress() { diff --git a/src/turnstile.h b/src/turnstile.h index a73af50..aba1a3e 100644 --- a/src/turnstile.h +++ b/src/turnstile.h @@ -39,7 +39,6 @@ class Turnstile { public: Turnstile(RPC* _rpc, MainWindow* mainwindow); - ~Turnstile(); void planMigration(QString zaddr, QString destAddr, int splits, int numBlocks); QList splitAmount(double amount, int parts); diff --git a/zec-qt-wallet.pro b/zec-qt-wallet.pro index 2ab0e3a..be1c031 100644 --- a/zec-qt-wallet.pro +++ b/zec-qt-wallet.pro @@ -54,6 +54,7 @@ SOURCES += \ src/utils.cpp \ src/qrcodelabel.cpp \ src/connection.cpp \ + src/fillediconlabel.cpp \ src/addressbook.cpp HEADERS += \ @@ -73,6 +74,7 @@ HEADERS += \ src/utils.h \ src/qrcodelabel.h \ src/connection.h \ + src/fillediconlabel.h \ src/addressbook.h FORMS += \