mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-18 16:32:06 +00:00
Sort network plug-in versions newest first and mark only the loaded build
Discovered builds were inserted positionally behind the whitelist entry they anchored to, so an OTA-installed 02.08.01.53 listed below 02.08.01.52. The list is now built by appending and sorting once, which also drops the per-entry insertion scans. "(installed)" tested whether the library was present on disk, so it marked every version ever selected - switching leaves the previous library in place. The flag is now is_loaded, resolved against the plug-in actually loaded, and the two combo populators share one label helper.
This commit is contained in:
@@ -205,6 +205,19 @@ void NetworkPluginDownloadDialog::create_update_available_ui(const std::string&
|
||||
main_sizer->Add(0, 0, 0, wxBOTTOM, FromDIP(20));
|
||||
}
|
||||
|
||||
wxString network_version_label(const NetworkLibraryVersionInfo& ver)
|
||||
{
|
||||
wxString label = wxString::FromUTF8(ver.display_name);
|
||||
if (!ver.suffix.empty())
|
||||
label = wxString::FromUTF8("\xE2\x94\x94 ") + label;
|
||||
// Both can apply: the loaded build may also be the highest listed one.
|
||||
if (ver.is_latest)
|
||||
label += wxString(" ") + _L("(Latest)");
|
||||
if (ver.is_loaded)
|
||||
label += wxString(" ") + _L("(installed)");
|
||||
return label;
|
||||
}
|
||||
|
||||
void NetworkPluginDownloadDialog::setup_version_selector()
|
||||
{
|
||||
m_version_combo = new ComboBox(this, wxID_ANY, wxEmptyString,
|
||||
@@ -212,24 +225,8 @@ void NetworkPluginDownloadDialog::setup_version_selector()
|
||||
m_version_combo->SetFont(::Label::Body_13);
|
||||
|
||||
m_available_versions = get_all_available_versions();
|
||||
for (size_t i = 0; i < m_available_versions.size(); ++i) {
|
||||
const auto& ver = m_available_versions[i];
|
||||
wxString label;
|
||||
if (!ver.suffix.empty()) {
|
||||
label = wxString::FromUTF8("\xE2\x94\x94 ") + wxString::FromUTF8(ver.display_name);
|
||||
} else {
|
||||
label = wxString::FromUTF8(ver.display_name);
|
||||
}
|
||||
// Same labeling as the Preferences selector: "(Latest)" = highest listed
|
||||
// version, "(installed)" = library already on disk; both can apply.
|
||||
if (ver.is_latest) {
|
||||
label += wxString(" ") + _L("(Latest)");
|
||||
}
|
||||
if (ver.is_installed) {
|
||||
label += wxString(" ") + _L("(installed)");
|
||||
}
|
||||
m_version_combo->Append(label);
|
||||
}
|
||||
for (const auto& ver : m_available_versions)
|
||||
m_version_combo->Append(network_version_label(ver));
|
||||
|
||||
m_version_combo->SetSelection(0);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
// Dropdown label for one network plug-in version: tree glyph for a dev build, then
|
||||
// "(Latest)" for the highest listed version and "(installed)" for the build currently
|
||||
// loaded. Shared by the Preferences selector and the download dialog so both stay
|
||||
// consistent.
|
||||
wxString network_version_label(const NetworkLibraryVersionInfo& ver);
|
||||
|
||||
class NetworkPluginDownloadDialog : public DPIDialog
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "Widgets/RadioGroup.hpp"
|
||||
#include "slic3r/Utils/bambu_networking.hpp"
|
||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||
#include "NetworkPluginDialog.hpp"
|
||||
#include "DownloadProgressDialog.hpp"
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
@@ -1262,23 +1263,7 @@ wxBoxSizer *PreferencesDialog::create_item_network_plugin_version(wxString title
|
||||
|
||||
for (size_t i = 0; i < m_available_versions.size(); i++) {
|
||||
const auto& ver = m_available_versions[i];
|
||||
wxString label;
|
||||
|
||||
if (!ver.suffix.empty()) {
|
||||
label = wxString::FromUTF8("\xE2\x94\x94 ") + wxString::FromUTF8(ver.display_name);
|
||||
} else {
|
||||
label = wxString::FromUTF8(ver.display_name);
|
||||
}
|
||||
|
||||
// "(Latest)" marks the highest listed version; "(installed)" marks versions
|
||||
// whose library is already on disk. One entry can carry both.
|
||||
if (ver.is_latest) {
|
||||
label += " " + _L("(Latest)");
|
||||
}
|
||||
if (ver.is_installed) {
|
||||
label += " " + _L("(installed)");
|
||||
}
|
||||
m_network_version_combo->Append(label);
|
||||
m_network_version_combo->Append(network_version_label(ver));
|
||||
if (current_version == ver.version) {
|
||||
current_selection = i;
|
||||
}
|
||||
|
||||
@@ -776,6 +776,14 @@ void BBLNetworkPlugin::clear_all_function_pointers()
|
||||
}
|
||||
|
||||
std::vector<NetworkLibraryVersionInfo> get_all_available_versions()
|
||||
{
|
||||
// get_version() reports the "00.00.00.00" sentinel when nothing is loaded; resolve
|
||||
// that here so the list builder only ever sees a real version or an empty string.
|
||||
const BBLNetworkPlugin& plugin = BBLNetworkPlugin::instance();
|
||||
return get_all_available_versions(plugin.is_loaded() ? plugin.get_version() : std::string());
|
||||
}
|
||||
|
||||
std::vector<NetworkLibraryVersionInfo> get_all_available_versions(const std::string& loaded_version)
|
||||
{
|
||||
std::vector<NetworkLibraryVersionInfo> result;
|
||||
std::set<std::string> known_base_versions;
|
||||
@@ -789,118 +797,58 @@ std::vector<NetworkLibraryVersionInfo> get_all_available_versions()
|
||||
|
||||
std::vector<std::string> discovered = BBLNetworkPlugin::scan_plugin_versions();
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> suffixed_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.
|
||||
for (const auto& version : discovered) {
|
||||
if (all_known_versions.count(version) > 0)
|
||||
continue;
|
||||
|
||||
std::string base = extract_base_version(version);
|
||||
std::string suffix = extract_suffix(version);
|
||||
|
||||
if (suffix.empty())
|
||||
continue;
|
||||
|
||||
if (known_base_versions.count(base) == 0)
|
||||
continue;
|
||||
|
||||
suffixed_versions.emplace_back(base, version);
|
||||
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));
|
||||
}
|
||||
all_known_versions.insert(version);
|
||||
}
|
||||
|
||||
std::sort(suffixed_versions.begin(), suffixed_versions.end(),
|
||||
[](const auto& a, const auto& b) {
|
||||
if (a.first != b.first) return a.first > b.first;
|
||||
return a.second < b.second;
|
||||
// Newest first. Version components are fixed-width and zero-padded, so a plain
|
||||
// string compare orders them numerically, and the legacy series sorts last on its
|
||||
// own. Suffixed dev builds sort directly under the base version they build on.
|
||||
std::sort(result.begin(), result.end(),
|
||||
[](const NetworkLibraryVersionInfo& a, const NetworkLibraryVersionInfo& b) {
|
||||
if (a.base_version != b.base_version) return a.base_version > b.base_version;
|
||||
return a.suffix < b.suffix;
|
||||
});
|
||||
|
||||
for (const auto& [base, full] : suffixed_versions) {
|
||||
size_t insert_pos = 0;
|
||||
for (size_t i = 0; i < result.size(); ++i) {
|
||||
if (result[i].base_version == base) {
|
||||
insert_pos = i + 1;
|
||||
while (insert_pos < result.size() &&
|
||||
result[insert_pos].base_version == base) {
|
||||
++insert_pos;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string sfx = extract_suffix(full);
|
||||
result.insert(result.begin() + insert_pos,
|
||||
NetworkLibraryVersionInfo::from_discovered(full, base, sfx));
|
||||
}
|
||||
|
||||
// Also surface discovered versions WITHOUT a suffix (typically installed by the
|
||||
// OTA plug-in update) when they belong to a whitelisted release series: the plugin
|
||||
// ABI is stable within an AA.BB.CC series (the OTA sync only ever accepts same-series
|
||||
// updates), so these load with the same struct layout as the static entry they follow.
|
||||
// The legacy entry is excluded as an anchor - is_legacy_version() matches exactly, so
|
||||
// a different 01.x build would be loaded with the modern layout and break.
|
||||
std::vector<std::pair<std::string, std::string>> series_versions; // {anchor whitelist version, discovered version}
|
||||
for (const auto& version : discovered) {
|
||||
if (all_known_versions.count(version) > 0)
|
||||
continue;
|
||||
if (!extract_suffix(version).empty())
|
||||
continue;
|
||||
if (version.size() < 8)
|
||||
continue;
|
||||
|
||||
for (const auto& base : known_base_versions) {
|
||||
if (BBLNetworkPlugin::is_legacy_version(base))
|
||||
continue;
|
||||
if (base.size() >= 8 && base.compare(0, 8, version, 0, 8) == 0) {
|
||||
series_versions.emplace_back(base, version);
|
||||
all_known_versions.insert(version);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ascending sort + insertion right behind the anchor leaves the newest build
|
||||
// closest to its whitelist entry.
|
||||
std::sort(series_versions.begin(), series_versions.end(),
|
||||
[](const auto& a, const auto& b) {
|
||||
if (a.first != b.first) return a.first > b.first;
|
||||
return a.second < b.second;
|
||||
});
|
||||
|
||||
for (const auto& [anchor, full] : series_versions) {
|
||||
size_t insert_pos = result.size();
|
||||
for (size_t i = 0; i < result.size(); ++i) {
|
||||
if (result[i].version == anchor) {
|
||||
// Skip past the whole base-version block (the entry itself plus any
|
||||
// suffixed dev builds shown under it) so the tree glyphs stay adjacent.
|
||||
insert_pos = i + 1;
|
||||
while (insert_pos < result.size() &&
|
||||
result[insert_pos].base_version == anchor) {
|
||||
++insert_pos;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
result.insert(result.begin() + insert_pos,
|
||||
NetworkLibraryVersionInfo::from_discovered(full, full, ""));
|
||||
}
|
||||
|
||||
// Final pass: record what is actually present on disk, and put the "(Latest)"
|
||||
// label on the truly highest full version in the list - an OTA-installed build
|
||||
// can be newer than the newest whitelisted entry. get_latest_network_version()
|
||||
// intentionally keeps returning the static whitelist default, which drives the
|
||||
// download and update-check decisions.
|
||||
size_t highest = result.size();
|
||||
for (size_t i = 0; i < result.size(); ++i) {
|
||||
auto& info = result[i];
|
||||
info.is_installed = info.is_discovered ||
|
||||
BBLNetworkPlugin::versioned_library_exists(info.version);
|
||||
for (auto& info : result) {
|
||||
info.is_loaded = !loaded_version.empty() && info.version == loaded_version;
|
||||
info.is_latest = false;
|
||||
if (info.suffix.empty() &&
|
||||
(highest == result.size() || info.version > result[highest].version))
|
||||
highest = i;
|
||||
}
|
||||
if (highest < result.size())
|
||||
result[highest].is_latest = true;
|
||||
|
||||
// "(Latest)" goes on the highest full version in the list, which after the sort is
|
||||
// simply the first entry without a dev suffix - an OTA-installed build can be newer
|
||||
// than the newest whitelisted entry. get_latest_network_version() intentionally
|
||||
// keeps returning the static whitelist default, which drives the download and
|
||||
// update-check decisions.
|
||||
auto latest = std::find_if(result.begin(), result.end(),
|
||||
[](const NetworkLibraryVersionInfo& info) { return info.suffix.empty(); });
|
||||
if (latest != result.end())
|
||||
latest->is_latest = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -406,8 +406,10 @@ struct NetworkLibraryVersionInfo {
|
||||
bool is_latest;
|
||||
std::string warning;
|
||||
bool is_discovered;
|
||||
// Whether the versioned library file for this entry is present on disk.
|
||||
bool is_installed = false;
|
||||
// Whether this is the build currently loaded in this session. Deliberately not
|
||||
// "present on disk": switching versions leaves the previous library in place, so
|
||||
// an on-disk test marks every version ever selected.
|
||||
bool is_loaded = false;
|
||||
|
||||
static NetworkLibraryVersionInfo from_static(const NetworkLibraryVersion& v) {
|
||||
return {
|
||||
@@ -416,7 +418,7 @@ struct NetworkLibraryVersionInfo {
|
||||
"",
|
||||
v.display_name,
|
||||
v.url_override ? v.url_override : "",
|
||||
v.is_latest,
|
||||
false, // assigned by get_all_available_versions() once the list is sorted
|
||||
v.warning ? v.warning : "",
|
||||
false
|
||||
};
|
||||
@@ -439,7 +441,12 @@ inline std::string extract_suffix(const std::string& full_version) {
|
||||
return (pos == std::string::npos) ? "" : full_version.substr(pos + 1);
|
||||
}
|
||||
|
||||
// Selectable versions, newest first. Marks the entry matching the plug-in currently
|
||||
// loaded in this session.
|
||||
std::vector<NetworkLibraryVersionInfo> get_all_available_versions();
|
||||
// Same list, resolving is_loaded against an explicitly supplied version rather than the
|
||||
// live plug-in. Pass an empty string for "nothing loaded".
|
||||
std::vector<NetworkLibraryVersionInfo> get_all_available_versions(const std::string& loaded_version);
|
||||
|
||||
struct NetworkLibraryLoadError {
|
||||
bool has_error = false;
|
||||
|
||||
@@ -75,33 +75,53 @@ TEST_CASE_METHOD(PluginFolderFixture, "Same-series OTA plugin versions are surfa
|
||||
REQUIRE(count_version(versions, "02.08.01.52-custom") == 1);
|
||||
REQUIRE(count_version(versions, "02.03.00.62") == 0);
|
||||
|
||||
size_t latest_pos = versions.size(), ota_pos = versions.size();
|
||||
for (size_t i = 0; i < versions.size(); ++i) {
|
||||
if (versions[i].version == "02.08.01.52") latest_pos = i;
|
||||
if (versions[i].version == "02.08.01.55") ota_pos = i;
|
||||
}
|
||||
REQUIRE(latest_pos < versions.size());
|
||||
// The discovered build lands right after the whole 02.08.01.52 block
|
||||
// (the whitelist entry plus its suffixed dev build).
|
||||
REQUIRE(ota_pos == latest_pos + 2);
|
||||
REQUIRE(versions[ota_pos - 1].base_version == "02.08.01.52");
|
||||
REQUIRE(versions[ota_pos - 1].version == "02.08.01.52-custom");
|
||||
// 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.
|
||||
REQUIRE(versions.back().version == BAMBU_NETWORK_AGENT_VERSION_LEGACY);
|
||||
|
||||
const auto& ota = versions[ota_pos];
|
||||
const auto& ota = versions[0];
|
||||
REQUIRE(ota.is_discovered);
|
||||
REQUIRE(ota.suffix.empty());
|
||||
|
||||
// "(Latest)" is dynamic: the OTA build is the highest listed version, so it takes
|
||||
// the label from the static whitelist entry - and it is also marked installed.
|
||||
// the label from the static whitelist entry.
|
||||
REQUIRE(ota.is_latest);
|
||||
REQUIRE(ota.is_installed);
|
||||
REQUIRE_FALSE(versions[latest_pos].is_latest);
|
||||
REQUIRE_FALSE(versions[latest_pos].is_installed);
|
||||
REQUIRE_FALSE(versions[1].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");
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(PluginFolderFixture, "Only the loaded build 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");
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
REQUIRE(marked == 1);
|
||||
|
||||
// Nothing loaded (plug-in disabled or failed to load) marks nothing, even though
|
||||
// both 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.
|
||||
@@ -144,7 +164,7 @@ TEST_CASE_METHOD(PluginFolderFixture, "Legacy series never adopts discovered bui
|
||||
for (const auto& info : versions) {
|
||||
if (info.version == "02.08.01.52") {
|
||||
REQUIRE(info.is_latest);
|
||||
REQUIRE_FALSE(info.is_installed);
|
||||
REQUIRE_FALSE(info.is_loaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user