Merge branch 'main' into fix/clang-cl-windows-support

This commit is contained in:
Gabriel Monteiro
2026-07-10 11:10:33 -03:00
committed by GitHub
8166 changed files with 137760 additions and 28056 deletions
+222 -62
View File
@@ -304,6 +304,9 @@ public:
m_bg_color = StateColor::darkModeColorFor(wxColour("#FFFFFF"));
m_fg_color = StateColor::darkModeColorFor(wxColour("#6B6A6A"));
m_progress_bg_color = StateColor::darkModeColorFor(wxColour("#DFDFDF"));
m_progress_fg_color = StateColor::darkModeColorFor(wxColour("#009688"));
m_progress_h = FromDIP(6);
bool dark_mode = m_fg_color != wxColour("#6B6A6A");
wxSize sz = m_window->GetClientSize();
BitmapCache bmp_cache;
@@ -333,20 +336,34 @@ public:
dc.DrawLabel(m_text_version, rc, wxALIGN_CENTER);
dc.SetFont(m_font_action);
rc.y = c_sz.GetHeight() * 0.88;
rc.y = c_sz.GetHeight() * 0.85;
rc.height = dc.GetTextExtent(m_text_action).GetHeight();
dc.DrawLabel(m_text_action, rc, wxALIGN_CENTER);
const wxRect progress_rc(0, c_sz.GetHeight() - m_progress_h, c_sz.GetWidth(), m_progress_h);
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(wxBrush(m_progress_bg_color));
dc.DrawRectangle(progress_rc);
const int fill_width = progress_rc.GetWidth() * m_progress * 0.01;
if (fill_width > 0) {
dc.SetBrush(wxBrush(m_progress_fg_color));
dc.DrawRectangle(0, progress_rc.GetTop(), fill_width, m_progress_h);
}
}
void SetText(const wxString& text)
void SetText(const wxString& text, int progress)
{
if (!text.empty()) {
int calc_progress = std::max(m_progress, std::clamp(progress, 0, 100));
if (m_text_action != text || m_progress != calc_progress){
m_text_action = text;
m_progress = calc_progress;
m_window->Refresh();
m_window->Update();
#ifdef __WXOSX__
// without this code splash screen wouldn't be updated under OSX
wxYield();
// without this code splash screen wouldn't be updated under OSX
wxYield();
#endif
}
}
@@ -383,9 +400,13 @@ private:
wxBitmap m_logo_bmp;
wxColour m_fg_color;
wxColour m_bg_color;
wxColour m_progress_bg_color;
wxColour m_progress_fg_color;
wxString m_text_version = GUI_App::format_display_version();
wxString m_text_action = _L("Loading configuration") + dots;
int m_progress = 0;
int m_progress_h = 6;
wxFont m_font_version = Label::Body_16;
wxFont m_font_action = Label::Body_16;
@@ -743,6 +764,15 @@ void GUI_App::post_init()
if (! this->initialized())
throw Slic3r::RuntimeError("Calling post_init() while not yet initialized");
#if wxUSE_WEBVIEW_EDGE
// Ensure the Microsoft WebView2 runtime is installed before any WebView is
// created. The setup wizard and several dialogs render entirely through
// WebView2; without the runtime they come up blank. This runs here (not in the
// constructor) so that wxWidgets is fully initialized and the event loop is
// running, and so it precedes the first WebView creation (the setup wizard).
init_webview_runtime();
#endif
m_open_method = "double_click";
bool switch_to_3d = false;
@@ -940,6 +970,33 @@ void GUI_App::post_init()
});
}
// Orca: notify users upgrading from a pre-2.4.0 version that profile syncing
// moved from Bambu Cloud to Orca Cloud.
if (is_editor() && m_last_config_version && m_last_config_version->valid()
&& *m_last_config_version < Semver(2, 4, 0)) {
CallAfter([] {
const wxString wiki_url = "https://www.orcaslicer.com/wiki/user_profiles/user_profiles.html#profiles-missing-after-updating-from-bambu-cloud";
MessageDialog dlg(nullptr,
_L("Since version 2.4.0, OrcaSlicer syncs user profiles through Orca Cloud instead of Bambu Cloud.\n\n"
"To migrate your existing profiles, log in to Orca Cloud and they will be transferred automatically. "
"To learn more about how OrcaSlicer stores and syncs your profiles, or to migrate your presets manually, check out our wiki.\n\n"
"If you did not use Bambu Cloud to sync profiles, this change does not affect you and you can safely ignore this message."),
_L("Profile syncing change"),
wxOK,
"",
_L("Learn more"),
[wiki_url](const wxString &) { wxLaunchDefaultBrowser(wiki_url); });
// Hack: the "Learn more" link renders the message in a wxHtmlWindow whose
// height is underestimated for multi-paragraph text, leaving a scrollbar.
// The html sits in a proportion-1 sizer chain, so grow the dialog (never
// shrink it below its content width) to give the text enough room.
const wxSize sz = dlg.GetSize();
dlg.SetSize(std::max(sz.x, dlg.FromDIP(280)), std::max(sz.y, dlg.FromDIP(200)));
dlg.CenterOnParent();
dlg.ShowModal();
});
}
if(!m_networking_need_update && m_agent) {
m_agent->set_on_ssdp_msg_fn(
[this](std::string json_str) {
@@ -1030,9 +1087,10 @@ GUI_App::GUI_App()
//app config initializes early becasuse it is used in instance checking in OrcaSlicer.cpp
this->init_app_config();
this->init_download_path();
#if wxUSE_WEBVIEW_EDGE
this->init_webview_runtime();
#endif
// Note: the WebView2 runtime check (init_webview_runtime) used to run here, but
// the constructor executes before wxWidgets is fully initialized and before the
// event loop starts, so its modal prompt/installer could silently fail to appear.
// It now runs in post_init(), before the first WebView (the setup wizard) is created.
reset_to_active();
}
@@ -1116,7 +1174,7 @@ std::string GUI_App::get_plugin_url(std::string name, std::string country_code)
std::string url = get_http_url(country_code);
std::string curr_version;
if (NetworkAgent::use_legacy_network) {
if (use_legacy_network_plugin()) {
curr_version = BAMBU_NETWORK_AGENT_VERSION_LEGACY;
} else if (name == "plugins" && app_config) {
std::string user_version = app_config->get_network_plugin_version();
@@ -1171,7 +1229,7 @@ int GUI_App::download_plugin(std::string name, std::string package_name, Install
// Determine OS type for plugin download (must be set per-request since global
// extra headers are no longer initialised on this branch).
#if defined(__WINDOWS__)
std::string os_type = (is_running_on_arm64() && !NetworkAgent::use_legacy_network) ? "windows_arm" : "windows";
std::string os_type = (is_running_on_arm64() && !use_legacy_network_plugin()) ? "windows_arm" : "windows";
#elif defined(__APPLE__)
std::string os_type = "macos";
#elif defined(__linux__)
@@ -1400,8 +1458,32 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
boost::filesystem::create_directories(dest_path.parent_path());
std::string dest_zip_file = encode_path(dest_path.string().c_str());
try {
if (fs::exists(dest_path))
fs::remove(dest_path);
if (fs::exists(dest_path)) {
boost::system::error_code ec;
fs::remove(dest_path, ec);
if (ec) {
// On Windows a currently-loaded DLL (e.g. BambuSource.dll, or the
// networking library in legacy mode) cannot be deleted or overwritten
// in place, which failed the whole install with "The plug-in file may
// be in use" (issue #14373). It CAN however be renamed aside: the
// running module keeps mapping the renamed file while we write the new
// one. The stale ".old" copy is cleared on the next install/launch.
boost::filesystem::path aside = dest_path;
aside += ".old";
boost::system::error_code ec2;
fs::remove(aside, ec2);
fs::rename(dest_path, aside, ec2);
if (ec2) {
close_zip_reader(&archive);
BOOST_LOG_TRIVIAL(error) << "[install_plugin] cannot replace in-use file "
<< dest_path.string() << ": " << ec2.message();
if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); }
return InstallStatusUnzipFailed;
}
BOOST_LOG_TRIVIAL(warning) << "[install_plugin] " << dest_path.filename().string()
<< " was in use, renamed aside to .old";
}
}
mz_bool res = 0;
#ifndef WIN32
if (S_ISLNK(stat.m_external_attr >> 16)) {
@@ -1850,7 +1932,7 @@ bool GUI_App::check_networking_version()
}
std::string studio_ver;
if (NetworkAgent::use_legacy_network) {
if (use_legacy_network_plugin()) {
studio_ver = BAMBU_NETWORK_AGENT_VERSION_LEGACY;
} else if (app_config) {
std::string user_version = app_config->get_network_plugin_version();
@@ -1872,6 +1954,11 @@ bool GUI_App::check_networking_version()
return false;
}
bool GUI_App::use_legacy_network_plugin() const
{
return app_config && BBLNetworkPlugin::is_legacy_version(app_config->get_network_plugin_version());
}
bool GUI_App::is_compatibility_version()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": m_networking_compatible=%1%")%m_networking_compatible;
@@ -2174,6 +2261,7 @@ GUI_App::~GUI_App()
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": exit");
shutdown_console_logging();
}
bool GUI_App::is_blocking_printing(MachineObject *obj_)
@@ -2274,13 +2362,37 @@ void GUI_App::init_download_path()
#if wxUSE_WEBVIEW_EDGE
void GUI_App::init_webview_runtime()
{
// Check WebView Runtime
if (!WebView::CheckWebViewRuntime()) {
int nRet = wxMessageBox(_L("Orca Slicer requires the Microsoft WebView2 Runtime to operate certain features.\nClick Yes to install it now."),
_L("WebView2 Runtime"), wxYES_NO);
if (nRet == wxYES) {
WebView::DownloadAndInstallWebViewRuntime();
}
// Check whether the Microsoft WebView2 runtime is already present.
if (WebView::CheckWebViewRuntime()) {
BOOST_LOG_TRIVIAL(info) << "WebView2 runtime detected.";
return;
}
BOOST_LOG_TRIVIAL(warning) << "WebView2 runtime not found; prompting user to install.";
int nRet = wxMessageBox(_L("Orca Slicer requires the Microsoft WebView2 Runtime to operate certain features.\nClick Yes to install it now."),
_L("WebView2 Runtime"), wxYES_NO);
if (nRet != wxYES) {
BOOST_LOG_TRIVIAL(warning) << "User declined WebView2 runtime installation.";
return;
}
// The bootstrapper auto-detects the device architecture (x64/x86/ARM64) and
// installs the matching runtime. The install is synchronous, and because this
// runs before the first WebView is created, a successful install takes effect
// in this same process without a restart.
bool installed = WebView::DownloadAndInstallWebViewRuntime();
// Re-check: the install can still fail (declined UAC elevation, no network,
// etc.). Without the runtime the setup wizard and other WebView dialogs render
// blank, so surface an explicit message rather than failing silently.
if (installed && WebView::CheckWebViewRuntime()) {
BOOST_LOG_TRIVIAL(info) << "WebView2 runtime installed successfully.";
} else {
BOOST_LOG_TRIVIAL(error) << "WebView2 runtime installation failed or still not detected.";
wxMessageBox(_L("The Microsoft WebView2 Runtime could not be installed.\n"
"Some features, including the setup wizard, may appear blank until it is installed.\n"
"Please install it manually from https://developer.microsoft.com/microsoft-edge/webview2/ and restart Orca Slicer."),
_L("WebView2 Runtime"), wxOK | wxICON_WARNING);
}
}
#endif
@@ -2749,31 +2861,35 @@ bool GUI_App::on_init_inner()
//update_label_colours_from_appconfig();
}
if (bool new_sys_menu_enabled = app_config->get("sys_menu_enabled") == "1";
init_sys_menu_enabled != new_sys_menu_enabled)
init_sys_menu_enabled != new_sys_menu_enabled) {
#ifdef __WINDOWS__
NppDarkMode::SetSystemMenuForApp(new_sys_menu_enabled);
#endif
}
#endif
if (m_last_config_version) {
int last_major = m_last_config_version->maj();
int last_minor = m_last_config_version->min();
int last_patch = m_last_config_version->patch()/100;
std::string studio_ver = SLIC3R_VERSION;
int cur_major = atoi(studio_ver.substr(0,2).c_str());
int cur_minor = atoi(studio_ver.substr(3,2).c_str());
int cur_patch = atoi(studio_ver.substr(6,2).c_str());
BOOST_LOG_TRIVIAL(info) << boost::format("last app version {%1%.%2%.%3%}, current version {%4%.%5%.%6%}")
%last_major%last_minor%last_patch%cur_major%cur_minor%cur_patch;
if ((last_major != cur_major)
||(last_minor != cur_minor)
||(last_patch != cur_patch)) {
remove_old_networking_plugins();
}
}
// Orca: we allow user to pin the version of plugin, so we don't need to remove old networking plugins when the app version is updated
//
// if (m_last_config_version) {
// int last_major = m_last_config_version->maj();
// int last_minor = m_last_config_version->min();
// int last_patch = m_last_config_version->patch()/100;
// std::string studio_ver = SLIC3R_VERSION;
// int cur_major = atoi(studio_ver.substr(0,2).c_str());
// int cur_minor = atoi(studio_ver.substr(3,2).c_str());
// int cur_patch = atoi(studio_ver.substr(6,2).c_str());
// BOOST_LOG_TRIVIAL(info) << boost::format("last app version {%1%.%2%.%3%}, current version {%4%.%5%.%6%}")
// %last_major%last_minor%last_patch%cur_major%cur_minor%cur_patch;
// if ((last_major != cur_major)
// ||(last_minor != cur_minor)
// ||(last_patch != cur_patch)) {
// remove_old_networking_plugins();
// }
// }
if(app_config->get("version") != SLIC3R_VERSION) {
app_config->set("version", SLIC3R_VERSION);
//Orca: write OrcaSlicer version
if(app_config->get("version") != SoftFever_VERSION) {
app_config->set("version", SoftFever_VERSION);
}
// Orca: use wxWeakRef to provent wild pointer.
@@ -2792,7 +2908,7 @@ bool GUI_App::on_init_inner()
//BBS use BBL splashScreen
scrn = new SplashScreen(splashscreen_pos);
wxYield();
scrn->SetText(_L("Loading configuration") + dots);
scrn->SetText(_L("Loading configuration") + dots, 5);
}
BOOST_LOG_TRIVIAL(info) << "loading systen presets...";
@@ -2939,9 +3055,8 @@ bool GUI_App::on_init_inner()
// Orca: select network plugin version based on configured version string
std::string configured_version = app_config->get_network_plugin_version();
NetworkAgent::use_legacy_network = (configured_version == BAMBU_NETWORK_AGENT_VERSION_LEGACY);
BOOST_LOG_TRIVIAL(info) << "Network plugin mode: "
<< (NetworkAgent::use_legacy_network ? ("legacy (version: " + std::string(BAMBU_NETWORK_AGENT_VERSION_LEGACY) + ")") : ("modern (version: " + configured_version + ")"));
<< (use_legacy_network_plugin() ? ("legacy (version: " + std::string(BAMBU_NETWORK_AGENT_VERSION_LEGACY) + ")") : ("modern (version: " + configured_version + ")"));
// Force legacy network plugin if debugger attached
// See https://github.com/bambulab/BambuStudio/issues/6726
/* if (!NetworkAgent::use_legacy_network) {
@@ -2971,7 +3086,7 @@ bool GUI_App::on_init_inner()
// Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only.
// If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force
// installation of a compatible system preset, thus nullifying the system preset substitutions.
if (scrn) { scrn->SetText(_L("Loading printer & filament profiles") + dots); wxYield(); }
if (scrn) { scrn->SetText(_L("Loading printer & filament profiles") + dots, 30); wxYield(); }
init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent);
}
catch (const std::exception& ex) {
@@ -3002,7 +3117,7 @@ bool GUI_App::on_init_inner()
if (scrn) {
const auto scrn_txt = _L("Creating main window") + dots;
scrn->SetText(scrn_txt);
scrn->SetText(scrn_txt, 70);
wxYield();
}
BOOST_LOG_TRIVIAL(info) << "create the main window";
@@ -3030,7 +3145,7 @@ bool GUI_App::on_init_inner()
plater_->set_printer_technology(ptFFF);
}
else {
if (scrn) { scrn->SetText(_L("Loading current preset") + dots); wxYield(); }
if (scrn) { scrn->SetText(_L("Loading current preset") + dots, 85); wxYield(); }
load_current_presets();
}
@@ -3044,10 +3159,10 @@ bool GUI_App::on_init_inner()
#ifdef __WINDOWS__
mainframe->topbar()->SaveNormalRect();
#endif
if (scrn) { scrn->SetText(_L("Showing main window") + dots); wxYield(); }
if (scrn) { scrn->SetText(_L("Showing main window") + dots, 95); wxYield(); }
mainframe->Show(true);
// Close the splash now that the main UI is visible.
if (scrn) { scrn->Destroy(); scrn = nullptr; }
if (scrn) { scrn->SetText(_L("Showing main window") + dots, 100); scrn->Destroy(); scrn = nullptr; }
BOOST_LOG_TRIVIAL(info) << "main frame firstly shown";
//#if BBL_HAS_FIRST_PAGE
@@ -3220,7 +3335,7 @@ void GUI_App::copy_network_if_available()
fs::remove(network_library);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Copying network library from " << network_library << " to " << network_library_dst << " successfully.";
app_config->set(SETTING_NETWORK_PLUGIN_VERSION, cached_version);
app_config->set_network_plugin_version(cached_version);
app_config->save();
}
@@ -3256,6 +3371,26 @@ void GUI_App::copy_network_if_available()
bool GUI_App::on_init_network(bool try_backup)
{
// Clean up stale ".old" files left by install_plugin() when it had to rename an in-use
// DLL aside (see the rename-aside path in install_plugin). This runs before the plug-in
// is (re)loaded - at startup nothing is mapped yet, and on a hot reload the previous
// module has already been unloaded - so the previously locked files can now be removed.
{
boost::filesystem::path plugin_folder = boost::filesystem::path(data_dir()) / "plugins";
boost::system::error_code ec;
if (boost::filesystem::is_directory(plugin_folder, ec)) {
for (boost::filesystem::directory_iterator it(plugin_folder, ec), end; !ec && it != end; it.increment(ec)) {
if (it->path().extension() == ".old") {
boost::system::error_code rm_ec;
boost::filesystem::remove(it->path(), rm_ec);
if (rm_ec)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": could not remove stale " << it->path().filename().string()
<< " (" << rm_ec.message() << "), will retry next launch";
}
}
}
}
auto should_load_networking_plugin = app_config->get_bool("installed_networking");
std::string config_version = app_config->get_network_plugin_version();
@@ -3285,7 +3420,7 @@ bool GUI_App::on_init_network(bool try_backup)
if (config_base != loaded_version) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": syncing config version from " << config_version << " to loaded "
<< loaded_version;
app_config->set(SETTING_NETWORK_PLUGIN_VERSION, loaded_version);
app_config->set_network_plugin_version(loaded_version);
app_config->save();
}
}
@@ -3411,7 +3546,7 @@ bool GUI_App::on_init_network(bool try_backup)
m_user_manager = new Slic3r::UserManager();
}
if (should_load_networking_plugin && m_networking_compatible && !NetworkAgent::use_legacy_network) {
if (should_load_networking_plugin && m_networking_compatible && !use_legacy_network_plugin()) {
app_config->clear_remind_network_update_later();
if (has_network_update_available()) {
@@ -4965,21 +5100,28 @@ void GUI_App::on_http_error(wxCommandEvent &evt)
if (plater != nullptr && wxGetApp().imgui()->display_initialized()) {
std::string text;
// Name the specific preset in the header so the user knows which one conflicts.
// The agent injects the local preset name into every 409 conflict body, so this is
// normally populated; fall back to a generic header if it is somehow missing.
const std::string header = conflict_preset_name.empty()
? _u8L("Cloud sync conflict:")
: format(_u8L("Cloud sync conflict for preset \"%s\":"), conflict_preset_name);
switch (conflict_code) {
case -1:
text = _u8L("Cloud sync conflict: this preset has a newer version in OrcaCloud.\n"
text = header + " " + _u8L("This preset has a newer version in OrcaCloud.\n"
"Pull downloads the cloud copy. Force push overwrites it with your local preset.");
break;
case -2:
text = _u8L("Cloud sync conflict: a preset with this name already exists in OrcaCloud.\n"
text = header + " " + _u8L("A preset with this name already exists in OrcaCloud.\n"
"Pull downloads the cloud copy. Force push overwrites it with your local preset.");
break;
case -3:
text = _u8L("Cloud sync conflict: a preset with the same name was previously deleted from the cloud.\n"
text = header + " " + _u8L("A preset with the same name was previously deleted from the cloud.\n"
"Delete will delete your local preset. Force push overwrites it with your local preset.");
break;
default:
text = _u8L("Cloud sync conflict: there was an unexpected or unidentified preset conflict.\n"
text = header + " " + _u8L("There was an unexpected or unidentified preset conflict.\n"
"Pull downloads the cloud copy. Force push overwrites it with your local preset.");
break;
};
@@ -4998,10 +5140,12 @@ void GUI_App::on_http_error(wxCommandEvent &evt)
[this, conflict_setting_id, conflict_preset_name, conflict_user_id](wxEvtHandler*) {
if (mainframe == nullptr)
return false;
MessageDialog
dlg(mainframe,
_L("Force push will overwrite the cloud copy with your local preset changes.\nDo you want to continue?"),
_L("Resolve cloud sync conflict"), wxCENTER | wxYES_NO | wxNO_DEFAULT | wxICON_WARNING);
const wxString confirm_msg = conflict_preset_name.empty()
? _L("Force push will overwrite the cloud copy with your local preset changes.\nDo you want to continue?")
: format_wxstr(_L("Force push will overwrite the cloud copy of preset \"%s\" with your local changes.\nDo you want to continue?"),
conflict_preset_name);
MessageDialog dlg(mainframe, confirm_msg, _L("Resolve cloud sync conflict"),
wxCENTER | wxYES_NO | wxNO_DEFAULT | wxICON_WARNING);
if (dlg.ShowModal() != wxID_YES)
return false;
@@ -6437,14 +6581,14 @@ void GUI_App::sync_preset(Preset* preset, bool force)
result = 0; // Set to 0 so the sync_info gets saved below
// Show user notification
CallAfter([this] {
CallAfter([this, name = preset->name] {
static bool size_limit_dialog_notified = false;
if (size_limit_dialog_notified)
return;
size_limit_dialog_notified = true;
if (mainframe == nullptr)
return;
auto msg = _L("The preset content is too large to sync to the cloud (exceeds 1MB). Please reduce the preset size by removing custom configurations or use it locally only.");
auto msg = format_wxstr(_L("The preset \"%s\" is too large to sync to the cloud (exceeds 1MB). Please reduce the preset size by removing custom configurations or use it locally only."), name);
MessageDialog(mainframe, msg, _L("Sync user presets"), wxICON_WARNING | wxOK).ShowModal();
});
// NOTE: Don't return here - let execution continue to save the sync_info
@@ -6821,6 +6965,10 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
// finishFn tears down the progress dialog (and clears the re-entrancy guard), so it
// must run on every exit path — otherwise an early bail-out would leak the modal
// dialog and leave the guard stuck, blocking all later manual syncs.
// Guard the whole thread body: an uncaught exception here (e.g. a transient
// boost::filesystem error while scanning the preset folder) would otherwise
// propagate out of the thread and terminate the entire application.
try {
if (!m_agent) { finishFn(false); return; }
// One-time scan for orphaned .info files left over from offline deletions; queues HTTP DELETEs.
@@ -7062,6 +7210,11 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
}
}
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "user preset sync thread terminated by exception: " << e.what();
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "user preset sync thread terminated by unknown exception";
}
});
}
@@ -8382,8 +8535,13 @@ void GUI_App::scan_orphaned_info_files()
if (!fs::exists(type_dir))
continue;
// Iterate through all .info files
for (auto& entry : boost::filesystem::directory_iterator(type_dir)) {
// Iterate through all .info files. Use the error_code-based iterator so a transient
// directory-read failure (e.g. macOS readdir returning ENOTSUP) is logged and skipped
// instead of throwing an uncaught exception that would terminate the app from the
// background sync thread this runs on.
boost::system::error_code ec;
for (boost::filesystem::directory_iterator it(type_dir, ec), end; !ec && it != end; it.increment(ec)) {
const auto& entry = *it;
if (entry.path().extension() != ".info")
continue;
@@ -8402,6 +8560,8 @@ void GUI_App::scan_orphaned_info_files()
}
}
}
if (ec)
BOOST_LOG_TRIVIAL(warning) << "scan_orphaned_info_files: failed to scan " << type_dir.string() << ": " << ec.message();
}
}