mirror of
https://github.com/feather-wallet/feather.git
synced 2024-11-17 17:57:39 +00:00
Merge pull request 'Misc networking fixes' (#160) from tobtoht/feather:nodes into master
Reviewed-on: https://git.wownero.com/feather/feather/pulls/160
This commit is contained in:
commit
e6e669845c
21 changed files with 325 additions and 278 deletions
|
@ -31,7 +31,7 @@ if(DEBUG)
|
||||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(MONERO_HEAD "2390030d10b69c357165f82aaf417391a9e11019")
|
set(MONERO_HEAD "2fc0c6355d7f3756f9cc01f1165aeec42bc52201")
|
||||||
set(BUILD_GUI_DEPS ON)
|
set(BUILD_GUI_DEPS ON)
|
||||||
set(ARCH "x86-64")
|
set(ARCH "x86-64")
|
||||||
set(BUILD_64 ON)
|
set(BUILD_64 ON)
|
||||||
|
|
2
monero
2
monero
|
@ -1 +1 @@
|
||||||
Subproject commit 2390030d10b69c357165f82aaf417391a9e11019
|
Subproject commit 2fc0c6355d7f3756f9cc01f1165aeec42bc52201
|
|
@ -113,7 +113,6 @@ AppContext::AppContext(QCommandLineParser *cmdargs) {
|
||||||
this->nodes = new Nodes(this, this->networkClearnet);
|
this->nodes = new Nodes(this, this->networkClearnet);
|
||||||
connect(this, &AppContext::nodeSourceChanged, this->nodes, &Nodes::onNodeSourceChanged);
|
connect(this, &AppContext::nodeSourceChanged, this->nodes, &Nodes::onNodeSourceChanged);
|
||||||
connect(this, &AppContext::setCustomNodes, this->nodes, &Nodes::setCustomNodes);
|
connect(this, &AppContext::setCustomNodes, this->nodes, &Nodes::setCustomNodes);
|
||||||
connect(this, &AppContext::walletClosing, this->nodes, &Nodes::onWalletClosing);
|
|
||||||
|
|
||||||
// Tor & socks proxy
|
// Tor & socks proxy
|
||||||
this->ws = new WSClient(this, m_wsUrl);
|
this->ws = new WSClient(this, m_wsUrl);
|
||||||
|
@ -261,7 +260,6 @@ void AppContext::onCreateTransactionError(const QString &msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppContext::walletClose(bool emitClosedSignal) {
|
void AppContext::walletClose(bool emitClosedSignal) {
|
||||||
this->nodes->stopTimer();
|
|
||||||
if(this->currentWallet == nullptr) return;
|
if(this->currentWallet == nullptr) return;
|
||||||
emit walletClosing();
|
emit walletClosing();
|
||||||
//ctx->currentWallet->store(); @TODO: uncomment to store on wallet close
|
//ctx->currentWallet->store(); @TODO: uncomment to store on wallet close
|
||||||
|
@ -341,6 +339,9 @@ void AppContext::onWalletOpened(Wallet *wallet) {
|
||||||
|
|
||||||
emit walletOpened();
|
emit walletOpened();
|
||||||
|
|
||||||
|
connect(this->currentWallet, &Wallet::connectionStatusChanged, [this]{
|
||||||
|
this->nodes->autoConnect();
|
||||||
|
});
|
||||||
this->nodes->connectToNode();
|
this->nodes->connectToNode();
|
||||||
this->updateBalance();
|
this->updateBalance();
|
||||||
|
|
||||||
|
@ -723,13 +724,15 @@ void AppContext::onWalletUpdate() {
|
||||||
this->storeWallet();
|
this->storeWallet();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppContext::onWalletRefreshed() {
|
void AppContext::onWalletRefreshed(bool success) {
|
||||||
if (!this->refreshed) {
|
if (!this->refreshed) {
|
||||||
refreshModels();
|
refreshModels();
|
||||||
this->refreshed = true;
|
this->refreshed = true;
|
||||||
this->storeWallet();
|
this->storeWallet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qDebug() << "Wallet refresh status: " << success;
|
||||||
|
|
||||||
this->currentWallet->refreshHeightAsync();
|
this->currentWallet->refreshHeightAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -746,7 +749,7 @@ void AppContext::onWalletNewBlock(quint64 blockheight, quint64 targetHeight) {
|
||||||
void AppContext::onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight) {
|
void AppContext::onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight) {
|
||||||
qDebug() << Q_FUNC_INFO << walletHeight << daemonHeight << targetHeight;
|
qDebug() << Q_FUNC_INFO << walletHeight << daemonHeight << targetHeight;
|
||||||
|
|
||||||
if (!this->currentWallet->connected())
|
if (this->currentWallet->connectionStatus() == Wallet::ConnectionStatus_Disconnected)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (daemonHeight < targetHeight) {
|
if (daemonHeight < targetHeight) {
|
||||||
|
|
|
@ -126,7 +126,7 @@ private slots:
|
||||||
void onMoneyReceived(const QString &txId, quint64 amount);
|
void onMoneyReceived(const QString &txId, quint64 amount);
|
||||||
void onUnconfirmedMoneyReceived(const QString &txId, quint64 amount);
|
void onUnconfirmedMoneyReceived(const QString &txId, quint64 amount);
|
||||||
void onWalletUpdate();
|
void onWalletUpdate();
|
||||||
void onWalletRefreshed();
|
void onWalletRefreshed(bool success);
|
||||||
void onWalletOpened(Wallet *wallet);
|
void onWalletOpened(Wallet *wallet);
|
||||||
void onWalletNewBlock(quint64 blockheight, quint64 targetHeight);
|
void onWalletNewBlock(quint64 blockheight, quint64 targetHeight);
|
||||||
void onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight);
|
void onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight);
|
||||||
|
|
|
@ -12,15 +12,26 @@
|
||||||
DebugInfoDialog::DebugInfoDialog(AppContext *ctx, QWidget *parent)
|
DebugInfoDialog::DebugInfoDialog(AppContext *ctx, QWidget *parent)
|
||||||
: QDialog(parent)
|
: QDialog(parent)
|
||||||
, ui(new Ui::DebugInfoDialog)
|
, ui(new Ui::DebugInfoDialog)
|
||||||
|
, m_ctx(ctx)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
connect(ui->btn_Copy, &QPushButton::clicked, this, &DebugInfoDialog::copyToClipboad);
|
||||||
|
|
||||||
|
m_updateTimer.start(5000);
|
||||||
|
connect(&m_updateTimer, &QTimer::timeout, this, &DebugInfoDialog::updateInfo);
|
||||||
|
this->updateInfo();
|
||||||
|
|
||||||
|
this->adjustSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugInfoDialog::updateInfo() {
|
||||||
QString torStatus;
|
QString torStatus;
|
||||||
if(ctx->isTorSocks)
|
if(m_ctx->isTorSocks)
|
||||||
torStatus = "Torsocks";
|
torStatus = "Torsocks";
|
||||||
else if(ctx->tor->localTor)
|
else if(m_ctx->tor->localTor)
|
||||||
torStatus = "Local (assumed to be running)";
|
torStatus = "Local (assumed to be running)";
|
||||||
else if(ctx->tor->torConnected)
|
else if(m_ctx->tor->torConnected)
|
||||||
torStatus = "Running";
|
torStatus = "Running";
|
||||||
else
|
else
|
||||||
torStatus = "Unknown";
|
torStatus = "Unknown";
|
||||||
|
@ -28,32 +39,28 @@ DebugInfoDialog::DebugInfoDialog(AppContext *ctx, QWidget *parent)
|
||||||
ui->label_featherVersion->setText(QString("%1-%2").arg(FEATHER_VERSION, FEATHER_BRANCH));
|
ui->label_featherVersion->setText(QString("%1-%2").arg(FEATHER_VERSION, FEATHER_BRANCH));
|
||||||
ui->label_moneroVersion->setText(QString("%1-%2").arg(MONERO_VERSION, MONERO_BRANCH));
|
ui->label_moneroVersion->setText(QString("%1-%2").arg(MONERO_VERSION, MONERO_BRANCH));
|
||||||
|
|
||||||
ui->label_walletHeight->setText(QString::number(ctx->currentWallet->blockChainHeight()));
|
ui->label_walletHeight->setText(QString::number(m_ctx->currentWallet->blockChainHeight()));
|
||||||
ui->label_daemonHeight->setText(QString::number(ctx->currentWallet->daemonBlockChainHeight()));
|
ui->label_daemonHeight->setText(QString::number(m_ctx->currentWallet->daemonBlockChainHeight()));
|
||||||
ui->label_targetHeight->setText(QString::number(ctx->currentWallet->daemonBlockChainTargetHeight()));
|
ui->label_targetHeight->setText(QString::number(m_ctx->currentWallet->daemonBlockChainTargetHeight()));
|
||||||
ui->label_restoreHeight->setText(QString::number(ctx->currentWallet->getWalletCreationHeight()));
|
ui->label_restoreHeight->setText(QString::number(m_ctx->currentWallet->getWalletCreationHeight()));
|
||||||
ui->label_synchronized->setText(ctx->currentWallet->synchronized() ? "True" : "False");
|
ui->label_synchronized->setText(m_ctx->currentWallet->synchronized() ? "True" : "False");
|
||||||
|
|
||||||
auto node = ctx->nodes->connection();
|
auto node = m_ctx->nodes->connection();
|
||||||
ui->label_remoteNode->setText(node.full);
|
ui->label_remoteNode->setText(node.full);
|
||||||
ui->label_walletStatus->setText(this->statusToString(ctx->currentWallet->connected()));
|
ui->label_walletStatus->setText(this->statusToString(m_ctx->currentWallet->connectionStatus()));
|
||||||
ui->label_torStatus->setText(torStatus);
|
ui->label_torStatus->setText(torStatus);
|
||||||
ui->label_websocketStatus->setText(Utils::QtEnumToString(ctx->ws->webSocket.state()));
|
ui->label_websocketStatus->setText(Utils::QtEnumToString(m_ctx->ws->webSocket.state()));
|
||||||
|
|
||||||
ui->label_netType->setText(Utils::QtEnumToString(ctx->currentWallet->nettype()));
|
ui->label_netType->setText(Utils::QtEnumToString(m_ctx->currentWallet->nettype()));
|
||||||
ui->label_seedType->setText(ctx->currentWallet->getCacheAttribute("feather.seed").isEmpty() ? "25 word" : "14 word");
|
ui->label_seedType->setText(m_ctx->currentWallet->getCacheAttribute("feather.seed").isEmpty() ? "25 word" : "14 word");
|
||||||
ui->label_viewOnly->setText(ctx->currentWallet->viewOnly() ? "True" : "False");
|
ui->label_viewOnly->setText(m_ctx->currentWallet->viewOnly() ? "True" : "False");
|
||||||
|
|
||||||
QString os = QSysInfo::prettyProductName();
|
QString os = QSysInfo::prettyProductName();
|
||||||
if (ctx->isTails) {
|
if (m_ctx->isTails) {
|
||||||
os = QString("Tails %1").arg(TailsOS::version());
|
os = QString("Tails %1").arg(TailsOS::version());
|
||||||
}
|
}
|
||||||
ui->label_OS->setText(os);
|
ui->label_OS->setText(os);
|
||||||
ui->label_timestamp->setText(QString::number(QDateTime::currentSecsSinceEpoch()));
|
ui->label_timestamp->setText(QString::number(QDateTime::currentSecsSinceEpoch()));
|
||||||
|
|
||||||
connect(ui->btn_Copy, &QPushButton::clicked, this, &DebugInfoDialog::copyToClipboad);
|
|
||||||
|
|
||||||
this->adjustSize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DebugInfoDialog::statusToString(Wallet::ConnectionStatus status) {
|
QString DebugInfoDialog::statusToString(Wallet::ConnectionStatus status) {
|
||||||
|
@ -72,27 +79,28 @@ QString DebugInfoDialog::statusToString(Wallet::ConnectionStatus status) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugInfoDialog::copyToClipboad() {
|
void DebugInfoDialog::copyToClipboad() {
|
||||||
|
// Two spaces at the end of each line are for newlines in Markdown
|
||||||
QString text = "";
|
QString text = "";
|
||||||
text += QString("Feather version: %1\n").arg(ui->label_featherVersion->text());
|
text += QString("Feather version: %1 \n").arg(ui->label_featherVersion->text());
|
||||||
text += QString("Monero version: %1\n").arg(ui->label_moneroVersion->text());
|
text += QString("Monero version: %1 \n").arg(ui->label_moneroVersion->text());
|
||||||
|
|
||||||
text += QString("Wallet height: %1\n").arg(ui->label_walletHeight->text());
|
text += QString("Wallet height: %1 \n").arg(ui->label_walletHeight->text());
|
||||||
text += QString("Daemon height: %1\n").arg(ui->label_daemonHeight->text());
|
text += QString("Daemon height: %1 \n").arg(ui->label_daemonHeight->text());
|
||||||
text += QString("Target height: %1\n").arg(ui->label_targetHeight->text());
|
text += QString("Target height: %1 \n").arg(ui->label_targetHeight->text());
|
||||||
text += QString("Restore height: %1\n").arg(ui->label_restoreHeight->text());
|
text += QString("Restore height: %1 \n").arg(ui->label_restoreHeight->text());
|
||||||
text += QString("Synchronized: %1\n").arg(ui->label_synchronized->text());
|
text += QString("Synchronized: %1 \n").arg(ui->label_synchronized->text());
|
||||||
|
|
||||||
text += QString("Remote node: %1\n").arg(ui->label_remoteNode->text());
|
text += QString("Remote node: %1 \n").arg(ui->label_remoteNode->text());
|
||||||
text += QString("Wallet status: %1\n").arg(ui->label_walletStatus->text());
|
text += QString("Wallet status: %1 \n").arg(ui->label_walletStatus->text());
|
||||||
text += QString("Tor status: %1\n").arg(ui->label_torStatus->text());
|
text += QString("Tor status: %1 \n").arg(ui->label_torStatus->text());
|
||||||
text += QString("Websocket status: %1\n").arg(ui->label_websocketStatus->text());
|
text += QString("Websocket status: %1 \n").arg(ui->label_websocketStatus->text());
|
||||||
|
|
||||||
text += QString("Network type: %1\n").arg(ui->label_netType->text());
|
text += QString("Network type: %1 \n").arg(ui->label_netType->text());
|
||||||
text += QString("Seed type: %1\n").arg(ui->label_seedType->text());
|
text += QString("Seed type: %1 \n").arg(ui->label_seedType->text());
|
||||||
text += QString("View only: %1\n").arg(ui->label_viewOnly->text());
|
text += QString("View only: %1 \n").arg(ui->label_viewOnly->text());
|
||||||
|
|
||||||
text += QString("Operating system: %1\n").arg(ui->label_OS->text());
|
text += QString("Operating system: %1 \n").arg(ui->label_OS->text());
|
||||||
text += QString("Timestamp: %1\n").arg(ui->label_timestamp->text());
|
text += QString("Timestamp: %1 \n").arg(ui->label_timestamp->text());
|
||||||
|
|
||||||
Utils::copyToClipboard(text);
|
Utils::copyToClipboard(text);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,10 @@ public:
|
||||||
private:
|
private:
|
||||||
QString statusToString(Wallet::ConnectionStatus status);
|
QString statusToString(Wallet::ConnectionStatus status);
|
||||||
void copyToClipboad();
|
void copyToClipboad();
|
||||||
|
void updateInfo();
|
||||||
|
|
||||||
|
QTimer m_updateTimer;
|
||||||
|
AppContext *m_ctx;
|
||||||
|
|
||||||
Ui::DebugInfoDialog *ui;
|
Ui::DebugInfoDialog *ui;
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>693</width>
|
<width>693</width>
|
||||||
<height>580</height>
|
<height>612</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
|
@ -311,6 +311,9 @@
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>TextLabel</string>
|
<string>TextLabel</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
|
@ -47,6 +47,11 @@ Wallet::Wallet(QObject * parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Wallet::ConnectionStatus Wallet::connectionStatus() const
|
||||||
|
{
|
||||||
|
return m_connectionStatus;
|
||||||
|
}
|
||||||
|
|
||||||
QString Wallet::getSeed() const
|
QString Wallet::getSeed() const
|
||||||
{
|
{
|
||||||
return QString::fromStdString(m_walletImpl->seed());
|
return QString::fromStdString(m_walletImpl->seed());
|
||||||
|
@ -72,36 +77,6 @@ NetworkType::Type Wallet::nettype() const
|
||||||
return static_cast<NetworkType::Type>(m_walletImpl->nettype());
|
return static_cast<NetworkType::Type>(m_walletImpl->nettype());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Wallet::updateConnectionStatusAsync()
|
|
||||||
{
|
|
||||||
m_scheduler.run([this] {
|
|
||||||
if (m_connectionStatus == Wallet::ConnectionStatus_Disconnected)
|
|
||||||
{
|
|
||||||
setConnectionStatus(ConnectionStatus_Connecting);
|
|
||||||
}
|
|
||||||
ConnectionStatus newStatus = static_cast<ConnectionStatus>(m_walletImpl->connected());
|
|
||||||
if (newStatus != m_connectionStatus || !m_initialized) {
|
|
||||||
m_initialized = true;
|
|
||||||
setConnectionStatus(newStatus);
|
|
||||||
}
|
|
||||||
// Release lock
|
|
||||||
m_connectionStatusRunning = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Wallet::ConnectionStatus Wallet::connected(bool forceCheck)
|
|
||||||
{
|
|
||||||
// cache connection status
|
|
||||||
if (forceCheck || !m_initialized || (m_connectionStatusTime.elapsed() / 1000 > m_connectionStatusTtl && !m_connectionStatusRunning) || m_connectionStatusTime.elapsed() > 30000) {
|
|
||||||
m_connectionStatusRunning = true;
|
|
||||||
m_connectionStatusTime.restart();
|
|
||||||
updateConnectionStatusAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_connectionStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Wallet::disconnected() const
|
bool Wallet::disconnected() const
|
||||||
{
|
{
|
||||||
return m_disconnected;
|
return m_disconnected;
|
||||||
|
@ -120,6 +95,9 @@ void Wallet::refreshingSet(bool value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Wallet::setConnectionTimeout(int timeout) {
|
||||||
|
m_connectionTimeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
void Wallet::setConnectionStatus(ConnectionStatus value)
|
void Wallet::setConnectionStatus(ConnectionStatus value)
|
||||||
{
|
{
|
||||||
|
@ -232,16 +210,16 @@ bool Wallet::init(const QString &daemonAddress, bool trustedDaemon, quint64 uppe
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&m_proxyMutex);
|
QMutexLocker locker(&m_proxyMutex);
|
||||||
|
|
||||||
if (!m_walletImpl->init(daemonAddress.toStdString(), upperTransactionLimit, m_daemonUsername.toStdString(), m_daemonPassword.toStdString(), false, false, proxyAddress.toStdString()))
|
if (!m_walletImpl->init(daemonAddress.toStdString(), upperTransactionLimit, m_daemonUsername.toStdString(), m_daemonPassword.toStdString(), m_useSSL, false, proxyAddress.toStdString()))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
m_proxyAddress = proxyAddress;
|
m_proxyAddress = proxyAddress;
|
||||||
}
|
}
|
||||||
emit proxyAddressChanged();
|
emit proxyAddressChanged();
|
||||||
|
|
||||||
|
setTrustedDaemon(trustedDaemon);
|
||||||
setTrustedDaemon(trustedDaemon);
|
setTrustedDaemon(trustedDaemon);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -269,7 +247,7 @@ void Wallet::initAsync(
|
||||||
{
|
{
|
||||||
emit walletCreationHeightChanged();
|
emit walletCreationHeightChanged();
|
||||||
qDebug() << "init async finished - starting refresh";
|
qDebug() << "init async finished - starting refresh";
|
||||||
connected(true);
|
refreshHeightAsync();
|
||||||
startRefresh();
|
startRefresh();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -313,6 +291,11 @@ void Wallet::setTrustedDaemon(bool arg)
|
||||||
m_walletImpl->setTrustedDaemon(arg);
|
m_walletImpl->setTrustedDaemon(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Wallet::setUseSSL(bool ssl)
|
||||||
|
{
|
||||||
|
m_useSSL = ssl;
|
||||||
|
}
|
||||||
|
|
||||||
bool Wallet::viewOnly() const
|
bool Wallet::viewOnly() const
|
||||||
{
|
{
|
||||||
return m_walletImpl->watchOnly();
|
return m_walletImpl->watchOnly();
|
||||||
|
@ -425,6 +408,8 @@ void Wallet::refreshHeightAsync()
|
||||||
daemonHeightFuture.second.waitForFinished();
|
daemonHeightFuture.second.waitForFinished();
|
||||||
targetHeightFuture.second.waitForFinished();
|
targetHeightFuture.second.waitForFinished();
|
||||||
|
|
||||||
|
setConnectionStatus(ConnectionStatus_Connected);
|
||||||
|
|
||||||
emit heightRefreshed(walletHeight, daemonHeight, targetHeight);
|
emit heightRefreshed(walletHeight, daemonHeight, targetHeight);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -439,7 +424,8 @@ quint64 Wallet::daemonBlockChainHeight() const
|
||||||
// cache daemon blockchain height for some time (60 seconds by default)
|
// cache daemon blockchain height for some time (60 seconds by default)
|
||||||
|
|
||||||
if (m_daemonBlockChainHeight == 0
|
if (m_daemonBlockChainHeight == 0
|
||||||
|| m_daemonBlockChainHeightTime.elapsed() / 1000 > m_daemonBlockChainHeightTtl) {
|
|| m_daemonBlockChainHeightTime.elapsed() / 1000 > m_daemonBlockChainHeightTtl)
|
||||||
|
{
|
||||||
m_daemonBlockChainHeight = m_walletImpl->daemonBlockChainHeight();
|
m_daemonBlockChainHeight = m_walletImpl->daemonBlockChainHeight();
|
||||||
m_daemonBlockChainHeightTime.restart();
|
m_daemonBlockChainHeightTime.restart();
|
||||||
}
|
}
|
||||||
|
@ -449,7 +435,8 @@ quint64 Wallet::daemonBlockChainHeight() const
|
||||||
quint64 Wallet::daemonBlockChainTargetHeight() const
|
quint64 Wallet::daemonBlockChainTargetHeight() const
|
||||||
{
|
{
|
||||||
if (m_daemonBlockChainTargetHeight <= 1
|
if (m_daemonBlockChainTargetHeight <= 1
|
||||||
|| m_daemonBlockChainTargetHeightTime.elapsed() / 1000 > m_daemonBlockChainTargetHeightTtl) {
|
|| m_daemonBlockChainTargetHeightTime.elapsed() / 1000 > m_daemonBlockChainTargetHeightTtl)
|
||||||
|
{
|
||||||
m_daemonBlockChainTargetHeight = m_walletImpl->daemonBlockChainTargetHeight();
|
m_daemonBlockChainTargetHeight = m_walletImpl->daemonBlockChainTargetHeight();
|
||||||
|
|
||||||
// Target height is set to 0 if daemon is synced.
|
// Target height is set to 0 if daemon is synced.
|
||||||
|
@ -501,6 +488,7 @@ bool Wallet::importTransaction(const QString& txid, const QVector<quint64>& outp
|
||||||
void Wallet::startRefresh()
|
void Wallet::startRefresh()
|
||||||
{
|
{
|
||||||
m_refreshEnabled = true;
|
m_refreshEnabled = true;
|
||||||
|
m_refreshNow = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Wallet::pauseRefresh()
|
void Wallet::pauseRefresh()
|
||||||
|
@ -1089,6 +1077,14 @@ void Wallet::onWalletPassphraseNeeded(bool on_device)
|
||||||
emit this->walletPassphraseNeeded(on_device);
|
emit this->walletPassphraseNeeded(on_device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
quint64 Wallet::getBytesReceived() const {
|
||||||
|
return m_walletImpl->getBytesReceived();
|
||||||
|
}
|
||||||
|
|
||||||
|
quint64 Wallet::getBytesSent() const {
|
||||||
|
return m_walletImpl->getBytesSent();
|
||||||
|
}
|
||||||
|
|
||||||
void Wallet::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort)
|
void Wallet::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort)
|
||||||
{
|
{
|
||||||
if (m_walletListener != nullptr)
|
if (m_walletListener != nullptr)
|
||||||
|
@ -1117,9 +1113,11 @@ Wallet::Wallet(Monero::Wallet *w, QObject *parent)
|
||||||
, m_subaddressAccount(nullptr)
|
, m_subaddressAccount(nullptr)
|
||||||
, m_subaddressAccountModel(nullptr)
|
, m_subaddressAccountModel(nullptr)
|
||||||
, m_coinsModel(nullptr)
|
, m_coinsModel(nullptr)
|
||||||
|
, m_refreshNow(false)
|
||||||
, m_refreshEnabled(false)
|
, m_refreshEnabled(false)
|
||||||
, m_refreshing(false)
|
, m_refreshing(false)
|
||||||
, m_scheduler(this)
|
, m_scheduler(this)
|
||||||
|
, m_useSSL(true)
|
||||||
{
|
{
|
||||||
m_history = new TransactionHistory(m_walletImpl->history(), this);
|
m_history = new TransactionHistory(m_walletImpl->history(), this);
|
||||||
m_addressBook = new AddressBook(m_walletImpl->addressBook(), this);
|
m_addressBook = new AddressBook(m_walletImpl->addressBook(), this);
|
||||||
|
@ -1191,8 +1189,9 @@ void Wallet::startRefreshThread()
|
||||||
{
|
{
|
||||||
const auto now = std::chrono::steady_clock::now();
|
const auto now = std::chrono::steady_clock::now();
|
||||||
const auto elapsed = now - last;
|
const auto elapsed = now - last;
|
||||||
if (elapsed >= refreshInterval)
|
if (elapsed >= refreshInterval || m_refreshNow)
|
||||||
{
|
{
|
||||||
|
m_refreshNow = false;
|
||||||
refresh(false);
|
refresh(false);
|
||||||
last = std::chrono::steady_clock::now();
|
last = std::chrono::steady_clock::now();
|
||||||
}
|
}
|
||||||
|
@ -1206,3 +1205,11 @@ void Wallet::startRefreshThread()
|
||||||
throw std::runtime_error("failed to start auto refresh thread");
|
throw std::runtime_error("failed to start auto refresh thread");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Wallet::onRefreshed(bool success) {
|
||||||
|
if (success) {
|
||||||
|
setConnectionStatus(ConnectionStatus_Connected);
|
||||||
|
} else {
|
||||||
|
setConnectionStatus(ConnectionStatus_Disconnected);
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,7 +62,7 @@ Q_OBJECT
|
||||||
Q_PROPERTY(QString seedLanguage READ getSeedLanguage)
|
Q_PROPERTY(QString seedLanguage READ getSeedLanguage)
|
||||||
Q_PROPERTY(Status status READ status)
|
Q_PROPERTY(Status status READ status)
|
||||||
Q_PROPERTY(NetworkType::Type nettype READ nettype)
|
Q_PROPERTY(NetworkType::Type nettype READ nettype)
|
||||||
// Q_PROPERTY(ConnectionStatus connected READ connected)
|
Q_PROPERTY(ConnectionStatus connectionStatus READ connectionStatus)
|
||||||
Q_PROPERTY(quint32 currentSubaddressAccount READ currentSubaddressAccount NOTIFY currentSubaddressAccountChanged)
|
Q_PROPERTY(quint32 currentSubaddressAccount READ currentSubaddressAccount NOTIFY currentSubaddressAccountChanged)
|
||||||
Q_PROPERTY(bool synchronized READ synchronized)
|
Q_PROPERTY(bool synchronized READ synchronized)
|
||||||
Q_PROPERTY(QString errorString READ errorString)
|
Q_PROPERTY(QString errorString READ errorString)
|
||||||
|
@ -105,6 +105,9 @@ public:
|
||||||
|
|
||||||
Q_ENUM(ConnectionStatus)
|
Q_ENUM(ConnectionStatus)
|
||||||
|
|
||||||
|
//! return connection status
|
||||||
|
ConnectionStatus connectionStatus() const;
|
||||||
|
|
||||||
//! returns mnemonic seed
|
//! returns mnemonic seed
|
||||||
QString getSeed() const;
|
QString getSeed() const;
|
||||||
|
|
||||||
|
@ -120,10 +123,6 @@ public:
|
||||||
//! returns network type of the wallet.
|
//! returns network type of the wallet.
|
||||||
NetworkType::Type nettype() const;
|
NetworkType::Type nettype() const;
|
||||||
|
|
||||||
//! returns whether the wallet is connected, and version status
|
|
||||||
Q_INVOKABLE ConnectionStatus connected(bool forceCheck = false);
|
|
||||||
void updateConnectionStatusAsync();
|
|
||||||
|
|
||||||
//! returns true if wallet was ever synchronized
|
//! returns true if wallet was ever synchronized
|
||||||
bool synchronized() const;
|
bool synchronized() const;
|
||||||
|
|
||||||
|
@ -167,9 +166,15 @@ public:
|
||||||
//! connects to daemon
|
//! connects to daemon
|
||||||
Q_INVOKABLE bool connectToDaemon();
|
Q_INVOKABLE bool connectToDaemon();
|
||||||
|
|
||||||
//! indicates id daemon is trusted
|
//! set connect to daemon timeout
|
||||||
|
Q_INVOKABLE void setConnectionTimeout(int timeout);
|
||||||
|
|
||||||
|
//! indicates if daemon is trusted
|
||||||
Q_INVOKABLE void setTrustedDaemon(bool arg);
|
Q_INVOKABLE void setTrustedDaemon(bool arg);
|
||||||
|
|
||||||
|
//! indicates if ssl should be used to connect to daemon
|
||||||
|
Q_INVOKABLE void setUseSSL(bool ssl);
|
||||||
|
|
||||||
//! returns balance
|
//! returns balance
|
||||||
Q_INVOKABLE quint64 balance() const;
|
Q_INVOKABLE quint64 balance() const;
|
||||||
Q_INVOKABLE quint64 balance(quint32 accountIndex) const;
|
Q_INVOKABLE quint64 balance(quint32 accountIndex) const;
|
||||||
|
@ -394,6 +399,9 @@ public:
|
||||||
Q_INVOKABLE void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort=false);
|
Q_INVOKABLE void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort=false);
|
||||||
virtual void onWalletPassphraseNeeded(bool on_device) override;
|
virtual void onWalletPassphraseNeeded(bool on_device) override;
|
||||||
|
|
||||||
|
Q_INVOKABLE quint64 getBytesReceived() const;
|
||||||
|
Q_INVOKABLE quint64 getBytesSent() const;
|
||||||
|
|
||||||
// TODO: setListenter() when it implemented in API
|
// TODO: setListenter() when it implemented in API
|
||||||
signals:
|
signals:
|
||||||
// emitted on every event happened with wallet
|
// emitted on every event happened with wallet
|
||||||
|
@ -402,7 +410,7 @@ signals:
|
||||||
|
|
||||||
// emitted when refresh process finished (could take a long time)
|
// emitted when refresh process finished (could take a long time)
|
||||||
// signalling only after we
|
// signalling only after we
|
||||||
void refreshed();
|
void refreshed(bool success);
|
||||||
|
|
||||||
void moneySpent(const QString &txId, quint64 amount);
|
void moneySpent(const QString &txId, quint64 amount);
|
||||||
void moneyReceived(const QString &txId, quint64 amount);
|
void moneyReceived(const QString &txId, quint64 amount);
|
||||||
|
@ -445,6 +453,7 @@ private:
|
||||||
bool disconnected() const;
|
bool disconnected() const;
|
||||||
bool refreshing() const;
|
bool refreshing() const;
|
||||||
void refreshingSet(bool value);
|
void refreshingSet(bool value);
|
||||||
|
void onRefreshed(bool success);
|
||||||
|
|
||||||
void setConnectionStatus(ConnectionStatus value);
|
void setConnectionStatus(ConnectionStatus value);
|
||||||
QString getProxyAddress() const;
|
QString getProxyAddress() const;
|
||||||
|
@ -489,10 +498,13 @@ private:
|
||||||
QString m_daemonPassword;
|
QString m_daemonPassword;
|
||||||
QString m_proxyAddress;
|
QString m_proxyAddress;
|
||||||
mutable QMutex m_proxyMutex;
|
mutable QMutex m_proxyMutex;
|
||||||
|
std::atomic<bool> m_refreshNow;
|
||||||
std::atomic<bool> m_refreshEnabled;
|
std::atomic<bool> m_refreshEnabled;
|
||||||
std::atomic<bool> m_refreshing;
|
std::atomic<bool> m_refreshing;
|
||||||
WalletListenerImpl *m_walletListener;
|
WalletListenerImpl *m_walletListener;
|
||||||
FutureScheduler m_scheduler;
|
FutureScheduler m_scheduler;
|
||||||
|
int m_connectionTimeout = 30;
|
||||||
|
bool m_useSSL;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -41,10 +41,11 @@ void WalletListenerImpl::updated()
|
||||||
}
|
}
|
||||||
|
|
||||||
// called when wallet refreshed by background thread or explicitly
|
// called when wallet refreshed by background thread or explicitly
|
||||||
void WalletListenerImpl::refreshed()
|
void WalletListenerImpl::refreshed(bool success)
|
||||||
{
|
{
|
||||||
qDebug() << __FUNCTION__;
|
qDebug() << __FUNCTION__;
|
||||||
emit m_wallet->refreshed();
|
m_wallet->onRefreshed(success);
|
||||||
|
emit m_wallet->refreshed(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WalletListenerImpl::onDeviceButtonRequest(uint64_t code)
|
void WalletListenerImpl::onDeviceButtonRequest(uint64_t code)
|
||||||
|
|
|
@ -25,7 +25,7 @@ public:
|
||||||
virtual void updated() override;
|
virtual void updated() override;
|
||||||
|
|
||||||
// called when wallet refreshed by background thread or explicitly
|
// called when wallet refreshed by background thread or explicitly
|
||||||
virtual void refreshed() override;
|
virtual void refreshed(bool success) override;
|
||||||
|
|
||||||
virtual void onDeviceButtonRequest(uint64_t code) override;
|
virtual void onDeviceButtonRequest(uint64_t code) override;
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ public:
|
||||||
virtual void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) override { (void)txId; (void)amount; };
|
virtual void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) override { (void)txId; (void)amount; };
|
||||||
virtual void newBlock(uint64_t height) override { (void) height; };
|
virtual void newBlock(uint64_t height) override { (void) height; };
|
||||||
virtual void updated() override {};
|
virtual void updated() override {};
|
||||||
virtual void refreshed() override {};
|
virtual void refreshed(bool success) override {};
|
||||||
|
|
||||||
virtual void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) override
|
virtual void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort) override
|
||||||
{
|
{
|
||||||
|
@ -335,7 +335,7 @@ bool WalletManager::isMining() const
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&m_mutex);
|
QMutexLocker locker(&m_mutex);
|
||||||
if (m_currentWallet == nullptr || !m_currentWallet->connected())
|
if (m_currentWallet == nullptr || !m_currentWallet->connectionStatus())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -366,6 +366,8 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
|
||||||
this->initMain();
|
this->initMain();
|
||||||
this->initWidgets();
|
this->initWidgets();
|
||||||
this->initMenu();
|
this->initMenu();
|
||||||
|
|
||||||
|
connect(&m_updateBytes, &QTimer::timeout, this, &MainWindow::updateNetStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::initMain() {
|
void MainWindow::initMain() {
|
||||||
|
@ -669,6 +671,8 @@ void MainWindow::onWalletOpened() {
|
||||||
|
|
||||||
this->touchbarShowWallet();
|
this->touchbarShowWallet();
|
||||||
this->updatePasswordIcon();
|
this->updatePasswordIcon();
|
||||||
|
|
||||||
|
m_updateBytes.start(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onBalanceUpdated(double balance, double unlocked, const QString &balance_str, const QString &unlocked_str) {
|
void MainWindow::onBalanceUpdated(double balance, double unlocked, const QString &balance_str, const QString &unlocked_str) {
|
||||||
|
@ -688,6 +692,7 @@ void MainWindow::onBalanceUpdated(double balance, double unlocked, const QString
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onSynchronized() {
|
void MainWindow::onSynchronized() {
|
||||||
|
this->updateNetStats();
|
||||||
m_statusLabelStatus->setText("Synchronized");
|
m_statusLabelStatus->setText("Synchronized");
|
||||||
this->onConnectionStatusChanged(Wallet::ConnectionStatus_Connected);
|
this->onConnectionStatusChanged(Wallet::ConnectionStatus_Connected);
|
||||||
}
|
}
|
||||||
|
@ -700,11 +705,12 @@ void MainWindow::onBlockchainSync(int height, int target) {
|
||||||
void MainWindow::onRefreshSync(int height, int target) {
|
void MainWindow::onRefreshSync(int height, int target) {
|
||||||
QString heightText = QString("Wallet refresh: %1/%2").arg(height).arg(target);
|
QString heightText = QString("Wallet refresh: %1/%2").arg(height).arg(target);
|
||||||
m_statusLabelStatus->setText(heightText);
|
m_statusLabelStatus->setText(heightText);
|
||||||
|
this->updateNetStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onConnectionStatusChanged(int status)
|
void MainWindow::onConnectionStatusChanged(int status)
|
||||||
{
|
{
|
||||||
qDebug() << "Wallet connection status changed " << status;
|
qDebug() << "Wallet connection status changed " << Utils::QtEnumToString(static_cast<Wallet::ConnectionStatus>(status));
|
||||||
|
|
||||||
// Update connection info in status bar.
|
// Update connection info in status bar.
|
||||||
|
|
||||||
|
@ -746,7 +752,7 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QStrin
|
||||||
auto tx_err = tx->errorString();
|
auto tx_err = tx->errorString();
|
||||||
qCritical() << tx_err;
|
qCritical() << tx_err;
|
||||||
|
|
||||||
if(m_ctx->currentWallet->connected() == Wallet::ConnectionStatus_WrongVersion)
|
if (m_ctx->currentWallet->connectionStatus() == Wallet::ConnectionStatus_WrongVersion)
|
||||||
err = QString("%1 Wrong daemon version: %2").arg(err).arg(tx_err);
|
err = QString("%1 Wrong daemon version: %2").arg(err).arg(tx_err);
|
||||||
else
|
else
|
||||||
err = QString("%1 %2").arg(err).arg(tx_err);
|
err = QString("%1 %2").arg(err).arg(tx_err);
|
||||||
|
@ -826,6 +832,10 @@ void MainWindow::create_status_bar() {
|
||||||
m_statusLabelStatus->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
m_statusLabelStatus->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
this->statusBar()->addWidget(m_statusLabelStatus);
|
this->statusBar()->addWidget(m_statusLabelStatus);
|
||||||
|
|
||||||
|
m_statusLabelNetStats = new QLabel("", this);
|
||||||
|
m_statusLabelNetStats->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
|
this->statusBar()->addWidget(m_statusLabelNetStats);
|
||||||
|
|
||||||
m_statusLabelBalance = new QLabel("Balance: 0.00 XMR", this);
|
m_statusLabelBalance = new QLabel("Balance: 0.00 XMR", this);
|
||||||
m_statusLabelBalance->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
m_statusLabelBalance->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||||
this->statusBar()->addPermanentWidget(m_statusLabelBalance);
|
this->statusBar()->addPermanentWidget(m_statusLabelBalance);
|
||||||
|
@ -864,7 +874,7 @@ void MainWindow::showSeedDialog() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::showConnectionStatusDialog() {
|
void MainWindow::showConnectionStatusDialog() {
|
||||||
auto status = m_ctx->currentWallet->connected(true);
|
auto status = m_ctx->currentWallet->connectionStatus();
|
||||||
bool synchronized = m_ctx->currentWallet->synchronized();
|
bool synchronized = m_ctx->currentWallet->synchronized();
|
||||||
|
|
||||||
QString statusMsg;
|
QString statusMsg;
|
||||||
|
@ -891,6 +901,9 @@ void MainWindow::showConnectionStatusDialog() {
|
||||||
statusMsg = "Unknown connection status (this should never happen).";
|
statusMsg = "Unknown connection status (this should never happen).";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statusMsg += QString("\n\nTx: %1, Rx: %2").arg(Utils::formatBytes(m_ctx->currentWallet->getBytesSent()),
|
||||||
|
Utils::formatBytes(m_ctx->currentWallet->getBytesReceived()));
|
||||||
|
|
||||||
QMessageBox::information(this, "Connection Status", statusMsg);
|
QMessageBox::information(this, "Connection Status", statusMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1304,6 +1317,20 @@ void MainWindow::importTransaction() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::updateNetStats() {
|
||||||
|
if (!m_ctx->currentWallet) {
|
||||||
|
m_statusLabelNetStats->setText("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_ctx->currentWallet->connectionStatus() == Wallet::ConnectionStatus_Connected && m_ctx->currentWallet->synchronized()) {
|
||||||
|
m_statusLabelNetStats->setText("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_statusLabelNetStats->setText(QString("(D: %1)").arg(Utils::formatBytes(m_ctx->currentWallet->getBytesReceived())));
|
||||||
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow() {
|
MainWindow::~MainWindow() {
|
||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,6 +163,7 @@ private:
|
||||||
void touchbarShowWizard();
|
void touchbarShowWizard();
|
||||||
void touchbarShowWallet();
|
void touchbarShowWallet();
|
||||||
void updatePasswordIcon();
|
void updatePasswordIcon();
|
||||||
|
void updateNetStats();
|
||||||
|
|
||||||
WalletWizard *createWizard(WalletWizard::Page startPage);
|
WalletWizard *createWizard(WalletWizard::Page startPage);
|
||||||
|
|
||||||
|
@ -192,6 +193,7 @@ private:
|
||||||
// lower status bar
|
// lower status bar
|
||||||
QLabel *m_statusLabelBalance;
|
QLabel *m_statusLabelBalance;
|
||||||
QLabel *m_statusLabelStatus;
|
QLabel *m_statusLabelStatus;
|
||||||
|
QLabel *m_statusLabelNetStats;
|
||||||
StatusBarButton *m_statusBtnConnectionStatusIndicator;
|
StatusBarButton *m_statusBtnConnectionStatusIndicator;
|
||||||
StatusBarButton *m_statusBtnPassword;
|
StatusBarButton *m_statusBtnPassword;
|
||||||
StatusBarButton *m_statusBtnPreferences;
|
StatusBarButton *m_statusBtnPreferences;
|
||||||
|
@ -213,6 +215,8 @@ private:
|
||||||
|
|
||||||
QMap<QString, QString> m_skins;
|
QMap<QString, QString> m_skins;
|
||||||
|
|
||||||
|
QTimer m_updateBytes;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void menuToggleTabVisible(const QString &key);
|
void menuToggleTabVisible(const QString &key);
|
||||||
};
|
};
|
||||||
|
|
|
@ -70,9 +70,9 @@ QVariant NodeModel::data(const QModelIndex &index, int role) const {
|
||||||
}
|
}
|
||||||
else if(role == Qt::BackgroundRole) {
|
else if(role == Qt::BackgroundRole) {
|
||||||
if (node.isConnecting)
|
if (node.isConnecting)
|
||||||
return QBrush(QColor(186, 247, 255));
|
return QBrush(QColor("#A9DEF9"));
|
||||||
else if (node.isActive)
|
else if (node.isActive)
|
||||||
return QBrush(QColor(158, 250, 158));
|
return QBrush(QColor("#78BC61"));
|
||||||
}
|
}
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ Settings::Settings(QWidget *parent) :
|
||||||
// nodes
|
// nodes
|
||||||
ui->nodeWidget->setupUI(m_ctx);
|
ui->nodeWidget->setupUI(m_ctx);
|
||||||
connect(ui->nodeWidget, &NodeWidget::nodeSourceChanged, m_ctx->nodes, &Nodes::onNodeSourceChanged);
|
connect(ui->nodeWidget, &NodeWidget::nodeSourceChanged, m_ctx->nodes, &Nodes::onNodeSourceChanged);
|
||||||
connect(ui->nodeWidget, &NodeWidget::connectToNode, m_ctx->nodes, QOverload<FeatherNode>::of(&Nodes::connectToNode));
|
connect(ui->nodeWidget, &NodeWidget::connectToNode, m_ctx->nodes, QOverload<const FeatherNode&>::of(&Nodes::connectToNode));
|
||||||
|
|
||||||
// setup checkboxes
|
// setup checkboxes
|
||||||
ui->checkBox_externalLink->setChecked(config()->get(Config::warnOnExternalLink).toBool());
|
ui->checkBox_externalLink->setChecked(config()->get(Config::warnOnExternalLink).toBool());
|
||||||
|
|
|
@ -1831,6 +1831,7 @@ QColumnView:selected {
|
||||||
color: #32414B;
|
color: #32414B;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QTreeView:hover,
|
QTreeView:hover,
|
||||||
QListView:hover,
|
QListView:hover,
|
||||||
QTableView:hover,
|
QTableView:hover,
|
||||||
|
@ -1838,6 +1839,7 @@ QColumnView:hover {
|
||||||
background-color: #19232D;
|
background-color: #19232D;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QTreeView::item:pressed,
|
QTreeView::item:pressed,
|
||||||
QListView::item:pressed,
|
QListView::item:pressed,
|
||||||
QTableView::item:pressed,
|
QTableView::item:pressed,
|
||||||
|
|
|
@ -4,11 +4,9 @@
|
||||||
#include <QtCore>
|
#include <QtCore>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QNetworkAccessManager>
|
#include <QNetworkAccessManager>
|
||||||
#include <QNetworkReply>
|
|
||||||
|
|
||||||
#include "nodes.h"
|
#include "nodes.h"
|
||||||
#include "utils/utils.h"
|
#include "utils/utils.h"
|
||||||
#include "utils/networking.h"
|
|
||||||
#include "appcontext.h"
|
#include "appcontext.h"
|
||||||
|
|
||||||
Nodes::Nodes(AppContext *ctx, QNetworkAccessManager *networkAccessManager, QObject *parent) :
|
Nodes::Nodes(AppContext *ctx, QNetworkAccessManager *networkAccessManager, QObject *parent) :
|
||||||
|
@ -19,23 +17,20 @@ Nodes::Nodes(AppContext *ctx, QNetworkAccessManager *networkAccessManager, QObje
|
||||||
modelWebsocket(new NodeModel(NodeSource::websocket, this)),
|
modelWebsocket(new NodeModel(NodeSource::websocket, this)),
|
||||||
modelCustom(new NodeModel(NodeSource::custom, this)) {
|
modelCustom(new NodeModel(NodeSource::custom, this)) {
|
||||||
this->loadConfig();
|
this->loadConfig();
|
||||||
|
|
||||||
connect(m_connectionTimer, &QTimer::timeout, this, &Nodes::onConnectionTimer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nodes::loadConfig() {
|
void Nodes::loadConfig() {
|
||||||
QString msg;
|
|
||||||
auto configNodes = config()->get(Config::nodes).toByteArray();
|
auto configNodes = config()->get(Config::nodes).toByteArray();
|
||||||
auto key = QString::number(m_ctx->networkType);
|
auto key = QString::number(m_ctx->networkType);
|
||||||
if (!Utils::validateJSON(configNodes)) {
|
if (!Utils::validateJSON(configNodes)) {
|
||||||
m_configJson[key] = QJsonObject();
|
m_configJson[key] = QJsonObject();
|
||||||
qCritical() << "fixed malformed config key \"nodes\"";
|
qCritical() << "Fixed malformed config key \"nodes\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(configNodes);
|
QJsonDocument doc = QJsonDocument::fromJson(configNodes);
|
||||||
m_configJson = doc.object();
|
m_configJson = doc.object();
|
||||||
|
|
||||||
if(!m_configJson.contains(key))
|
if (!m_configJson.contains(key))
|
||||||
m_configJson[key] = QJsonObject();
|
m_configJson[key] = QJsonObject();
|
||||||
|
|
||||||
auto obj = m_configJson.value(key).toObject();
|
auto obj = m_configJson.value(key).toObject();
|
||||||
|
@ -46,7 +41,7 @@ void Nodes::loadConfig() {
|
||||||
|
|
||||||
// load custom nodes
|
// load custom nodes
|
||||||
auto nodes = obj.value("custom").toArray();
|
auto nodes = obj.value("custom").toArray();
|
||||||
foreach (const QJsonValue &value, nodes) {
|
for (auto value: nodes) {
|
||||||
auto customNode = FeatherNode(value.toString());
|
auto customNode = FeatherNode(value.toString());
|
||||||
customNode.custom = true;
|
customNode.custom = true;
|
||||||
|
|
||||||
|
@ -62,15 +57,15 @@ void Nodes::loadConfig() {
|
||||||
|
|
||||||
// load cached websocket nodes
|
// load cached websocket nodes
|
||||||
auto ws = obj.value("ws").toArray();
|
auto ws = obj.value("ws").toArray();
|
||||||
foreach (const QJsonValue &value, ws) {
|
for (auto value: ws) {
|
||||||
auto wsNode = FeatherNode(value.toString());
|
auto wsNode = FeatherNode(value.toString());
|
||||||
wsNode.custom = false;
|
wsNode.custom = false;
|
||||||
wsNode.online = true; // assume online
|
wsNode.online = true; // assume online
|
||||||
|
|
||||||
if(m_connection == wsNode) {
|
if (m_connection == wsNode) {
|
||||||
if(m_connection.isActive)
|
if (m_connection.isActive)
|
||||||
wsNode.isActive = true;
|
wsNode.isActive = true;
|
||||||
else if(m_connection.isConnecting)
|
else if (m_connection.isConnecting)
|
||||||
wsNode.isConnecting = true;
|
wsNode.isConnecting = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,14 +76,12 @@ void Nodes::loadConfig() {
|
||||||
obj["source"] = NodeSource::websocket;
|
obj["source"] = NodeSource::websocket;
|
||||||
m_source = static_cast<NodeSource>(obj.value("source").toInt());
|
m_source = static_cast<NodeSource>(obj.value("source").toInt());
|
||||||
|
|
||||||
if(m_websocketNodes.count() > 0){
|
if (m_websocketNodes.count() > 0) {
|
||||||
msg = QString("Loaded %1 cached websocket nodes from config").arg(m_websocketNodes.count());
|
qDebug() << QString("Loaded %1 cached websocket nodes from config").arg(m_websocketNodes.count());
|
||||||
activityLog.append(msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(m_customNodes.count() > 0){
|
if (m_customNodes.count() > 0) {
|
||||||
msg = QString("Loaded %1 custom nodes from config").arg(m_customNodes.count());
|
qDebug() << QString("Loaded %1 custom nodes from config").arg(m_customNodes.count());
|
||||||
activityLog.append(msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_configJson[key] = obj;
|
m_configJson[key] = obj;
|
||||||
|
@ -101,33 +94,30 @@ void Nodes::writeConfig() {
|
||||||
QString output(doc.toJson(QJsonDocument::Compact));
|
QString output(doc.toJson(QJsonDocument::Compact));
|
||||||
config()->set(Config::nodes, output);
|
config()->set(Config::nodes, output);
|
||||||
|
|
||||||
auto msg = QString("Saved node config.");
|
qDebug() << "Saved node config.";
|
||||||
activityLog.append(msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nodes::connectToNode() {
|
void Nodes::connectToNode() {
|
||||||
// auto connect
|
// auto connect
|
||||||
m_connectionAttempts.clear();
|
|
||||||
m_wsExhaustedWarningEmitted = false;
|
m_wsExhaustedWarningEmitted = false;
|
||||||
m_customExhaustedWarningEmitted = false;
|
m_customExhaustedWarningEmitted = false;
|
||||||
m_connectionTimer->start(2000);
|
this->autoConnect();
|
||||||
this->onConnectionTimer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nodes::connectToNode(FeatherNode node) {
|
void Nodes::connectToNode(const FeatherNode &node) {
|
||||||
if(node.address.isEmpty())
|
if (node.address.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
emit updateStatus(QString("Connecting to %1").arg(node.address));
|
emit updateStatus(QString("Connecting to %1").arg(node.address));
|
||||||
auto msg = QString("Attempting to connect to %1 (%2)")
|
qInfo() << QString("Attempting to connect to %1 (%2)").arg(node.address).arg(node.custom ? "custom" : "ws");
|
||||||
.arg(node.address).arg(node.custom ? "custom" : "ws");
|
|
||||||
qInfo() << msg;
|
|
||||||
activityLog.append(msg);
|
|
||||||
|
|
||||||
if (!node.username.isEmpty() && !node.password.isEmpty())
|
if (!node.username.isEmpty() && !node.password.isEmpty())
|
||||||
m_ctx->currentWallet->setDaemonLogin(node.username, node.password);
|
m_ctx->currentWallet->setDaemonLogin(node.username, node.password);
|
||||||
|
|
||||||
|
// Don't use SSL over Tor
|
||||||
|
m_ctx->currentWallet->setUseSSL(!node.tor);
|
||||||
|
|
||||||
m_ctx->currentWallet->initAsync(node.address, true, 0, false, false, 0);
|
m_ctx->currentWallet->initAsync(node.address, true, 0, false, false, 0);
|
||||||
m_connectionAttemptTime = std::time(nullptr);
|
|
||||||
|
|
||||||
m_connection = node;
|
m_connection = node;
|
||||||
m_connection.isActive = false;
|
m_connection.isActive = false;
|
||||||
|
@ -135,88 +125,55 @@ void Nodes::connectToNode(FeatherNode node) {
|
||||||
|
|
||||||
this->resetLocalState();
|
this->resetLocalState();
|
||||||
this->updateModels();
|
this->updateModels();
|
||||||
|
|
||||||
m_connectionTimer->start(1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nodes::onConnectionTimer() {
|
void Nodes::autoConnect(bool forceReconnect) {
|
||||||
// this function is responsible for automatically connecting to a daemon.
|
// this function is responsible for automatically connecting to a daemon.
|
||||||
if (m_ctx->currentWallet == nullptr) {
|
if (m_ctx->currentWallet == nullptr || !m_enableAutoconnect) {
|
||||||
m_connectionTimer->stop();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString msg;
|
Wallet::ConnectionStatus status = m_ctx->currentWallet->connectionStatus();
|
||||||
Wallet::ConnectionStatus status = m_ctx->currentWallet->connected(true);
|
bool wsMode = (this->source() == NodeSource::websocket);
|
||||||
NodeSource nodeSource = this->source();
|
|
||||||
auto wsMode = (nodeSource == NodeSource::websocket);
|
|
||||||
auto nodes = wsMode ? m_customNodes : m_websocketNodes;
|
auto nodes = wsMode ? m_customNodes : m_websocketNodes;
|
||||||
|
|
||||||
if (wsMode && !m_wsNodesReceived && m_websocketNodes.count() == 0) {
|
if (wsMode && !m_wsNodesReceived && m_websocketNodes.count() == 0) {
|
||||||
// this situation should rarely occur due to the usage of the websocket node cache on startup.
|
// this situation should rarely occur due to the usage of the websocket node cache on startup.
|
||||||
msg = QString("Feather is in websocket connection mode but was not able to receive any nodes (yet).");
|
qInfo() << "Feather is in websocket connection mode but was not able to receive any nodes (yet).";
|
||||||
qInfo() << msg;
|
|
||||||
activityLog.append(msg);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == Wallet::ConnectionStatus::ConnectionStatus_Disconnected) {
|
if (status == Wallet::ConnectionStatus_Disconnected || forceReconnect) {
|
||||||
|
if (!m_connection.address.isEmpty() && !forceReconnect) {
|
||||||
|
m_recentFailures << m_connection.address;
|
||||||
|
}
|
||||||
|
|
||||||
// try a connect
|
// try a connect
|
||||||
auto node = this->pickEligibleNode();
|
auto node = this->pickEligibleNode();
|
||||||
this->connectToNode(node);
|
this->connectToNode(node);
|
||||||
return;
|
return;
|
||||||
} else if (status == Wallet::ConnectionStatus::ConnectionStatus_Connecting){
|
}
|
||||||
if (!m_connection.isConnecting) {
|
else if (status == Wallet::ConnectionStatus_Connected && m_connection.isConnecting) {
|
||||||
// Weirdly enough, status == connecting directly after a wallet is opened.
|
qInfo() << QString("Node connected to %1").arg(m_connection.address);
|
||||||
auto node = this->pickEligibleNode();
|
|
||||||
this->connectToNode(node);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine timeout
|
|
||||||
unsigned int nodeConnectionTimeout = 6;
|
|
||||||
if(m_connection.tor)
|
|
||||||
nodeConnectionTimeout = 25;
|
|
||||||
|
|
||||||
auto connectionTimeout = static_cast<unsigned int>(std::time(nullptr) - m_connectionAttemptTime);
|
|
||||||
if(connectionTimeout < nodeConnectionTimeout)
|
|
||||||
return; // timeout not reached yet
|
|
||||||
|
|
||||||
msg = QString("Node connection attempt stale after %1 seconds, picking new node").arg(nodeConnectionTimeout);
|
|
||||||
activityLog.append(msg);
|
|
||||||
qInfo() << msg;
|
|
||||||
|
|
||||||
auto newNode = this->pickEligibleNode();
|
|
||||||
this->connectToNode(newNode);
|
|
||||||
return;
|
|
||||||
} else if(status == Wallet::ConnectionStatus::ConnectionStatus_Connected) {
|
|
||||||
// wallet is connected to daemon successfully, poll status every 3 seconds
|
|
||||||
if(!m_connection.isConnecting)
|
|
||||||
return;
|
|
||||||
|
|
||||||
msg = QString("Node connected to %1").arg(m_connection.address);
|
|
||||||
qInfo() << msg;
|
|
||||||
activityLog.append(msg);
|
|
||||||
|
|
||||||
// set current connection object
|
// set current connection object
|
||||||
m_connection.isConnecting = false;
|
m_connection.isConnecting = false;
|
||||||
m_connection.isActive = true;
|
m_connection.isActive = true;
|
||||||
this->resetLocalState();
|
|
||||||
this->updateModels();
|
|
||||||
|
|
||||||
// reset node exhaustion state
|
// reset node exhaustion state
|
||||||
m_connectionAttempts.clear();
|
|
||||||
m_wsExhaustedWarningEmitted = false;
|
m_wsExhaustedWarningEmitted = false;
|
||||||
m_customExhaustedWarningEmitted = false;
|
m_customExhaustedWarningEmitted = false;
|
||||||
m_connectionTimer->setInterval(3000);
|
m_recentFailures.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->resetLocalState();
|
||||||
|
this->updateModels();
|
||||||
}
|
}
|
||||||
|
|
||||||
FeatherNode Nodes::pickEligibleNode() {
|
FeatherNode Nodes::pickEligibleNode() {
|
||||||
// Pick a node at random to connect to
|
// Pick a node at random to connect to
|
||||||
auto rtn = FeatherNode();
|
auto rtn = FeatherNode();
|
||||||
NodeSource nodeSource = this->source();
|
auto wsMode = (this->source() == NodeSource::websocket);
|
||||||
auto wsMode = nodeSource == NodeSource::websocket;
|
|
||||||
auto nodes = wsMode ? m_websocketNodes : m_customNodes;
|
auto nodes = wsMode ? m_websocketNodes : m_customNodes;
|
||||||
|
|
||||||
if (nodes.count() == 0) {
|
if (nodes.count() == 0) {
|
||||||
|
@ -224,51 +181,23 @@ FeatherNode Nodes::pickEligibleNode() {
|
||||||
return rtn;
|
return rtn;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<int> heights;
|
QVector<int> node_indeces;
|
||||||
|
int i = 0;
|
||||||
for (const auto &node: nodes) {
|
for (const auto &node: nodes) {
|
||||||
heights.push_back(node.height);
|
node_indeces.push_back(i);
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
|
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
|
||||||
|
std::shuffle(node_indeces.begin(), node_indeces.end(), std::default_random_engine(seed));
|
||||||
|
|
||||||
std::sort(heights.begin(), heights.end());
|
// Pick random eligible node
|
||||||
|
int mode_height = this->modeHeight(nodes);
|
||||||
|
for (int index : node_indeces) {
|
||||||
|
const FeatherNode &node = nodes.at(index);
|
||||||
|
|
||||||
// Calculate mode of node heights
|
// This may fail to detect bad nodes if cached nodes are used
|
||||||
int max_count = 1, mode_height = heights[0], count = 1;
|
// Todo: wait on websocket before connecting, only use cache if websocket is unavailable
|
||||||
for (int i = 1; i < heights.count(); i++) {
|
if (wsMode && m_wsNodesReceived) {
|
||||||
if (heights[i] == 0) { // Don't consider 0 height nodes
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (heights[i] == heights[i - 1])
|
|
||||||
count++;
|
|
||||||
else {
|
|
||||||
if (count > max_count) {
|
|
||||||
max_count = count;
|
|
||||||
mode_height = heights[i - 1];
|
|
||||||
}
|
|
||||||
count = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (count > max_count)
|
|
||||||
{
|
|
||||||
max_count = count;
|
|
||||||
mode_height = heights[heights.count() - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
while(true) {
|
|
||||||
// keep track of nodes we have previously tried to connect to
|
|
||||||
if (m_connectionAttempts.count() == nodes.count()) {
|
|
||||||
this->exhausted();
|
|
||||||
m_connectionTimer->stop();
|
|
||||||
return rtn;
|
|
||||||
}
|
|
||||||
|
|
||||||
int random = QRandomGenerator::global()->bounded(nodes.count());
|
|
||||||
FeatherNode node = nodes.at(random);
|
|
||||||
if (m_connectionAttempts.contains(node.full))
|
|
||||||
continue;
|
|
||||||
m_connectionAttempts.append(node.full);
|
|
||||||
|
|
||||||
if (wsMode) {
|
|
||||||
// Ignore offline nodes
|
// Ignore offline nodes
|
||||||
if (!node.online)
|
if (!node.online)
|
||||||
continue;
|
continue;
|
||||||
|
@ -282,19 +211,28 @@ FeatherNode Nodes::pickEligibleNode() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't connect to nodes that failed to connect recently
|
||||||
|
if (m_recentFailures.contains(node.address)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All nodes tried, and none eligible
|
||||||
|
this->exhausted();
|
||||||
|
return rtn;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nodes::onWSNodesReceived(const QList<QSharedPointer<FeatherNode>> &nodes) {
|
void Nodes::onWSNodesReceived(const QList<QSharedPointer<FeatherNode>> &nodes) {
|
||||||
m_websocketNodes.clear();
|
m_websocketNodes.clear();
|
||||||
m_wsNodesReceived = true;
|
m_wsNodesReceived = true;
|
||||||
|
|
||||||
for(auto &node: nodes) {
|
for (auto &node: nodes) {
|
||||||
if(m_connection == *node) {
|
if (m_connection == *node) {
|
||||||
if(m_connection.isActive)
|
if (m_connection.isActive)
|
||||||
node->isActive = true;
|
node->isActive = true;
|
||||||
else if(m_connection.isConnecting)
|
else if (m_connection.isConnecting)
|
||||||
node->isConnecting = true;
|
node->isConnecting = true;
|
||||||
}
|
}
|
||||||
m_websocketNodes.push_back(*node);
|
m_websocketNodes.push_back(*node);
|
||||||
|
@ -304,7 +242,7 @@ void Nodes::onWSNodesReceived(const QList<QSharedPointer<FeatherNode>> &nodes) {
|
||||||
auto key = QString::number(m_ctx->networkType);
|
auto key = QString::number(m_ctx->networkType);
|
||||||
auto obj = m_configJson.value(key).toObject();
|
auto obj = m_configJson.value(key).toObject();
|
||||||
auto ws = QJsonArray();
|
auto ws = QJsonArray();
|
||||||
for(auto const &node: m_websocketNodes)
|
for (auto const &node: m_websocketNodes)
|
||||||
ws.push_back(node.address);
|
ws.push_back(node.address);
|
||||||
|
|
||||||
obj["ws"] = ws;
|
obj["ws"] = ws;
|
||||||
|
@ -315,7 +253,10 @@ void Nodes::onWSNodesReceived(const QList<QSharedPointer<FeatherNode>> &nodes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nodes::onNodeSourceChanged(NodeSource nodeSource) {
|
void Nodes::onNodeSourceChanged(NodeSource nodeSource) {
|
||||||
if(nodeSource == this->source()) return;
|
if (nodeSource == this->source())
|
||||||
|
return;
|
||||||
|
m_source = nodeSource;
|
||||||
|
|
||||||
auto key = QString::number(m_ctx->networkType);
|
auto key = QString::number(m_ctx->networkType);
|
||||||
auto obj = m_configJson.value(key).toObject();
|
auto obj = m_configJson.value(key).toObject();
|
||||||
obj["source"] = nodeSource;
|
obj["source"] = nodeSource;
|
||||||
|
@ -324,16 +265,18 @@ void Nodes::onNodeSourceChanged(NodeSource nodeSource) {
|
||||||
this->writeConfig();
|
this->writeConfig();
|
||||||
this->resetLocalState();
|
this->resetLocalState();
|
||||||
this->updateModels();
|
this->updateModels();
|
||||||
|
|
||||||
|
this->autoConnect(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nodes::setCustomNodes(QList<FeatherNode> nodes) {
|
void Nodes::setCustomNodes(const QList<FeatherNode> &nodes) {
|
||||||
m_customNodes.clear();
|
m_customNodes.clear();
|
||||||
auto key = QString::number(m_ctx->networkType);
|
auto key = QString::number(m_ctx->networkType);
|
||||||
auto obj = m_configJson.value(key).toObject();
|
auto obj = m_configJson.value(key).toObject();
|
||||||
|
|
||||||
QStringList nodesList;
|
QStringList nodesList;
|
||||||
for(auto const &node: nodes) {
|
for (auto const &node: nodes) {
|
||||||
if(nodesList.contains(node.full)) continue;
|
if (nodesList.contains(node.full)) continue;
|
||||||
nodesList.append(node.full);
|
nodesList.append(node.full);
|
||||||
m_customNodes.append(node);
|
m_customNodes.append(node);
|
||||||
}
|
}
|
||||||
|
@ -352,56 +295,47 @@ void Nodes::updateModels() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nodes::resetLocalState() {
|
void Nodes::resetLocalState() {
|
||||||
QList<QList<FeatherNode>*> models = {&m_customNodes, &m_websocketNodes};
|
auto resetState = [this](QList<FeatherNode> *model){
|
||||||
|
for (auto&& node: *model) {
|
||||||
|
node.isConnecting = false;
|
||||||
|
node.isActive = false;
|
||||||
|
|
||||||
for(QList<FeatherNode> *model: models) {
|
if (node == m_connection) {
|
||||||
for (FeatherNode &_node: *model) {
|
node.isActive = m_connection.isActive;
|
||||||
_node.isConnecting = false;
|
node.isConnecting = m_connection.isConnecting;
|
||||||
_node.isActive = false;
|
|
||||||
|
|
||||||
if (_node == m_connection) {
|
|
||||||
_node.isActive = m_connection.isActive;
|
|
||||||
_node.isConnecting = m_connection.isConnecting;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
resetState(&m_customNodes);
|
||||||
|
resetState(&m_websocketNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nodes::exhausted() {
|
void Nodes::exhausted() {
|
||||||
NodeSource nodeSource = this->source();
|
bool wsMode = (this->source() == NodeSource::websocket);
|
||||||
auto wsMode = nodeSource == NodeSource::websocket;
|
|
||||||
if(wsMode)
|
if (wsMode)
|
||||||
this->WSNodeExhaustedWarning();
|
this->WSNodeExhaustedWarning();
|
||||||
else
|
else
|
||||||
this->nodeExhaustedWarning();
|
this->nodeExhaustedWarning();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nodes::nodeExhaustedWarning(){
|
void Nodes::nodeExhaustedWarning(){
|
||||||
if(m_customExhaustedWarningEmitted) return;
|
if (m_customExhaustedWarningEmitted)
|
||||||
|
return;
|
||||||
|
|
||||||
emit nodeExhausted();
|
emit nodeExhausted();
|
||||||
|
qWarning() << "Could not find an eligible custom node to connect to.";
|
||||||
auto msg = QString("Could not find an eligible custom node to connect to.");
|
|
||||||
qWarning() << msg;
|
|
||||||
activityLog.append(msg);
|
|
||||||
|
|
||||||
m_customExhaustedWarningEmitted = true;
|
m_customExhaustedWarningEmitted = true;
|
||||||
this->m_connectionTimer->stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nodes::WSNodeExhaustedWarning() {
|
void Nodes::WSNodeExhaustedWarning() {
|
||||||
if(m_wsExhaustedWarningEmitted) return;
|
if (m_wsExhaustedWarningEmitted)
|
||||||
|
return;
|
||||||
|
|
||||||
emit WSNodeExhausted();
|
emit WSNodeExhausted();
|
||||||
|
qWarning() << "Could not find an eligible websocket node to connect to.";
|
||||||
auto msg = QString("Could not find an eligible websocket node to connect to.");
|
|
||||||
qWarning() << msg;
|
|
||||||
activityLog.append(msg);
|
|
||||||
|
|
||||||
m_wsExhaustedWarningEmitted = true;
|
m_wsExhaustedWarningEmitted = true;
|
||||||
this->m_connectionTimer->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Nodes::onWalletClosing() {
|
|
||||||
m_connectionTimer->stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<FeatherNode> Nodes::customNodes() {
|
QList<FeatherNode> Nodes::customNodes() {
|
||||||
|
@ -412,10 +346,38 @@ FeatherNode Nodes::connection() {
|
||||||
return m_connection;
|
return m_connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nodes::stopTimer(){
|
|
||||||
m_connectionTimer->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
NodeSource Nodes::source() {
|
NodeSource Nodes::source() {
|
||||||
return m_source;
|
return m_source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Nodes::modeHeight(const QList<FeatherNode> &nodes) {
|
||||||
|
QVector<int> heights;
|
||||||
|
for (const auto &node: nodes) {
|
||||||
|
heights.push_back(node.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(heights.begin(), heights.end());
|
||||||
|
|
||||||
|
int max_count = 1, mode_height = heights[0], count = 1;
|
||||||
|
for (int i = 1; i < heights.count(); i++) {
|
||||||
|
if (heights[i] == 0) { // Don't consider 0 height nodes
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heights[i] == heights[i - 1])
|
||||||
|
count++;
|
||||||
|
else {
|
||||||
|
if (count > max_count) {
|
||||||
|
max_count = count;
|
||||||
|
mode_height = heights[i - 1];
|
||||||
|
}
|
||||||
|
count = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count > max_count)
|
||||||
|
{
|
||||||
|
mode_height = heights[heights.count() - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return mode_height;
|
||||||
|
}
|
|
@ -86,7 +86,6 @@ public:
|
||||||
explicit Nodes(AppContext *ctx, QNetworkAccessManager *networkAccessManager, QObject *parent = nullptr);
|
explicit Nodes(AppContext *ctx, QNetworkAccessManager *networkAccessManager, QObject *parent = nullptr);
|
||||||
void loadConfig();
|
void loadConfig();
|
||||||
void writeConfig();
|
void writeConfig();
|
||||||
void stopTimer();
|
|
||||||
|
|
||||||
NodeSource source();
|
NodeSource source();
|
||||||
FeatherNode connection();
|
FeatherNode connection();
|
||||||
|
@ -95,18 +94,13 @@ public:
|
||||||
NodeModel *modelWebsocket;
|
NodeModel *modelWebsocket;
|
||||||
NodeModel *modelCustom;
|
NodeModel *modelCustom;
|
||||||
|
|
||||||
QStringList activityLog;
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void onWalletClosing();
|
|
||||||
void connectToNode();
|
void connectToNode();
|
||||||
void connectToNode(FeatherNode node);
|
void connectToNode(const FeatherNode &node);
|
||||||
void onWSNodesReceived(const QList<QSharedPointer<FeatherNode>>& nodes);
|
void onWSNodesReceived(const QList<QSharedPointer<FeatherNode>>& nodes);
|
||||||
void onNodeSourceChanged(NodeSource nodeSource);
|
void onNodeSourceChanged(NodeSource nodeSource);
|
||||||
void setCustomNodes(QList<FeatherNode> nodes);
|
void setCustomNodes(const QList<FeatherNode>& nodes);
|
||||||
|
void autoConnect(bool forceReconnect = false);
|
||||||
private slots:
|
|
||||||
void onConnectionTimer();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void WSNodeExhausted();
|
void WSNodeExhausted();
|
||||||
|
@ -119,18 +113,17 @@ private:
|
||||||
QNetworkAccessManager *m_networkAccessManager = nullptr;
|
QNetworkAccessManager *m_networkAccessManager = nullptr;
|
||||||
QJsonObject m_configJson;
|
QJsonObject m_configJson;
|
||||||
|
|
||||||
|
QStringList m_recentFailures;
|
||||||
|
|
||||||
QList<FeatherNode> m_customNodes;
|
QList<FeatherNode> m_customNodes;
|
||||||
QList<FeatherNode> m_websocketNodes;
|
QList<FeatherNode> m_websocketNodes;
|
||||||
|
|
||||||
FeatherNode m_connection; // current active connection, if any
|
FeatherNode m_connection; // current active connection, if any
|
||||||
QTimer *m_connectionTimer = new QTimer(this);
|
|
||||||
time_t m_connectionAttemptTime = 0;
|
|
||||||
QStringList m_connectionAttempts;
|
|
||||||
|
|
||||||
bool m_wsNodesReceived = false;
|
bool m_wsNodesReceived = false;
|
||||||
|
|
||||||
bool m_wsExhaustedWarningEmitted = true;
|
bool m_wsExhaustedWarningEmitted = true;
|
||||||
bool m_customExhaustedWarningEmitted = true;
|
bool m_customExhaustedWarningEmitted = true;
|
||||||
|
bool m_enableAutoconnect = true;
|
||||||
|
|
||||||
FeatherNode pickEligibleNode();
|
FeatherNode pickEligibleNode();
|
||||||
|
|
||||||
|
@ -139,6 +132,7 @@ private:
|
||||||
void exhausted();
|
void exhausted();
|
||||||
void WSNodeExhaustedWarning();
|
void WSNodeExhaustedWarning();
|
||||||
void nodeExhaustedWarning();
|
void nodeExhaustedWarning();
|
||||||
|
int modeHeight(const QList<FeatherNode> &nodes);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //FEATHER_NODES_H
|
#endif //FEATHER_NODES_H
|
||||||
|
|
|
@ -541,9 +541,8 @@ QFont Utils::relativeFont(int delta) {
|
||||||
double Utils::roundSignificant(double N, double n)
|
double Utils::roundSignificant(double N, double n)
|
||||||
{
|
{
|
||||||
int h;
|
int h;
|
||||||
double l, a, b, c, d, e, i, j, m, f, g;
|
double b, d, e, i, j, m, f;
|
||||||
b = N;
|
b = N;
|
||||||
c = floor(N);
|
|
||||||
|
|
||||||
for (i = 0; b >= 1; ++i)
|
for (i = 0; b >= 1; ++i)
|
||||||
b = b / 10;
|
b = b / 10;
|
||||||
|
@ -564,3 +563,22 @@ double Utils::roundSignificant(double N, double n)
|
||||||
j = j / m;
|
j = j / m;
|
||||||
return j;
|
return j;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Utils::formatBytes(quint64 bytes)
|
||||||
|
{
|
||||||
|
QVector<QString> sizes = { "B", "KB", "MB", "GB", "TB" };
|
||||||
|
|
||||||
|
int i;
|
||||||
|
double _data;
|
||||||
|
for (i = 0; i < sizes.count() && bytes >= 1000; i++, bytes /= 1000)
|
||||||
|
_data = bytes / 1000.0;
|
||||||
|
|
||||||
|
if (_data < 0)
|
||||||
|
_data = 0;
|
||||||
|
|
||||||
|
// unrealistic
|
||||||
|
if (_data > 1000)
|
||||||
|
_data = 0;
|
||||||
|
|
||||||
|
return QString("%1 %2").arg(QString::number(_data, 'f', 1), sizes[i]);
|
||||||
|
}
|
|
@ -95,6 +95,8 @@ public:
|
||||||
static bool pixmapWrite(const QString &path, const QPixmap &pixmap);
|
static bool pixmapWrite(const QString &path, const QPixmap &pixmap);
|
||||||
static QFont relativeFont(int delta);
|
static QFont relativeFont(int delta);
|
||||||
static double roundSignificant(double N, double n);
|
static double roundSignificant(double N, double n);
|
||||||
|
static QString formatBytes(quint64 bytes);
|
||||||
|
|
||||||
static QStringList randomHTTPAgents;
|
static QStringList randomHTTPAgents;
|
||||||
|
|
||||||
template<typename QEnum>
|
template<typename QEnum>
|
||||||
|
|
Loading…
Reference in a new issue