mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-30 23:40:06 +00:00 
			
		
		
		
	Download manager implementation
This commit is contained in:
		
							parent
							
								
									94726631cb
								
							
						
					
					
						commit
						4ac2626b65
					
				
					 5 changed files with 351 additions and 2 deletions
				
			
		|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										201
									
								
								scwx-qt/source/scwx/qt/manager/download_manager.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								scwx-qt/source/scwx/qt/manager/download_manager.cpp
									
										
									
									
									
										Normal 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
 | ||||
							
								
								
									
										36
									
								
								scwx-qt/source/scwx/qt/manager/download_manager.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								scwx-qt/source/scwx/qt/manager/download_manager.hpp
									
										
									
									
									
										Normal 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
 | ||||
							
								
								
									
										57
									
								
								scwx-qt/source/scwx/qt/request/download_request.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								scwx-qt/source/scwx/qt/request/download_request.cpp
									
										
									
									
									
										Normal 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
 | ||||
							
								
								
									
										51
									
								
								scwx-qt/source/scwx/qt/request/download_request.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								scwx-qt/source/scwx/qt/request/download_request.hpp
									
										
									
									
									
										Normal 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
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat