diff --git a/MiddlePanel.qml b/MiddlePanel.qml index e14255a5..16cc9492 100644 --- a/MiddlePanel.qml +++ b/MiddlePanel.qml @@ -32,6 +32,7 @@ import QtQuick 2.2 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.1 import QtGraphicalEffects 1.0 +import moneroComponents.Wallet 1.0 import "./pages" @@ -56,9 +57,6 @@ Rectangle { signal generatePaymentIdInvoked() signal checkPaymentClicked(string address, string txid, string txkey); - // Disable transfer page if daemon isnt fully synced - enabled: (currentView !== transferView || appWindow.daemonSynced) - color: "#F0EEEE" onCurrentViewChanged: { @@ -72,6 +70,10 @@ Rectangle { } } + function updateStatus(){ + transferView.updateStatus(); + } + // XXX: just for memo, to be removed // states: [ @@ -292,14 +294,6 @@ Rectangle { color: "#DBDBDB" } - Rectangle { - id:desaturate - color:"black" - anchors.fill: parent - opacity: 0.1 - visible: (root.enabled)? 0 : 1; - } - /* connect "payment" click */ Connections { diff --git a/components/DaemonManagerDialog.qml b/components/DaemonManagerDialog.qml new file mode 100644 index 00000000..da3cf582 --- /dev/null +++ b/components/DaemonManagerDialog.qml @@ -0,0 +1,120 @@ +// Copyright (c) 2014-2015, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import QtQuick 2.0 +import QtQuick.Controls 1.4 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Window 2.0 + +import "../components" as MoneroComponents + +Window { + id: root + modality: Qt.ApplicationModal + flags: Qt.Window | Qt.FramelessWindowHint + + signal rejected() + signal started(); + + function open() { + show() + } + + // TODO: implement without hardcoding sizes + width: 480 + height: 200 + + ColumnLayout { + id: mainLayout + spacing: 10 + anchors { fill: parent; margins: 35 } + + ColumnLayout { + id: column + //anchors {fill: parent; margins: 16 } + Layout.alignment: Qt.AlignHCenter + + Label { + text: qsTr("Daemon doesn't appear to be running") + Layout.alignment: Qt.AlignHCenter + Layout.columnSpan: 2 + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + font.pixelSize: 24 + font.family: "Arial" + color: "#555555" + } + + } + + RowLayout { + id: buttons + spacing: 60 + Layout.alignment: Qt.AlignHCenter + + MoneroComponents.StandardButton { + id: okButton + width: 120 + fontSize: 14 + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + text: qsTr("Start daemon") + KeyNavigation.tab: cancelButton + onClicked: { + root.close() + appWindow.startDaemon(); + root.started() + } + } + + MoneroComponents.StandardButton { + id: cancelButton + width: 120 + fontSize: 14 + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + text: qsTr("Cancel") + + onClicked: { + root.close() + root.rejected() + } + } + } + } + +} + + + diff --git a/components/DaemonProgress.qml b/components/DaemonProgress.qml index 5fac2079..53a2f5c7 100644 --- a/components/DaemonProgress.qml +++ b/components/DaemonProgress.qml @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import QtQuick 2.0 +import moneroComponents.Wallet 1.0 Item { id: item @@ -45,7 +46,7 @@ Item { // TODO: lower daemon block height cache, ttl and refresh interval? - item.visible = (currentBlock < targetBlock) + item.visible = (currentWallet.connected !== Wallet.ConnectionStatus_Disconnected) && (currentBlock < targetBlock) } } diff --git a/components/HistoryTable.qml b/components/HistoryTable.qml index 5f4584c7..0c9e9b60 100644 --- a/components/HistoryTable.qml +++ b/components/HistoryTable.qml @@ -69,6 +69,7 @@ ListView { StandardDialog { id: detailsPopup + width:600 cancelVisible: false okVisible: true } diff --git a/components/PasswordDialog.qml b/components/PasswordDialog.qml index 325bf2b3..19414987 100644 --- a/components/PasswordDialog.qml +++ b/components/PasswordDialog.qml @@ -152,8 +152,8 @@ Window { text: qsTr("Cancel") KeyNavigation.tab: passwordInput onClicked: { - root.rejected() root.close() + root.rejected() } } MoneroComponents.StandardButton { @@ -167,8 +167,8 @@ Window { text: qsTr("Ok") KeyNavigation.tab: cancelButton onClicked: { - root.accepted() root.close() + root.accepted() } } } diff --git a/components/StandardDialog.qml b/components/StandardDialog.qml index 0e081ae5..03c64a64 100644 --- a/components/StandardDialog.qml +++ b/components/StandardDialog.qml @@ -40,9 +40,12 @@ Window { modality: Qt.ApplicationModal flags: Qt.Window | Qt.FramelessWindowHint property alias title: dialogTitle.text - property alias content: dialogContent.text + property alias text: dialogContent.text + property alias content: root.text property alias cancelVisible: cancelButton.visible property alias okVisible: okButton.visible + property alias textArea: dialogContent + property var icon // same signals as Dialog has signal accepted() @@ -54,8 +57,8 @@ Window { } // TODO: implement without hardcoding sizes - width: 600 - height: 480 + width: 480 + height: 280 ColumnLayout { id: mainLayout @@ -81,6 +84,7 @@ Window { TextArea { id : dialogContent Layout.fillWidth: true + Layout.fillHeight: true font.family: "Arial" textFormat: TextEdit.AutoText readOnly: true @@ -105,8 +109,9 @@ Window { text: qsTr("Ok") KeyNavigation.tab: cancelButton onClicked: { - root.accepted() root.close() + root.accepted() + } } @@ -120,8 +125,8 @@ Window { pressedColor: "#FF4304" text: qsTr("Cancel") onClicked: { - root.rejected() root.close() + root.rejected() } } } diff --git a/main.cpp b/main.cpp index fe42cb61..e9ea5a56 100644 --- a/main.cpp +++ b/main.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "clipboardAdapter.h" #include "filter.h" #include "oscursor.h" @@ -44,6 +45,7 @@ #include "TransactionHistory.h" #include "model/TransactionHistoryModel.h" #include "model/TransactionHistorySortFilterModel.h" +#include "daemon/DaemonManager.h" int main(int argc, char *argv[]) @@ -88,6 +90,8 @@ int main(int argc, char *argv[]) qmlRegisterUncreatableType("moneroComponents.TransactionInfo", 1, 0, "TransactionInfo", "TransactionHistory can't be instantiated directly"); + qmlRegisterUncreatableType("moneroComponents.DaemonManager", 1, 0, "DaemonManager", + "DaemonManager can't be instantiated directly"); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); @@ -104,6 +108,10 @@ int main(int argc, char *argv[]) engine.rootContext()->setContextProperty("translationManager", TranslationManager::instance()); engine.addImageProvider(QLatin1String("qrcode"), new QRCodeImageProvider()); + const QStringList arguments = QCoreApplication::arguments(); + DaemonManager * daemonManager = DaemonManager::instance(&arguments); + QObject::connect(&app, SIGNAL(aboutToQuit()), daemonManager, SLOT(closing())); + engine.rootContext()->setContextProperty("daemonManager", daemonManager); // export to QML monero accounts root directory // wizard is talking about where diff --git a/main.qml b/main.qml index 5de0d429..d8be1e6a 100644 --- a/main.qml +++ b/main.qml @@ -59,6 +59,7 @@ ApplicationWindow { property int restoreHeight:0 property bool daemonSynced: false property int maxWindowHeight: (Screen.height < 900)? 720 : 800; + property bool daemonRunning: false // true if wallet ever synchronized property bool walletInitialized : false @@ -212,6 +213,7 @@ ApplicationWindow { currentWallet.moneySpent.disconnect(onWalletMoneySent) currentWallet.moneyReceived.disconnect(onWalletMoneyReceived) currentWallet.transactionCreated.disconnect(onTransactionCreated) + currentWallet.connectionStatusChanged.disconnect(onWalletConnectionStatusChanged) currentWallet.refreshed.connect(onWalletRefresh) currentWallet.updated.connect(onWalletUpdate) @@ -219,6 +221,7 @@ ApplicationWindow { currentWallet.moneySpent.connect(onWalletMoneySent) currentWallet.moneyReceived.connect(onWalletMoneyReceived) currentWallet.transactionCreated.connect(onTransactionCreated) + currentWallet.connectionStatusChanged.connect(onWalletConnectionStatusChanged) console.log("initializing with daemon address: ", persistentSettings.daemon_address) @@ -232,6 +235,11 @@ ApplicationWindow { return wallet_path; } + function onWalletConnectionStatusChanged(){ + console.log("Wallet connection status changed") + middlePanel.updateStatus(); + } + function onWalletOpened(wallet) { console.log(">>> wallet opened: " + wallet) if (wallet.status !== Wallet.Status_Ok) { @@ -280,18 +288,23 @@ ApplicationWindow { hideProcessingSplash() } + // Daemon connected + leftPanel.networkStatus.connected = currentWallet.connected + // Check daemon status var dCurrentBlock = currentWallet.daemonBlockChainHeight(); var dTargetBlock = currentWallet.daemonBlockChainTargetHeight(); - leftPanel.daemonProgress.updateProgress(dCurrentBlock,dTargetBlock); - - // Daemon connected - leftPanel.networkStatus.connected = currentWallet.connected // Daemon fully synced // TODO: implement onDaemonSynced or similar in wallet API and don't start refresh thread before daemon is synced daemonSynced = (currentWallet.connected != Wallet.ConnectionStatus_Disconnected && dCurrentBlock >= dTargetBlock) + leftPanel.daemonProgress.updateProgress(dCurrentBlock,dTargetBlock); + middlePanel.updateStatus(); + // If wallet isnt connected and no daemon is running - Ask + if(currentWallet.connected === Wallet.ConnectionStatus_Disconnected && !daemonManager.running() && !walletInitialized){ + daemonManagerDialog.open(); + } // Refresh is succesfull if blockchain height > 1 if (currentWallet.blockChainHeight() > 1){ @@ -317,10 +330,29 @@ ApplicationWindow { walletInitialized = true } - onWalletUpdate(); } + function startDaemon(){ + appWindow.showProcessingSplash(qsTr("Waiting for daemon to start...")) + daemonManager.start(); + } + + function stopDaemon(){ + appWindow.showProcessingSplash(qsTr("Waiting for daemon to stop...")) + daemonManager.stop(); + } + + function onDaemonStarted(){ + console.log("daemon started"); + daemonRunning = true; + } + function onDaemonStopped(){ + console.log("daemon stopped"); + daemonRunning = false; + } + + function onWalletNewBlock(blockHeight) { if (splash.visible) { var currHeight = blockHeight @@ -633,6 +665,9 @@ ApplicationWindow { walletManager.walletOpened.connect(onWalletOpened); walletManager.walletClosed.connect(onWalletClosed); + daemonManager.daemonStarted.connect(onDaemonStarted); + daemonManager.daemonStopped.connect(onDaemonStopped); + if(!walletsFound()) { rootItem.state = "wizard" } else { @@ -668,11 +703,11 @@ ApplicationWindow { // TODO: replace with customized popups // Information dialog - MessageDialog { + StandardDialog { // dynamically change onclose handler property var onCloseCallback id: informationPopup - standardButtons: StandardButton.Ok + cancelVisible: false onAccepted: { if (onCloseCallback) { onCloseCallback() @@ -681,10 +716,10 @@ ApplicationWindow { } // Confrirmation aka question dialog - MessageDialog { + StandardDialog { id: transactionConfirmationPopup - standardButtons: StandardButton.Ok + StandardButton.Cancel onAccepted: { + close(); handleTransactionConfirmed() } } @@ -721,6 +756,10 @@ ApplicationWindow { } + DaemonManagerDialog { + id: daemonManagerDialog + + } ProcessingSplash { id: splash @@ -1032,10 +1071,13 @@ ApplicationWindow { } } onClosing: { - // Close and save to disk on app close - if (currentWallet != undefined) { - walletManager.closeWallet(currentWallet); - currentWallet = undefined - } + // Make sure wallet is closed before app exit (~Wallet() isn't always invoked) + // Daemon shutdown is handled with signal/slot in main.cpp + if (currentWallet != undefined) { + walletManager.closeWallet(currentWallet); + currentWallet = undefined + } + // Stop daemon + daemonManager.stop(); } } diff --git a/monero-core.pro b/monero-core.pro index 5e47d732..249a65f5 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -30,7 +30,8 @@ HEADERS += \ src/model/TransactionHistorySortFilterModel.h \ src/QR-Code-generator/BitBuffer.hpp \ src/QR-Code-generator/QrCode.hpp \ - src/QR-Code-generator/QrSegment.hpp + src/QR-Code-generator/QrSegment.hpp \ + src/daemon/DaemonManager.h SOURCES += main.cpp \ @@ -49,7 +50,8 @@ SOURCES += main.cpp \ src/model/TransactionHistorySortFilterModel.cpp \ src/QR-Code-generator/BitBuffer.cpp \ src/QR-Code-generator/QrCode.cpp \ - src/QR-Code-generator/QrSegment.cpp + src/QR-Code-generator/QrSegment.cpp \ + src/daemon/DaemonManager.cpp lupdate_only { SOURCES = *.qml \ diff --git a/pages/Settings.qml b/pages/Settings.qml index 4f1af78d..c2fc08ef 100644 --- a/pages/Settings.qml +++ b/pages/Settings.qml @@ -37,7 +37,6 @@ import "../components" import moneroComponents.Clipboard 1.0 Rectangle { - property var daemonAddress color: "#F0EEEE" @@ -272,15 +271,94 @@ Rectangle { } } + RowLayout { + Label { + id: manageDaemonLabel + color: "#4A4949" + text: qsTr("Manage daemon") + translationManager.emptyString + fontSize: 16 + } + + StandardButton { + visible: true + enabled: !appWindow.daemonRunning + id: startDaemonButton + width: 110 + text: qsTr("Start daemon") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + onClicked: { + appWindow.startDaemon() + } + } + + StandardButton { + visible: true + enabled: appWindow.daemonRunning + id: stopDaemonButton + width: 110 + text: qsTr("Stop daemon") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + onClicked: { + appWindow.stopDaemon() + } + } + + StandardButton { + visible: true + // enabled: appWindow.daemonRunning + id: daemonConsolePopupButton + width: 110 + text: qsTr("Show log") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + onClicked: { + daemonConsolePopup.open(); + } + } + + } + + } + + // Daemon console + StandardDialog { + id: daemonConsolePopup + height:500 + width:800 + cancelVisible: false + title: qsTr("Daemon log") + onAccepted: { + close(); + } } - + // fires on every page load function onPageCompleted() { console.log("Settings page loaded"); initSettings(); } + // fires only once + Component.onCompleted: { + daemonManager.daemonConsoleUpdated.connect(onDaemonConsoleUpdated) + } + + function onDaemonConsoleUpdated(message){ + // Update daemon console + daemonConsolePopup.textArea.append(message) + } + + + } diff --git a/pages/Transfer.qml b/pages/Transfer.qml index 5552902c..127c74cb 100644 --- a/pages/Transfer.qml +++ b/pages/Transfer.qml @@ -29,6 +29,7 @@ import QtQuick 2.0 import moneroComponents.PendingTransaction 1.0 import "../components" +import moneroComponents.Wallet 1.0 Rectangle { @@ -331,4 +332,69 @@ Rectangle { } } + + Rectangle { + id:desaturate + color:"black" + anchors.fill: parent + opacity: 0.1 + visible: (root.enabled)? 0 : 1; + } + + Rectangle { + x: root.width/2 - width/2 + y: root.height/2 - height/2 + height:statusText.paintedHeight + 50 + width:statusText.paintedWidth + 40 + visible: statusText.text != "" + opacity: 0.9 + + Text { + id: statusText + anchors.fill:parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + Component.onCompleted: { + //Disable password page until enabled by updateStatus + root.enabled = false + } + + // fires on every page load + function onPageCompleted() { + console.log("transfer page loaded") + updateStatus(); + } + + //TODO: Add daemon sync status + //TODO: enable send page when we're connected and daemon is synced + + function updateStatus() { + console.log("updated transfer page status") + if(typeof currentWallet === "undefined") { + statusText.text = qsTr("Wallet is not connected to daemon.") + return; + } + + switch (currentWallet.connected) { + case Wallet.ConnectionStatus_Disconnected: + statusText.text = qsTr("Wallet is not connected to daemon.") + break + case Wallet.ConnectionStatus_WrongVersion: + statusText.text = qsTr("Connected daemon is not compatible with GUI. \n" + + "Please upgrade or connect to another daemon") + break + default: + if(!appWindow.daemonSynced){ + statusText.text = qsTr("Waiting on daemon synchronization to finish") + } else { + // everything OK, enable transfer page + root.enabled = true; + statusText.text = ""; + } + + } + } } diff --git a/qml.qrc b/qml.qrc index 17873589..c1aa0ece 100644 --- a/qml.qrc +++ b/qml.qrc @@ -120,5 +120,7 @@ components/DaemonProgress.qml components/StandardDialog.qml pages/Sign.qml + components/DaemonManagerDialog.qml + components/StandardDialog.qml diff --git a/src/daemon/DaemonManager.cpp b/src/daemon/DaemonManager.cpp new file mode 100644 index 00000000..185cde1e --- /dev/null +++ b/src/daemon/DaemonManager.cpp @@ -0,0 +1,142 @@ +#include "DaemonManager.h" +#include +#include +#include +#include +#include +#include +#include +#include + +DaemonManager * DaemonManager::m_instance = nullptr; +QStringList DaemonManager::m_clArgs; + +DaemonManager *DaemonManager::instance(const QStringList *args) +{ + if (!m_instance) { + m_instance = new DaemonManager; + // store command line arguments for later use + m_clArgs = *args; + m_clArgs.removeFirst(); + } + + return m_instance; +} + +bool DaemonManager::start() +{ + // + QString process; +#ifdef Q_OS_WIN + process = QApplication::applicationDirPath() + "/monerod.exe"; +#elif defined(Q_OS_UNIX) + process = QApplication::applicationDirPath() + "/monerod"; +#endif + + if (process.length() == 0) { + qDebug() << "no daemon binary defined for current platform"; + return false; + } + + + // prepare command line arguments and pass to monerod + QStringList arguments; + foreach (const QString &str, m_clArgs) { + qDebug() << QString(" [%1] ").arg(str); + arguments << str; + } + + qDebug() << "starting monerod " + process; + qDebug() << "With command line arguments " << arguments; + + m_daemon = new QProcess(); + initialized = true; + + // Connect output slots + connect (m_daemon, SIGNAL(readyReadStandardOutput()), this, SLOT(printOutput())); + connect (m_daemon, SIGNAL(readyReadStandardError()), this, SLOT(printError())); + + // Start monerod + m_daemon->start(process,arguments); + bool started = m_daemon->waitForStarted(); + + // add state changed listener + connect(m_daemon,SIGNAL(stateChanged(QProcess::ProcessState)),this,SLOT(stateChanged(QProcess::ProcessState))); + + if (!started) { + qDebug() << "Daemon start error: " + m_daemon->errorString(); + } else { + emit daemonStarted(); + } + + return started; +} + +bool DaemonManager::stop() +{ + if (initialized) { + qDebug() << "stopping daemon"; + // we can't use QProcess::terminate() on windows console process + // write exit command to stdin + m_daemon->write("exit\n"); + } + + return true; +} + +void DaemonManager::stateChanged(QProcess::ProcessState state) +{ + qDebug() << "STATE CHANGED: " << state; + if (state == QProcess::NotRunning) { + emit daemonStopped(); + } +} + +void DaemonManager::printOutput() +{ + QByteArray byteArray = m_daemon->readAllStandardOutput(); + QStringList strLines = QString(byteArray).split("\n"); + + foreach (QString line, strLines) { + emit daemonConsoleUpdated(line); + qDebug() << "Daemon: " + line; + } +} + +void DaemonManager::printError() +{ + QByteArray byteArray = m_daemon->readAllStandardError(); + QStringList strLines = QString(byteArray).split("\n"); + + foreach (QString line, strLines) { + emit daemonConsoleUpdated(line); + qDebug() << "Daemon ERROR: " + line; + } +} + +bool DaemonManager::running() const +{ + if (initialized) { + qDebug() << m_daemon->state(); + qDebug() << QProcess::NotRunning; + // m_daemon->write("status\n"); + return m_daemon->state() > QProcess::NotRunning; + } + return false; +} + +DaemonManager::DaemonManager(QObject *parent) + : QObject(parent) +{ + +} + +void DaemonManager::closing() +{ + qDebug() << __FUNCTION__; + stop(); + // Wait for daemon to stop before exiting (max 10 secs) + if (initialized) { + m_daemon->waitForFinished(10000); + } +} diff --git a/src/daemon/DaemonManager.h b/src/daemon/DaemonManager.h new file mode 100644 index 00000000..8f425fd5 --- /dev/null +++ b/src/daemon/DaemonManager.h @@ -0,0 +1,43 @@ +#ifndef DAEMONMANAGER_H +#define DAEMONMANAGER_H + +#include +#include +#include + +class DaemonManager : public QObject +{ + Q_OBJECT + +public: + + static DaemonManager * instance(const QStringList *args); + + Q_INVOKABLE bool start(); + Q_INVOKABLE bool stop(); + + // return true if daemon process is started + Q_INVOKABLE bool running() const; + +signals: + void daemonStarted(); + void daemonStopped(); + void daemonConsoleUpdated(QString message); + +public slots: + void printOutput(); + void printError(); + void closing(); + void stateChanged(QProcess::ProcessState state); + +private: + + explicit DaemonManager(QObject *parent = 0); + static DaemonManager * m_instance; + static QStringList m_clArgs; + QProcess *m_daemon; + bool initialized = false; + +}; + +#endif // DAEMONMANAGER_H diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 9d814d82..915b4d4f 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -15,6 +15,7 @@ namespace { static const int DAEMON_BLOCKCHAIN_HEIGHT_CACHE_TTL_SECONDS = 10; static const int DAEMON_BLOCKCHAIN_TARGET_HEIGHT_CACHE_TTL_SECONDS = 60; + static const int WALLET_CONNECTION_STATUS_CACHE_TTL_SECONDS = 5; } class WalletListenerImpl : public Bitmonero::WalletListener @@ -89,7 +90,18 @@ Wallet::Status Wallet::status() const Wallet::ConnectionStatus Wallet::connected() const { - return static_cast(m_walletImpl->connected()); + // cache connection status + if (!m_initialized || m_connectionStatusTime.elapsed() / 1000 > m_connectionStatusTtl) { + m_initialized = true; + ConnectionStatus newStatus = static_cast(m_walletImpl->connected()); + if (newStatus != m_connectionStatus) { + m_connectionStatus = newStatus; + emit connectionStatusChanged(); + } + m_connectionStatusTime.restart(); + } + + return m_connectionStatus; } bool Wallet::synchronized() const @@ -424,9 +436,16 @@ Wallet::Wallet(Bitmonero::Wallet *w, QObject *parent) , m_daemonBlockChainHeightTtl(DAEMON_BLOCKCHAIN_HEIGHT_CACHE_TTL_SECONDS) , m_daemonBlockChainTargetHeight(0) , m_daemonBlockChainTargetHeightTtl(DAEMON_BLOCKCHAIN_TARGET_HEIGHT_CACHE_TTL_SECONDS) + , m_connectionStatusTtl(WALLET_CONNECTION_STATUS_CACHE_TTL_SECONDS) { m_history = new TransactionHistory(m_walletImpl->history(), this); m_walletImpl->setListener(new WalletListenerImpl(this)); + m_connectionStatus = Wallet::ConnectionStatus_Disconnected; + // start cache timers + m_connectionStatusTime.restart(); + m_daemonBlockChainHeightTime.restart(); + m_daemonBlockChainTargetHeightTime.restart(); + m_initialized = false; } Wallet::~Wallet() diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 492080d4..ad4c393e 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -200,6 +200,8 @@ signals: // emitted when transaction is created async void transactionCreated(PendingTransaction * transaction, QString address, QString paymentId, quint32 mixinCount); + void connectionStatusChanged() const; + private: Wallet(QObject * parent = nullptr); Wallet(Bitmonero::Wallet *w, QObject * parent = 0); @@ -221,6 +223,10 @@ private: mutable QTime m_daemonBlockChainTargetHeightTime; mutable quint64 m_daemonBlockChainTargetHeight; int m_daemonBlockChainTargetHeightTtl; + mutable ConnectionStatus m_connectionStatus; + int m_connectionStatusTtl; + mutable QTime m_connectionStatusTime; + mutable bool m_initialized; };