diff --git a/scripts/disable_validation.entitlements b/scripts/disable_validation.entitlements
index d2fae18a76..123d12a53e 100644
--- a/scripts/disable_validation.entitlements
+++ b/scripts/disable_validation.entitlements
@@ -2,30 +2,7 @@
-
- com.apple.application-identifier
- 5NGD3B3V37.snapmaker-orca
- com.apple.developer.team-identifier
- 5NGD3B3V37
-
-
com.apple.security.cs.disable-library-validation
- com.apple.runningboard.assertions.webkit
-
- com.apple.security.network.client
-
- com.apple.security.network.server
-
- com.apple.security.files.user-selected.read-write
-
- com.apple.security.files.downloads.read-write
-
- com.apple.security.cs.allow-jit
-
- com.apple.security.cs.allow-unsigned-executable-memory
-
- com.apple.security.cs.allow-dyld-environment-variables
-
diff --git a/src/sentry_wrapper/SentryWrapper.cpp b/src/sentry_wrapper/SentryWrapper.cpp
index 95894ecff6..380220196b 100644
--- a/src/sentry_wrapper/SentryWrapper.cpp
+++ b/src/sentry_wrapper/SentryWrapper.cpp
@@ -251,6 +251,10 @@ void initSentryEx()
sentry_options_set_before_send_log(options, before_send_log, NULL);
sentry_options_set_logs_with_attributes(options, true);
+ // Set release version for symbolication
+ // This must match the release used when uploading symbols
+ sentry_options_set_release(options, Snapmaker_VERSION);
+
sentry_init(options);
sentry_start_session();
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 28596e3fb9..e74b6c66ed 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -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
diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp
index 6b338b5f03..5b6b619e86 100644
--- a/src/slic3r/GUI/GUI.cpp
+++ b/src/slic3r/GUI/GUI.cpp
@@ -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.
diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp
index db8cf06a61..acceaa8e4f 100644
--- a/src/slic3r/GUI/GUI.hpp
+++ b/src/slic3r/GUI/GUI.hpp
@@ -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
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index efc2846661..5d8b699e55 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -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())
, m_downloader(std::make_unique())
+ , m_wcp_download_manager(&WCPDownloadManager::getInstance())
, m_other_instance_message_handler(std::make_unique())
{
//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)
diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp
index 40c61dd0c1..38a950676a 100644
--- a/src/slic3r/GUI/GUI_App.hpp
+++ b/src/slic3r/GUI/GUI_App.hpp
@@ -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 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;
diff --git a/src/slic3r/GUI/SSWCP.cpp b/src/slic3r/GUI/SSWCP.cpp
index 57930222ed..33de5066f0 100644
--- a/src/slic3r/GUI/SSWCP.cpp
+++ b/src/slic3r/GUI/SSWCP.cpp
@@ -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 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 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() : 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() : "";
+ wxFileName file(file_path);
+
+ if (!file.FileExists()) {
+ handle_general_fail();
+ //wxMessageBox(wxT("file not exsit"), wxT("tips"), wxOK | wxICON_WARNING);
+ return;
+ }
+
+ std::weak_ptr 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 {
diff --git a/src/slic3r/GUI/SSWCP.hpp b/src/slic3r/GUI/SSWCP.hpp
index 12717471ec..fe40a45a42 100644
--- a/src/slic3r/GUI/SSWCP.hpp
+++ b/src/slic3r/GUI/SSWCP.hpp
@@ -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
diff --git a/src/slic3r/GUI/WCPDownloadManager.cpp b/src/slic3r/GUI/WCPDownloadManager.cpp
new file mode 100644
index 0000000000..a9d521aac3
--- /dev/null
+++ b/src/slic3r/GUI/WCPDownloadManager.cpp
@@ -0,0 +1,276 @@
+#include "WCPDownloadManager.hpp"
+#include "GUI_App.hpp"
+#include
+#include
+#include
+
+namespace Slic3r { namespace GUI {
+
+size_t WCPDownloadManager::start_download(const std::string& file_url,
+ const std::string& file_name,
+ std::shared_ptr wcp_instance) {
+
+ 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();
+
+ // Create task
+ auto task = std::make_shared(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 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 wcp_to_destroy;
+
+ {
+ std::lock_guard 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 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 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 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 WCPDownloadManager::get_task(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;
+ }
+ return nullptr;
+}
+
+void WCPDownloadManager::send_progress_update(std::shared_ptr 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 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 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 lock(m_tasks_mutex);
+ m_tasks.erase(task_id);
+ m_last_percent.erase(task_id);
+ m_last_update.erase(task_id);
+}
+
+}} // namespace Slic3r::GUI
+
diff --git a/src/slic3r/GUI/WCPDownloadManager.hpp b/src/slic3r/GUI/WCPDownloadManager.hpp
new file mode 100644
index 0000000000..fda468f777
--- /dev/null
+++ b/src/slic3r/GUI/WCPDownloadManager.hpp
@@ -0,0 +1,104 @@
+#ifndef slic3r_WCPDownloadManager_hpp_
+#define slic3r_WCPDownloadManager_hpp_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include "../Utils/Http.hpp"
+#include "SSWCP.hpp"
+#include
+#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 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 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 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 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> m_tasks;
+ std::atomic m_next_task_id{1};
+
+ // Track last progress update for throttling
+ 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);
+
+ // Send completion message to WCP
+ void send_complete_update(std::shared_ptr task, const std::string& file_path);
+
+ // Send error message to WCP
+ void send_error_update(std::shared_ptr task, const std::string& error);
+
+ // Clean up completed task
+ void cleanup_task(size_t task_id);
+};
+
+}} // namespace Slic3r::GUI
+
+#endif // slic3r_WCPDownloadManager_hpp_
+