Merge pull request #150 from Snapmaker/dev_2.3.0_alves

feature add donwload func for module station
This commit is contained in:
Alves
2026-02-04 18:29:45 +08:00
committed by GitHub
17 changed files with 1465 additions and 456 deletions
+4 -2
View File
@@ -504,8 +504,10 @@ set(SLIC3R_GUI_SOURCES
GUI/SMPhysicalPrinterDialog.cpp
GUI/SSWCP.cpp
GUI/SSWCP.hpp
GUI/WCPDownloadManager.cpp
GUI/WCPDownloadManager.hpp
GUI/DownloadManager.cpp
GUI/DownloadManager.hpp
GUI/GenericDownloadDialog.cpp
GUI/GenericDownloadDialog.hpp
GUI/WebPresetDialog.hpp
GUI/WebPresetDialog.cpp
GUI/WebSMUserLoginDialog.cpp
+1
View File
@@ -57,6 +57,7 @@ BBLStatusBarSend::BBLStatusBarSend(wxWindow *parent, int id)
m_cancelbutton->SetBorderColor(btn_bd_white);
m_cancelbutton->SetTextColor(btn_txt_white);
m_cancelbutton->SetCornerRadius(m_self->FromDIP(12));
m_cancelbutton->SetCursor(wxCURSOR_HAND);
m_cancelbutton->Bind(wxEVT_BUTTON,
[this](wxCommandEvent &evt) {
m_was_cancelled = true;
+570
View File
@@ -0,0 +1,570 @@
#include "DownloadManager.hpp"
#include "GUI_App.hpp"
#include <boost/filesystem.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/log/trivial.hpp>
#include <boost/format.hpp>
#include <vector>
#include <ctime>
namespace Slic3r { namespace GUI {
// ============================================================================
// Helper Functions
// ============================================================================
std::string DownloadManager::get_unique_file_path(const boost::filesystem::path& file_path)
{
// file_path should be the complete absolute path: directory + filename
std::string original_path = file_path.string();
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: Checking path '%1%'") % original_path;
// Check if file exists, if not return original path
if (!boost::filesystem::exists(file_path)) {
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: File does not exist, returning original path '%1%'") % original_path;
return original_path;
}
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: File exists, generating unique name");
boost::filesystem::path parent_dir = file_path.parent_path();
std::string filename = file_path.filename().string();
std::string extension = file_path.extension().string();
std::string name_without_ext;
if (extension.empty()) {
name_without_ext = filename;
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: No extension found, filename='%1%'") % filename;
} else {
name_without_ext = filename.substr(0, filename.size() - extension.size());
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: filename='%1%', extension='%2%', name_without_ext='%3%'")
% filename % extension % name_without_ext;
}
// Generate unique filename with Windows-style numbering: filename(1).ext, filename(2).ext, etc.
size_t version = 1;
boost::filesystem::path unique_path;
do {
std::string new_filename;
if (extension.empty()) {
// No extension: filename(1), filename(2), etc.
new_filename = name_without_ext + "(" + std::to_string(version) + ")";
} else {
// Has extension: filename(1).ext, filename(2).ext, etc.
new_filename = name_without_ext + "(" + std::to_string(version) + ")" + extension;
}
unique_path = parent_dir / new_filename;
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: Trying version %1%: '%2%'") % version % unique_path.string();
version++;
} while (boost::filesystem::exists(unique_path) && version < 10000); // Safety limit
if (version >= 10000) {
// If we hit the limit, log a warning and return a timestamp-based name
BOOST_LOG_TRIVIAL(warning) << boost::format("DownloadManager::get_unique_file_path: Too many duplicate files for '%1%', using timestamp-based name")
% original_path;
std::string timestamp = std::to_string(std::time(nullptr));
std::string new_filename;
if (extension.empty()) {
new_filename = name_without_ext + "_" + timestamp;
} else {
new_filename = name_without_ext + "_" + timestamp + extension;
}
unique_path = parent_dir / new_filename;
}
std::string result = unique_path.string();
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::get_unique_file_path: Final unique path: '%1%'") % result;
return result;
}
// ============================================================================
// 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<SSWCP_Instance> wcp_instance,
bool use_original_event_id) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
size_t task_id = m_next_task_id++;
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;
// Generate unique file path if file already exists
std::string dest_path = get_unique_file_path(dest_file);
// Update file_name if it was changed due to duplicate
std::string actual_file_name = boost::filesystem::path(dest_path).filename().string();
auto task = std::make_shared<DownloadTask>(task_id,
file_url,
actual_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<std::mutex> lock(m_tasks_mutex);
size_t task_id = m_next_task_id++;
boost::filesystem::path dest_path_obj(dest_path);
boost::filesystem::path dest_file_path;
if (boost::filesystem::is_directory(dest_path_obj) || dest_path_obj.filename().empty()) {
// dest_path is a directory, need to append file_name
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::start_internal_download: dest_path '%1%' is a directory, appending file_name '%2%'")
% dest_path % file_name;
dest_file_path = dest_path_obj / file_name;
} else {
// dest_path is already a complete file path (directory + filename)
BOOST_LOG_TRIVIAL(debug) << boost::format("DownloadManager::start_internal_download: dest_path '%1%' is a complete file path")
% dest_path;
dest_file_path = dest_path_obj;
}
boost::filesystem::create_directories(dest_file_path.parent_path());
std::string unique_dest_path = get_unique_file_path(dest_file_path);
auto task = std::make_shared<DownloadTask>(task_id,
file_url,
file_name,
unique_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);
boost::filesystem::path dest_file = dest_folder / file_name;
// Generate unique file path if file already exists
std::string dest_path = get_unique_file_path(dest_file);
return start_internal_download(file_url, file_name, dest_path, std::move(callbacks));
}
void DownloadManager::start_download_impl(std::shared_ptr<DownloadTask> task) {
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) {
// Check if task is canceled or already cleaned up
{
std::lock_guard<std::mutex> lock(m_tasks_mutex);
if (m_tasks.find(task->task_id) == m_tasks.end()) {
// Task has been cleaned up, cancel the download
cancel = true;
return;
}
}
if (task->state == DownloadTaskState::Canceled) {
cancel = true;
return;
}
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);
// Double-check task still exists after acquiring lock
if (m_tasks.find(task->task_id) == m_tasks.end()) {
cancel = true;
return;
}
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]() {
// Check if task still exists before sending update
std::lock_guard<std::mutex> lock(m_tasks_mutex);
if (m_tasks.find(task->task_id) != m_tasks.end() &&
task->state != DownloadTaskState::Canceled) {
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]() {
// Check if task still exists and is not canceled (without lock to avoid deadlock with cleanup_task)
if (task->state == DownloadTaskState::Canceled) {
// Task has been canceled, ignore completion
BOOST_LOG_TRIVIAL(debug) << "DownloadManager: Ignoring complete callback for canceled task " << task->task_id;
return;
}
try {
// Save file
boost::nowide::ofstream file(task->dest_path, std::ios::binary);
if (!file.is_open()) {
std::string error_msg = "Failed to open file for writing: " + task->dest_path;
BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg;
send_error_update(task, error_msg);
cleanup_task(task->task_id);
return;
}
file.write(body.c_str(), body.size());
if (file.fail()) {
std::string error_msg = "Failed to write file: " + task->dest_path;
BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg << ", body size: " << body.size();
file.close();
send_error_update(task, error_msg);
cleanup_task(task->task_id);
return;
}
file.close();
task->state = DownloadTaskState::Completed;
task->percent = 100;
send_complete_update(task, task->dest_path);
cleanup_task(task->task_id);
} catch (std::exception& e) {
std::string error_msg = std::string("File write exception: ") + e.what();
BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg << ", file: " << task->dest_path;
send_error_update(task, error_msg);
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]() {
// Check if task was canceled (without lock to avoid deadlock with cleanup_task)
if (task->state == DownloadTaskState::Canceled) {
// Task was canceled, ignore error callback (cancel already handled cleanup)
BOOST_LOG_TRIVIAL(debug) << "DownloadManager: Ignoring error callback for canceled task " << task->task_id;
return;
}
std::string error_msg = boost::str(boost::format("HTTP error: %1% (status: %2%)") % error % status);
BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg
<< ", URL: " << task->file_url
<< ", file: " << task->file_name
<< ", dest: " << task->dest_path;
task->state = DownloadTaskState::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) {
std::string error_msg = std::string("Download exception: ") + e.what();
BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg
<< ", URL: " << task->file_url
<< ", file: " << task->file_name
<< ", dest: " << task->dest_path;
task->state = DownloadTaskState::Error;
task->error_message = e.what();
send_error_update(task, e.what());
cleanup_task(task->task_id);
}
});
}
//this function not currently in use
bool DownloadManager::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 == DownloadTaskState::Downloading) {
task->state = DownloadTaskState::Canceled;
if (task->http_object) {
task->http_object->cancel();
}
// Only for WCP downloads
if (task->is_wcp_download()) {
wcp_to_destroy = task->wcp_instance.lock();
} else {
task->callbacks.on_error = nullptr;
task->callbacks.on_progress = nullptr;
task->callbacks.on_complete = nullptr;
}
// Cleanup task directly (already holding the lock, don't call cleanup_task)
m_tasks.erase(task_id);
m_last_percent.erase(task_id);
m_last_update.erase(task_id);
} else {
return false;
}
}
if (wcp_to_destroy) {
wcp_to_destroy->finish_job();
}
return true;
}
// this function not currently in use
bool DownloadManager::pause_download(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() && 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) {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
auto it = m_tasks.find(task_id);
if (it != m_tasks.end() && it->second->state == DownloadTaskState::Paused) {
return false;
}
return false;
}
// this function not currently in use
DownloadTaskState DownloadManager::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 DownloadTaskState::Error;
}
// this function not currently in use
std::shared_ptr<DownloadTask> DownloadManager::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;
}
// this function not currently in use
std::vector<std::shared_ptr<DownloadTask>> DownloadManager::get_all_tasks() {
std::lock_guard<std::mutex> lock(m_tasks_mutex);
std::vector<std::shared_ptr<DownloadTask>> 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<DownloadTask> 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<DownloadTask> 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;
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";
json header;
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;
wcp->send_to_js();
}
}
void DownloadManager::call_internal_progress_callback(std::shared_ptr<DownloadTask> task,
int percent,
size_t downloaded,
size_t total) {
if (task->callbacks.on_progress && task->state != DownloadTaskState::Canceled) {
task->callbacks.on_progress(task->task_id, percent, downloaded, total);
}
}
void DownloadManager::send_complete_update(std::shared_ptr<DownloadTask> 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<DownloadTask> 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;
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();
wcp->finish_job();
}
}
void DownloadManager::call_internal_complete_callback(std::shared_ptr<DownloadTask> task,
const std::string& file_path) {
if (task->callbacks.on_complete && task->state != DownloadTaskState::Canceled) {
task->callbacks.on_complete(task->task_id, file_path);
}
}
void DownloadManager::send_error_update(std::shared_ptr<DownloadTask> 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<DownloadTask> 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;
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();
wcp->finish_job();
}
}
void DownloadManager::call_internal_error_callback(std::shared_ptr<DownloadTask> task,
const std::string& error) {
if (task->callbacks.on_error && task->state != DownloadTaskState::Canceled) {
task->callbacks.on_error(task->task_id, error);
}
}
void DownloadManager::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
+210
View File
@@ -0,0 +1,210 @@
#ifndef slic3r_DownloadManager_hpp_
#define slic3r_DownloadManager_hpp_
#include <memory>
#include <string>
#include <unordered_map>
#include <mutex>
#include <atomic>
#include <chrono>
#include <functional>
#include "../Utils/Http.hpp"
#include "SSWCP.hpp"
#include <boost/filesystem/path.hpp>
#include "nlohmann/json.hpp"
namespace Slic3r { namespace GUI {
// Download task state (renamed to avoid conflict with Downloader::DownloadState)
enum class DownloadTaskState {
Pending,
Downloading,
Paused,
Completed,
Error,
Canceled
};
// Download callback interface for internal downloads
struct DownloadCallbacks {
std::function<void(size_t task_id, int percent, size_t downloaded, size_t total)> on_progress;
std::function<void(size_t task_id, const std::string& file_path)> on_complete;
std::function<void(size_t task_id, const std::string& error)> on_error;
DownloadCallbacks() = default;
DownloadCallbacks(
std::function<void(size_t, int, size_t, size_t)> progress,
std::function<void(size_t, const std::string&)> complete,
std::function<void(size_t, const std::string&)> 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<SSWCP_Instance> 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<SSWCP_Instance> 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();
}
};
class DownloadManager {
public:
static DownloadManager& getInstance() {
static DownloadManager instance;
return 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<SSWCP_Instance> 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);
// 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
DownloadTaskState get_task_state(size_t task_id);
// Get task information
std::shared_ptr<DownloadTask> get_task(size_t task_id);
// Get all active tasks
std::vector<std::shared_ptr<DownloadTask>> get_all_tasks();
private:
DownloadManager() = default;
~DownloadManager() = default;
DownloadManager(const DownloadManager&) = delete;
DownloadManager& operator=(const DownloadManager&) = delete;
std::mutex m_tasks_mutex;
std::unordered_map<size_t, std::shared_ptr<DownloadTask>> 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;
// ============================================================================
// Internal Implementation
// ============================================================================
// Common download implementation (used by both WCP and internal downloads)
void start_download_impl(std::shared_ptr<DownloadTask> task);
// Send progress update (handles both WCP and internal modes)
void send_progress_update(std::shared_ptr<DownloadTask> 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<DownloadTask> task,
const std::string& file_path);
// Send error message (handles both WCP and internal modes)
void send_error_update(std::shared_ptr<DownloadTask> task,
const std::string& error);
// WCP-specific: Send progress update via WCP instance
void send_wcp_progress_update(std::shared_ptr<DownloadTask> task,
int percent,
size_t downloaded,
size_t total);
// WCP-specific: Send completion via WCP instance
void send_wcp_complete_update(std::shared_ptr<DownloadTask> task,
const std::string& file_path);
// WCP-specific: Send error via WCP instance
void send_wcp_error_update(std::shared_ptr<DownloadTask> task,
const std::string& error);
// Internal-specific: Call progress callback
void call_internal_progress_callback(std::shared_ptr<DownloadTask> task,
int percent,
size_t downloaded,
size_t total);
// Internal-specific: Call complete callback
void call_internal_complete_callback(std::shared_ptr<DownloadTask> task,
const std::string& file_path);
// Internal-specific: Call error callback
void call_internal_error_callback(std::shared_ptr<DownloadTask> task,
const std::string& error);
// Clean up completed task
void cleanup_task(size_t task_id);
// Generate unique file path if file already exists
// Returns path like "file(1).zip", "file(2).zip" etc.
static std::string get_unique_file_path(const boost::filesystem::path& file_path);
};
}} // namespace Slic3r::GUI
#endif // slic3r_DownloadManager_hpp_
+4 -4
View File
@@ -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"
@@ -1066,7 +1066,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_download_manager(&DownloadManager::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
@@ -6478,9 +6478,9 @@ Downloader* GUI_App::downloader()
return m_downloader.get();
}
WCPDownloadManager* GUI_App::wcp_download_manager()
DownloadManager* GUI_App::download_manager()
{
return m_wcp_download_manager;
return m_download_manager;
}
void GUI_App::load_url(wxString url)
+3 -3
View File
@@ -89,7 +89,7 @@ class Plater;
class ParamsPanel;
class NotificationManager;
class Downloader;
class WCPDownloadManager;
class DownloadManager;
struct GUI_InitParams;
class ParamsDialog;
class HMSQuery;
@@ -298,7 +298,7 @@ private:
size_t m_instance_hash_int;
std::unique_ptr<Downloader> m_downloader;
WCPDownloadManager* m_wcp_download_manager;
DownloadManager* m_download_manager;
//BBS
bool m_is_closing {false};
@@ -686,7 +686,7 @@ private:
Model& model();
NotificationManager * notification_manager();
Downloader* downloader();
WCPDownloadManager* wcp_download_manager();
DownloadManager* download_manager();
std::string m_mall_model_download_url;
+431
View File
@@ -0,0 +1,431 @@
#include "GenericDownloadDialog.hpp"
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include <wx/hyperlink.h>
#include <wx/textctrl.h>
#include <wx/scrolwin.h>
#include <wx/event.h>
#include <wx/dcgraph.h>
#include <boost/log/trivial.hpp>
#include <boost/format.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/filesystem.hpp>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "I18N.hpp"
#include "wxExtensions.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "GUI_App.hpp"
#include "slic3r/GUI/DownloadManager.hpp"
namespace Slic3r {
namespace GUI {
GenericDownloadDialog::GenericDownloadDialog(wxString title,
const std::string& file_url,
const std::string& file_name,
const std::string& dest_path,
wxWindow* parent)
: DPIDialog(parent ? parent : static_cast<wxWindow *>(wxGetApp().mainframe),
wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
, m_title(title)
, m_file_url(file_url)
, m_file_name(file_name)
, m_dest_path(dest_path)
{
std::string icon_path = (boost::format("%1%/images/Snapmaker_OrcaTitle.ico") % resources_dir()).str();
SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
SetBackgroundColour(*wxWHITE);
setup_ui();
Bind(wxEVT_CLOSE_WINDOW, &GenericDownloadDialog::on_close, this);
wxGetApp().UpdateDlgDarkUI(this);
}
GenericDownloadDialog::~GenericDownloadDialog()
{
// Set destroying flag first to prevent any callbacks from accessing this object
m_is_destroying = true;
// Cancel any active download before destruction
if (m_task_id > 0) {
DownloadManager::getInstance().cancel_download(m_task_id);
m_task_id = 0;
}
}
void GenericDownloadDialog::setup_ui()
{
wxBoxSizer *m_sizer_main = new wxBoxSizer(wxVERTICAL);
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1));
m_line_top->SetBackgroundColour(wxColour(166, 169, 170));
m_sizer_main->Add(m_line_top, 0, wxEXPAND, 0);
m_simplebook_status = new wxSimplebook(this);
m_simplebook_status->SetSize(wxSize(FromDIP(420), FromDIP(100)));
m_simplebook_status->SetMinSize(wxSize(FromDIP(420), FromDIP(100)));
m_simplebook_status->SetMaxSize(wxSize(FromDIP(420), FromDIP(250)));
// Progress page
m_status_bar = std::make_shared<BBLStatusBarSend>(m_simplebook_status);
m_panel_download = m_status_bar->get_panel();
m_panel_download->SetSize(wxSize(FromDIP(400), FromDIP(70)));
m_panel_download->SetMinSize(wxSize(FromDIP(400), FromDIP(70)));
m_panel_download->SetMaxSize(wxSize(FromDIP(400), FromDIP(70)));
// Complete page
m_panel_complete = new wxPanel(m_simplebook_status, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
wxBoxSizer* sizer_complete = new wxBoxSizer(wxVERTICAL);
m_complete_text = new wxStaticText(m_panel_complete, wxID_ANY, _L("Download completed successfully!"),
wxDefaultPosition, wxDefaultSize, 0);
m_complete_text->SetForegroundColour(*wxBLACK);
m_complete_text->Wrap(FromDIP(360));
sizer_complete->Add(m_complete_text, 0, wxALIGN_CENTER | wxALL, 5);
StateColor btn_close_bg(std::pair<wxColour, int>(wxColour(0x90, 0x90, 0x90), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(231, 231, 231), StateColor::Normal));
StateColor btn_close_bd(std::pair<wxColour, int>(wxColour(255, 255, 254), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
StateColor btn_close_txt(std::pair<wxColour, int>(wxColour("#FFFFFE"), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(36, 36, 36), StateColor::Normal));
m_close_button = new Button(m_panel_complete, _L("Close"));
m_close_button->SetSize(wxSize(FromDIP(80), FromDIP(28)));
m_close_button->SetMinSize(wxSize(FromDIP(80), FromDIP(28)));
m_close_button->SetMaxSize(wxSize(FromDIP(80), FromDIP(28)));
m_close_button->SetBackgroundColour(*wxWHITE);
m_close_button->SetBackgroundColor(btn_close_bg);
m_close_button->SetBorderColor(btn_close_bd);
m_close_button->SetTextColor(btn_close_txt);
m_close_button->SetCornerRadius(FromDIP(12));
m_close_button->SetCursor(wxCURSOR_HAND);
m_close_button->Bind(wxEVT_BUTTON, &GenericDownloadDialog::on_close_clicked, this);
sizer_complete->Add(m_close_button, 0, wxALIGN_CENTER | wxALL, 5);
m_panel_complete->SetSizer(sizer_complete);
m_panel_complete->Layout();
sizer_complete->Fit(m_panel_complete);
// Error page
m_panel_error = new wxPanel(m_simplebook_status, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
wxBoxSizer* sizer_error = new wxBoxSizer(wxVERTICAL);
// Simple label for error message display
m_error_text = new wxStaticText(m_panel_error, wxID_ANY, wxEmptyString,
wxDefaultPosition, wxSize(FromDIP(380), -1),
wxALIGN_LEFT | wxST_ELLIPSIZE_END);
m_error_text->SetForegroundColour(*wxBLACK);
m_error_text->Wrap(FromDIP(380));
sizer_error->Add(m_error_text, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, FromDIP(10));
// Button sizer aligned to right
wxBoxSizer* sizer_buttons = new wxBoxSizer(wxHORIZONTAL);
sizer_buttons->AddStretchSpacer();
StateColor btn_retry_bg(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(23, 99, 226), StateColor::Hovered), // Same as Normal
std::pair<wxColour, int>(wxColour(23, 99, 226), StateColor::Normal));
// Set border color to same as background to avoid corner color issues
StateColor btn_retry_bd(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(23, 99, 226), StateColor::Enabled)); // Same as background
StateColor btn_retry_txt(std::pair<wxColour, int>(wxColour("#FFFFFE"), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Normal));
m_retry_button = new Button(m_panel_error, _L("Retry"));
m_retry_button->SetSize(wxSize(FromDIP(80), FromDIP(28)));
m_retry_button->SetMinSize(wxSize(FromDIP(80), FromDIP(28)));
m_retry_button->SetMaxSize(wxSize(FromDIP(80), FromDIP(28)));
// Set window background color to white to ensure rounded corners are white
m_retry_button->SetBackgroundColour(*wxWHITE);
m_retry_button->SetBackgroundColor(btn_retry_bg);
m_retry_button->SetBorderColor(btn_retry_bg);
m_retry_button->SetTextColor(btn_retry_txt);
m_retry_button->SetCornerRadius(FromDIP(12));
m_retry_button->SetCursor(wxCURSOR_HAND);
m_retry_button->Bind(wxEVT_BUTTON, &GenericDownloadDialog::on_retry_clicked, this);
// Setup StateColor for determine button (gray style)
StateColor btn_determine_bg(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(231, 231, 231), StateColor::Normal));
StateColor btn_determine_bd(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
StateColor btn_determine_txt(std::pair<wxColour, int>(wxColour("#FFFFFE"), StateColor::Disabled),
std::pair<wxColour, int>(wxColour(36, 36, 36), StateColor::Normal));
m_error_close_button = new Button(m_panel_error, _L("Determine"));
m_error_close_button->SetSize(wxSize(FromDIP(80), FromDIP(28)));
m_error_close_button->SetMinSize(wxSize(FromDIP(80), FromDIP(28)));
m_error_close_button->SetMaxSize(wxSize(FromDIP(80), FromDIP(28)));
// Set window background color to white to ensure rounded corners are white
m_error_close_button->SetBackgroundColour(*wxWHITE);
m_error_close_button->SetBackgroundColor(btn_determine_bg);
m_error_close_button->SetBorderColor(btn_determine_bg);
m_error_close_button->SetTextColor(btn_determine_txt);
m_error_close_button->SetCornerRadius(FromDIP(12));
m_error_close_button->SetCursor(wxCURSOR_HAND);
m_error_close_button->Bind(wxEVT_BUTTON, &GenericDownloadDialog::on_close_clicked, this);
sizer_buttons->Add(m_error_close_button, 0, 0);
sizer_buttons->AddSpacer(FromDIP(6));
sizer_buttons->Add(m_retry_button, 0, 0);
sizer_error->AddSpacer(FromDIP(40));
sizer_error->Add(sizer_buttons, 0, wxALIGN_RIGHT | wxRIGHT, FromDIP(24));
m_panel_error->SetSizer(sizer_error);
m_panel_error->Layout();
sizer_error->Fit(m_panel_error);
m_sizer_main->Add(m_simplebook_status, 0, wxALL, FromDIP(16));
m_simplebook_status->AddPage(m_panel_download, wxEmptyString, true);
m_simplebook_status->AddPage(m_panel_complete, wxEmptyString, false);
m_simplebook_status->AddPage(m_panel_error, wxEmptyString, false);
SetSizer(m_sizer_main);
Layout();
Fit();
CentreOnParent();
}
wxString GenericDownloadDialog::format_text(wxStaticText* st, wxString str, int warp)
{
if (wxGetApp().app_config->get("language") != "zh_CN") {
return str;
}
wxString out_txt = str;
wxString count_txt = "";
for (int i = 0; i < str.length(); i++) {
auto text_size = st->GetTextExtent(count_txt);
if (text_size.x < warp) {
count_txt += str[i];
} else {
out_txt.insert(i - 1, '\n');
count_txt = "";
}
}
return out_txt;
}
void GenericDownloadDialog::start_download()
{
show_progress_page();
m_status_bar->set_progress(0);
m_status_bar->set_status_text(_L("Preparing download..."));
m_status_bar->change_button_label(_L("Cancel"));
m_status_bar->set_cancel_callback_fina([this]() {
if (m_task_id > 0) {
DownloadManager::getInstance().cancel_download(m_task_id);
m_task_id = 0;
}
EndModal(wxID_CANCEL);
});
// Create download callbacks
DownloadCallbacks callbacks;
callbacks.on_progress = [this](size_t task_id, int percent, size_t downloaded, size_t total) {
on_download_progress(task_id, percent, downloaded, total);
};
callbacks.on_complete = [this](size_t task_id, const std::string& file_path) {
on_download_complete(task_id, file_path);
};
callbacks.on_error = [this](size_t task_id, const std::string& error) {
on_download_error(task_id, error);
};
// Start download
if (m_dest_path.empty()) {
m_task_id = DownloadManager::getInstance().start_internal_download(
m_file_url, m_file_name, std::move(callbacks));
} else {
m_task_id = DownloadManager::getInstance().start_internal_download(
m_file_url, m_file_name, m_dest_path, std::move(callbacks));
}
}
int GenericDownloadDialog::ShowModal()
{
start_download();
return DPIDialog::ShowModal();
}
void GenericDownloadDialog::on_download_progress(size_t task_id, int percent, size_t downloaded, size_t total)
{
wxGetApp().CallAfter([this, percent, downloaded, total]() {
// Check if dialog is being destroyed
if (m_is_destroying || IsBeingDeleted()) {
return;
}
update_progress(percent);
// Format status text
wxString status_text;
if (total > 0) {
double downloaded_mb = downloaded / (1024.0 * 1024.0);
double total_mb = total / (1024.0 * 1024.0);
status_text = wxString::Format(_L("Downloading: %.1f MB / %.1f MB (%d%%)"),
downloaded_mb, total_mb, percent);
} else {
double downloaded_mb = downloaded / (1024.0 * 1024.0);
status_text = wxString::Format(_L("Downloading: %.1f MB..."), downloaded_mb);
}
m_status_bar->set_status_text(status_text);
// Call user callback if set
if (m_on_progress) {
m_on_progress(m_task_id, percent, downloaded, total);
}
});
}
void GenericDownloadDialog::on_download_complete(size_t task_id, const std::string& file_path)
{
wxGetApp().CallAfter([this, file_path]() {
// Check if dialog is being destroyed
if (m_is_destroying || IsBeingDeleted()) {
return;
}
m_download_success = true;
m_file_path = file_path;
show_complete_page();
// Mark task as completed - no need to cancel it
m_task_id = 0;
// Call user callback if set
if (m_on_complete) {
m_on_complete(m_task_id, file_path);
}
});
}
void GenericDownloadDialog::on_download_error(size_t task_id, const std::string& error)
{
// Log detailed error information for debugging
BOOST_LOG_TRIVIAL(error) << boost::format("GenericDownloadDialog: Download failed for file '%1%' from URL '%2%'. Error: %3%")
% m_file_name % m_file_url % error;
wxGetApp().CallAfter([this, error]() {
// Check if dialog is being destroyed
if (m_is_destroying || IsBeingDeleted()) {
return;
}
m_download_success = false;
m_error_message = error;
show_error_page(error);
// Mark task as completed (failed) - no need to cancel it
m_task_id = 0;
// Call user callback if set
if (m_on_error) {
m_on_error(m_task_id, error);
}
});
}
void GenericDownloadDialog::on_retry_clicked(wxCommandEvent& event)
{
if (m_on_retry) {
m_on_retry();
}
start_download();
event.Skip();
}
void GenericDownloadDialog::on_close_clicked(wxCommandEvent& event)
{
if (m_task_id > 0) {
DownloadManager::getInstance().cancel_download(m_task_id);
m_task_id = 0;
}
EndModal(m_download_success ? wxID_OK : wxID_CANCEL);
event.Skip();
}
void GenericDownloadDialog::on_close(wxCloseEvent& event)
{
if (m_task_id > 0) {
DownloadManager::getInstance().cancel_download(m_task_id);
m_task_id = 0;
}
event.Skip();
}
void GenericDownloadDialog::show_progress_page()
{
m_simplebook_status->SetSelection(0);
m_status_bar->set_progress(0);
m_status_bar->show_cancel_button();
}
void GenericDownloadDialog::show_complete_page()
{
//m_simplebook_status->SetSelection(1);
//m_status_bar->hide_cancel_button();
EndModal(wxID_OK);
}
void GenericDownloadDialog::show_error_page(const std::string& error_msg)
{
m_simplebook_status->SetSelection(2);
// Display simple error message: filename + "Download failed"
wxString filename = wxString::FromUTF8(m_file_name.c_str());
wxString error_text = filename + " - Download failed";
// Set error text in simple label
m_error_text->SetLabel(error_text);
m_error_text->Wrap(FromDIP(380));
m_panel_error->Layout();
m_simplebook_status->Layout();
Layout();
Fit();
m_status_bar->hide_cancel_button();
}
void GenericDownloadDialog::update_progress(int percent, const wxString& status_text)
{
m_status_bar->set_progress(percent);
if (!status_text.IsEmpty()) {
m_status_bar->set_status_text(status_text);
}
}
void GenericDownloadDialog::on_dpi_changed(const wxRect &suggested_rect)
{
// Handle DPI changes if needed
}
}} // namespace Slic3r::GUI
+113
View File
@@ -0,0 +1,113 @@
#ifndef slic3r_GenericDownloadDialog_hpp_
#define slic3r_GenericDownloadDialog_hpp_
#include <string>
#include <functional>
#include <memory>
#include <atomic>
#include "GUI_Utils.hpp"
#include <wx/dialog.h>
#include <wx/simplebook.h>
#include "BBLStatusBar.hpp"
#include "BBLStatusBarSend.hpp"
#include "Jobs/Worker.hpp"
#include "slic3r/GUI/DownloadManager.hpp"
#include "Widgets/Button.hpp"
class wxBoxSizer;
class wxPanel;
class wxStaticText;
class wxHyperlinkCtrl;
namespace Slic3r {
namespace GUI {
// Generic download dialog for custom download tasks with progress display
class GenericDownloadDialog : public DPIDialog
{
public:
// Callback types
using DownloadCallback = std::function<void(size_t task_id, int percent, size_t downloaded, size_t total)>;
using CompleteCallback = std::function<void(size_t task_id, const std::string& file_path)>;
using ErrorCallback = std::function<void(size_t task_id, const std::string& error)>;
using RetryCallback = std::function<void()>;
GenericDownloadDialog(wxString title,
const std::string& file_url,
const std::string& file_name,
const std::string& dest_path = "",
wxWindow* parent = nullptr);
~GenericDownloadDialog();
// Start download
void start_download();
// Set callbacks (optional)
void set_on_progress(DownloadCallback callback) { m_on_progress = callback; }
void set_on_complete(CompleteCallback callback) { m_on_complete = callback; }
void set_on_error(ErrorCallback callback) { m_on_error = callback; }
void set_on_retry(RetryCallback callback) { m_on_retry = callback; }
// Get download result
bool is_success() const { return m_download_success; }
std::string get_file_path() const { return m_file_path; }
std::string get_error_message() const { return m_error_message; }
// Show modal and return result
int ShowModal() override;
protected:
void on_close(wxCloseEvent& event);
void on_dpi_changed(const wxRect &suggested_rect) override;
wxString format_text(wxStaticText* st, wxString str, int warp);
// Event handlers
void on_download_progress(size_t task_id, int percent, size_t downloaded, size_t total);
void on_download_complete(size_t task_id, const std::string& file_path);
void on_download_error(size_t task_id, const std::string& error);
void on_retry_clicked(wxCommandEvent& event);
void on_close_clicked(wxCommandEvent& event);
private:
void setup_ui();
void show_progress_page();
void show_complete_page();
void show_error_page(const std::string& error_msg);
void update_progress(int percent, const wxString& status_text = "");
wxString m_title;
std::string m_file_url;
std::string m_file_name;
std::string m_dest_path;
size_t m_task_id{0};
bool m_download_success{false};
std::string m_file_path;
std::string m_error_message;
// Callbacks
DownloadCallback m_on_progress;
CompleteCallback m_on_complete;
ErrorCallback m_on_error;
RetryCallback m_on_retry;
// UI components
wxSimplebook* m_simplebook_status{nullptr};
std::shared_ptr<BBLStatusBarSend> m_status_bar;
wxPanel* m_panel_download{nullptr};
wxPanel* m_panel_complete{nullptr};
wxPanel* m_panel_error{nullptr};
wxStaticText* m_complete_text{nullptr};
wxStaticText* m_error_text{nullptr};
Button* m_retry_button{nullptr};
Button* m_close_button{nullptr};
Button* m_error_close_button{nullptr};
std::atomic<bool> m_is_destroying{false};
};
}} // namespace Slic3r::GUI
#endif // slic3r_GenericDownloadDialog_hpp_
+38 -2
View File
@@ -75,7 +75,7 @@
#endif // _WIN32
#include <slic3r/GUI/CreatePresetsDialog.hpp>
#include "sentry_wrapper/SentryWrapper.hpp"
#include "GenericDownloadDialog.hpp"
#define UPDATE_BUSER true
#define UPDATE_BUAUTO false
@@ -2249,7 +2249,8 @@ static wxMenu* generate_help_menu()
// //TODO
// });
// Check New Version
append_menu_item(helpMenu, wxID_ANY, _L("Check for Update"), _L("Check for Update"),
append_menu_item(
helpMenu, wxID_ANY, _L("Check for Update"), _L("Check for Update"),
[](wxCommandEvent&) {
wxGetApp().check_new_version_sf(true, UPDATE_BUSER);
}, "", nullptr, []() {
@@ -4006,6 +4007,41 @@ void MainFrame::RunScript(wxString js)
m_webview->RunScript(js);
}
void MainFrame::downloadOpenProject(const std::string& fileUrl, const std::string& fileName, std::string completeFilePath)
{
// std::string fileUrl = "https://public.resource.snapmaker.com/model/public/3mf/test_for_download.3mf";
// std::string filename = "test_for_download.3mf";
GenericDownloadDialog dlg(_L("downloading the model"), fileUrl, fileName, completeFilePath);
auto res = dlg.ShowModal();
if (res != wxID_OK)
return;
if (completeFilePath.empty()) {
auto downloadPath = wxGetApp().app_config->get("download_path");
completeFilePath = downloadPath + "/" + fileName;
}
if (!boost::filesystem::exists(completeFilePath))
{
BOOST_LOG_TRIVIAL(warning) << boost::format("the file '%1%' not exists") % completeFilePath;
return;
}
// Auto-open project if it's a .3mf file
boost::filesystem::path path(completeFilePath);
std::string extension = boost::algorithm::to_lower_copy(path.extension().string());
if (extension == ".3mf") {
BOOST_LOG_TRIVIAL(info) << boost::format("GenericDownloadDialog: Auto-opening project file '%1%'") % completeFilePath;
wxString wx_file_path = wxString::FromUTF8(completeFilePath.c_str());
if (wxGetApp().can_load_project() && wxGetApp().mainframe && wxGetApp().mainframe->plater()) {
wxGetApp().mainframe->plater()->load_project(wx_file_path);
}
}
}
void MainFrame::technology_changed()
{
// update menu titles
+5 -1
View File
@@ -348,7 +348,11 @@ public:
void load_printer_url();
bool is_printer_view() const;
void refresh_plugin_tips();
void RunScript(wxString js);
void RunScript(wxString js);
void downloadOpenProject(const std::string& fileUrl,
const std::string& fileName,
std::string completeFilePath = "");
//SoftFever
void show_device(bool bBBLPrinter);
-42
View File
@@ -13685,18 +13685,6 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
islegal = (c_preset == connect_preset);
/* if (!islegal) {
MessageDialog msg_window(nullptr,
_L(" Your connected machine is ") + (connect_preset == "" ? "Unknown" : connect_preset) + _L("\nYour model's preset is ") + c_preset + _L("\nDo you want to continue?"),
L("machine check"),
wxICON_QUESTION | wxOK);
int res = msg_window.ShowModal();
if (res != wxID_OK) {
return;
}
}*/
DynamicPrintConfig* physical_printer_config = &Slic3r::GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
if (! physical_printer_config || p->model.objects.empty())
return;
@@ -13715,34 +13703,6 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
local_name.erase(std::remove(local_name.begin(), local_name.end(), '('), local_name.end());
local_name.erase(std::remove(local_name.begin(), local_name.end(), ')'), local_name.end());
/*if (wxGetApp().app_config->get("use_new_connect") == "true") {
upload_job = PrintHostJob(wxGetApp().get_host_config());
} */
// if (local_name == "Snapmaker U1 0.4 nozzle" && devices.size() == 0) {
// MessageDialog msg_window(nullptr, _L("You don't have active machine, do you want to add one?"), _L("Info"), wxICON_QUESTION | wxOK | wxCANCEL);
// int res = msg_window.ShowModal();
// if (res == wxID_OK) {
// wxGetApp().mainframe->request_select_tab(MainFrame::TabPosition::tpMonitor);
// auto view = wxGetApp().mainframe->m_printer_view;
// if (view) {
// json msg;
// msg["head"] = json::object();
// json payload = json::object();
// payload["cmd"] = "devicepage_add_device";
// payload["method"] = "call_flutter";
// payload["params"] = json::object();
// msg["payload"] = payload;
// std::string str_msg = msg.dump(4, ' ', true);
// view->sendMessage(str_msg);
// }
// }
// return;
// }
if (wxGetApp().app_config->get("use_new_connect") == "true" || local_name == "Snapmaker U1 0.4 nozzle") {
// 先不创建job,直接创建上传 / 上传下载对话框
// 获取默认文件名
@@ -13804,8 +13764,6 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn, bool us
dialog->set_display_file_name(upload_job.upload_data.upload_path.string());
bool res = dialog->run();
// wxGetApp().mainframe->m_printer_view->reload();
if (dialog->is_finish()) {
wxGetApp().mainframe->select_tab(MainFrame::TabPosition::tpMonitor);
}
+43 -18
View File
@@ -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"
@@ -3001,10 +3001,11 @@ void SSWCP_MachineOption_Instance::sw_FinishFilamentMapping()
if (wxGetApp().get_web_preprint_dialog()) {
WebPreprintDialog* dialog = dynamic_cast<WebPreprintDialog*>(wxGetApp().get_web_preprint_dialog());
if (dialog) {
// BBS: Use SafeEndModal to prevent duplicate EndModal calls
if(dialog->is_finish()){
dialog->EndModal(wxID_OK);
dialog->SafeEndModal(wxID_OK);
}else{
dialog->EndModal(wxID_CANCEL);
dialog->SafeEndModal(wxID_CANCEL);
}
}
}
@@ -3186,14 +3187,10 @@ void SSWCP_MachineOption_Instance::sw_GetFileFilamentMapping()
response["thumbnails"] = thumbnails;
// file name
response["filename"] = SSWCP::get_display_filename();
response["filepath"] = SSWCP::get_active_filename();
m_res_data = response;
send_to_js();
finish_job();
@@ -4386,7 +4383,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>() : "";
std::string fileUrl = m_param_data.count("file_url") ? m_param_data["file_url"].get<std::string>() : "";
@@ -4396,17 +4394,46 @@ void SSWCP_UserLogin_Instance::sw_DownloadFile() {
return;
}
// Use WCP Download Manager
WCPDownloadManager* download_mgr = wxGetApp().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;
}
wxGetApp().mainframe->downloadOpenProject(fileUrl, fileName, "");
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>() : "";
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;
}
// Start download task
size_t task_id = download_mgr->start_download(fileUrl, fileName, shared_from_this());
// 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);
// Return task ID to Flutter
json response;
response["task_id"] = task_id;
response["file_name"] = fileName;
@@ -4415,9 +4442,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());
}
@@ -4432,7 +4457,7 @@ void SSWCP_UserLogin_Instance::sw_CancelDownload() {
return;
}
WCPDownloadManager* download_mgr = wxGetApp().wcp_download_manager();
DownloadManager* download_mgr = wxGetApp().download_manager();
if (!download_mgr) {
handle_general_fail(-1, "WCP Download Manager not available");
return;
+3
View File
@@ -541,6 +541,9 @@ private:
void sw_SubUserUpdatePrivacy();
void sw_DownloadFile();
void sw_DownloadFileEx();
void sw_CancelDownload();
void sw_FileView();
-276
View File
@@ -1,276 +0,0 @@
#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
-104
View File
@@ -1,104 +0,0 @@
#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_
+35 -3
View File
@@ -96,6 +96,22 @@ void WebPreprintDialog::set_display_file_name(const std::string& filename) {
void WebPreprintDialog::set_gcode_file_name(const std::string& filename)
{ m_gcode_file_name = filename; }
void WebPreprintDialog::set_finish(bool flag)
{
m_finish = flag;
// BBS: Don't call EndModal here to avoid conflict with sw_FinishFilamentMapping()
// The external sw_FinishFilamentMapping() function will handle EndModal based on m_finish flag
}
void WebPreprintDialog::SafeEndModal(int returnCode)
{
// BBS: Prevent duplicate EndModal calls which can cause crashes
if (IsModal() && !m_modal_ended) {
m_modal_ended = true;
EndModal(returnCode);
}
}
void WebPreprintDialog::reload()
{
load_url(m_prePrint_url);
@@ -123,8 +139,16 @@ bool WebPreprintDialog::run()
}
this->load_url(real_url);
if (this->ShowModal() == wxID_OK) {
return true;
// BBS: Reset flags before showing modal
m_finish = false;
m_modal_ended = false;
int result = this->ShowModal();
// BBS: Check finish flag to determine return value
if (result == wxID_OK || (result == wxID_CANCEL && m_finish)) {
return m_finish;
}
return false;
}
@@ -186,7 +210,15 @@ void WebPreprintDialog::OnClose(wxCloseEvent& evt)
{
auto noti_manager = wxGetApp().mainframe->plater()->get_notification_manager();
noti_manager->close_notification_of_type(NotificationType::PrintHostUpload);
evt.Skip();
// BBS: Use SafeEndModal to prevent duplicate EndModal calls
// This ensures consistency with sw_FinishFilamentMapping() and prevents crashes
SafeEndModal(wxID_CANCEL);
// If not modal or already ended, skip the event
if (!IsModal() || m_modal_ended) {
evt.Skip();
}
}
}} // namespace Slic3r::GUI
+5 -1
View File
@@ -33,7 +33,10 @@ public:
bool is_finish() { return m_finish; }
void set_finish(bool flag) { m_finish = flag; }
void set_finish(bool flag);
// BBS: Safely end modal dialog, preventing duplicate EndModal calls
void SafeEndModal(int returnCode);
private:
void OnClose(wxCloseEvent& evt);
@@ -53,6 +56,7 @@ private:
bool m_switch_to_device = false;
bool m_finish = false;
bool m_modal_ended = false; // BBS: Flag to prevent duplicate EndModal calls
DECLARE_EVENT_TABLE()
};