Download manager implementation

This commit is contained in:
Dan Paulat 2024-03-24 23:48:35 -05:00
parent 94726631cb
commit 4ac2626b65
5 changed files with 351 additions and 2 deletions

View file

@ -86,6 +86,7 @@ set(SRC_GL_DRAW source/scwx/qt/gl/draw/draw_item.cpp
source/scwx/qt/gl/draw/placefile_triangles.cpp
source/scwx/qt/gl/draw/rectangle.cpp)
set(HDR_MANAGER source/scwx/qt/manager/alert_manager.hpp
source/scwx/qt/manager/download_manager.hpp
source/scwx/qt/manager/font_manager.hpp
source/scwx/qt/manager/media_manager.hpp
source/scwx/qt/manager/placefile_manager.hpp
@ -98,6 +99,7 @@ set(HDR_MANAGER source/scwx/qt/manager/alert_manager.hpp
source/scwx/qt/manager/timeline_manager.hpp
source/scwx/qt/manager/update_manager.hpp)
set(SRC_MANAGER source/scwx/qt/manager/alert_manager.cpp
source/scwx/qt/manager/download_manager.cpp
source/scwx/qt/manager/font_manager.cpp
source/scwx/qt/manager/media_manager.cpp
source/scwx/qt/manager/placefile_manager.cpp
@ -154,8 +156,10 @@ set(SRC_MODEL source/scwx/qt/model/alert_model.cpp
source/scwx/qt/model/radar_site_model.cpp
source/scwx/qt/model/tree_item.cpp
source/scwx/qt/model/tree_model.cpp)
set(HDR_REQUEST source/scwx/qt/request/nexrad_file_request.hpp)
set(SRC_REQUEST source/scwx/qt/request/nexrad_file_request.cpp)
set(HDR_REQUEST source/scwx/qt/request/download_request.hpp
source/scwx/qt/request/nexrad_file_request.hpp)
set(SRC_REQUEST source/scwx/qt/request/download_request.cpp
source/scwx/qt/request/nexrad_file_request.cpp)
set(HDR_SETTINGS source/scwx/qt/settings/audio_settings.hpp
source/scwx/qt/settings/general_settings.hpp
source/scwx/qt/settings/map_settings.hpp

View file

@ -0,0 +1,201 @@
#include <scwx/qt/manager/download_manager.hpp>
#include <scwx/util/logger.hpp>
#include <fstream>
#include <boost/asio/post.hpp>
#include <boost/asio/thread_pool.hpp>
#include <cpr/cpr.h>
namespace scwx
{
namespace qt
{
namespace manager
{
static const std::string logPrefix_ = "scwx::qt::manager::download_manager";
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
class DownloadManager::Impl
{
public:
explicit Impl(DownloadManager* self) : self_ {self} {}
~Impl() { threadPool_.join(); }
boost::asio::thread_pool threadPool_ {1u};
DownloadManager* self_;
};
DownloadManager::DownloadManager() : p(std::make_unique<Impl>(this)) {}
DownloadManager::~DownloadManager() = default;
void DownloadManager::Download(
const std::shared_ptr<request::DownloadRequest>& request)
{
boost::asio::post(
p->threadPool_,
[=]()
{
// Prepare destination file
const std::filesystem::path& destinationPath =
request->destination_path();
if (!destinationPath.has_parent_path())
{
logger_->error("Destination has no parent path: \"{}\"");
Q_EMIT request->RequestComplete(
request::DownloadRequest::CompleteReason::IOError);
return;
}
const std::filesystem::path parentPath = destinationPath.parent_path();
// Create directory if it doesn't exist
if (!std::filesystem::exists(parentPath))
{
if (!std::filesystem::create_directories(parentPath))
{
logger_->error("Unable to create download directory: \"{}\"",
parentPath.string());
Q_EMIT request->RequestComplete(
request::DownloadRequest::CompleteReason::IOError);
return;
}
}
// Remove file if it exists
if (std::filesystem::exists(destinationPath))
{
std::error_code error;
if (!std::filesystem::remove(destinationPath, error))
{
logger_->error(
"Unable to remove existing destination file ({}): \"{}\"",
error.message(),
destinationPath.string());
Q_EMIT request->RequestComplete(
request::DownloadRequest::CompleteReason::IOError);
return;
}
}
// Open file for writing
std::ofstream ofs {destinationPath,
std::ios_base::out | std::ios_base::binary |
std::ios_base::trunc};
if (!ofs.is_open() || !ofs.good())
{
logger_->error(
"Unable to open destination file for writing: \"{}\"",
destinationPath.string());
Q_EMIT request->RequestComplete(
request::DownloadRequest::CompleteReason::IOError);
return;
}
// Download file
cpr::Response response = cpr::Get(
cpr::Url {request->url()},
cpr::ProgressCallback(
[=](cpr::cpr_off_t downloadTotal,
cpr::cpr_off_t downloadNow,
cpr::cpr_off_t /* uploadTotal */,
cpr::cpr_off_t /* uploadNow */,
std::intptr_t /* userdata */)
{
Q_EMIT request->ProgressUpdated(downloadNow, downloadTotal);
return !request->IsCanceled();
}),
cpr::WriteCallback(
[=, &ofs](std::string data, std::intptr_t /* userdata */)
{
// Write file
ofs << data;
return !request->IsCanceled();
}));
bool ofsGood = ofs.good();
ofs.close();
// Handle response
if (response.error.code == cpr::ErrorCode::OK &&
!request->IsCanceled() && ofsGood)
{
logger_->info("Download complete: \"{}\"", request->url());
Q_EMIT request->RequestComplete(
request::DownloadRequest::CompleteReason::OK);
}
else
{
request::DownloadRequest::CompleteReason reason =
request::DownloadRequest::CompleteReason::IOError;
if (request->IsCanceled())
{
logger_->info("Download request cancelled: \"{}\"",
request->url());
reason = request::DownloadRequest::CompleteReason::Canceled;
}
else if (response.error.code != cpr::ErrorCode::OK)
{
logger_->error("Error downloading file ({}): \"{}\"",
response.error.message,
request->url());
reason = request::DownloadRequest::CompleteReason::RemoteError;
}
else if (!ofsGood)
{
logger_->error("File I/O error: \"{}\"",
destinationPath.string());
reason = request::DownloadRequest::CompleteReason::IOError;
}
std::error_code error;
if (!std::filesystem::remove(destinationPath, error))
{
logger_->error("Unable to remove destination file: \"{}\", {}",
destinationPath.string(),
error.message());
}
Q_EMIT request->RequestComplete(reason);
}
});
}
std::shared_ptr<DownloadManager> DownloadManager::Instance()
{
static std::weak_ptr<DownloadManager> downloadManagerReference_ {};
static std::mutex instanceMutex_ {};
std::unique_lock lock(instanceMutex_);
std::shared_ptr<DownloadManager> downloadManager =
downloadManagerReference_.lock();
if (downloadManager == nullptr)
{
downloadManager = std::make_shared<DownloadManager>();
downloadManagerReference_ = downloadManager;
}
return downloadManager;
}
} // namespace manager
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,36 @@
#pragma once
#include <scwx/qt/request/download_request.hpp>
#include <memory>
#include <string>
#include <QObject>
namespace scwx
{
namespace qt
{
namespace manager
{
class DownloadManager : public QObject
{
Q_OBJECT
public:
explicit DownloadManager();
~DownloadManager();
void Download(const std::shared_ptr<request::DownloadRequest>& request);
static std::shared_ptr<DownloadManager> Instance();
private:
class Impl;
std::unique_ptr<Impl> p;
};
} // namespace manager
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,57 @@
#include <scwx/qt/request/download_request.hpp>
namespace scwx
{
namespace qt
{
namespace request
{
static const std::string logPrefix_ = "scwx::qt::request::download_request";
class DownloadRequest::Impl
{
public:
explicit Impl(const std::string& url,
const std::filesystem::path& destinationPath) :
url_ {url}, destinationPath_ {destinationPath}
{
}
~Impl() = default;
const std::string url_;
const std::filesystem::path destinationPath_;
bool canceled_ = false;
};
DownloadRequest::DownloadRequest(const std::string& url,
const std::filesystem::path& destinationPath) :
p(std::make_unique<Impl>(url, destinationPath))
{
}
DownloadRequest::~DownloadRequest() = default;
const std::string& DownloadRequest::url() const
{
return p->url_;
}
const std::filesystem::path& DownloadRequest::destination_path() const
{
return p->destinationPath_;
}
void DownloadRequest::Cancel()
{
p->canceled_ = true;
}
bool DownloadRequest::IsCanceled() const
{
return p->canceled_;
}
} // namespace request
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,51 @@
#pragma once
#include <filesystem>
#include <memory>
#include <QObject>
namespace scwx
{
namespace qt
{
namespace request
{
class DownloadRequest : public QObject
{
Q_OBJECT
public:
enum class CompleteReason
{
OK,
Canceled,
IOError,
RemoteError
};
explicit DownloadRequest(const std::string& url,
const std::filesystem::path& destinationPath);
~DownloadRequest();
const std::string& url() const;
const std::filesystem::path& destination_path() const;
void Cancel();
bool IsCanceled() const;
private:
class Impl;
std::unique_ptr<Impl> p;
signals:
void ProgressUpdated(std::ptrdiff_t downloadedBytes,
std::ptrdiff_t totalBytes);
void RequestComplete(CompleteReason reason);
};
} // namespace request
} // namespace qt
} // namespace scwx