fix merge conflicts

This commit is contained in:
Ian Chua
2026-07-15 22:09:47 +08:00
1024 changed files with 214936 additions and 22122 deletions

View File

@@ -4,7 +4,6 @@
#include "GUI_App.hpp"
#include "I18N.hpp"
#include "OrcaCloudServiceAgent.hpp"
#include "PluginScriptRunner.hpp"
#include "slic3r/plugin/PluginFsUtils.hpp"
#include "slic3r/plugin/PluginManager.hpp"
@@ -16,7 +15,6 @@
#include <slic3r/plugin/PluginDescriptor.hpp>
#include <slic3r/plugin/PythonPluginInterface.hpp>
#include <slic3r/plugin/PluginLoader.hpp>
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
@@ -24,6 +22,7 @@
#include <algorithm>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <vector>
#include <wx/dialog.h>
@@ -120,19 +119,19 @@ struct PluginDialogItem
constexpr bool kFetchCloudCatalog = true;
constexpr bool kUseCurrentCloudCatalog = false;
// The type of a package's first loaded capability. A package that is not loaded has no type.
PluginCapabilityType primary_capability_type_of(PluginManager& manager, const std::string& plugin_key)
{
const auto capabilities = manager.get_plugin_capabilities(plugin_key, PluginCapabilityType::Unknown, /*only_enabled=*/false);
return capabilities.empty() ? PluginCapabilityType::Unknown : capabilities.front()->type();
}
std::vector<PluginDescriptor> current_cloud_catalog_snapshot()
{
const auto& catalog = PluginManager::instance().get_catalog();
std::vector<PluginDescriptor> cloud_entries;
auto append_cloud_entries = [&cloud_entries](const std::vector<PluginDescriptor>& entries) {
for (const PluginDescriptor& entry : entries)
if (entry.is_cloud_plugin())
cloud_entries.push_back(entry);
};
append_cloud_entries(catalog.get_all_plugin_descriptors());
append_cloud_entries(catalog.get_invalid_plugins());
for (const PluginDescriptor& entry : PluginManager::instance().get_plugin_descriptors(/*include_invalid=*/true))
if (entry.is_cloud_plugin())
cloud_entries.push_back(entry);
return cloud_entries;
}
@@ -162,26 +161,30 @@ void refresh_plugin_catalog_blocking(bool fetch_cloud)
manager.rescan_plugins();
if (!fetch_cloud) {
manager.get_catalog().update_cloud_catalog(current_cloud_catalog);
manager.update_cloud_catalog(current_cloud_catalog);
return;
}
manager.fetch_plugins_from_cloud(&not_found, &unauthorized);
auto plater = wxGetApp().plater();
wxGetApp().CallAfter([not_found = std::move(not_found), unauthorized = std::move(unauthorized)]() {
if (wxGetApp().is_closing())
return;
Plater* plater = wxGetApp().plater();
if (plater == nullptr)
return;
if (plater) {
for (const auto& uuid : not_found) {
plater->get_notification_manager()->push_notification(NotificationType::CustomNotification,
NotificationManager::NotificationLevel::RegularNotificationLevel,
format(_L("Plugin %s is no longer available."), uuid));
}
for (const auto& uuid : unauthorized) {
plater->get_notification_manager()->push_notification(NotificationType::CustomNotification,
NotificationManager::NotificationLevel::RegularNotificationLevel,
format(_L("Plugin %s access is unauthorized."), uuid));
}
}
for (const auto& uuid : not_found)
plater->get_notification_manager()->push_notification(
NotificationType::CustomNotification,
NotificationManager::NotificationLevel::RegularNotificationLevel,
format(_L("Plugin %s is no longer available."), uuid));
for (const auto& uuid : unauthorized)
plater->get_notification_manager()->push_notification(
NotificationType::CustomNotification,
NotificationManager::NotificationLevel::RegularNotificationLevel,
format(_L("Plugin %s access is unauthorized."), uuid));
});
}
std::string to_string(PluginUpdateStatus status);
@@ -302,7 +305,7 @@ PluginAvailableActions evaluate_action_policy(const PluginDialogItem& item)
PluginDialogItem build_plugin_dialog_item(const PluginDescriptor& descriptor)
{
PluginDialogItem item;
const auto& loader = PluginManager::instance().get_loader();
PluginManager& manager = PluginManager::instance();
item.plugin_key = descriptor.plugin_key;
item.display_name = descriptor.name;
@@ -320,18 +323,25 @@ PluginDialogItem build_plugin_dialog_item(const PluginDescriptor& descriptor)
// why: sort by the same version the row displays (GetDisplayVersion in index.js) - installed when
// installed, otherwise latest - so the Version sort matches what the user sees.
item.sort_version = item.installed_version.empty() ? item.latest_version : item.installed_version;
item.type_label = descriptor.type_label();
item.type_key = plugin_capability_type_to_string(descriptor.primary_capability_type());
// A package that is not loaded has no capabilities, so its row contributes none and does not
// expand.
const std::vector<std::shared_ptr<PluginCapabilityInterface>> capabilities =
manager.get_plugin_capabilities(descriptor.plugin_key, PluginCapabilityType::Unknown, /*only_enabled=*/false);
const PluginCapabilityType primary_type = capabilities.empty() ? PluginCapabilityType::Unknown :
capabilities.front()->type();
item.type_label = plugin_capability_type_to_string(primary_type);
item.type_key = plugin_capability_type_to_string(primary_type);
// "types" is the display-only compatibility list. Cloud plugins show the raw labels the
// service returned (which may not map to real capability types); local plugins derive them
// from the capabilities actually discovered/loaded.
// from the capabilities actually loaded.
if (descriptor.is_cloud_plugin() && !descriptor.display_types.empty()) {
for (const std::string& label : descriptor.display_types)
if (std::find(item.type_labels.begin(), item.type_labels.end(), label) == item.type_labels.end())
item.type_labels.push_back(label);
} else {
for (PluginCapabilityType type : descriptor.capability_types) {
const std::string label = plugin_capability_type_display_name(type);
for (const auto& capability : capabilities) {
const std::string label = plugin_capability_type_display_name(capability->type());
if (std::find(item.type_labels.begin(), item.type_labels.end(), label) == item.type_labels.end())
item.type_labels.push_back(label);
}
@@ -345,30 +355,14 @@ PluginDialogItem build_plugin_dialog_item(const PluginDescriptor& descriptor)
item.is_cloud_plugin = descriptor.is_cloud_plugin();
item.has_local_package = descriptor.has_local_package();
item.unauthorized = descriptor.is_unauthorized();
item.has_script_capability = descriptor.has_capability_type(Slic3r::PluginCapabilityType::Script);
item.is_loaded = loader.is_plugin_loaded(descriptor.plugin_key);
item.loading = loader.is_plugin_load_in_progress(descriptor.plugin_key);
if (item.is_loaded) {
for (const auto& cap : loader.get_loaded_plugin_capabilities(descriptor.plugin_key)) {
if (cap) {
item.capabilities.push_back({cap->name, plugin_capability_type_display_name(cap->type),
plugin_capability_type_to_string(cap->type), cap->enabled, true, false});
if (cap->type == PluginCapabilityType::Script)
item.has_script_capability = true;
}
}
}
if (item.capabilities.empty()) {
for (PluginCapabilityType type : descriptor.capability_types) {
const std::string type_key = plugin_capability_type_to_string(type);
const auto existing = std::find_if(item.capabilities.begin(), item.capabilities.end(),
[&type_key](const PluginCapabilityView& capability) {
return capability.type_key == type_key;
});
if (existing != item.capabilities.end())
continue;
item.capabilities.push_back({std::string{}, plugin_capability_type_display_name(type), type_key, false, false, false});
}
item.is_loaded = manager.is_plugin_loaded(descriptor.plugin_key);
item.loading = manager.is_plugin_load_in_progress(descriptor.plugin_key);
item.has_script_capability = false;
for (const auto& capability : capabilities) {
item.capabilities.push_back({capability->name(), plugin_capability_type_display_name(capability->type()),
plugin_capability_type_to_string(capability->type()), capability->is_enabled(), true, false});
if (capability->type() == PluginCapabilityType::Script)
item.has_script_capability = true;
}
item.sharing_token = descriptor.sharing_token;
item.thumbnail_url = descriptor.thumbnail_url;
@@ -444,7 +438,7 @@ PluginsDialog::PluginsDialog(wxWindow* parent, wxWindowID id, const wxString&, c
: WebViewHostDialog(parent, id, _L("Plugins"), pos, size, style)
{ create_webview("web/dialog/PluginsDialog/index.html", _L("Plugins"), wxSize(900, 820), wxSize(760, 715)); }
PluginsDialog::~PluginsDialog() = default;
PluginsDialog::~PluginsDialog() { m_alive->store(false, std::memory_order_release); }
void PluginsDialog::set_open_terminal_dlg_fn()
{
@@ -464,8 +458,8 @@ void PluginsDialog::resolve_pending_activation()
if (m_activating_plugin_key.empty())
return;
PluginLoader& loader = PluginManager::instance().get_loader();
if (loader.is_plugin_load_in_progress(m_activating_plugin_key))
PluginManager& manager = PluginManager::instance();
if (manager.is_plugin_load_in_progress(m_activating_plugin_key))
return; // Still loading: keep the "Activating..." message until the load resolves.
const std::string plugin_key = m_activating_plugin_key;
@@ -475,7 +469,7 @@ void PluginsDialog::resolve_pending_activation()
PluginDescriptor descriptor;
if (get_descriptor(plugin_key, descriptor) && descriptor.has_error())
show_status(wxString::Format(_L("Failed to activate \"%s\"."), plugin_display_name(plugin_key)), "error");
else if (loader.is_plugin_loaded(plugin_key))
else if (manager.is_plugin_loaded(plugin_key))
show_status(wxString::Format(_L("Activated \"%s\"."), plugin_display_name(plugin_key)), "success");
// Otherwise it ended up inactive (toggled off or cancelled mid-load): stay silent.
}
@@ -540,18 +534,13 @@ nlohmann::json PluginsDialog::build_plugins_payload() const
response["sort_order"] = to_string(m_plugin_sort_order);
response["data"] = nlohmann::json::array();
const auto& catalog = PluginManager::instance().get_catalog();
const auto valid = catalog.get_all_plugin_descriptors();
const auto invalid = catalog.get_invalid_plugins();
BOOST_LOG_TRIVIAL(info) << "Prepared " << valid.size() + invalid.size() << " plugin rows for Plugins dialog";
const auto rows = PluginManager::instance().get_plugin_descriptors(/*include_invalid=*/true);
BOOST_LOG_TRIVIAL(info) << "Prepared " << rows.size() << " plugin rows for Plugins dialog";
std::vector<PluginDialogItem> items;
items.reserve(valid.size() + invalid.size());
items.reserve(rows.size());
for (const PluginDescriptor& row : valid)
items.push_back(build_plugin_dialog_item(row));
for (const PluginDescriptor& row : invalid)
for (const PluginDescriptor& row : rows)
items.push_back(build_plugin_dialog_item(row));
// In-place sort
@@ -565,19 +554,19 @@ nlohmann::json PluginsDialog::build_plugins_payload() const
bool PluginsDialog::get_descriptor(const std::string& plugin_key, PluginDescriptor& descriptor) const
{
const auto& catalog = PluginManager::instance().get_catalog();
if (const PluginDescriptor* valid_descriptor = catalog.find_valid_plugin_descriptor(plugin_key)) {
descriptor = *valid_descriptor;
PluginManager& manager = PluginManager::instance();
if (manager.try_get_valid_plugin_descriptor(plugin_key, descriptor))
return true;
}
return catalog.try_get_invalid_plugin_descriptor(plugin_key, descriptor);
return manager.try_get_plugin_descriptor(plugin_key, descriptor) && descriptor.is_invalid_package();
}
std::shared_ptr<LoadedPluginCapability> PluginsDialog::get_capability(const std::string& plugin_key,
PluginCapabilityType type,
const std::string& capability_name) const
std::shared_ptr<PluginCapabilityInterface> PluginsDialog::get_capability(const std::string& plugin_key,
PluginCapabilityType type,
const std::string& capability_name) const
{
return PluginManager::instance().get_loader().get_plugin_capability_by_name(plugin_key, type, capability_name);
// only_enabled=false: this is an existence check used to gate both enabling and disabling a
// capability from the dialog, so a currently-disabled capability must still resolve.
return PluginManager::instance().get_plugin_capability(plugin_key, capability_name, type, /*only_enabled=*/false);
}
void PluginsDialog::refresh_plugin_catalog_async(const wxString& title, const wxString& message, bool fetch_cloud)
@@ -613,7 +602,7 @@ void PluginsDialog::toggle_plugin(const std::string& plugin_key, bool enabled)
PluginManager& manager = PluginManager::instance();
if (!enabled) {
if (!manager.get_loader().unload_plugin(plugin_key)) {
if (!manager.unload_plugin(plugin_key)) {
BOOST_LOG_TRIVIAL(error) << "Failed to unload plugin from Plugins dialog: " << plugin_key;
show_status(_L("Failed to unload plugin."), "warn");
send_plugins();
@@ -629,9 +618,6 @@ void PluginsDialog::toggle_plugin(const std::string& plugin_key, bool enabled)
return;
}
// Check for capabilities that are currently in use
auto loaded_capabilities = manager.get_loader().get_loaded_plugin_capabilities(plugin_key);
if (!available_actions.can_toggle) {
if (dialog_item.unauthorized && available_actions.toggle_installs_cloud_plugin == false && row_data.has_local_package() == false) {
const std::string install_error = "Unauthorized cloud plugins cannot be installed.";
@@ -663,25 +649,25 @@ void PluginsDialog::toggle_plugin(const std::string& plugin_key, bool enabled)
}
}
if (manager.get_loader().is_plugin_loaded(plugin_key)) {
if (manager.is_plugin_loaded(plugin_key)) {
send_plugins();
return;
}
const PluginDescriptor* descriptor_ptr = manager.get_catalog().find_valid_plugin_descriptor(plugin_key);
if (!descriptor_ptr) {
PluginDescriptor valid_descriptor;
if (!manager.try_get_valid_plugin_descriptor(plugin_key, valid_descriptor)) {
show_status(_L("Plugin manifest was not found."), "warn");
send_plugins();
return;
}
if (manager.get_loader().is_plugin_load_in_progress(plugin_key)) {
if (manager.is_plugin_load_in_progress(plugin_key)) {
send_plugins();
return;
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Starting plugin load from web dialog: " << plugin_key;
manager.get_loader().load_plugin(manager.get_catalog(), plugin_key);
manager.load_plugin(plugin_key);
send_plugins();
// Loading is asynchronous: show a pending message now; resolve_pending_activation() turns it into
// Activated/Failed once the loader's completion callback runs update_plugin_dialog_ui().
@@ -710,14 +696,14 @@ void PluginsDialog::toggle_plugin_capability(const std::string& plugin_key, Plug
return;
}
PluginLoader& loader = PluginManager::instance().get_loader();
PluginManager& manager = PluginManager::instance();
if (enabled) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Enabling plugin capability: " << plugin_key << " | " << capability_name;
loader.enable_capability(plugin_key, capability_name, type);
manager.set_capability_enabled(plugin_key, capability_name, true);
} else {
// check if the capability is currently in use here.
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Disabling plugin capability: " << plugin_key << " | " << capability_name;
loader.disable_capability(plugin_key, capability_name, type);
manager.set_capability_enabled(plugin_key, capability_name, false);
}
send_plugins();
@@ -792,8 +778,7 @@ bool PluginsDialog::install_plugin_package(const std::string& package_path)
};
try {
if (!PluginManager::instance().get_loader().inspect_local_plugin_package(package_file, plugin_descriptor, existing_installation,
error))
if (!PluginManager::instance().inspect_local_plugin_package(package_file, plugin_descriptor, existing_installation, error))
return report_inspection_failure();
} catch (const std::exception& ex) {
error = ex.what();
@@ -895,22 +880,48 @@ wxString PluginsDialog::plugin_display_name(const std::string& plugin_key) const
void PluginsDialog::run_script_plugin(const std::string& plugin_key, const std::string& capability_name)
{
// Shared with the speed dial; validation, GIL/UI-thread execution, re-entrancy latch
// and error bookkeeping all live in run_script_plugin_capability.
const ScriptRunOutcome outcome = run_script_plugin_capability(plugin_key, capability_name);
if (outcome.level == ScriptRunOutcome::Level::Busy)
return;
PluginManager& manager = PluginManager::instance();
std::string error;
ExecutionResult result;
auto complete_with_error = [this, &manager, &plugin_key](const std::string& plugin_error, const wxString& status_message) {
const std::string normalized_error = plugin_error.empty() ? "Script plugin failed." : plugin_error;
if (!manager.set_plugin_error(plugin_key, normalized_error))
BOOST_LOG_TRIVIAL(warning) << "Failed to record plugin error. plugin_key=" << plugin_key;
if (!manager.unload_plugin(plugin_key))
BOOST_LOG_TRIVIAL(error) << "Failed to unload plugin after script error. plugin_key=" << plugin_key;
send_plugins();
// The row now shows an "Error" status and the Diagnostics tab holds the full text, so surface
// the outcome in the footer status bar instead of a modal box (prefer the friendlier override).
const wxString message = status_message.empty() ? from_u8(normalized_error) : status_message;
show_status(message, "error");
};
{
wxBusyCursor busy;
result = manager.run_script_capability(plugin_key, capability_name, error);
}
if (!error.empty()) {
complete_with_error(error, wxString());
return;
}
BOOST_LOG_TRIVIAL(info) << "Script plugin execution completed. plugin_key=" << plugin_key
<< " status=" << static_cast<int>(result.status) << " message=" << result.message << " data=" << result.data;
const bool failed = result.status == PluginResult::RecoverableError || result.status == PluginResult::FatalError || !error.empty();
if (failed) {
complete_with_error(result.message, wxString());
return;
}
manager.clear_plugin_error(plugin_key);
send_plugins();
// The row shows any "Error" status and the Diagnostics tab holds the full text, so surface
// the outcome in the footer status bar instead of a modal box (prefer the friendlier override).
if (outcome.message.empty())
return;
const char* level = outcome.level == ScriptRunOutcome::Level::Error ? "error" :
outcome.level == ScriptRunOutcome::Level::Success ? "success" :
"info";
show_status(outcome.message, level);
}
void PluginsDialog::update_plugin(const std::string& plugin_key)
@@ -1061,18 +1072,35 @@ void PluginsDialog::reinstall_local_plugin(const std::string& plugin_key)
if (plugin_key.empty())
return;
PluginManager& manager = PluginManager::instance();
const bool was_loaded = manager.get_loader().is_plugin_loaded(plugin_key);
if (!manager.get_loader().unload_plugin(plugin_key)) {
show_status(_L("Failed to unload plugin."), "warn");
send_plugins();
return;
const bool was_loaded = PluginManager::instance().is_plugin_loaded(plugin_key);
std::pair<bool, std::string> reload_result{false, ""};
try {
reload_result = run_with_dialog_wait(
[plugin_key, was_loaded]() -> std::pair<bool, std::string> {
PluginManager& manager = PluginManager::instance();
if (!manager.unload_plugin(plugin_key))
return {false, "Failed to unload plugin."};
manager.load_plugin(plugin_key, false);
std::string error;
if (!manager.wait_for_plugin_load(plugin_key, std::chrono::minutes(5), error) ||
!manager.is_plugin_loaded(plugin_key))
return {false, error.empty() ? "Plugin failed to load." : error};
if (!was_loaded && !manager.unload_plugin(plugin_key))
return {false, "Plugin reloaded, but failed to restore the inactive state."};
return {true, {}};
},
_L("Reloading plugin"), _L("Reloading plugin"));
} catch (const std::exception& ex) {
reload_result = {false, ex.what()};
} catch (...) {
reload_result = {false, "Unknown plugin reload error."};
}
manager.get_loader().load_plugin(manager.get_catalog(), plugin_key, false);
if (!was_loaded && !manager.get_loader().unload_plugin(plugin_key)) {
show_status(_L("Plugin reloaded, but failed to restore the inactive state."), "warn");
if (!reload_result.first) {
show_status(reload_result.second.empty() ? _L("Failed to reload plugin.") : from_u8(reload_result.second), "warn");
send_plugins();
return;
}
@@ -1088,7 +1116,7 @@ void PluginsDialog::reinstall_cloud_plugin(const PluginDescriptor& plugin)
return;
PluginManager& manager = PluginManager::instance();
const bool was_loaded = manager.get_loader().is_plugin_loaded(plugin_key);
const bool was_loaded = PluginManager::instance().is_plugin_loaded(plugin_key);
std::string error;
if (plugin.has_local_package()) {
@@ -1098,7 +1126,7 @@ void PluginsDialog::reinstall_cloud_plugin(const PluginDescriptor& plugin)
return;
}
manager.get_catalog().update_cloud_catalog(std::vector<PluginDescriptor>{as_cloud_only_descriptor(plugin)});
manager.update_cloud_catalog(std::vector<PluginDescriptor>{as_cloud_only_descriptor(plugin)});
}
if (!install_cloud_plugin(plugin_key, plugin.version, from_u8(plugin.name))) {
@@ -1107,7 +1135,30 @@ void PluginsDialog::reinstall_cloud_plugin(const PluginDescriptor& plugin)
}
if (was_loaded) {
manager.get_loader().load_plugin(manager.get_catalog(), plugin_key);
std::pair<bool, std::string> reload_result{false, ""};
try {
reload_result = run_with_dialog_wait(
[plugin_key]() -> std::pair<bool, std::string> {
PluginManager& manager = PluginManager::instance();
manager.load_plugin(plugin_key);
std::string error;
if (!manager.wait_for_plugin_load(plugin_key, std::chrono::minutes(5), error) ||
!manager.is_plugin_loaded(plugin_key))
return {false, error.empty() ? "Plugin failed to load." : error};
return {true, {}};
},
_L("Reloading plugin"), _L("Reloading plugin"));
} catch (const std::exception& ex) {
reload_result = {false, ex.what()};
} catch (...) {
reload_result = {false, "Unknown plugin reload error."};
}
if (!reload_result.first) {
show_status(reload_result.second.empty() ? _L("Failed to reload plugin.") : from_u8(reload_result.second), "warn");
send_plugins();
return;
}
}
send_plugins();