mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-25 11:52:05 +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));
|
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()
|
void NetworkPluginDownloadDialog::setup_version_selector()
|
||||||
{
|
{
|
||||||
m_version_combo = new ComboBox(this, wxID_ANY, wxEmptyString,
|
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_version_combo->SetFont(::Label::Body_13);
|
||||||
|
|
||||||
m_available_versions = get_all_available_versions();
|
m_available_versions = get_all_available_versions();
|
||||||
for (size_t i = 0; i < m_available_versions.size(); ++i) {
|
for (const auto& ver : m_available_versions)
|
||||||
const auto& ver = m_available_versions[i];
|
m_version_combo->Append(network_version_label(ver));
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_version_combo->SetSelection(0);
|
m_version_combo->SetSelection(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,12 @@
|
|||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace GUI {
|
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
|
class NetworkPluginDownloadDialog : public DPIDialog
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "Widgets/RadioGroup.hpp"
|
#include "Widgets/RadioGroup.hpp"
|
||||||
#include "slic3r/Utils/bambu_networking.hpp"
|
#include "slic3r/Utils/bambu_networking.hpp"
|
||||||
#include "slic3r/Utils/NetworkAgent.hpp"
|
#include "slic3r/Utils/NetworkAgent.hpp"
|
||||||
|
#include "NetworkPluginDialog.hpp"
|
||||||
#include "DownloadProgressDialog.hpp"
|
#include "DownloadProgressDialog.hpp"
|
||||||
|
|
||||||
#ifdef __WINDOWS__
|
#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++) {
|
for (size_t i = 0; i < m_available_versions.size(); i++) {
|
||||||
const auto& ver = m_available_versions[i];
|
const auto& ver = m_available_versions[i];
|
||||||
wxString label;
|
m_network_version_combo->Append(network_version_label(ver));
|
||||||
|
|
||||||
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);
|
|
||||||
if (current_version == ver.version) {
|
if (current_version == ver.version) {
|
||||||
current_selection = i;
|
current_selection = i;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -776,6 +776,14 @@ void BBLNetworkPlugin::clear_all_function_pointers()
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<NetworkLibraryVersionInfo> get_all_available_versions()
|
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::vector<NetworkLibraryVersionInfo> result;
|
||||||
std::set<std::string> known_base_versions;
|
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::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) {
|
for (const auto& version : discovered) {
|
||||||
if (all_known_versions.count(version) > 0)
|
if (all_known_versions.count(version) > 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
std::string base = extract_base_version(version);
|
const std::string suffix = extract_suffix(version);
|
||||||
std::string suffix = extract_suffix(version);
|
if (suffix.empty()) {
|
||||||
|
if (!is_supported_network_version(version))
|
||||||
if (suffix.empty())
|
continue;
|
||||||
continue;
|
result.push_back(NetworkLibraryVersionInfo::from_discovered(version, version, ""));
|
||||||
|
} else {
|
||||||
if (known_base_versions.count(base) == 0)
|
const std::string base = extract_base_version(version);
|
||||||
continue;
|
if (known_base_versions.count(base) == 0)
|
||||||
|
continue;
|
||||||
suffixed_versions.emplace_back(base, version);
|
result.push_back(NetworkLibraryVersionInfo::from_discovered(version, base, suffix));
|
||||||
|
}
|
||||||
all_known_versions.insert(version);
|
all_known_versions.insert(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::sort(suffixed_versions.begin(), suffixed_versions.end(),
|
// Newest first. Version components are fixed-width and zero-padded, so a plain
|
||||||
[](const auto& a, const auto& b) {
|
// string compare orders them numerically, and the legacy series sorts last on its
|
||||||
if (a.first != b.first) return a.first > b.first;
|
// own. Suffixed dev builds sort directly under the base version they build on.
|
||||||
return a.second < b.second;
|
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) {
|
for (auto& info : result) {
|
||||||
size_t insert_pos = 0;
|
info.is_loaded = !loaded_version.empty() && info.version == loaded_version;
|
||||||
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);
|
|
||||||
info.is_latest = false;
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -406,8 +406,10 @@ struct NetworkLibraryVersionInfo {
|
|||||||
bool is_latest;
|
bool is_latest;
|
||||||
std::string warning;
|
std::string warning;
|
||||||
bool is_discovered;
|
bool is_discovered;
|
||||||
// Whether the versioned library file for this entry is present on disk.
|
// Whether this is the build currently loaded in this session. Deliberately not
|
||||||
bool is_installed = false;
|
// "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) {
|
static NetworkLibraryVersionInfo from_static(const NetworkLibraryVersion& v) {
|
||||||
return {
|
return {
|
||||||
@@ -416,7 +418,7 @@ struct NetworkLibraryVersionInfo {
|
|||||||
"",
|
"",
|
||||||
v.display_name,
|
v.display_name,
|
||||||
v.url_override ? v.url_override : "",
|
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 : "",
|
v.warning ? v.warning : "",
|
||||||
false
|
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);
|
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();
|
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 {
|
struct NetworkLibraryLoadError {
|
||||||
bool has_error = false;
|
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.08.01.52-custom") == 1);
|
||||||
REQUIRE(count_version(versions, "02.03.00.62") == 0);
|
REQUIRE(count_version(versions, "02.03.00.62") == 0);
|
||||||
|
|
||||||
size_t latest_pos = versions.size(), ota_pos = versions.size();
|
// Newest first, regardless of whether a version came from the whitelist or from
|
||||||
for (size_t i = 0; i < versions.size(); ++i) {
|
// disk: the OTA build outranks the older whitelist entry it was discovered under.
|
||||||
if (versions[i].version == "02.08.01.52") latest_pos = i;
|
REQUIRE(versions[0].version == "02.08.01.55");
|
||||||
if (versions[i].version == "02.08.01.55") ota_pos = i;
|
REQUIRE(versions[1].version == "02.08.01.52");
|
||||||
}
|
// Suffixed dev builds stay nested directly under the base version they build on.
|
||||||
REQUIRE(latest_pos < versions.size());
|
REQUIRE(versions[2].version == "02.08.01.52-custom");
|
||||||
// The discovered build lands right after the whole 02.08.01.52 block
|
REQUIRE(versions[2].base_version == "02.08.01.52");
|
||||||
// (the whitelist entry plus its suffixed dev build).
|
// The legacy series is the oldest, so it sorts last.
|
||||||
REQUIRE(ota_pos == latest_pos + 2);
|
REQUIRE(versions.back().version == BAMBU_NETWORK_AGENT_VERSION_LEGACY);
|
||||||
REQUIRE(versions[ota_pos - 1].base_version == "02.08.01.52");
|
|
||||||
REQUIRE(versions[ota_pos - 1].version == "02.08.01.52-custom");
|
|
||||||
|
|
||||||
const auto& ota = versions[ota_pos];
|
const auto& ota = versions[0];
|
||||||
REQUIRE(ota.is_discovered);
|
REQUIRE(ota.is_discovered);
|
||||||
REQUIRE(ota.suffix.empty());
|
REQUIRE(ota.suffix.empty());
|
||||||
|
|
||||||
// "(Latest)" is dynamic: the OTA build is the highest listed version, so it takes
|
// "(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_latest);
|
||||||
REQUIRE(ota.is_installed);
|
REQUIRE_FALSE(versions[1].is_latest);
|
||||||
REQUIRE_FALSE(versions[latest_pos].is_latest);
|
|
||||||
REQUIRE_FALSE(versions[latest_pos].is_installed);
|
|
||||||
|
|
||||||
// The static default used for download and update-check decisions is unchanged.
|
// The static default used for download and update-check decisions is unchanged.
|
||||||
REQUIRE(std::string(get_latest_network_version()) == "02.08.01.52");
|
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]")
|
TEST_CASE("Only whitelisted series pass the load gate", "[NetworkVersions]")
|
||||||
{
|
{
|
||||||
// Exact whitelist entries.
|
// Exact whitelist entries.
|
||||||
@@ -144,7 +164,7 @@ TEST_CASE_METHOD(PluginFolderFixture, "Legacy series never adopts discovered bui
|
|||||||
for (const auto& info : versions) {
|
for (const auto& info : versions) {
|
||||||
if (info.version == "02.08.01.52") {
|
if (info.version == "02.08.01.52") {
|
||||||
REQUIRE(info.is_latest);
|
REQUIRE(info.is_latest);
|
||||||
REQUIRE_FALSE(info.is_installed);
|
REQUIRE_FALSE(info.is_loaded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user