diff --git a/src/dev-utils/generate_system_cache.cpp b/src/dev-utils/generate_system_cache.cpp index 23bf66fbdc..426ccee997 100644 --- a/src/dev-utils/generate_system_cache.cpp +++ b/src/dev-utils/generate_system_cache.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include using namespace Slic3r; diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index da36321b6f..8df82dc92c 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -27,6 +27,9 @@ #include #include +// The serialize() members below archive ConfigOption hierarchies through +// cereal::base_class, whose registration machinery lives in polymorphic.hpp. +#include namespace Slic3r { struct FloatOrPercent diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 9955210e06..a2336beca3 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -16,13 +16,7 @@ #include "Semver.hpp" #include "ProjectTask.hpp" -#include -#include -#include -#include -#include -#include -#include +#include //BBS: change system directories #define PRESET_SYSTEM_DIR "system" diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index f093223842..49d790f9d5 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -6,8 +6,11 @@ #include "PresetBundle.hpp" #include +#include +#include #include #include +#include #include #include #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 vendor_names_in(const boost::filesystem::path& dir) { std::set names; @@ -2298,21 +2310,21 @@ std::set 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 PresetBundle::load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule) { @@ -2456,11 +2479,7 @@ std::pair 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 PresetBundle::load_system_pre std::vector parallel_substitutions(other_vendors.size()); std::vector 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(0, other_vendors.size()), [&](const tbb::blocked_range& range) { for (size_t i = range.begin(); i < range.end(); ++i) { @@ -2487,7 +2511,7 @@ std::pair 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 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 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{ &this->prints, &this->sla_prints, &this->filaments, &this->sla_materials, &this->printers }) @@ -5550,7 +5575,8 @@ std::pair 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(&fhdr), sizeof(fhdr)); ofs.write(blob.data(), static_cast(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 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; } } diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index bde0bbbeba..37998142a1 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -14,6 +14,10 @@ #include #include +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 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 vendor_names_in(const boost::filesystem::path& dir); diff --git a/src/slic3r/GUI/WebGuideDialog.cpp b/src/slic3r/GUI/WebGuideDialog.cpp index a076fb635e..8d63fbb531 100644 --- a/src/slic3r/GUI/WebGuideDialog.cpp +++ b/src/slic3r/GUI/WebGuideDialog.cpp @@ -1,6 +1,7 @@ #include "WebGuideDialog.hpp" #include "ConfigWizard.hpp" +#include #include #include #include @@ -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("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; } } diff --git a/src/slic3r/GUI/WebGuideDialog.hpp b/src/slic3r/GUI/WebGuideDialog.hpp index 71e2409ae5..b9592d03fe 100644 --- a/src/slic3r/GUI/WebGuideDialog.hpp +++ b/src/slic3r/GUI/WebGuideDialog.hpp @@ -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 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> m_cancel_token{std::make_shared>(false)}; std::unique_ptr m_load_task; diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 0e94d00ceb..2cab377c9d 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -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) { diff --git a/tests/libslic3r/test_vendor_cache.cpp b/tests/libslic3r/test_vendor_cache.cpp index c3b90fcde8..9b26f392ae 100644 --- a/tests/libslic3r/test_vendor_cache.cpp +++ b/tests/libslic3r/test_vendor_cache.cpp @@ -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(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(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(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"));