// 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 #include #include "updater.h" namespace { class DownloaderStateGuard { public: DownloaderStateGuard(bool &active, QReadWriteLock &mutex, std::function 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 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 QString &hash, const QJSValue &callback) { auto future = m_scheduler.run( [this, url, hash]() { 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"}); } try { const QByteArray calculatedHash = Updater().getHash(&response[0], response.size()); if (QByteArray::fromHex(hash.toUtf8()) != calculatedHash) { return QJSValueList({"hash sum mismatch"}); } } catch (const std::exception &e) { return QJSValueList({e.what()}); } { 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(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(); }