Cut redundant work and duplication from the preset cache paths

The schema fingerprint, filament library version and cache blob are no
longer recomputed, re-read or copied once per vendor on the startup path,
and the setup wizard's completeness check now sees vendors shipped as
caches alone. Duplicated reset/join/slurp blocks are folded into helpers.
This commit is contained in:
SoftFever
2026-07-29 21:56:17 +08:00
parent e5aa9a8cfc
commit ba5ecdfea9
9 changed files with 145 additions and 115 deletions

View File

@@ -6,7 +6,6 @@
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <boost/program_options.hpp>
#include <boost/system/error_code.hpp>
#include <iostream>
using namespace Slic3r;

View File

@@ -27,6 +27,9 @@
#include <cereal/access.hpp>
#include <cereal/types/base_class.hpp>
// The serialize() members below archive ConfigOption hierarchies through
// cereal::base_class, whose registration machinery lives in polymorphic.hpp.
#include <cereal/types/polymorphic.hpp>
namespace Slic3r {
struct FloatOrPercent

View File

@@ -16,13 +16,7 @@
#include "Semver.hpp"
#include "ProjectTask.hpp"
#include <cereal/archives/binary.hpp>
#include <cereal/cereal.hpp>
#include <cereal/types/map.hpp>
#include <cereal/types/polymorphic.hpp>
#include <cereal/types/set.hpp>
#include <cereal/types/string.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/access.hpp>
//BBS: change system directories
#define PRESET_SYSTEM_DIR "system"

View File

@@ -6,8 +6,11 @@
#include "PresetBundle.hpp"
#include <boost/crc.hpp>
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
#include <cereal/archives/binary.hpp>
#include <cereal/types/map.hpp>
#include <cereal/types/set.hpp>
#include <cereal/types/string.hpp>
#include <cereal/types/vector.hpp>
#include "PrintConfig.hpp"
@@ -2281,6 +2284,15 @@ Semver installed_vendor_version(const std::string& vendor)
return ver ? *ver : Semver();
}
void remove_installed_vendor(const std::string& vendor)
{
const boost::filesystem::path dir = boost::filesystem::path(data_dir()) / PRESET_SYSTEM_DIR;
boost::filesystem::remove(dir / (vendor + ".json"));
boost::filesystem::remove(dir / (vendor + ".opc"));
if (boost::filesystem::exists(dir / vendor))
boost::filesystem::remove_all(dir / vendor);
}
std::set<std::string> vendor_names_in(const boost::filesystem::path& dir)
{
std::set<std::string> names;
@@ -2298,21 +2310,21 @@ std::set<std::string> vendor_names_in(const boost::filesystem::path& dir)
// generated before that profile was bumped is out of date, and a cache that cannot
// be read is no installation at all — and the vendor is installed the way it was
// before caches existed, as its profile and the preset JSONs it points at. Returns
// the version the cache is stamped with, or nothing when it is not the form to install.
static std::string installable_cache_version(const boost::filesystem::path& dir, const std::string& vendor)
// the version the cache is stamped with, invalid when it is not the form to install.
static Semver installable_cache_version(const boost::filesystem::path& dir, const std::string& vendor)
{
const auto cache_ver = Semver::parse(PresetBundle::peek_vendor_cache_version((dir / (vendor + ".opc")).string(), vendor));
if (! cache_ver)
return {};
return Semver::invalid();
const Semver profile_ver = get_version_from_json((dir / (vendor + ".json")).string());
return profile_ver.valid() && *cache_ver < profile_ver ? std::string() : cache_ver->to_string();
return profile_ver.valid() && *cache_ver < profile_ver ? Semver::invalid() : *cache_ver;
}
Semver resource_vendor_version(const std::string& vendor)
{
const boost::filesystem::path dir = boost::filesystem::path(resources_dir()) / "profiles";
const auto ver = Semver::parse(installable_cache_version(dir, vendor));
return ver ? *ver : get_version_from_json((dir / (vendor + ".json")).string());
const Semver ver = installable_cache_version(dir, vendor);
return ver.valid() ? ver : get_version_from_json((dir / (vendor + ".json")).string());
}
bool install_vendor_bundles_from_resources(
@@ -2347,7 +2359,7 @@ bool install_vendor_bundles_from_resources(
std::string error_message;
bool installed_cache = false;
if (! installable_cache_version(rsrc_path, bundle).empty()) {
if (installable_cache_version(rsrc_path, bundle).valid()) {
installed_cache = copy_file(cache_in_rsrc.string(), cache_in_vendors.string(), error_message, false) == CopyFileResult::SUCCESS;
if (! installed_cache)
BOOST_LOG_TRIVIAL(warning) << "Failed to copy " << bundle << ".opc: " << error_message;
@@ -2405,6 +2417,17 @@ bool install_vendor_bundles_from_resources(
return true;
}
// m_printer_hold_alias survives reset() (and a cache body that failed partway
// in), so every full-bundle rebuild clears all five collections' maps by hand.
void PresetBundle::clear_printer_hold_aliases()
{
this->prints.m_printer_hold_alias.clear();
this->sla_prints.m_printer_hold_alias.clear();
this->filaments.m_printer_hold_alias.clear();
this->sla_materials.m_printer_hold_alias.clear();
this->printers.m_printer_hold_alias.clear();
}
//BBS: add json related logic, load system presets from json
std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule)
{
@@ -2456,11 +2479,7 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
// Match a fresh launch before parsing: hold aliases and the error
// counter survive reset(), and would otherwise leak prior-cycle
// state into the library cache the load below writes.
this->prints.m_printer_hold_alias.clear();
this->sla_prints.m_printer_hold_alias.clear();
this->filaments.m_printer_hold_alias.clear();
this->sla_materials.m_printer_hold_alias.clear();
this->printers.m_printer_hold_alias.clear();
this->clear_printer_hold_aliases();
this->m_errors = 0;
append(substitutions, this->load_vendor_configs_from_json(dir.string(), orca_lib_vendor, PresetBundle::LoadSystem, compatibility_rule).first);
first = false;
@@ -2479,6 +2498,11 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
std::vector<PresetsConfigSubstitutions> parallel_substitutions(other_vendors.size());
std::vector<std::string> parallel_errors(other_vendors.size());
// The filament library version every vendor below is judged against. Fixed
// from here on — step 1 was the last thing that could touch the library on
// disk — so resolve it once instead of once per vendor.
const std::string lib_version = effective_lib_version(dir);
tbb::parallel_for(tbb::blocked_range<size_t>(0, other_vendors.size()),
[&](const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
@@ -2487,7 +2511,7 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
bundle->set_generate_vendor_caches(m_generate_vendor_caches);
try {
auto result = bundle->load_vendor_configs_from_json(
dir.string(), other_vendors[i], PresetBundle::LoadSystem, compatibility_rule, this);
dir.string(), other_vendors[i], PresetBundle::LoadSystem, compatibility_rule, this, lib_version);
parallel_substitutions[i] = std::move(result.first);
parallel_bundles[i] = std::move(bundle);
} catch (const std::runtime_error &err) {
@@ -4953,7 +4977,8 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
//BBS: Load a config bundle file from json
std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_from_json(
const std::string &dir, const std::string &vendor_name, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule, const PresetBundle* base_bundle)
const std::string &dir, const std::string &vendor_name, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule, const PresetBundle* base_bundle,
const std::string &lib_version_hint)
{
// Enable substitutions for user config bundle, throw an exception when loading a system profile.
ConfigSubstitutionContext substitution_context { compatibility_rule };
@@ -4969,7 +4994,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
// scans want a slice of one. Validation reads the JSONs whatever is cached.
const boost::filesystem::path dir_path(dir);
const bool cacheable = flags.has(LoadConfigBundleAttribute::LoadSystem) && ! flags.has(LoadConfigBundleAttribute::LoadFilamentOnly);
if (cacheable && ! validation_mode && this->load_vendor_cache(dir_path, vendor_name)) {
if (cacheable && ! validation_mode && this->load_vendor_cache(dir_path, vendor_name, lib_version_hint)) {
size_t presets_loaded = 0;
for (const PresetCollection* coll : std::initializer_list<const PresetCollection*>{
&this->prints, &this->sla_prints, &this->filaments, &this->sla_materials, &this->printers })
@@ -5550,7 +5575,8 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
const std::string version = vendor_profile.config_version.to_string();
// The library is its own reference point; every other vendor's cache holds
// filaments resolved against it, and is stamped with the version in effect.
const std::string lib_version = vendor_name == ORCA_FILAMENT_LIBRARY ? version : effective_lib_version(dir_path);
const std::string lib_version = vendor_name == ORCA_FILAMENT_LIBRARY ? version
: ! lib_version_hint.empty() ? lib_version_hint : effective_lib_version(dir_path);
if (! lib_version.empty() &&
! this->save_vendor_cache((dir_path / (vendor_name + ".opc")).string(), vendor_name, version, lib_version))
BOOST_LOG_TRIVIAL(warning) << "PresetBundle: failed to save vendor cache for " << vendor_name;
@@ -6218,21 +6244,26 @@ static bool cache_covers_version(const std::string& cached, const std::string& o
// values — serialization_key_ordinal IS the config wire format). Any mismatch
// means bytes written by another build could deserialize into the wrong
// fields, so the cache is rejected wholesale before its body is read.
std::string compute_cache_schema_fingerprint()
const std::string& compute_cache_schema_fingerprint()
{
std::string schema;
schema += SLIC3R_VERSION;
schema += ';';
for (const auto& [key, def] : print_config_def.options) { // std::map => stable order
schema += key;
schema += '#'; schema += std::to_string(int(def.type));
schema += '@'; schema += std::to_string(def.serialization_key_ordinal);
for (const std::string& ev : def.enum_values) { schema += ','; schema += ev; }
// Constant for the lifetime of the process (print_config_def is immutable
// after static initialization), and asked for once per cache load and save.
static const std::string fingerprint = [] {
std::string schema;
schema += SLIC3R_VERSION;
schema += ';';
}
boost::crc_32_type crc;
crc.process_bytes(schema.data(), schema.size());
return std::to_string(crc.checksum());
for (const auto& [key, def] : print_config_def.options) { // std::map => stable order
schema += key;
schema += '#'; schema += std::to_string(int(def.type));
schema += '@'; schema += std::to_string(def.serialization_key_ordinal);
for (const std::string& ev : def.enum_values) { schema += ','; schema += ev; }
schema += ';';
}
boost::crc_32_type crc;
crc.process_bytes(schema.data(), schema.size());
return std::to_string(crc.checksum());
}();
return fingerprint;
}
} // anonymous namespace
@@ -6268,7 +6299,7 @@ bool PresetBundle::read_cache_blob(const std::string& path, std::string& out_blo
}
// static
void PresetBundle::write_cache_blob(const std::string& path, const std::string& blob)
bool PresetBundle::write_cache_blob(const std::string& path, const std::string& blob)
{
boost::crc_32_type crc;
crc.process_bytes(blob.data(), blob.size());
@@ -6277,7 +6308,7 @@ void PresetBundle::write_cache_blob(const std::string& path, const std::string&
boost::nowide::ofstream ofs(path, std::ios::binary | std::ios::trunc);
if (!ofs.is_open()) {
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: cannot open for writing: " << path;
return;
return false;
}
CacheFileHeader fhdr;
fhdr.magic = CACHE_MAGIC;
@@ -6286,8 +6317,13 @@ void PresetBundle::write_cache_blob(const std::string& path, const std::string&
fhdr.crc32 = crc.checksum();
ofs.write(reinterpret_cast<const char*>(&fhdr), sizeof(fhdr));
ofs.write(blob.data(), static_cast<std::streamsize>(blob.size()));
ofs.close(); // flush; close() raises failbit on error
if (! ofs.good())
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: write failed (" << path << ")";
return ofs.good();
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: write failed (" << path << "): " << e.what();
return false;
}
}
@@ -6373,9 +6409,7 @@ bool PresetBundle::save_vendor_cache(const std::string& cache_path, const std::s
this->obsolete_presets.printers);
ar(this->m_errors);
}
write_cache_blob(cache_path, body.str());
std::string verify;
return read_cache_blob(cache_path, verify); // magic + size + CRC
return write_cache_blob(cache_path, body.str());
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(warning) << "PresetBundle: failed to save vendor cache " << cache_path << ": " << e.what();
return false;
@@ -6415,7 +6449,7 @@ std::string PresetBundle::peek_vendor_cache_version(const std::string& cache_pat
}
}
bool PresetBundle::load_vendor_cache(const boost::filesystem::path& dir, const std::string& vendor_name)
bool PresetBundle::load_vendor_cache(const boost::filesystem::path& dir, const std::string& vendor_name, const std::string& lib_version_hint)
{
// Whichever cache answers is judged against the vendor as installed in `dir`:
// the profile there, or — with none, as when the cache is the whole of the
@@ -6425,7 +6459,7 @@ bool PresetBundle::load_vendor_cache(const boost::filesystem::path& dir, const s
const boost::filesystem::path profile = dir / (vendor_name + ".json");
const std::string version = boost::filesystem::exists(profile) ? get_vendor_cache_version(profile.string())
: std::string(CACHE_ANY_VERSION);
const std::string lib_version = effective_lib_version(dir);
const std::string lib_version = ! lib_version_hint.empty() ? lib_version_hint : effective_lib_version(dir);
const boost::filesystem::path rsrc = boost::filesystem::path(resources_dir()) / "profiles";
return this->load_vendor_cache((dir / (vendor_name + ".opc")).string(), vendor_name, version, lib_version)
|| (dir != rsrc && this->load_vendor_cache((rsrc / (vendor_name + ".opc")).string(), vendor_name, version, lib_version));
@@ -6438,7 +6472,9 @@ bool PresetBundle::load_vendor_cache(const std::string& cache_path, const std::s
if (! read_cache_blob(cache_path, blob))
return false;
try {
std::istringstream body(blob, std::ios::binary);
// Read in place: an istringstream would copy the blob (tens of MB for
// the largest vendors) once more just to stream over it.
boost::iostreams::stream<boost::iostreams::array_source> body(blob.data(), blob.size());
cereal::BinaryInputArchive ar(body);
uint32_t cache_version = 0;
ar(cache_version);
@@ -6474,14 +6510,10 @@ bool PresetBundle::load_vendor_cache(const std::string& cache_path, const std::s
this->m_config_maps.clear();
this->m_filament_id_maps.clear();
this->m_errors = 0;
// reset() does not clear m_printer_hold_alias, and a mid-body failure may
// have left collections it never reached (each load_collection clears its
// own collection's map only when it runs) with stale aliases.
this->prints.m_printer_hold_alias.clear();
this->sla_prints.m_printer_hold_alias.clear();
this->filaments.m_printer_hold_alias.clear();
this->sla_materials.m_printer_hold_alias.clear();
this->printers.m_printer_hold_alias.clear();
// A mid-body failure may have left collections the deserialization never
// reached (each load_collection clears its own collection's map only when
// it runs) with stale aliases.
this->clear_printer_hold_aliases();
return false;
}
}

View File

@@ -14,6 +14,10 @@
#include <boost/filesystem/path.hpp>
#include <unordered_set>
namespace cereal {
class BinaryInputArchive;
class BinaryOutputArchive;
}
#define DEFAULT_USER_FOLDER_NAME "default"
#define BUNDLE_STRUCTURE_JSON_NAME "bundle_structure.json"
@@ -488,8 +492,11 @@ public:
// not the profile JSONs are still there. A whole-vendor load comes from the
// vendor's preset cache whenever one covers the profile on disk, and is parsed
// from the JSONs (falling back to the ones in resources) only when none does.
// `lib_version_hint` is the filament library version in effect, when the caller
// loads many vendors and has already resolved it once; empty resolves it here.
std::pair<PresetsConfigSubstitutions, size_t> load_vendor_configs_from_json(
const std::string &dir, const std::string &vendor_name, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule, const PresetBundle* base_bundle = nullptr);
const std::string &dir, const std::string &vendor_name, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule, const PresetBundle* base_bundle = nullptr,
const std::string &lib_version_hint = {});
// Export a config bundle file containing all the presets and the names of the active presets.
//void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false);
@@ -573,15 +580,17 @@ public:
private:
// Load one vendor from its preset cache: the one in `dir`, or — when that is
// missing or stale — the one shipped in resources/profiles, both judged against
// the vendor as installed in `dir` and against the filament library in effect.
// the vendor as installed in `dir` and against the filament library in effect
// (resolved here unless the caller passes the already-resolved version).
// False, with this bundle left clean, when neither is usable and the vendor has
// to be parsed. This is how load_vendor_configs_from_json reads a cache.
bool load_vendor_cache(const boost::filesystem::path& dir, const std::string& vendor_name);
bool load_vendor_cache(const boost::filesystem::path& dir, const std::string& vendor_name, const std::string& lib_version_hint = {});
// Read raw cache blob: verify magic, size, CRC.
static bool read_cache_blob(const std::string& path, std::string& out_blob);
// Write a cache blob with the standard 20-byte file header.
static void write_cache_blob(const std::string& path, const std::string& blob);
// Write a cache blob with the standard 20-byte file header. False when the
// file could not be opened or written whole.
static bool write_cache_blob(const std::string& path, const std::string& blob);
// (De)serialization of one collection's slice for the per-vendor cache:
// every non-default, non-external preset (system or user) plus
@@ -589,6 +598,9 @@ private:
static void save_collection(cereal::BinaryOutputArchive& ar, const PresetCollection& coll);
static void load_collection(cereal::BinaryInputArchive& ar, PresetCollection& coll, const VendorMap& vendors);
// Clear every collection's m_printer_hold_alias, which reset() leaves alone.
void clear_printer_hold_aliases();
// Whether to (re)write a per-vendor cache after a JSON parse.
bool m_generate_vendor_caches { false };
@@ -642,6 +654,10 @@ extern bool is_vendor_installed(const std::string& vendor);
// was stamped with where only the cache is installed. Invalid Semver if neither is.
extern Semver installed_vendor_version(const std::string& vendor);
// Remove every form `vendor` can be installed as from data_dir()/system: its
// profile, its preset cache, and its preset directory.
extern void remove_installed_vendor(const std::string& vendor);
// The vendors `dir` holds, sorted: one is named by its profile or, in a build that
// ships preset caches instead of the raw profile JSONs, by its cache alone.
extern std::set<std::string> vendor_names_in(const boost::filesystem::path& dir);

View File

@@ -1,6 +1,7 @@
#include "WebGuideDialog.hpp"
#include "ConfigWizard.hpp"
#include <boost/algorithm/string/join.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/filesystem/path.hpp>
@@ -190,8 +191,7 @@ GuideFrame::GuideFrame(GUI_App *pGUI, long style)
GuideFrame::~GuideFrame()
{
m_destroy = true;
*m_cancel_token = true; // signal any queued CallAfter lambdas before join
*m_cancel_token = true; // stop the loading thread and any queued CallAfter lambdas before join
if (m_load_task && m_load_task->joinable())
m_load_task->join();
m_load_task.reset();
@@ -300,13 +300,19 @@ void GuideFrame::OnNavigationRequest(wxWebViewEvent &evt)
/**
* Callback invoked when a navigation request was accepted
*/
void GuideFrame::init_guide_paths()
// The empty shape every profile-loading path starts from or falls back to.
void GuideFrame::reset_profile_json()
{
m_ProfileJson = json::parse("{}");
m_ProfileJson["model"] = json::array();
m_ProfileJson["machine"] = json::object();
m_ProfileJson["filament"] = json::object();
m_ProfileJson["process"] = json::array();
}
void GuideFrame::init_guide_paths()
{
m_ProfileJson = json::parse("{}");
reset_profile_json();
vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
@@ -349,7 +355,7 @@ void GuideFrame::OnNavigationComplete(wxWebViewEvent &evt)
try {
init_guide_paths();
if (BuildProfileDataFromPresetBundle()) {
if (!m_destroy)
if (!*m_cancel_token)
on_profile_loaded();
} else {
// Presets not yet in memory — delegate to background thread.
@@ -1184,11 +1190,7 @@ bool GuideFrame::BuildProfileJson(const PresetBundle& bundle, bool require_all_r
if (!nozzle_str.empty()) nozzle_str += ";";
nozzle_str += v.name;
}
std::string materials_str;
for (const auto& m : model.default_materials) {
if (!materials_str.empty()) materials_str += ";";
materials_str += m;
}
const std::string materials_str = boost::algorithm::join(model.default_materials, ";");
boost::filesystem::path cover_path =
(boost::filesystem::path(resources_dir()) / "profiles" / vp.id / (model.id + "_cover.png"))
.make_preferred();
@@ -1224,6 +1226,7 @@ bool GuideFrame::BuildProfileJson(const PresetBundle& bundle, bool require_all_r
}
// Filament map from system filament presets (vendor/type already resolved in config)
const json& machines = m_ProfileJson["machine"];
for (const Preset& p : bundle.filaments()) {
if (!p.is_system || !p.vendor) continue;
const auto* fila_vendor = p.config.option<ConfigOptionStrings>("filament_vendor");
@@ -1236,9 +1239,10 @@ bool GuideFrame::BuildProfileJson(const PresetBundle& bundle, bool require_all_r
std::string model_list;
if (compat_printers) {
for (const std::string& pname : compat_printers->values) {
if (m_ProfileJson["machine"].contains(pname)) {
std::string m = m_ProfileJson["machine"][pname]["model"];
std::string n = m_ProfileJson["machine"][pname]["nozzle"];
auto it = machines.find(pname);
if (it != machines.end()) {
const std::string m = (*it)["model"];
const std::string n = (*it)["nozzle"];
model_list += "[" + m + "++" + n + "]";
}
}
@@ -1264,20 +1268,16 @@ bool GuideFrame::BuildProfileJson(const PresetBundle& bundle, bool require_all_r
}
if (require_all_resource_vendors) {
// If rsrc_vendor_dir has vendor JSONs not covered by the current bundle, the
// If rsrc_vendor_dir has vendors (profile JSONs, or the preset caches a
// packaged build ships instead) not covered by the current bundle, the
// bundle is incomplete (e.g. dev env where data_dir/system only has
// OrcaFilamentLibrary+Custom). Fall back so LoadProfileFamily reads both dirs.
// OrcaFilamentLibrary+Custom). Fall back so the slow path reads both dirs.
try {
for (const auto& e : boost::filesystem::directory_iterator(rsrc_vendor_dir)) {
if (e.path().extension().string() != ".json") continue;
const std::string stem = e.path().stem().string();
if (bundle.vendors.find(stem) == bundle.vendors.end()) {
BOOST_LOG_TRIVIAL(info) << "GuideFrame: vendor '" << stem
for (const std::string& name : vendor_names_in(rsrc_vendor_dir)) {
if (bundle.vendors.find(name) == bundle.vendors.end()) {
BOOST_LOG_TRIVIAL(info) << "GuideFrame: vendor '" << name
<< "' in resources but not in preset_bundle — falling back to JSON loading";
m_ProfileJson["model"] = json::array();
m_ProfileJson["machine"] = json::object();
m_ProfileJson["filament"] = json::object();
m_ProfileJson["process"] = json::array();
reset_profile_json();
return false;
}
}
@@ -1292,10 +1292,7 @@ bool GuideFrame::BuildProfileJson(const PresetBundle& bundle, bool require_all_r
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(warning) << "GuideFrame::BuildProfileJson failed: " << e.what()
<< " — falling back to JSON loading";
m_ProfileJson["model"] = json::array();
m_ProfileJson["machine"] = json::object();
m_ProfileJson["filament"] = json::object();
m_ProfileJson["process"] = json::array();
reset_profile_json();
return false;
}
}
@@ -1340,7 +1337,7 @@ bool GuideFrame::BuildProfileDataFromVendors()
if (vendor_files.count(filament_library))
load_vendor(bundle, filament_library, nullptr);
for (const auto& entry : vendor_files) {
if (m_destroy)
if (*m_cancel_token)
return false; // as in the scan below: a vendor without a cache is parsed, and that takes time
const std::string& vendor = entry.first;
// A cache is only ever written for a versioned vendor; a JSON has to be
@@ -1358,10 +1355,7 @@ bool GuideFrame::BuildProfileDataFromVendors()
return BuildProfileJson(bundle, /*require_all_resource_vendors=*/false);
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed: " << e.what();
m_ProfileJson["model"] = json::array();
m_ProfileJson["machine"] = json::object();
m_ProfileJson["filament"] = json::object();
m_ProfileJson["process"] = json::array();
reset_profile_json();
return false;
}
}
@@ -1395,7 +1389,7 @@ int GuideFrame::LoadProfileData()
LoadProfileFamily(w2s(strVendor), iter->path().string());
loaded_vendors.insert(w2s(strVendor));
}
if (m_destroy) return 0;
if (*m_cancel_token) return 0;
}
boost::filesystem::directory_iterator others_endIter;
@@ -1410,7 +1404,7 @@ int GuideFrame::LoadProfileData()
LoadProfileFamily(w2s(strVendor), iter->path().string());
loaded_vendors.insert(w2s(strVendor));
}
if (m_destroy) return 0;
if (*m_cancel_token) return 0;
}
}

View File

@@ -87,6 +87,7 @@ public:
bool BuildProfileJson(const PresetBundle& bundle, bool require_all_resource_vendors);
bool BuildProfileDataFromPresetBundle();
bool BuildProfileDataFromVendors();
void reset_profile_json();
int SaveProfile();
int GetFilamentInfo( std::string VendorDirectory,json & pFilaList, std::string filepath, std::string &sVendor, std::string &sType);
@@ -121,9 +122,9 @@ private:
//First Load
bool bFirstComplete{false};
std::atomic<bool> m_destroy{false};
// Shared cancel token captured by CallAfter lambdas so they don't touch
// `this` after the destructor has run and the object is freed.
// Set once in the destructor. Read through `this` by the loading thread
// (joined before `this` dies) and captured as the shared_ptr by CallAfter
// lambdas so they don't touch `this` after the object is freed.
std::shared_ptr<std::atomic<bool>> m_cancel_token{std::make_shared<std::atomic<bool>>(false)};
std::unique_ptr<boost::thread> m_load_task;

View File

@@ -1069,12 +1069,7 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const
}
else {
//need to be removed because not installed
const auto path_in_vendor = vendor_path / (vendor_name + ".json");
fs::remove(path_in_vendor);
fs::remove(vendor_path / (vendor_name + ".opc"));
const auto path_of_vendor = vendor_path / vendor_name;
if (fs::exists(path_of_vendor))
fs::remove_all(path_of_vendor);
remove_installed_vendor(vendor_name);
}
}
else if (is_vendor_enabled) {

View File

@@ -54,6 +54,14 @@ std::string write_versionless_vendor_json(const fs::path& dir, const std::string
return p.string();
}
// Whole file as bytes, for the byte-identity comparisons below.
std::string slurp(const fs::path& p)
{
std::string s;
load_string_file(p, s);
return s;
}
void corrupt_blob_byte(const std::string& path)
{
std::fstream f(path, std::ios::in | std::ios::out | std::ios::binary);
@@ -907,10 +915,6 @@ TEST_CASE("printer hold aliases survive a cache round-trip", "[VendorCache]")
REQUIRE(loaded.load_vendor_cache(cache1.string(), vid, "1.0.0","1.0.0"));
REQUIRE(save_one_vendor(loaded, cache2.string(), vid, "1.0.0"));
auto slurp = [](const fs::path& p) {
std::ifstream ifs(p.string(), std::ios::binary);
return std::string(std::istreambuf_iterator<char>(ifs), {});
};
REQUIRE(slurp(cache1) == slurp(cache2));
}
@@ -964,10 +968,6 @@ TEST_CASE("a loaded cache re-serializes to byte-identical output", "[VendorCache
PresetBundle loaded;
REQUIRE(loaded.load_vendor_cache(cache1.string(), "Acme", "1.0.0", "1.0.0"));
REQUIRE(loaded.save_vendor_cache(cache2.string(), "Acme", "1.0.0", "1.0.0"));
auto slurp = [](const fs::path& p) {
std::ifstream ifs(p.string(), std::ios::binary);
return std::string(std::istreambuf_iterator<char>(ifs), {});
};
REQUIRE(slurp(cache1) == slurp(cache2));
}
@@ -1023,11 +1023,7 @@ TEST_CASE("a cache that fails mid-body deserialization is rejected and leaves th
PresetBundle clean;
REQUIRE(out.save_vendor_cache(out_after.string(), vid, "1.0.0","1.0.0"));
REQUIRE(clean.save_vendor_cache(clean_ref.string(), vid, "1.0.0","1.0.0"));
auto slurp2 = [](const fs::path& p) {
std::ifstream ifs(p.string(), std::ios::binary);
return std::string(std::istreambuf_iterator<char>(ifs), {});
};
CHECK(slurp2(out_after) == slurp2(clean_ref));
CHECK(slurp(out_after) == slurp(clean_ref));
// The recovery must leave a bundle a caller can still load a good cache into.
REQUIRE(out.load_vendor_cache(valid_cache.string(), vid, "1.0.0","1.0.0"));