Merge pull request #2819

6ed7fce UpdateDialog: implement update download functionality (xiphon)
This commit is contained in:
luigi1111
2020-04-13 15:38:06 -05:00
13 changed files with 671 additions and 121 deletions

View File

@@ -61,6 +61,7 @@
#include "wallet/api/wallet2_api.h"
#include "Logger.h"
#include "MainApp.h"
#include "qt/downloader.h"
#include "qt/ipc.h"
#include "qt/network.h"
#include "qt/utils.h"
@@ -295,6 +296,7 @@ int main(int argc, char *argv[])
// registering types for QML
qmlRegisterType<clipboardAdapter>("moneroComponents.Clipboard", 1, 0, "Clipboard");
qmlRegisterType<Downloader>("moneroComponents.Downloader", 1, 0, "Downloader");
// Temporary Qt.labs.settings replacement
qmlRegisterType<MoneroSettings>("moneroComponents.Settings", 1, 0, "MoneroSettings");

View File

@@ -27,6 +27,8 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "oshelper.h"
#include <QFileDialog>
#include <QStandardPaths>
#include <QTemporaryFile>
#include <QDir>
#include <QDebug>
@@ -82,6 +84,11 @@ OSHelper::OSHelper(QObject *parent) : QObject(parent)
}
QString OSHelper::downloadLocation() const
{
return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
}
bool OSHelper::openContainingFolder(const QString &filePath) const
{
#if defined(Q_OS_WIN)
@@ -105,6 +112,12 @@ bool OSHelper::openContainingFolder(const QString &filePath) const
return QDesktopServices::openUrl(url);
}
QString OSHelper::openSaveFileDialog(const QString &title, const QString &folder, const QString &filename) const
{
const QString hint = (folder.isEmpty() ? "" : folder + QDir::separator()) + filename;
return QFileDialog::getSaveFileName(nullptr, title, hint);
}
QString OSHelper::temporaryFilename() const
{
QString tempFileName;

View File

@@ -39,7 +39,9 @@ class OSHelper : public QObject
public:
explicit OSHelper(QObject *parent = 0);
Q_INVOKABLE QString downloadLocation() const;
Q_INVOKABLE bool openContainingFolder(const QString &filePath) const;
Q_INVOKABLE QString openSaveFileDialog(const QString &title, const QString &folder, const QString &filename) const;
Q_INVOKABLE QString temporaryFilename() const;
Q_INVOKABLE QString temporaryPath() const;
Q_INVOKABLE bool removeTemporaryWallet(const QString &walletName) const;

207
src/qt/downloader.cpp Normal file
View File

@@ -0,0 +1,207 @@
// Copyright (c) 2020, 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.
#include "downloader.h"
#include <QReadLocker>
#include <QWriteLocker>
namespace
{
class DownloaderStateGuard
{
public:
DownloaderStateGuard(bool &active, QReadWriteLock &mutex, std::function<void()> onActiveChanged)
: m_active(active)
, m_acquired(false)
, m_mutex(mutex)
, m_onActiveChanged(std::move(onActiveChanged))
{
{
QWriteLocker locker(&m_mutex);
if (m_active)
{
return;
}
m_active = true;
}
m_onActiveChanged();
m_acquired = true;
}
~DownloaderStateGuard()
{
if (!m_acquired)
{
return;
}
{
QWriteLocker locker(&m_mutex);
m_active = false;
}
m_onActiveChanged();
}
bool acquired() const
{
return m_acquired;
}
private:
bool &m_active;
bool m_acquired;
QReadWriteLock &m_mutex;
std::function<void()> m_onActiveChanged;
};
} // namespace
Downloader::Downloader(QObject *parent)
: QObject(parent)
, m_active(false)
, m_httpClient(new HttpClient())
, m_network(this)
, m_scheduler(this)
{
QObject::connect(m_httpClient.get(), SIGNAL(contentLengthChanged()), this, SIGNAL(totalChanged()));
QObject::connect(m_httpClient.get(), SIGNAL(receivedChanged()), this, SIGNAL(loadedChanged()));
}
Downloader::~Downloader()
{
cancel();
}
void Downloader::cancel()
{
m_httpClient->cancel();
QWriteLocker locker(&m_mutex);
m_contents.clear();
}
bool Downloader::get(const QString &url, const QJSValue &callback)
{
auto future = m_scheduler.run(
[this, url]() {
DownloaderStateGuard stateGuard(m_active, m_mutex, [this]() {
emit activeChanged();
});
if (!stateGuard.acquired())
{
return QJSValueList({"downloading is already running"});
}
{
QWriteLocker locker(&m_mutex);
m_contents.clear();
}
std::string response;
{
QString error;
auto task = m_scheduler.run([this, &error, &response, &url] {
error = m_network.get(m_httpClient, url, response);
});
if (!task.first)
{
return QJSValueList({"failed to start downloading task"});
}
task.second.waitForFinished();
if (!error.isEmpty())
{
return QJSValueList({error});
}
}
if (response.empty())
{
return QJSValueList({"empty response"});
}
{
QWriteLocker locker(&m_mutex);
m_contents = std::move(response);
}
return QJSValueList({});
},
callback);
return future.first;
}
bool Downloader::saveToFile(const QString &path) const
{
QWriteLocker locker(&m_mutex);
if (m_active || m_contents.empty())
{
return false;
}
QFile file(path);
if (!file.open(QIODevice::WriteOnly))
{
return false;
}
if (static_cast<size_t>(file.write(m_contents.data(), m_contents.size())) != m_contents.size())
{
return false;
}
return true;
}
bool Downloader::active() const
{
QReadLocker locker(&m_mutex);
return m_active;
}
quint64 Downloader::loaded() const
{
return m_httpClient->received();
}
quint64 Downloader::total() const
{
return m_httpClient->contentLength();
}

67
src/qt/downloader.h Normal file
View File

@@ -0,0 +1,67 @@
// Copyright (c) 2020, 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.
#pragma once
#include <QReadWriteLock>
#include "network.h"
class Downloader : public QObject
{
Q_OBJECT
Q_PROPERTY(bool active READ active NOTIFY activeChanged);
Q_PROPERTY(quint64 loaded READ loaded NOTIFY loadedChanged);
Q_PROPERTY(quint64 total READ total NOTIFY totalChanged);
public:
Downloader(QObject *parent = nullptr);
~Downloader();
Q_INVOKABLE void cancel();
Q_INVOKABLE bool get(const QString &url, const QJSValue &callback);
Q_INVOKABLE bool saveToFile(const QString &path) const;
signals:
void activeChanged() const;
void loadedChanged() const;
void totalChanged() const;
private:
bool active() const;
quint64 loaded() const;
quint64 total() const;
private:
bool m_active;
std::string m_contents;
std::shared_ptr<HttpClient> m_httpClient;
mutable QReadWriteLock m_mutex;
Network m_network;
mutable FutureScheduler m_scheduler;
};

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2014-2019, The Monero Project
// Copyright (c) 2020, The Monero Project
//
// All rights reserved.
//
@@ -31,57 +31,83 @@
#include <QDebug>
#include <QtCore>
// TODO: wallet_merged - epee library triggers the warnings
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wreorder"
#include <net/http_client.h>
#pragma GCC diagnostic pop
#include "utils.h"
using epee::net_utils::http::fields_list;
using epee::net_utils::http::http_response_info;
using epee::net_utils::http::http_simple_client;
HttpClient::HttpClient(QObject *parent /* = nullptr */)
: QObject(parent)
, m_cancel(false)
, m_contentLength(0)
, m_received(0)
{
}
void HttpClient::cancel()
{
m_cancel = true;
}
quint64 HttpClient::contentLength() const
{
return m_contentLength;
}
quint64 HttpClient::received() const
{
return m_received;
}
bool HttpClient::on_header(const http_response_info &headers)
{
if (m_cancel.exchange(false))
{
return false;
}
size_t contentLength = 0;
if (!epee::string_tools::get_xtype_from_string(contentLength, headers.m_header_info.m_content_length))
{
qWarning() << "Failed to get Content-Length";
}
m_contentLength = contentLength;
emit contentLengthChanged();
m_received = 0;
emit receivedChanged();
return http_simple_client::on_header(headers);
}
bool HttpClient::handle_target_data(std::string &piece_of_transfer)
{
if (m_cancel.exchange(false))
{
return false;
}
m_received += piece_of_transfer.size();
emit receivedChanged();
return http_simple_client::handle_target_data(piece_of_transfer);
}
Network::Network(QObject *parent)
: QObject(parent)
, m_scheduler(this)
{
}
void Network::get(const QString &url, const QJSValue &callback, const QString &contentType) const
void Network::get(const QString &url, const QJSValue &callback, const QString &contentType /* = {} */) const
{
qDebug() << QString("Fetching: %1").arg(url);
m_scheduler.run(
[url, contentType] {
epee::net_utils::http::http_simple_client httpClient;
const QUrl urlParsed(url);
httpClient.set_server(urlParsed.host().toStdString(), urlParsed.scheme() == "https" ? "443" : "80", {});
const QString uri = (urlParsed.hasQuery() ? urlParsed.path() + "?" + urlParsed.query() : urlParsed.path());
const epee::net_utils::http::http_response_info *pri = NULL;
constexpr std::chrono::milliseconds timeout = std::chrono::seconds(15);
epee::net_utils::http::fields_list headers({{"User-Agent", randomUserAgent().toStdString()}});
if (!contentType.isEmpty())
{
headers.push_back({"Content-Type", contentType.toStdString()});
}
const bool result = httpClient.invoke(uri.toStdString(), "GET", {}, timeout, std::addressof(pri), headers);
if (!result)
{
return QJSValueList({QJSValue(), QJSValue(), "unknown error"});
}
if (!pri)
{
return QJSValueList({QJSValue(), QJSValue(), "internal error (null response ptr)"});
}
if (pri->m_response_code != 200)
{
return QJSValueList({QJSValue(), QJSValue(), QString("response code: %1").arg(pri->m_response_code)});
}
return QJSValueList({url, QString::fromStdString(pri->m_body)});
[this, url, contentType] {
std::string response;
std::shared_ptr<http_simple_client> httpClient(new http_simple_client());
QString error = get(httpClient, url, response, contentType);
return QJSValueList({url, QString::fromStdString(response), error});
},
callback);
}
@@ -90,3 +116,39 @@ void Network::getJSON(const QString &url, const QJSValue &callback) const
{
get(url, callback, "application/json; charset=utf-8");
}
QString Network::get(
std::shared_ptr<http_simple_client> httpClient,
const QString &url,
std::string &response,
const QString &contentType /* = {} */) const
{
const QUrl urlParsed(url);
httpClient->set_server(urlParsed.host().toStdString(), urlParsed.scheme() == "https" ? "443" : "80", {});
const QString uri = (urlParsed.hasQuery() ? urlParsed.path() + "?" + urlParsed.query() : urlParsed.path());
const http_response_info *pri = NULL;
constexpr std::chrono::milliseconds timeout = std::chrono::seconds(15);
fields_list headers({{"User-Agent", randomUserAgent().toStdString()}});
if (!contentType.isEmpty())
{
headers.push_back({"Content-Type", contentType.toStdString()});
}
const bool result = httpClient->invoke(uri.toStdString(), "GET", {}, timeout, std::addressof(pri), headers);
if (!result)
{
return "unknown error";
}
if (!pri)
{
return "internal error";
}
if (pri->m_response_code != 200)
{
return QString("response code %1").arg(pri->m_response_code);
}
response = std::move(pri->m_body);
return {};
}

View File

@@ -1,10 +1,72 @@
// Copyright (c) 2020, 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.
#pragma once
#include <QCoreApplication>
#include <QtNetwork>
// TODO: wallet_merged - epee library triggers the warnings
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wreorder"
#include <net/http_client.h>
#pragma GCC diagnostic pop
#include "FutureScheduler.h"
class HttpClient : public QObject, public epee::net_utils::http::http_simple_client
{
Q_OBJECT
Q_PROPERTY(quint64 contentLength READ contentLength NOTIFY contentLengthChanged);
Q_PROPERTY(quint64 received READ received NOTIFY receivedChanged);
public:
HttpClient(QObject *parent = nullptr);
void cancel();
quint64 contentLength() const;
quint64 received() const;
signals:
void contentLengthChanged() const;
void receivedChanged() const;
protected:
bool on_header(const epee::net_utils::http::http_response_info &headers) final;
bool handle_target_data(std::string &piece_of_transfer) final;
private:
std::atomic<bool> m_cancel;
std::atomic<size_t> m_contentLength;
std::atomic<size_t> m_received;
};
class Network : public QObject
{
Q_OBJECT
@@ -15,6 +77,12 @@ public:
Q_INVOKABLE void get(const QString &url, const QJSValue &callback, const QString &contentType = {}) const;
Q_INVOKABLE void getJSON(const QString &url, const QJSValue &callback) const;
QString get(
std::shared_ptr<epee::net_utils::http::http_simple_client> httpClient,
const QString &url,
std::string &response,
const QString &contentType = {}) const;
private:
mutable FutureScheduler m_scheduler;
};