From 3842444724985be039c727b753e04e5d860a7d99 Mon Sep 17 00:00:00 2001 From: alves Date: Tue, 3 Feb 2026 17:39:30 +0800 Subject: [PATCH] feature add download function for web and pc. --- src/slic3r/GUI/DownloadManager.cpp | 234 +++++++++++++++++++++++------ src/slic3r/GUI/DownloadManager.hpp | 148 +++++++++++++++--- src/slic3r/GUI/GUI_App.cpp | 2 +- src/slic3r/GUI/SSWCP.cpp | 57 +++++-- src/slic3r/GUI/SSWCP.hpp | 3 + 5 files changed, 364 insertions(+), 80 deletions(-) diff --git a/src/slic3r/GUI/DownloadManager.cpp b/src/slic3r/GUI/DownloadManager.cpp index 30391441e5..345ebf8dd0 100644 --- a/src/slic3r/GUI/DownloadManager.cpp +++ b/src/slic3r/GUI/DownloadManager.cpp @@ -1,20 +1,75 @@ -#include "WCPDownloadManager.hpp" +#include "DownloadManager.hpp" #include "GUI_App.hpp" #include #include #include +#include namespace Slic3r { namespace GUI { -size_t DownloadManager::start_download(const std::string& file_url, - const std::string& file_name, - std::shared_ptr wcp_instance) { - - std::lock_guard lock(m_tasks_mutex); +// ============================================================================ +// WCP Download Interface (for Web-to-PC communication) +// ============================================================================ +size_t DownloadManager::start_wcp_download(const std::string& file_url, + const std::string& file_name, + std::shared_ptr wcp_instance, + bool use_original_event_id) { + std::lock_guard lock(m_tasks_mutex); size_t task_id = m_next_task_id++; - // Get download path + auto downloadPath = wxGetApp().app_config->get("download_path"); + boost::filesystem::path dest_folder(downloadPath); + boost::filesystem::create_directories(dest_folder); + boost::filesystem::path dest_file = dest_folder / file_name; + std::string dest_path = dest_file.string(); + + auto task = std::make_shared(task_id, + file_url, + file_name, + dest_path, + wcp_instance, + use_original_event_id); + + task->state = DownloadTaskState::Downloading; + + m_tasks[task_id] = task; + start_download_impl(task); + return task_id; +} + +// ============================================================================ +// Internal Download Interface (for PC internal use) +// ============================================================================ +size_t DownloadManager::start_internal_download(const std::string& file_url, + const std::string& file_name, + const std::string& dest_path, + DownloadCallbacks callbacks) { + + std::lock_guard lock(m_tasks_mutex); + size_t task_id = m_next_task_id++; + + boost::filesystem::path dest_file_path(dest_path); + boost::filesystem::create_directories(dest_file_path.parent_path()); + + auto task = std::make_shared(task_id, + file_url, + file_name, + dest_path, + std::move(callbacks)); + + task->state = DownloadTaskState::Downloading; + + m_tasks[task_id] = task; + start_download_impl(task); + + return task_id; +} + +size_t DownloadManager::start_internal_download(const std::string& file_url, + const std::string& file_name, + DownloadCallbacks callbacks) { + // Get default download path auto downloadPath = wxGetApp().app_config->get("download_path"); boost::filesystem::path dest_folder(downloadPath); boost::filesystem::create_directories(dest_folder); @@ -22,26 +77,25 @@ size_t DownloadManager::start_download(const std::string& file_url, boost::filesystem::path dest_file = dest_folder / file_name; std::string dest_path = dest_file.string(); - // Create task - auto task = std::make_shared(task_id, file_url, file_name, dest_path, wcp_instance); - task->state = SMDownloadState::Downloading; + return start_internal_download(file_url, file_name, dest_path, std::move(callbacks)); +} + +void DownloadManager::start_download_impl(std::shared_ptr task) { - m_tasks[task_id] = task; - - // Start download wxGetApp().CallAfter([this, task]() { try { // Step 1: Create Http object Http http = Http::get(task->file_url); + http.timeout_max(0); + // Step 2: Set progress callback http.on_progress([this, task](Http::Progress progress, bool& cancel) { - if (task->state == SMDownloadState::Canceled) { + if (task->state == DownloadTaskState::Canceled) { cancel = true; return; } - // Calculate progress int percent = 0; if (progress.dltotal > 0) { percent = (int)(progress.dlnow * 100 / progress.dltotal); @@ -87,7 +141,7 @@ size_t DownloadManager::start_download(const std::string& file_url, file.write(body.c_str(), body.size()); file.close(); - task->state = SMDownloadState::Completed; + task->state = DownloadTaskState::Completed; task->percent = 100; send_complete_update(task, task->dest_path); cleanup_task(task->task_id); @@ -101,7 +155,7 @@ size_t DownloadManager::start_download(const std::string& file_url, // Step 4: Set error callback http.on_error([this, task](std::string body, std::string error, unsigned status) { wxGetApp().CallAfter([this, task, error, status]() { - task->state = SMDownloadState::Error; + task->state = DownloadTaskState::Error; task->error_message = error; send_error_update(task, error); cleanup_task(task->task_id); @@ -112,16 +166,15 @@ size_t DownloadManager::start_download(const std::string& file_url, task->http_object = http.perform(); } catch (std::exception& e) { - task->state = SMDownloadState::Error; + task->state = DownloadTaskState::Error; task->error_message = e.what(); send_error_update(task, e.what()); cleanup_task(task->task_id); } }); - - return task_id; } +//this function not currently in use bool DownloadManager::cancel_download(size_t task_id) { std::shared_ptr wcp_to_destroy; @@ -134,23 +187,32 @@ bool DownloadManager::cancel_download(size_t task_id) { } auto task = it->second; - if (task->state == SMDownloadState::Downloading) { - task->state = SMDownloadState::Canceled; + if (task->state == DownloadTaskState::Downloading) { + task->state = DownloadTaskState::Canceled; if (task->http_object) { task->http_object->cancel(); } - // Get WCP instance before cleanup (for destruction after lock release) - wcp_to_destroy = task->wcp_instance.lock(); + // Only for WCP downloads + if (task->is_wcp_download()) { + wcp_to_destroy = task->wcp_instance.lock(); + } else { + // For internal downloads, call error callback if available + if (task->callbacks.on_error) { + wxGetApp().CallAfter([task]() { + if (task->callbacks.on_error) { + task->callbacks.on_error(task->task_id, "Download canceled"); + } + }); + } + } cleanup_task(task_id); } else { return false; } } - - // Destroy WCP instance outside the lock to prevent deadlock - // This is the WCP instance from the original download request (sw_DownloadFile) + if (wcp_to_destroy) { wcp_to_destroy->finish_job(); } @@ -158,40 +220,38 @@ bool DownloadManager::cancel_download(size_t task_id) { return true; } +// this function not currently in use bool DownloadManager::pause_download(size_t task_id) { - // Pause functionality can be implemented if needed - // Current Http module may not support pause, need to implement resume from breakpoint std::lock_guard lock(m_tasks_mutex); auto it = m_tasks.find(task_id); - if (it != m_tasks.end() && it->second->state == SMDownloadState::Downloading) { - it->second->state = SMDownloadState::Paused; - // Note: Http module doesn't support pause directly, would need breakpoint resume + if (it != m_tasks.end() && it->second->state == DownloadTaskState::Downloading) { + it->second->state = DownloadTaskState::Paused; return true; } return false; } +// this function not currently in use bool DownloadManager::resume_download(size_t task_id) { - // Resume functionality can be implemented if needed - // Would require breakpoint resume support in Http module std::lock_guard lock(m_tasks_mutex); auto it = m_tasks.find(task_id); - if (it != m_tasks.end() && it->second->state == SMDownloadState::Paused) { - // Would need to restart download with range header - return false; // Not implemented yet + if (it != m_tasks.end() && it->second->state == DownloadTaskState::Paused) { + return false; } return false; } -SMDownloadState DownloadManager::get_task_state(size_t task_id) { +// this function not currently in use +DownloadTaskState DownloadManager::get_task_state(size_t task_id) { std::lock_guard lock(m_tasks_mutex); auto it = m_tasks.find(task_id); if (it != m_tasks.end()) { return it->second->state; } - return SMDownloadState::Error; + return DownloadTaskState::Error; } +// this function not currently in use std::shared_ptr DownloadManager::get_task(size_t task_id) { std::lock_guard lock(m_tasks_mutex); auto it = m_tasks.find(task_id); @@ -201,10 +261,39 @@ std::shared_ptr DownloadManager::get_task(size_t task_id) { return nullptr; } +// this function not currently in use +std::vector> DownloadManager::get_all_tasks() { + std::lock_guard lock(m_tasks_mutex); + std::vector> result; + result.reserve(m_tasks.size()); + for (const auto& pair : m_tasks) { + result.push_back(pair.second); + } + return result; +} + +// ============================================================================ +// Progress/Complete/Error Update Handlers +// ============================================================================ void DownloadManager::send_progress_update(std::shared_ptr task, int percent, size_t downloaded, size_t total) { + if (task->is_wcp_download()) { + send_wcp_progress_update(task, percent, downloaded, total); + } else { + call_internal_progress_callback(task, percent, downloaded, total); + } +} + +void DownloadManager::send_wcp_progress_update(std::shared_ptr task, + int percent, + size_t downloaded, + size_t total) { + if (!task->use_original_event_id) { + return; + } + if (auto wcp = task->wcp_instance.lock()) { json progress_data; progress_data["task_id"] = task->task_id; @@ -216,10 +305,14 @@ void DownloadManager::send_progress_update(std::shared_ptr task, wcp->m_res_data = progress_data; wcp->m_status = 0; wcp->m_msg = "Download progress"; - - // Use progress event ID + json header; - header["event_id"] = wcp->m_event_id + "_progress"; + if (task->use_original_event_id) { + header["event_id"] = wcp->m_event_id; + } else { + header["event_id"] = wcp->m_event_id + "_progress"; + } + header["command"] = "download_progress"; wcp->m_header = header; @@ -227,8 +320,31 @@ void DownloadManager::send_progress_update(std::shared_ptr task, } } +void DownloadManager::call_internal_progress_callback(std::shared_ptr task, + int percent, + size_t downloaded, + size_t total) { + if (task->callbacks.on_progress) { + task->callbacks.on_progress(task->task_id, percent, downloaded, total); + } +} + void DownloadManager::send_complete_update(std::shared_ptr task, const std::string& file_path) { + if (task->is_wcp_download()) { + send_wcp_complete_update(task, file_path); + } else { + call_internal_complete_callback(task, file_path); + } +} + +void DownloadManager::send_wcp_complete_update(std::shared_ptr task, + const std::string& file_path) { + + if (!task->use_original_event_id) { + return; + } + if (auto wcp = task->wcp_instance.lock()) { json complete_data; complete_data["task_id"] = task->task_id; @@ -240,15 +356,35 @@ void DownloadManager::send_complete_update(std::shared_ptr task, wcp->m_res_data = complete_data; wcp->m_status = 0; wcp->m_msg = "Download completed"; - wcp->send_to_js(); - // Release WCP instance to prevent memory leak + wcp->send_to_js(); wcp->finish_job(); } } +void DownloadManager::call_internal_complete_callback(std::shared_ptr task, + const std::string& file_path) { + if (task->callbacks.on_complete) { + task->callbacks.on_complete(task->task_id, file_path); + } +} + void DownloadManager::send_error_update(std::shared_ptr task, const std::string& error) { + if (task->is_wcp_download()) { + send_wcp_error_update(task, error); + } else { + call_internal_error_callback(task, error); + } +} + +void DownloadManager::send_wcp_error_update(std::shared_ptr task, + const std::string& error) { + + if (!task->use_original_event_id) { + return; + } + if (auto wcp = task->wcp_instance.lock()) { json error_data; error_data["task_id"] = task->task_id; @@ -258,13 +394,19 @@ void DownloadManager::send_error_update(std::shared_ptr task, wcp->m_res_data = error_data; wcp->m_status = -1; wcp->m_msg = error; + wcp->send_to_js(); - - // Release WCP instance to prevent memory leak wcp->finish_job(); } } +void DownloadManager::call_internal_error_callback(std::shared_ptr task, + const std::string& error) { + if (task->callbacks.on_error) { + task->callbacks.on_error(task->task_id, error); + } +} + void DownloadManager::cleanup_task(size_t task_id) { std::lock_guard lock(m_tasks_mutex); m_tasks.erase(task_id); diff --git a/src/slic3r/GUI/DownloadManager.hpp b/src/slic3r/GUI/DownloadManager.hpp index ebc0568b6d..c61ad574e6 100644 --- a/src/slic3r/GUI/DownloadManager.hpp +++ b/src/slic3r/GUI/DownloadManager.hpp @@ -1,5 +1,5 @@ -#ifndef slic3r_WCPDownloadManager_hpp_ -#define slic3r_WCPDownloadManager_hpp_ +#ifndef slic3r_DownloadManager_hpp_ +#define slic3r_DownloadManager_hpp_ #include #include @@ -7,6 +7,7 @@ #include #include #include +#include #include "../Utils/Http.hpp" #include "SSWCP.hpp" #include @@ -14,8 +15,8 @@ namespace Slic3r { namespace GUI { -// Download task state -enum class SMDownloadState { +// Download task state (renamed to avoid conflict with Downloader::DownloadState) +enum class DownloadTaskState { Pending, Downloading, Paused, @@ -24,25 +25,66 @@ enum class SMDownloadState { Canceled }; +// Download callback interface for internal downloads +struct DownloadCallbacks { + std::function on_progress; + std::function on_complete; + std::function on_error; + + DownloadCallbacks() = default; + DownloadCallbacks( + std::function progress, + std::function complete, + std::function error) + : on_progress(std::move(progress)) + , on_complete(std::move(complete)) + , on_error(std::move(error)) + {} +}; + // Download task information struct DownloadTask { size_t task_id; std::string file_url; std::string file_name; std::string dest_path; - std::weak_ptr wcp_instance; // Associated WCP instance - Http::Ptr http_object; // HTTP object for cancellation - SMDownloadState state; + + std::weak_ptr wcp_instance; + + DownloadCallbacks callbacks; + + Http::Ptr http_object; + DownloadTaskState state; int percent; std::string error_message; + bool auto_finish_job; + bool use_original_event_id; + + // Constructor for WCP downloads DownloadTask(size_t id, const std::string& url, const std::string& name, - const std::string& path, std::shared_ptr instance) - : task_id(id), file_url(url), file_name(name), dest_path(path), wcp_instance(instance), state(SMDownloadState::Pending), percent(0) + const std::string& path, std::shared_ptr instance, + bool use_original_event = false) + : task_id(id), file_url(url), file_name(name), dest_path(path) + , wcp_instance(instance), state(DownloadTaskState::Pending), percent(0) + , auto_finish_job(false), use_original_event_id(use_original_event) {} + + // Constructor for internal downloads + DownloadTask(size_t id, const std::string& url, const std::string& name, + const std::string& path, DownloadCallbacks cb) + : task_id(id), file_url(url), file_name(name), dest_path(path) + , callbacks(std::move(cb)), state(DownloadTaskState::Pending), percent(0) + , auto_finish_job(false), use_original_event_id(false) + {} + + // Check if this is a WCP download + bool is_wcp_download() const { + return !wcp_instance.expired(); + } }; -// Download Manager + class DownloadManager { public: static DownloadManager& getInstance() { @@ -50,11 +92,29 @@ public: return instance; } - // Start a download task - size_t start_download(const std::string& file_url, - const std::string& file_name, - std::shared_ptr wcp_instance); + // ============================================================================ + // WCP Download Interface (for Web-to-PC communication) + // ============================================================================ + size_t start_wcp_download(const std::string& file_url, + const std::string& file_name, + std::shared_ptr wcp_instance, + bool use_original_event_id = false); + // ============================================================================ + // Internal Download Interface (for PC internal use) + // ============================================================================ + size_t start_internal_download(const std::string& file_url, + const std::string& file_name, + const std::string& dest_path, + DownloadCallbacks callbacks); + + size_t start_internal_download(const std::string& file_url, + const std::string& file_name, + DownloadCallbacks callbacks); + + // ============================================================================ + // Common Interface (works for both WCP and internal downloads) + // ============================================================================ // Cancel a download task bool cancel_download(size_t task_id); @@ -65,11 +125,14 @@ public: bool resume_download(size_t task_id); // Get task state - SMDownloadState get_task_state(size_t task_id); + DownloadTaskState get_task_state(size_t task_id); // Get task information std::shared_ptr get_task(size_t task_id); + // Get all active tasks + std::vector> get_all_tasks(); + private: DownloadManager() = default; ~DownloadManager() = default; @@ -84,15 +147,54 @@ private: std::unordered_map m_last_percent; std::unordered_map m_last_update; - // Send progress update to WCP - void send_progress_update(std::shared_ptr task, int percent, - size_t downloaded, size_t total); + // ============================================================================ + // Internal Implementation + // ============================================================================ - // Send completion message to WCP - void send_complete_update(std::shared_ptr task, const std::string& file_path); + // Common download implementation (used by both WCP and internal downloads) + void start_download_impl(std::shared_ptr task); - // Send error message to WCP - void send_error_update(std::shared_ptr task, const std::string& error); + // Send progress update (handles both WCP and internal modes) + void send_progress_update(std::shared_ptr task, + int percent, + size_t downloaded, + size_t total); + + // Send completion message (handles both WCP and internal modes) + void send_complete_update(std::shared_ptr task, + const std::string& file_path); + + // Send error message (handles both WCP and internal modes) + void send_error_update(std::shared_ptr task, + const std::string& error); + + // WCP-specific: Send progress update via WCP instance + void send_wcp_progress_update(std::shared_ptr task, + int percent, + size_t downloaded, + size_t total); + + // WCP-specific: Send completion via WCP instance + void send_wcp_complete_update(std::shared_ptr task, + const std::string& file_path); + + // WCP-specific: Send error via WCP instance + void send_wcp_error_update(std::shared_ptr task, + const std::string& error); + + // Internal-specific: Call progress callback + void call_internal_progress_callback(std::shared_ptr task, + int percent, + size_t downloaded, + size_t total); + + // Internal-specific: Call complete callback + void call_internal_complete_callback(std::shared_ptr task, + const std::string& file_path); + + // Internal-specific: Call error callback + void call_internal_error_callback(std::shared_ptr task, + const std::string& error); // Clean up completed task void cleanup_task(size_t task_id); @@ -100,5 +202,5 @@ private: }} // namespace Slic3r::GUI -#endif // slic3r_WCPDownloadManager_hpp_ +#endif // slic3r_DownloadManager_hpp_ diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a0a511887f..958630d939 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -13,7 +13,7 @@ #include "slic3r/GUI/WebPresetDialog.hpp" #include "slic3r/GUI/SSWCP.hpp" -#include "slic3r/GUI/WCPDownloadManager.hpp" +#include "slic3r/GUI/DownloadManager.hpp" #include "slic3r/Utils/PresetUpdater.hpp" #include "slic3r/Config/Version.hpp" diff --git a/src/slic3r/GUI/SSWCP.cpp b/src/slic3r/GUI/SSWCP.cpp index 488072e504..e8aac1accb 100644 --- a/src/slic3r/GUI/SSWCP.cpp +++ b/src/slic3r/GUI/SSWCP.cpp @@ -2,7 +2,7 @@ #include "SSWCP.hpp" #include "GUI_App.hpp" #include "MainFrame.hpp" -#include "WCPDownloadManager.hpp" +#include "DownloadManager.hpp" #include "nlohmann/json.hpp" #include "slic3r/GUI/Tab.hpp" #include "sentry_wrapper/SentryWrapper.hpp" @@ -4387,7 +4387,8 @@ void SSWCP_UserLogin_Instance::sw_GetUserUpdatePrivacy() } -void SSWCP_UserLogin_Instance::sw_DownloadFile() { +void SSWCP_UserLogin_Instance::sw_DownloadFile() +{ try { std::string fileName = m_param_data.count("file_name") ? m_param_data["file_name"].get() : ""; std::string fileUrl = m_param_data.count("file_url") ? m_param_data["file_url"].get() : ""; @@ -4397,17 +4398,55 @@ void SSWCP_UserLogin_Instance::sw_DownloadFile() { return; } - // Use WCP Download Manager + // Use Download Manager DownloadManager* download_mgr = wxGetApp().download_manager(); if (!download_mgr) { - handle_general_fail(-1, "WCP Download Manager not available"); + handle_general_fail(-1, "Download Manager not available"); return; } - // Start download task - size_t task_id = download_mgr->start_download(fileUrl, fileName, shared_from_this()); - + size_t task_id = download_mgr->start_wcp_download(fileUrl, + fileName, + shared_from_this(), + false); // use_original_event_id = false (sw_DownloadFile: finish immediately) + // Return task ID to Flutter + json response; + response["task_id"] = task_id; + response["file_name"] = fileName; + response["file_url"] = fileUrl; + m_res_data = response; + m_status = 0; + m_msg = "Download started"; + send_to_js(); + finish_job(); + + } catch (std::exception& e) { + handle_general_fail(-1, e.what()); + } +} + +void SSWCP_UserLogin_Instance::sw_DownloadFileEx() { + try { + std::string fileName = m_param_data.count("file_name") ? m_param_data["file_name"].get() : ""; + std::string fileUrl = m_param_data.count("file_url") ? m_param_data["file_url"].get() : ""; + + if (fileUrl.empty() || fileName.empty()) { + handle_general_fail(-1, "file_url and file_name are required"); + return; + } + + // Use Download Manager + DownloadManager* download_mgr = wxGetApp().download_manager(); + if (!download_mgr) { + handle_general_fail(-1, "Download Manager not available"); + return; + } + size_t task_id = download_mgr->start_wcp_download(fileUrl, + fileName, + shared_from_this(), + true); + json response; response["task_id"] = task_id; response["file_name"] = fileName; @@ -4416,9 +4455,7 @@ void SSWCP_UserLogin_Instance::sw_DownloadFile() { m_status = 0; m_msg = "Download started"; send_to_js(); - // Note: Do not call finish_job() here, as download is asynchronous - // The manager will send progress updates and completion/error messages via WCP - + } catch (std::exception& e) { handle_general_fail(-1, e.what()); } diff --git a/src/slic3r/GUI/SSWCP.hpp b/src/slic3r/GUI/SSWCP.hpp index fe40a45a42..2e7c1bdd5a 100644 --- a/src/slic3r/GUI/SSWCP.hpp +++ b/src/slic3r/GUI/SSWCP.hpp @@ -541,6 +541,9 @@ private: void sw_SubUserUpdatePrivacy(); void sw_DownloadFile(); + + void sw_DownloadFileEx(); + void sw_CancelDownload(); void sw_FileView();