From afb4505ea77b75f5bbae0cf85e32f6892199948e Mon Sep 17 00:00:00 2001 From: SoftFever Date: Sun, 19 Jul 2026 00:26:05 +0800 Subject: [PATCH] Store network plug-in identity by AA.BB.CC series The stored plug-in identity is now the AA.BB.CC series (matching BambuStudio) rather than the full build version, so the meaningless 4th build digit stops driving config, the whitelist, filenames, and the version selector. This fixes the macOS "can't switch to an older build" bug: the cloud endpoint is series-keyed and only ever serves a series' newest build, so downgrade-by- download was impossible and the download silently adopted the latest. A startup migration normalizes an existing full-version config and file name to the series form with no re-download, the loader resolves a bare series to the newest same-series build on disk, and user-provided custom-named plug-ins (libbambu_networking_02.08.01_custom.*) are still enumerated and loaded. --- src/slic3r/GUI/GUI_App.cpp | 99 +++++++++++----- src/slic3r/GUI/GUI_App.hpp | 4 + src/slic3r/GUI/Preferences.cpp | 106 ++++++++++------- src/slic3r/Utils/BBLNetworkPlugin.cpp | 88 ++++++++------ src/slic3r/Utils/BBLNetworkPlugin.hpp | 3 + src/slic3r/Utils/bambu_networking.hpp | 21 +++- tests/slic3rutils/test_network_versions.cpp | 121 ++++++++++++-------- 7 files changed, 290 insertions(+), 152 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 6d0b66ec01..312c4f6731 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1198,7 +1198,11 @@ std::string GUI_App::get_plugin_url(std::string name, std::string country_code) curr_version = get_latest_network_version(); } - std::string using_version = curr_version.substr(0, 9) + "00"; + // The cloud endpoint is series-keyed and serves the series' newest build: AA.BB.CC.00. + // Build it from the series so an 8-char series string (02.08.01) works too. + std::string using_version = use_legacy_network_plugin() + ? curr_version.substr(0, 9) + "00" + : network_plugin_series(curr_version) + ".00"; if (name == "cameratools") using_version = curr_version.substr(0, 6) + "00.00"; url += (boost::format("?slicer/%1%/cloud=%2%") % name % using_version).str(); @@ -1256,14 +1260,13 @@ int GUI_App::download_plugin(std::string name, std::string package_name, Install // get_url std::string url = get_plugin_url(name, app_config->get_country_code()); std::string download_url; - std::string online_version; Slic3r::Http http_url = Slic3r::Http::get(url); BOOST_LOG_TRIVIAL(info) << "[download_plugin]: check the plugin from " << url; http_url.timeout_connect(TIMEOUT_CONNECT) .timeout_max(TIMEOUT_RESPONSE) .header("X-BBL-OS-Type", os_type) .on_complete( - [&download_url, &online_version](std::string body, unsigned status) { + [&download_url](std::string body, unsigned status) { try { json j = json::parse(body); std::string message = j["message"].get(); @@ -1296,7 +1299,6 @@ int GUI_App::download_plugin(std::string name, std::string package_name, Install } BOOST_LOG_TRIVIAL(info) << "[download_plugin 1]: get type " << type << ", version " << version.to_string() << ", url " << url; download_url = url; - online_version = version_str; } } } @@ -1387,25 +1389,10 @@ int GUI_App::download_plugin(std::string name, std::string package_name, Install }); http.perform_sync(); - // The cloud endpoint serves the newest build of the requested series, which may be - // newer than the configured version. Adopt the actual downloaded version so that - // install_plugin() names the library after what it really contains - otherwise the - // post-load config sync (on_init_network) points at a file name that does not exist - // and the plugin is "lost" on the following launch. Legacy mode is left untouched: - // is_legacy_version() matches exactly and drives the legacy struct layout. - if (result >= 0 && name == "plugins" && !online_version.empty() && !use_legacy_network_plugin()) { - std::string configured = app_config->get_network_plugin_version(); - if (configured.empty()) - configured = get_latest_network_version(); - if (configured != online_version - && configured.size() >= 8 && online_version.size() >= 8 - && configured.compare(0, 8, online_version, 0, 8) == 0) { - BOOST_LOG_TRIVIAL(info) << "[download_plugin] server returned " << online_version - << " for configured " << configured << ", adopting it"; - app_config->set_network_plugin_version(online_version); - } - } - + // No version adoption: the stored identity is the AA.BB.CC series, so install_plugin() names + // the library after the configured series regardless of which build the series-keyed endpoint + // served (02.08.01.53). The series config never diverges from the file name, so there is + // nothing to adopt. j["result"] = result < 0 ? "failed" : "success"; j["error_msg"] = err_msg; return result; @@ -1770,6 +1757,53 @@ bool GUI_App::wait_for_network_idle(int timeout_ms) return false; } +void GUI_App::migrate_network_plugin_config() +{ + if (!app_config) + return; + + const std::string cfg = app_config->get_network_plugin_version(); + if (!is_series_managed_version(cfg) || !is_supported_network_version(cfg)) + return; // empty / legacy / custom-named / unsupported old series -> nothing to migrate here + // (an unsupported old config is handled by the fallback in on_init_network) + + const std::string series = network_plugin_series(cfg); + if (cfg != series) { + app_config->set_network_plugin_version(series); + app_config->save(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": network_plugin_version " << cfg << " -> " << series; + } + + // Consolidate the on-disk files onto the series name. Runs at startup before the plug-in is + // loaded, so the rename is safe even on Windows (nothing holds the file open yet). If it is + // skipped or fails, resolve_library_path() still loads the specific build for this series. + std::string newest; + for (const auto& v : BBLNetworkPlugin::scan_plugin_versions()) + if (is_series_managed_version(v) && network_plugin_series(v) == series && (newest.empty() || v > newest)) + newest = v; + if (newest.empty()) + return; + + boost::system::error_code ec; + const boost::filesystem::path series_path(BBLNetworkPlugin::get_versioned_library_path(series)); + if (newest != series) { + // Make the newest same-series build the series file. Safe here (pre-load); if the rename + // fails, resolve_library_path() still loads the specific build for this series. + boost::filesystem::remove(series_path, ec); + boost::filesystem::rename(BBLNetworkPlugin::get_versioned_library_path(newest), series_path, ec); + if (ec) + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": rename " << newest << " -> " << series + << " failed (" << ec.message() << "), loader will resolve it"; + } + + // Tidy strictly-older same-series managed builds (best-effort; never touch custom/legacy). + for (const auto& v : BBLNetworkPlugin::scan_plugin_versions()) { + if (v == series || !is_series_managed_version(v) || network_plugin_series(v) != series) + continue; + boost::filesystem::remove(BBLNetworkPlugin::get_versioned_library_path(v), ec); + } +} + bool GUI_App::hot_reload_network_plugin() { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": starting hot reload"; @@ -3590,6 +3624,9 @@ bool GUI_App::on_init_network(bool try_backup) auto should_load_networking_plugin = app_config->get_bool("installed_networking"); + // Normalize an older full-version identity to the AA.BB.CC series before it drives loading. + migrate_network_plugin_config(); + std::string config_version = app_config->get_network_plugin_version(); if (should_load_networking_plugin) { @@ -3627,12 +3664,18 @@ bool GUI_App::on_init_network(bool try_backup) std::string loaded_version = Slic3r::NetworkAgent::get_version(); if (app_config && !loaded_version.empty() && loaded_version != "00.00.00.00") { + // Self-heal only when a genuinely different series loaded than configured (e.g. the + // configured build was unavailable and a fallback loaded). Within a series the + // loaded build (02.08.01.53) differs from the series config (02.08.01) by design, so + // compare series, not the raw string, and store the managed series form - a custom + // config (02.08.01_custom) keeps its own name. std::string config_version = app_config->get_network_plugin_version(); - std::string config_base = extract_base_version(config_version); - if (config_base != loaded_version) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": syncing config version from " << config_version << " to loaded " - << loaded_version; - app_config->set_network_plugin_version(loaded_version); + std::string loaded_series = network_plugin_series(loaded_version); + if (network_plugin_series(config_version) != loaded_series) { + std::string synced = is_series_managed_version(loaded_version) ? loaded_series : loaded_version; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": syncing config version from " << config_version + << " to loaded " << loaded_version << " (stored as " << synced << ")"; + app_config->set_network_plugin_version(synced); app_config->save(); } } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 186693dc4b..c90e58887e 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -763,6 +763,10 @@ public: void check_config_updates_from_updater() { check_updates(false); } void show_network_plugin_download_dialog(bool is_update = false); + // One-time normalization of an older full-version identity (config 02.08.01.53 + file + // ..._02.08.01.53.dylib) to the AA.BB.CC series form, with no re-download. Runs at startup + // before the plug-in is loaded. + void migrate_network_plugin_config(); bool hot_reload_network_plugin(); bool install_network_plugin_from_ota(bool& had_cache); std::string get_latest_network_version() const; diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 18fab4938c..2b5787c760 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1273,55 +1273,79 @@ wxBoxSizer *PreferencesDialog::create_item_network_plugin_version(wxString title m_sizer->Add(m_network_version_combo, 0, wxALIGN_CENTER); m_network_version_combo->GetDropDown().Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& e) { + e.Skip(); // order-independent flag read after this handler returns; every path just returns int selection = e.GetSelection(); - if (selection >= 0 && selection < (int)m_available_versions.size()) { - const auto& selected_ver = m_available_versions[selection]; - std::string new_version = selected_ver.version; - std::string old_version = app_config->get_network_plugin_version(); - if (old_version.empty()) { - old_version = get_latest_network_version(); - } + if (selection < 0 || selection >= (int) m_available_versions.size()) + return; - app_config->set_network_plugin_version(new_version); - app_config->save(); + const auto& selected_ver = m_available_versions[selection]; + const std::string new_version = selected_ver.version; + std::string old_version = app_config->get_network_plugin_version(); + if (old_version.empty()) + old_version = get_latest_network_version(); - if (new_version != old_version) { - BOOST_LOG_TRIVIAL(info) << "Network plugin version changed from " << old_version << " to " << new_version; - - if (!selected_ver.warning.empty()) { - MessageDialog warn_dlg(this, wxString::FromUTF8(selected_ver.warning), _L("Warning"), wxOK | wxCANCEL | wxICON_WARNING); - if (warn_dlg.ShowModal() != wxID_OK) { - app_config->set_network_plugin_version(old_version); - app_config->save(); - e.Skip(); - return; - } + // Move the combo back to the row for `version`, so the UI never shows a build other + // than the one actually configured/loaded (e.g. after a declined or refused switch). + auto reselect = [this](const std::string& version) { + for (size_t i = 0; i < m_available_versions.size(); ++i) + if (m_available_versions[i].version == version) { + m_network_version_combo->SetSelection((int) i); + break; } + }; - // Check if the selected version already exists on disk - if (Slic3r::NetworkAgent::versioned_library_exists(new_version)) { - BOOST_LOG_TRIVIAL(info) << "Version " << new_version << " already exists on disk, triggering hot reload"; - if (wxGetApp().hot_reload_network_plugin()) { - MessageDialog dlg(this, _L("Network plug-in switched successfully."), _L("Success"), wxOK | wxICON_INFORMATION); - dlg.ShowModal(); - } else { - MessageDialog dlg(this, _L("Failed to load network plug-in. Please restart the application."), _L("Restart Required"), wxOK | wxICON_WARNING); - dlg.ShowModal(); - } - } else { - wxString msg = wxString::Format( - _L("You've selected network plug-in version %s.\n\nWould you like to download and install this version now?\n\nNote: The application may need to restart after installation."), - wxString::FromUTF8(new_version)); + if (new_version == old_version) + return; - MessageDialog dlg(this, msg, _L("Download Network Plug-in"), wxYES_NO | wxICON_QUESTION); - if (dlg.ShowModal() == wxID_YES) { - DownloadProgressDialog progress_dlg(_L("Downloading Network Plug-in")); - progress_dlg.ShowModal(); - } - } + BOOST_LOG_TRIVIAL(info) << "Network plugin version selection changed from " << old_version << " to " << new_version; + + if (!selected_ver.warning.empty()) { + MessageDialog warn_dlg(this, wxString::FromUTF8(selected_ver.warning), _L("Warning"), wxOK | wxCANCEL | wxICON_WARNING); + if (warn_dlg.ShowModal() != wxID_OK) { + reselect(old_version); + return; } } - e.Skip(); + + // A build already present on disk loads directly with a hot reload - on any platform + // and across series (legacy <-> modern). Only claim success once the build that + // actually loaded is the one that was requested. + if (Slic3r::NetworkAgent::versioned_library_exists(new_version)) { + app_config->set_network_plugin_version(new_version); + app_config->save(); + BOOST_LOG_TRIVIAL(info) << "Version " << new_version << " already exists on disk, triggering hot reload"; + // Claim success only once the series that actually loaded is the one requested - the + // loaded plug-in reports its full build (02.08.01.53) while the requested identity is + // the series (02.08.01), so compare series, not the raw string. + if (wxGetApp().hot_reload_network_plugin() && + network_plugin_series(Slic3r::NetworkAgent::get_version()) == network_plugin_series(new_version)) { + MessageDialog dlg(this, _L("Network plug-in switched successfully."), _L("Success"), wxOK | wxICON_INFORMATION); + dlg.ShowModal(); + } else { + MessageDialog dlg(this, _L("Failed to load network plug-in. Please restart the application."), _L("Restart Required"), wxOK | wxICON_WARNING); + dlg.ShowModal(); + reselect(app_config->get_network_plugin_version()); + } + return; + } + + // Not on disk: offer to download it. The endpoint is series-keyed and serves that series' + // newest build. (A same-series custom build is only ever listed when its file is on disk, + // so it takes the hot-reload branch above; the only not-on-disk selectable is a series or + // legacy entry that genuinely needs fetching.) + wxString msg = wxString::Format( + _L("You've selected network plug-in version %s.\n\nWould you like to download and install this version now?\n\nNote: The application may need to restart after installation."), + wxString::FromUTF8(new_version)); + MessageDialog dlg(this, msg, _L("Download Network Plug-in"), wxYES_NO | wxICON_QUESTION); + if (dlg.ShowModal() == wxID_YES) { + app_config->set_network_plugin_version(new_version); + app_config->save(); + DownloadProgressDialog progress_dlg(_L("Downloading Network Plug-in")); + progress_dlg.ShowModal(); + reselect(app_config->get_network_plugin_version()); + } else { + reselect(old_version); + } }); auto reload_btn = new Button(m_parent, wxEmptyString, "refresh", 0, 16); diff --git a/src/slic3r/Utils/BBLNetworkPlugin.cpp b/src/slic3r/Utils/BBLNetworkPlugin.cpp index 630ec98086..b0dd277a79 100644 --- a/src/slic3r/Utils/BBLNetworkPlugin.cpp +++ b/src/slic3r/Utils/BBLNetworkPlugin.cpp @@ -100,17 +100,18 @@ int BBLNetworkPlugin::initialize(bool using_backup, const std::string& version) } } - // Load versioned library + // Load versioned library. In the normal plugins folder a bare series (02.08.01) resolves to + // whatever same-series build is actually on disk (see resolve_library_path); the backup + // folder keeps the exact versioned name. #if defined(_MSC_VER) || defined(_WIN32) - library = plugin_folder.string() + "\\" + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dll"; + std::string versioned_name = std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dll"; +#elif defined(__WXMAC__) + std::string versioned_name = std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".dylib"; #else - #if defined(__WXMAC__) - std::string lib_ext = ".dylib"; - #else - std::string lib_ext = ".so"; - #endif - library = plugin_folder.string() + "/" + std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + lib_ext; + std::string versioned_name = std::string("lib") + std::string(BAMBU_NETWORK_LIBRARY) + "_" + version + ".so"; #endif + library = using_backup ? (plugin_folder / versioned_name).string() + : resolve_library_path(version); #if defined(_MSC_VER) || defined(_WIN32) wchar_t lib_wstr[256]; @@ -382,12 +383,33 @@ std::string BBLNetworkPlugin::get_versioned_library_path(const std::string& vers #endif } +std::string BBLNetworkPlugin::resolve_library_path(const std::string& version) +{ + std::string exact = get_versioned_library_path(version); + if (boost::filesystem::exists(exact)) + return exact; + + // A bare series (02.08.01) is physically present only as a specific build (02.08.01.53) when + // the startup file-rename was skipped or failed. Resolve to the newest same-series build + // actually on disk. Custom and legacy names are exact and never resolved. + if (is_series_managed_version(version)) { + const std::string series = network_plugin_series(version); + std::string best; + for (const auto& v : scan_plugin_versions()) + if (is_series_managed_version(v) && network_plugin_series(v) == series && (best.empty() || v > best)) + best = v; + if (!best.empty()) + return get_versioned_library_path(best); + } + + return exact; // nonexistent -> caller downloads +} + bool BBLNetworkPlugin::versioned_library_exists(const std::string& version) { if (version.empty()) return false; - std::string path = get_versioned_library_path(version); - if (boost::filesystem::exists(path)) return true; + if (boost::filesystem::exists(resolve_library_path(version))) return true; if (is_legacy_version(version)) { return legacy_library_exists(); @@ -786,43 +808,30 @@ std::vector get_all_available_versions() std::vector get_all_available_versions(const std::string& loaded_version) { std::vector result; - std::set known_base_versions; std::set all_known_versions; for (size_t i = 0; i < AVAILABLE_NETWORK_VERSIONS_COUNT; ++i) { result.push_back(NetworkLibraryVersionInfo::from_static(AVAILABLE_NETWORK_VERSIONS[i])); - known_base_versions.insert(AVAILABLE_NETWORK_VERSIONS[i].version); all_known_versions.insert(AVAILABLE_NETWORK_VERSIONS[i].version); } std::vector discovered = BBLNetworkPlugin::scan_plugin_versions(); - // Surface discovered builds that are not whitelisted outright: - // - suffixed dev builds (02.08.01.52-custom) attach to the exact whitelisted base - // version they were built from, so they render nested under it; - // - unsuffixed builds (typically installed by the OTA plug-in update) are accepted - // when is_supported_network_version() recognises their release series - the plugin - // ABI is stable within an AA.BB.CC series and the OTA sync only ever accepts - // same-series updates, so they load with the whitelisted struct layout. That gate - // never matches the legacy entry, which would otherwise be loaded with the modern - // layout and break. - // Only the static whitelist anchors a dev build, never another discovered one, so - // known_base_versions must stay separate from all_known_versions here. + // A managed build (pure dotted-numeric AA.BB.CC[.DD]) is represented by its series entry + // above - the OTA-installed 02.08.01.53 and a bare 02.08.01 both collapse into the single + // 02.08.01 row. Only a custom-named build a user dropped in (02.08.01_custom, ..-dev) earns + // its own row, and only when its series is one this build can actually load. The part past + // the series is stored as the "suffix" so the entry sorts and renders nested under it. for (const auto& version : discovered) { if (all_known_versions.count(version) > 0) continue; - - const std::string suffix = extract_suffix(version); - if (suffix.empty()) { - if (!is_supported_network_version(version)) - continue; - result.push_back(NetworkLibraryVersionInfo::from_discovered(version, version, "")); - } else { - const std::string base = extract_base_version(version); - if (known_base_versions.count(base) == 0) - continue; - result.push_back(NetworkLibraryVersionInfo::from_discovered(version, base, suffix)); - } + if (is_series_managed_version(version)) + continue; + if (!is_supported_network_version(version)) + continue; + const std::string series = network_plugin_series(version); + const std::string sfx = version.size() > series.size() ? version.substr(series.size()) : version; + result.push_back(NetworkLibraryVersionInfo::from_discovered(version, series, sfx)); all_known_versions.insert(version); } @@ -835,8 +844,15 @@ std::vector get_all_available_versions(const std::str return a.suffix < b.suffix; }); + const std::string loaded_series = network_plugin_series(loaded_version); for (auto& info : result) { - info.is_loaded = !loaded_version.empty() && info.version == loaded_version; + // A managed (series) entry matches when a managed build of the same series is loaded - + // the loaded plug-in reports its full build (02.08.01.53) but the row is the series. + // Custom and legacy entries match their exact reported version. + info.is_loaded = !loaded_version.empty() && + (info.version == loaded_version || + (is_series_managed_version(info.version) && is_series_managed_version(loaded_version) && + network_plugin_series(info.version) == loaded_series)); info.is_latest = false; } diff --git a/src/slic3r/Utils/BBLNetworkPlugin.hpp b/src/slic3r/Utils/BBLNetworkPlugin.hpp index 7ca989c129..ed94b23f85 100644 --- a/src/slic3r/Utils/BBLNetworkPlugin.hpp +++ b/src/slic3r/Utils/BBLNetworkPlugin.hpp @@ -260,6 +260,9 @@ public: static std::string get_libpath_in_current_directory(const std::string& library_name); static std::string get_versioned_library_path(const std::string& version); + // Path to load for a stored version: the exact file if present, else - for a bare series + // whose file the startup rename did not produce - the newest same-series build on disk. + static std::string resolve_library_path(const std::string& version); static bool versioned_library_exists(const std::string& version); static bool legacy_library_exists(); static void remove_legacy_library(); diff --git a/src/slic3r/Utils/bambu_networking.hpp b/src/slic3r/Utils/bambu_networking.hpp index 8e25db169e..1093392423 100644 --- a/src/slic3r/Utils/bambu_networking.hpp +++ b/src/slic3r/Utils/bambu_networking.hpp @@ -364,7 +364,7 @@ struct NetworkLibraryVersion { // a dedicated shim for the legacy build. Older 02.0x series expect different layouts // and must not be loaded - see is_supported_network_version(). static const NetworkLibraryVersion AVAILABLE_NETWORK_VERSIONS[] = { - {"02.08.01.52", "02.08.01.52", nullptr, true, nullptr}, + {"02.08.01", "02.08.01", nullptr, true, nullptr}, {BAMBU_NETWORK_AGENT_VERSION_LEGACY, BAMBU_NETWORK_AGENT_VERSION_LEGACY " (legacy)", nullptr, false, nullptr}, }; @@ -441,6 +441,25 @@ inline std::string extract_suffix(const std::string& full_version) { return (pos == std::string::npos) ? "" : full_version.substr(pos + 1); } +// The AA.BB.CC series of a modern version string - the plug-in's stored identity. The 4th +// component is only which build of the series happens to be installed and is read live from +// the loaded plug-in for display. Legacy keeps its exact string (the shim matches exactly). +inline std::string network_plugin_series(const std::string& version) { + if (version.empty() || version == BAMBU_NETWORK_AGENT_VERSION_LEGACY) + return version; + return version.size() >= 8 ? version.substr(0, 8) : version; +} + +// True when the version is a pure dotted-numeric build (AA.BB.CC or AA.BB.CC.DD) whose identity +// collapses to its series - the managed/OTA build. Legacy and any custom-named build +// (02.08.01_custom, 02.08.01.52-dev) are NOT managed: they are genuinely distinct files kept +// under their own name and never folded into the series entry. +inline bool is_series_managed_version(const std::string& version) { + if (version.empty() || version == BAMBU_NETWORK_AGENT_VERSION_LEGACY) + return false; + return version.find_first_not_of("0123456789.") == std::string::npos; +} + // Selectable versions, newest first. Marks the entry matching the plug-in currently // loaded in this session. std::vector get_all_available_versions(); diff --git a/tests/slic3rutils/test_network_versions.cpp b/tests/slic3rutils/test_network_versions.cpp index a26725fc4c..af8a639025 100644 --- a/tests/slic3rutils/test_network_versions.cpp +++ b/tests/slic3rutils/test_network_versions.cpp @@ -61,76 +61,105 @@ int count_version(const std::vector& versions, const } // namespace -TEST_CASE_METHOD(PluginFolderFixture, "Same-series OTA plugin versions are surfaced", "[NetworkVersions]") +TEST_CASE("Series and managed classification", "[NetworkVersions]") { - add_plugin("02.08.01.55"); // same series as the whitelisted latest -> listed - add_plugin("02.09.00.10"); // unknown series -> not listed - add_plugin("02.08.01.52-custom"); // suffixed build of a whitelisted base -> listed (existing behavior) - add_plugin("02.03.00.62"); // series no longer whitelisted -> not listed + // The AA.BB.CC series is the stored identity of every modern build. + CHECK(network_plugin_series("02.08.01.53") == "02.08.01"); + CHECK(network_plugin_series("02.08.01") == "02.08.01"); // idempotent + CHECK(network_plugin_series("02.08.01_custom") == "02.08.01"); + CHECK(network_plugin_series("02.08.01.52-dev") == "02.08.01"); + CHECK(network_plugin_series(BAMBU_NETWORK_AGENT_VERSION_LEGACY) == BAMBU_NETWORK_AGENT_VERSION_LEGACY); + CHECK(network_plugin_series("").empty()); + + // Only pure dotted-numeric builds collapse into their series entry; legacy and any + // custom-named build keep their own identity. + CHECK(is_series_managed_version("02.08.01")); + CHECK(is_series_managed_version("02.08.01.53")); + CHECK_FALSE(is_series_managed_version("02.08.01_custom")); + CHECK_FALSE(is_series_managed_version("02.08.01.52-dev")); + CHECK_FALSE(is_series_managed_version(BAMBU_NETWORK_AGENT_VERSION_LEGACY)); + CHECK_FALSE(is_series_managed_version("")); +} + +TEST_CASE_METHOD(PluginFolderFixture, "Managed builds fold into the series; customs are surfaced", "[NetworkVersions]") +{ + add_plugin("02.08.01.55"); // managed, same series -> folded into the 02.08.01 row + add_plugin("02.09.00.10"); // managed, unknown series -> not listed + add_plugin("02.03.00.62"); // managed, series no longer whitelisted -> not listed + add_plugin("02.08.01_custom"); // custom, whitelisted series -> listed under it + add_plugin("02.08.01.52-dev"); // custom (dash-suffixed), whitelisted series -> listed auto versions = get_all_available_versions(); - REQUIRE(count_version(versions, "02.08.01.55") == 1); + // The specific managed build never gets its own row - the series represents it. + REQUIRE(count_version(versions, "02.08.01.55") == 0); + REQUIRE(count_version(versions, "02.08.01") == 1); REQUIRE(count_version(versions, "02.09.00.10") == 0); - REQUIRE(count_version(versions, "02.08.01.52-custom") == 1); REQUIRE(count_version(versions, "02.03.00.62") == 0); + // Custom-named builds are distinct files kept under their own name. + REQUIRE(count_version(versions, "02.08.01_custom") == 1); + REQUIRE(count_version(versions, "02.08.01.52-dev") == 1); - // Newest first, regardless of whether a version came from the whitelist or from - // disk: the OTA build outranks the older whitelist entry it was discovered under. - REQUIRE(versions[0].version == "02.08.01.55"); - REQUIRE(versions[1].version == "02.08.01.52"); - // Suffixed dev builds stay nested directly under the base version they build on. - REQUIRE(versions[2].version == "02.08.01.52-custom"); - REQUIRE(versions[2].base_version == "02.08.01.52"); - // The legacy series is the oldest, so it sorts last. + // Newest series first, its customs nested under it (suffix sort: "" < ".52-dev" < "_custom"), + // legacy last. + REQUIRE(versions[0].version == "02.08.01"); + REQUIRE(versions[1].version == "02.08.01.52-dev"); + REQUIRE(versions[2].version == "02.08.01_custom"); REQUIRE(versions.back().version == BAMBU_NETWORK_AGENT_VERSION_LEGACY); - const auto& ota = versions[0]; - REQUIRE(ota.is_discovered); - REQUIRE(ota.suffix.empty()); + // Customs sort/render nested under their series (non-empty suffix, base = the series). + REQUIRE(versions[1].base_version == "02.08.01"); + REQUIRE_FALSE(versions[1].suffix.empty()); + REQUIRE(versions[2].base_version == "02.08.01"); + REQUIRE_FALSE(versions[2].suffix.empty()); - // "(Latest)" is dynamic: the OTA build is the highest listed version, so it takes - // the label from the static whitelist entry. - REQUIRE(ota.is_latest); + // "(Latest)" is the series row, never a nested custom build. + REQUIRE(versions[0].suffix.empty()); + REQUIRE(versions[0].is_latest); REQUIRE_FALSE(versions[1].is_latest); + REQUIRE_FALSE(versions[2].is_latest); - // The static default used for download and update-check decisions is unchanged. - REQUIRE(std::string(get_latest_network_version()) == "02.08.01.52"); + // The stored default that drives download and update-check decisions is now the series. + REQUIRE(std::string(get_latest_network_version()) == "02.08.01"); } -TEST_CASE_METHOD(PluginFolderFixture, "Only the loaded build is marked installed", "[NetworkVersions]") +TEST_CASE_METHOD(PluginFolderFixture, "Only the loaded series is marked installed", "[NetworkVersions]") { - // Switching versions leaves the previous library on disk, so presence on disk is - // not what "(installed)" reports - the build actually loaded in this session is. add_plugin("02.08.01.55"); - add_plugin("02.08.01.52"); + add_plugin("02.08.01_custom"); - auto versions = get_all_available_versions("02.08.01.52"); - - int marked = 0; - for (const auto& info : versions) { - if (info.is_loaded) { - ++marked; - REQUIRE(info.version == "02.08.01.52"); - } + // The loaded plug-in reports its full build (02.08.01.55); the series row is what gets marked. + { + auto versions = get_all_available_versions("02.08.01.55"); + int marked = 0; + for (const auto& info : versions) + if (info.is_loaded) { ++marked; REQUIRE(info.version == "02.08.01"); } + REQUIRE(marked == 1); } - REQUIRE(marked == 1); - // Nothing loaded (plug-in disabled or failed to load) marks nothing, even though - // both libraries are on disk. + // A loaded custom build matches its own row, never the bare series. + { + auto versions = get_all_available_versions("02.08.01_custom"); + int marked = 0; + for (const auto& info : versions) + if (info.is_loaded) { ++marked; REQUIRE(info.version == "02.08.01_custom"); } + REQUIRE(marked == 1); + } + + // Nothing loaded marks nothing, even though libraries are on disk. for (const auto& info : get_all_available_versions("")) REQUIRE_FALSE(info.is_loaded); } TEST_CASE("Only whitelisted series pass the load gate", "[NetworkVersions]") { - // Exact whitelist entries. + // The whitelisted series, its builds, and custom-named builds of that series. + REQUIRE(is_supported_network_version("02.08.01")); REQUIRE(is_supported_network_version("02.08.01.52")); - REQUIRE(is_supported_network_version(BAMBU_NETWORK_AGENT_VERSION_LEGACY)); - - // Same-series OTA builds and suffixed dev builds of the whitelisted latest. REQUIRE(is_supported_network_version("02.08.01.55")); - REQUIRE(is_supported_network_version("02.08.01.52-custom")); + REQUIRE(is_supported_network_version("02.08.01_custom")); + REQUIRE(is_supported_network_version("02.08.01.52-dev")); + REQUIRE(is_supported_network_version(BAMBU_NETWORK_AGENT_VERSION_LEGACY)); // Series whitelisted by previous Orca releases - their ABI no longer matches. REQUIRE_FALSE(is_supported_network_version("02.03.00.62")); @@ -159,10 +188,10 @@ TEST_CASE_METHOD(PluginFolderFixture, "Legacy series never adopts discovered bui REQUIRE(count_version(versions, legacy_sibling) == 0); REQUIRE(count_version(versions, legacy) == 1); - // With nothing else on disk, the highest whitelisted version holds "(Latest)" - // even though its library is not installed. + // With nothing else on disk, the series holds "(Latest)" even though its library is + // not installed. for (const auto& info : versions) { - if (info.version == "02.08.01.52") { + if (info.version == "02.08.01") { REQUIRE(info.is_latest); REQUIRE_FALSE(info.is_loaded); }