diff options
| author | xiphon <xiphon@protonmail.com> | 2020-04-04 13:22:25 +0000 |
|---|---|---|
| committer | xiphon <xiphon@protonmail.com> | 2020-04-06 16:59:33 +0000 |
| commit | 6ed7fcec67109602a6b1dbe390b4274b9467931e (patch) | |
| tree | 4d983c69547036bc1c883d525ceb5d8e0425e3b7 /src | |
| parent | df54439972b885476ccc13b468b17508493521c7 (diff) | |
| download | monzero-gui-6ed7fcec67109602a6b1dbe390b4274b9467931e.tar.gz monzero-gui-6ed7fcec67109602a6b1dbe390b4274b9467931e.tar.xz monzero-gui-6ed7fcec67109602a6b1dbe390b4274b9467931e.zip | |
UpdateDialog: implement update download functionality
Diffstat (limited to 'src')
| -rw-r--r-- | src/main/main.cpp | 2 | ||||
| -rw-r--r-- | src/main/oshelper.cpp | 13 | ||||
| -rw-r--r-- | src/main/oshelper.h | 2 | ||||
| -rw-r--r-- | src/qt/downloader.cpp | 207 | ||||
| -rw-r--r-- | src/qt/downloader.h | 67 | ||||
| -rw-r--r-- | src/qt/network.cpp | 140 | ||||
| -rw-r--r-- | src/qt/network.h | 68 |
7 files changed, 460 insertions, 39 deletions
diff --git a/src/main/main.cpp b/src/main/main.cpp index c6c0ea52..c564c59f 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -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"); diff --git a/src/main/oshelper.cpp b/src/main/oshelper.cpp index 4402db6d..75c668f5 100644 --- a/src/main/oshelper.cpp +++ b/src/main/oshelper.cpp @@ -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; diff --git a/src/main/oshelper.h b/src/main/oshelper.h index 72f284af..ba498554 100644 --- a/src/main/oshelper.h +++ b/src/main/oshelper.h @@ -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; diff --git a/src/qt/downloader.cpp b/src/qt/downloader.cpp new file mode 100644 index 00000000..afd2049e --- /dev/null +++ b/src/qt/downloader.cpp @@ -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(); +} diff --git a/src/qt/downloader.h b/src/qt/downloader.h new file mode 100644 index 00000000..44840535 --- /dev/null +++ b/src/qt/downloader.h @@ -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; +}; diff --git a/src/qt/network.cpp b/src/qt/network.cpp index 0d523a5c..01d0f296 100644 --- a/src/qt/network.cpp +++ b/src/qt/network.cpp @@ -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" -Network::Network(QObject *parent) +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_scheduler(this) + , m_cancel(false) + , m_contentLength(0) + , m_received(0) { } -void Network::get(const QString &url, const QJSValue &callback, const QString &contentType) const +void HttpClient::cancel() { - qDebug() << QString("Fetching: %1").arg(url); + m_cancel = true; +} - m_scheduler.run( - [url, contentType] { - epee::net_utils::http::http_simple_client httpClient; +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); +} - const QUrl urlParsed(url); - httpClient.set_server(urlParsed.host().toStdString(), urlParsed.scheme() == "https" ? "443" : "80", {}); +bool HttpClient::handle_target_data(std::string &piece_of_transfer) +{ + if (m_cancel.exchange(false)) + { + return false; + } - 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); + m_received += piece_of_transfer.size(); + emit receivedChanged(); - 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); + return http_simple_client::handle_target_data(piece_of_transfer); +} - 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)}); - } +Network::Network(QObject *parent) + : QObject(parent) + , m_scheduler(this) +{ +} - return QJSValueList({url, QString::fromStdString(pri->m_body)}); +void Network::get(const QString &url, const QJSValue &callback, const QString &contentType /* = {} */) const +{ + m_scheduler.run( + [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 {}; +} diff --git a/src/qt/network.h b/src/qt/network.h index 765b2512..acdd6280 100644 --- a/src/qt/network.h +++ b/src/qt/network.h @@ -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; }; |
