mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-14 04:27:36 +00:00
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:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user