feature add file download, file view ,cancel download on wcp, and control on wcpdownloadmanager.

This commit is contained in:
alves
2026-01-30 14:07:25 +08:00
parent 734c80fd01
commit 2825eeebcc
9 changed files with 528 additions and 0 deletions

View File

@@ -504,6 +504,8 @@ set(SLIC3R_GUI_SOURCES
GUI/SMPhysicalPrinterDialog.cpp
GUI/SSWCP.cpp
GUI/SSWCP.hpp
GUI/WCPDownloadManager.cpp
GUI/WCPDownloadManager.hpp
GUI/WebPresetDialog.hpp
GUI/WebPresetDialog.cpp
GUI/WebSMUserLoginDialog.cpp

View File

@@ -549,6 +549,20 @@ void desktop_open_datadir_folder()
#endif
}
void desktop_open_any_folderEx(const std::string& path)
{
#ifdef _WIN32
// Convert path to Windows format (backslashes) and ensure it's properly quoted
boost::filesystem::path file_path(path);
file_path.make_preferred(); // Convert forward slashes to backslashes
wxString widepath = from_path(file_path);
// Quote the path to handle spaces and special characters
wxString cmd = L"explorer /select,\"" + widepath + L"\"";
::wxExecute(cmd, wxEXEC_ASYNC, nullptr);
#else
desktop_open_any_folder(path);
#endif
}
void desktop_open_any_folder( const std::string& path )
{
// Execute command to open a file explorer, platform dependent.

View File

@@ -84,6 +84,9 @@ extern void login();
extern void desktop_open_datadir_folder();
// Ask the destop to open one folder
extern void desktop_open_any_folder(const std::string& path);
//open dir and select the file to show
extern void desktop_open_any_folderEx(const std::string& path);
} // namespace GUI
} // namespace Slic3r

View File

@@ -13,6 +13,7 @@
#include "slic3r/GUI/WebPresetDialog.hpp"
#include "slic3r/GUI/SSWCP.hpp"
#include "slic3r/GUI/WCPDownloadManager.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "slic3r/Config/Version.hpp"
@@ -1061,6 +1062,7 @@ GUI_App::GUI_App()
, m_imgui(new ImGuiWrapper())
, m_removable_drive_manager(std::make_unique<RemovableDriveManager>())
, m_downloader(std::make_unique<Downloader>())
, m_wcp_download_manager(&WCPDownloadManager::getInstance())
, m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>())
{
//app config initializes early becasuse it is used in instance checking in Snapmaker_Orca.cpp
@@ -6464,6 +6466,11 @@ Downloader* GUI_App::downloader()
return m_downloader.get();
}
WCPDownloadManager* GUI_App::wcp_download_manager()
{
return m_wcp_download_manager;
}
void GUI_App::load_url(wxString url)
{
if (mainframe)

View File

@@ -89,6 +89,7 @@ class Plater;
class ParamsPanel;
class NotificationManager;
class Downloader;
class WCPDownloadManager;
struct GUI_InitParams;
class ParamsDialog;
class HMSQuery;
@@ -297,6 +298,7 @@ private:
size_t m_instance_hash_int;
std::unique_ptr<Downloader> m_downloader;
WCPDownloadManager* m_wcp_download_manager;
//BBS
bool m_is_closing {false};
@@ -684,6 +686,7 @@ private:
Model& model();
NotificationManager * notification_manager();
Downloader* downloader();
WCPDownloadManager* wcp_download_manager();
std::string m_mall_model_download_url;

View File

@@ -2,6 +2,7 @@
#include "SSWCP.hpp"
#include "GUI_App.hpp"
#include "MainFrame.hpp"
#include "WCPDownloadManager.hpp"
#include "nlohmann/json.hpp"
#include "slic3r/GUI/Tab.hpp"
#include "sentry_wrapper/SentryWrapper.hpp"
@@ -4297,6 +4298,12 @@ void SSWCP_UserLogin_Instance::process()
sw_SubUserUpdatePrivacy();
} else if (m_cmd == GET_PRIVACY_STATUS) {
sw_GetUserUpdatePrivacy();
} else if (m_cmd == DOWNLOAD_FILE) {
sw_DownloadFile();
} else if (m_cmd == CANCEL_DOWNLOAD) {
sw_CancelDownload();
} else if (m_cmd == FILE_VIEW) {
sw_FileView();
}
else {
handle_general_fail();
@@ -4379,6 +4386,110 @@ void SSWCP_UserLogin_Instance::sw_GetUserUpdatePrivacy()
}
void SSWCP_UserLogin_Instance::sw_DownloadFile() {
try {
std::string fileName = m_param_data.count("file_name") ? m_param_data["file_name"].get<std::string>() : "";
std::string fileUrl = m_param_data.count("file_url") ? m_param_data["file_url"].get<std::string>() : "";
if (fileUrl.empty() || fileName.empty()) {
handle_general_fail(-1, "file_url and file_name are required");
return;
}
// Use WCP Download Manager
WCPDownloadManager* download_mgr = wxGetApp().wcp_download_manager();
if (!download_mgr) {
handle_general_fail(-1, "WCP Download Manager not available");
return;
}
// Start download task
size_t task_id = download_mgr->start_download(fileUrl, fileName, shared_from_this());
// 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();
// 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());
}
}
void SSWCP_UserLogin_Instance::sw_CancelDownload() {
try {
size_t task_id = m_param_data.count("task_id") ? m_param_data["task_id"].get<size_t>() : 0;
if (task_id == 0) {
handle_general_fail(-1, "task_id is required");
return;
}
WCPDownloadManager* download_mgr = wxGetApp().wcp_download_manager();
if (!download_mgr) {
handle_general_fail(-1, "WCP Download Manager not available");
return;
}
bool success = download_mgr->cancel_download(task_id);
if (success) {
json response;
response["task_id"] = task_id;
response["canceled"] = true;
m_res_data = response;
m_status = 0;
m_msg = "Download canceled";
} else {
handle_general_fail(-1, "Failed to cancel download or task not found");
return;
}
send_to_js();
finish_job();
} catch (std::exception& e) {
handle_general_fail(-1, e.what());
}
}
void SSWCP_UserLogin_Instance::sw_FileView() {
try {
std::string file_path = m_param_data.count("file_path") ? m_param_data["file_path"].get<std::string>() : "";
wxFileName file(file_path);
if (!file.FileExists()) {
handle_general_fail();
//wxMessageBox(wxT("file not exsit"), wxT("tips"), wxOK | wxICON_WARNING);
return;
}
std::weak_ptr<SSWCP_Instance> weak_self = shared_from_this();
wxGetApp().CallAfter([file_path, weak_self]() {
auto self = weak_self.lock();
if (!self) {
return;
}
//open file in folder
desktop_open_any_folderEx(file_path);
self->send_to_js();
self->finish_job();
});
} catch (std::exception& e) {
handle_general_fail();
}
}
void SSWCP_UserLogin_Instance::sw_SubUserUpdatePrivacy()
{
try {

View File

@@ -30,6 +30,9 @@ using tcp = asio::ip::tcp;
#define UPLOAD_CAMERA_TIMELAPSE "sw_UploadCameraTimelapse"
#define DELETE_CAMERA_TIMELAPSE "sw_DeleteCameraTimelapse"
#define GET_DEVICEDATA_STORAGESPACE "sw_GetDeviceDataStorageSpace"
#define DOWNLOAD_FILE "sw_DownloadFile"
#define CANCEL_DOWNLOAD "sw_CancelDownload"
#define FILE_VIEW "sw_FileView"
namespace Slic3r { namespace GUI {
@@ -536,6 +539,11 @@ private:
void sw_GetUserUpdatePrivacy();
void sw_SubUserUpdatePrivacy();
void sw_DownloadFile();
void sw_CancelDownload();
void sw_FileView();
};
// Instance class for homepage business

View File

@@ -0,0 +1,276 @@
#include "WCPDownloadManager.hpp"
#include "GUI_App.hpp"
#include <boost/filesystem.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace GUI {
size_t WCPDownloadManager::start_download(const std::string& file_url,
const std::string& file_name,
std::shared_ptr<SSWCP_Instance> wcp_instance) {
std::lock_guard<std::mutex> 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();
// Create task
auto task = std::make_shared<WCPDownloadTask>(task_id, file_url, file_name, dest_path, wcp_instance);
task->state = WCPDownloadState::Downloading;
m_tasks[task_id] = task;
// Start download
wxGetApp().CallAfter([this, task]() {
try {
// Step 1: Create Http object
Http http = Http::get(task->file_url);
// Step 2: Set progress callback
http.on_progress([this, task](Http::Progress progress, bool& cancel) {
if (task->state == WCPDownloadState::Canceled) {
cancel = true;
return;
}
// Calculate progress
int percent = 0;
if (progress.dltotal > 0) {
percent = (int)(progress.dlnow * 100 / progress.dltotal);
}
task->percent = percent;
// Throttle progress updates: update every 5% or every second
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto& last_pct = m_last_percent[task->task_id];
auto& last_upd = m_last_update[task->task_id];
auto now = std::chrono::steady_clock::now();
bool should_update = false;
if (percent - last_pct >= 5) {
should_update = true;
last_pct = percent;
} else if (now - last_upd >= std::chrono::seconds(1)) {
should_update = true;
}
if (should_update) {
last_upd = now;
wxGetApp().CallAfter([this, task, percent, progress]() {
send_progress_update(task, percent, progress.dlnow, progress.dltotal);
});
}
});
// Step 3: Set complete callback
http.on_complete([this, task](std::string body, unsigned status) {
wxGetApp().CallAfter([this, task, body]() {
try {
// Save file
boost::nowide::ofstream file(task->dest_path, std::ios::binary);
if (!file.is_open()) {
send_error_update(task, "Failed to open file for writing");
cleanup_task(task->task_id);
return;
}
file.write(body.c_str(), body.size());
file.close();
task->state = WCPDownloadState::Completed;
task->percent = 100;
send_complete_update(task, task->dest_path);
cleanup_task(task->task_id);
} catch (std::exception& e) {
send_error_update(task, e.what());
cleanup_task(task->task_id);
}
});
});
// 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 = WCPDownloadState::Error;
task->error_message = error;
send_error_update(task, error);
cleanup_task(task->task_id);
});
});
// Step 5: Start download and save Http::Ptr for cancellation
task->http_object = http.perform();
} catch (std::exception& e) {
task->state = WCPDownloadState::Error;
task->error_message = e.what();
send_error_update(task, e.what());
cleanup_task(task->task_id);
}
});
return task_id;
}
bool WCPDownloadManager::cancel_download(size_t task_id) {
std::shared_ptr<SSWCP_Instance> wcp_to_destroy;
{
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it == m_tasks.end()) {
return false;
}
auto task = it->second;
if (task->state == WCPDownloadState::Downloading) {
task->state = WCPDownloadState::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();
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();
}
return true;
}
bool WCPDownloadManager::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<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end() && it->second->state == WCPDownloadState::Downloading) {
it->second->state = WCPDownloadState::Paused;
// Note: Http module doesn't support pause directly, would need breakpoint resume
return true;
}
return false;
}
bool WCPDownloadManager::resume_download(size_t task_id) {
// Resume functionality can be implemented if needed
// Would require breakpoint resume support in Http module
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end() && it->second->state == WCPDownloadState::Paused) {
// Would need to restart download with range header
return false; // Not implemented yet
}
return false;
}
WCPDownloadState WCPDownloadManager::get_task_state(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end()) {
return it->second->state;
}
return WCPDownloadState::Error;
}
std::shared_ptr<WCPDownloadTask> WCPDownloadManager::get_task(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end()) {
return it->second;
}
return nullptr;
}
void WCPDownloadManager::send_progress_update(std::shared_ptr<WCPDownloadTask> task,
int percent,
size_t downloaded,
size_t total) {
if (auto wcp = task->wcp_instance.lock()) {
json progress_data;
progress_data["task_id"] = task->task_id;
progress_data["percent"] = percent;
progress_data["downloaded"] = downloaded;
progress_data["total"] = total;
progress_data["state"] = "downloading";
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";
header["command"] = "download_progress";
wcp->m_header = header;
wcp->send_to_js();
}
}
void WCPDownloadManager::send_complete_update(std::shared_ptr<WCPDownloadTask> task,
const std::string& file_path) {
if (auto wcp = task->wcp_instance.lock()) {
json complete_data;
complete_data["task_id"] = task->task_id;
complete_data["file_path"] = file_path;
complete_data["file_name"] = task->file_name;
complete_data["percent"] = 100;
complete_data["state"] = "completed";
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->finish_job();
}
}
void WCPDownloadManager::send_error_update(std::shared_ptr<WCPDownloadTask> task,
const std::string& error) {
if (auto wcp = task->wcp_instance.lock()) {
json error_data;
error_data["task_id"] = task->task_id;
error_data["error"] = error;
error_data["state"] = "error";
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 WCPDownloadManager::cleanup_task(size_t task_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
m_tasks.erase(task_id);
m_last_percent.erase(task_id);
m_last_update.erase(task_id);
}
}} // namespace Slic3r::GUI

View File

@@ -0,0 +1,104 @@
#ifndef slic3r_WCPDownloadManager_hpp_
#define slic3r_WCPDownloadManager_hpp_
#include <memory>
#include <string>
#include <unordered_map>
#include <mutex>
#include <atomic>
#include <chrono>
#include "../Utils/Http.hpp"
#include "SSWCP.hpp"
#include <boost/filesystem/path.hpp>
#include "nlohmann/json.hpp"
namespace Slic3r { namespace GUI {
// Download task state
enum class WCPDownloadState {
Pending,
Downloading,
Paused,
Completed,
Error,
Canceled
};
// Download task information
struct WCPDownloadTask {
size_t task_id;
std::string file_url;
std::string file_name;
std::string dest_path;
std::weak_ptr<SSWCP_Instance> wcp_instance; // Associated WCP instance
Http::Ptr http_object; // HTTP object for cancellation
WCPDownloadState state;
int percent;
std::string error_message;
WCPDownloadTask(size_t id, const std::string& url, const std::string& name,
const std::string& path, std::shared_ptr<SSWCP_Instance> instance)
: task_id(id), file_url(url), file_name(name), dest_path(path),
wcp_instance(instance), state(WCPDownloadState::Pending), percent(0) {}
};
// WCP Download Manager
class WCPDownloadManager {
public:
static WCPDownloadManager& getInstance() {
static WCPDownloadManager instance;
return instance;
}
// Start a download task
size_t start_download(const std::string& file_url,
const std::string& file_name,
std::shared_ptr<SSWCP_Instance> wcp_instance);
// Cancel a download task
bool cancel_download(size_t task_id);
// Pause a download task (if needed)
bool pause_download(size_t task_id);
// Resume a download task (if needed)
bool resume_download(size_t task_id);
// Get task state
WCPDownloadState get_task_state(size_t task_id);
// Get task information
std::shared_ptr<WCPDownloadTask> get_task(size_t task_id);
private:
WCPDownloadManager() = default;
~WCPDownloadManager() = default;
WCPDownloadManager(const WCPDownloadManager&) = delete;
WCPDownloadManager& operator=(const WCPDownloadManager&) = delete;
std::mutex m_tasks_mutex;
std::unordered_map<size_t, std::shared_ptr<WCPDownloadTask>> m_tasks;
std::atomic<size_t> m_next_task_id{1};
// Track last progress update for throttling
std::unordered_map<size_t, int> m_last_percent;
std::unordered_map<size_t, std::chrono::steady_clock::time_point> m_last_update;
// Send progress update to WCP
void send_progress_update(std::shared_ptr<WCPDownloadTask> task, int percent,
size_t downloaded, size_t total);
// Send completion message to WCP
void send_complete_update(std::shared_ptr<WCPDownloadTask> task, const std::string& file_path);
// Send error message to WCP
void send_error_update(std::shared_ptr<WCPDownloadTask> task, const std::string& error);
// Clean up completed task
void cleanup_task(size_t task_id);
};
}} // namespace Slic3r::GUI
#endif // slic3r_WCPDownloadManager_hpp_