diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 655461ba51..b997c83fe1 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -36,6 +36,10 @@ static const std::string MODEL_PREFIX = "model:"; // are phased out, then we will revert to the original name. //static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version"; static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version2"; +// url to folder with profile archive zip +// TODO: Uncomment and delete 2nd when we have archive online +//static const std::string PROFILE_ARCHIVE_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Archive/Archive.zip"; +static const std::string PROFILE_ARCHIVE_URL = "https://raw.githubusercontent.com/kocikdav/PrusaSlicer-settings/master/live/Bundle/Archive.zip"; const std::string AppConfig::SECTION_FILAMENTS = "filaments"; const std::string AppConfig::SECTION_MATERIALS = "sla_materials"; @@ -648,6 +652,14 @@ std::string AppConfig::version_check_url() const return from_settings.empty() ? VERSION_CHECK_URL : from_settings; } +std::string AppConfig::profile_archive_url() const +{ + // Do we want to have settable url? + //auto from_settings = get("profile_archive_url"); + //return from_settings.empty() ? PROFILE_ARCHIVE_URL : from_settings; + return PROFILE_ARCHIVE_URL; +} + bool AppConfig::exists() { return boost::filesystem::exists(config_path()); diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index cd0f1a5aeb..69f3e3bfe7 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -139,6 +139,8 @@ public: // Get the Slic3r version check url. // This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file. std::string version_check_url() const; + // Get the Slic3r url to vendor profile archive zip. + std::string profile_archive_url() const; // Returns the original Slic3r version found in the ini file before it was overwritten // by the current version diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 009b0fd906..88512dcfce 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -159,6 +159,7 @@ void PresetBundle::setup_directories() data_dir, data_dir / "vendor", data_dir / "cache", + data_dir / "cache" / "vendor", data_dir / "shapes", #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR // Store the print/filament/printer presets into a "presets" directory. diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 9152a11242..23730ce488 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -65,10 +65,10 @@ using Config::SnapshotDB; // Configuration data structures extensions needed for the wizard -bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bundle) +bool Bundle::load(fs::path source_path, BundleLocation location, bool ais_prusa_bundle) { this->preset_bundle = std::make_unique(); - this->is_in_resources = ais_in_resources; + this->location = location; this->is_prusa_bundle = ais_prusa_bundle; std::string path_string = source_path.string(); @@ -96,7 +96,7 @@ bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bu Bundle::Bundle(Bundle &&other) : preset_bundle(std::move(other.preset_bundle)) , vendor_profile(other.vendor_profile) - , is_in_resources(other.is_in_resources) + , location(other.location) , is_prusa_bundle(other.is_prusa_bundle) { other.vendor_profile = nullptr; @@ -107,25 +107,33 @@ BundleMap BundleMap::load() BundleMap res; const auto vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / "vendor").make_preferred(); + const auto archive_dir = (boost::filesystem::path(Slic3r::data_dir()) / "cache" / "vendor").make_preferred(); const auto rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred(); + // Load Prusa bundle from the datadir/vendor directory or from datadir/cache/vendor (archive) or from resources/profiles. auto prusa_bundle_path = (vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); - auto prusa_bundle_rsrc = false; + BundleLocation prusa_bundle_loc = BundleLocation::IN_VENDOR; if (! boost::filesystem::exists(prusa_bundle_path)) { + prusa_bundle_path = (archive_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); + prusa_bundle_loc = BundleLocation::IN_ARCHIVE; + } + if (!boost::filesystem::exists(prusa_bundle_path)) { prusa_bundle_path = (rsrc_vendor_dir / PresetBundle::PRUSA_BUNDLE).replace_extension(".ini"); - prusa_bundle_rsrc = true; + prusa_bundle_loc = BundleLocation::IN_RESOURCES; } { Bundle prusa_bundle; - if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_rsrc, true)) + if (prusa_bundle.load(std::move(prusa_bundle_path), prusa_bundle_loc, true)) res.emplace(PresetBundle::PRUSA_BUNDLE, std::move(prusa_bundle)); } // Load the other bundles in the datadir/vendor directory - // and then additionally from resources/profiles. - bool is_in_resources = false; - for (auto dir : { &vendor_dir, &rsrc_vendor_dir }) { - for (const auto &dir_entry : boost::filesystem::directory_iterator(*dir)) { + // and then additionally from datadir/cache/vendor (archive) and resources/profiles. + // Should we concider case where archive has older profiles than resources (shouldnt happen)? + typedef std::pair DirData; + std::vector dir_list { {vendor_dir, BundleLocation::IN_VENDOR}, {archive_dir, BundleLocation::IN_ARCHIVE}, {rsrc_vendor_dir, BundleLocation::IN_RESOURCES} }; + for ( auto dir : dir_list) { + for (const auto &dir_entry : boost::filesystem::directory_iterator(dir.first)) { if (Slic3r::is_ini_file(dir_entry)) { std::string id = dir_entry.path().stem().string(); // stem() = filename() without the trailing ".ini" part @@ -133,12 +141,10 @@ BundleMap BundleMap::load() if (res.find(id) != res.end()) { continue; } Bundle bundle; - if (bundle.load(dir_entry.path(), is_in_resources)) + if (bundle.load(dir_entry.path(), dir.second)) res.emplace(std::move(id), std::move(bundle)); } } - - is_in_resources = true; } return res; @@ -2696,10 +2702,10 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese if (!check_unsaved_preset_changes) act_btns |= UnsavedChangesDialog::ActionButtons::SAVE; - // Install bundles from resources if needed: + // Install bundles from resources or cache / vendor if needed: std::vector install_bundles; for (const auto &pair : bundles) { - if (! pair.second.is_in_resources) { continue; } + if (pair.second.location == BundleLocation::IN_VENDOR) { continue; } if (pair.second.is_prusa_bundle) { // Always install Prusa bundle, because it has a lot of filaments/materials @@ -2764,12 +2770,12 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese return false; if (install_bundles.size() > 0) { - // Install bundles from resources. + // Install bundles from resources or cache / vendor. // Don't create snapshot - we've already done that above if applicable. if (! updater->install_bundles_rsrc(std::move(install_bundles), false)) return false; } else { - BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources"; + BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources or cache / vendor"; } if (page_welcome->reset_user_profile()) { diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index 0b4097758d..1d07a73f15 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -59,18 +59,25 @@ enum Technology { T_ANY = ~0, }; +enum BundleLocation{ + IN_VENDOR, + IN_ARCHIVE, + IN_RESOURCES +}; + struct Bundle { std::unique_ptr preset_bundle; VendorProfile* vendor_profile{ nullptr }; - bool is_in_resources{ false }; + //bool is_in_resources{ false }; + BundleLocation location; bool is_prusa_bundle{ false }; Bundle() = default; Bundle(Bundle&& other); // Returns false if not loaded. Reason for that is logged as boost::log error. - bool load(fs::path source_path, bool is_in_resources, bool is_prusa_bundle = false); + bool load(fs::path source_path, BundleLocation location, bool is_prusa_bundle = false); const std::string& vendor_id() const { return vendor_profile->id; } }; diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index cee611bdb4..bb3d91ea56 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -20,6 +20,7 @@ #include "libslic3r/format.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/miniz_extension.hpp" #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/UpdateDialogs.hpp" @@ -144,6 +145,7 @@ struct PresetUpdater::priv std::string version_check_url; fs::path cache_path; + fs::path cache_vendor_path; fs::path rsrc_path; fs::path vendor_path; @@ -168,6 +170,7 @@ struct PresetUpdater::priv PresetUpdater::priv::priv() : cache_path(fs::path(Slic3r::data_dir()) / "cache") + , cache_vendor_path(cache_path / "vendor") , rsrc_path(fs::path(resources_dir()) / "profiles") , vendor_path(fs::path(Slic3r::data_dir()) / "vendor") , cancel(false) @@ -234,44 +237,82 @@ void PresetUpdater::priv::prune_tmps() const // Download vendor indices. Also download new bundles if an index indicates there's a new one available. // Both are saved in cache. -void PresetUpdater::priv::sync_config(const VendorMap vendors) +void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string& profile_archive_url) { BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache"; if (!enabled_config_update) { return; } - // Donwload vendor preset bundles + // Download profiles archive zip + // dk: Do we want to return here on error? Or skip archive dwnld and unzip and work with previous run state cache / vendor? I think return. + // Any error here also doesnt show any info in UI. Do we want maybe notification? + fs::path archive_path(cache_path / "Archive.zip"); + if (profile_archive_url.empty()) { + BOOST_LOG_TRIVIAL(error) << "Downloading profile archive failed - url has no value."; + return; + } + BOOST_LOG_TRIVIAL(info) << "Downloading vedor profiles archive zip."; + //check if idx_url is leading to our site + if (!boost::starts_with(profile_archive_url, "http://files.prusa3d.com/wp-content/uploads/repository/") && + !boost::starts_with(profile_archive_url, "https://files.prusa3d.com/wp-content/uploads/repository/")) + { + BOOST_LOG_TRIVIAL(error) << "Unsafe url path for vedor profiles archive zip. Download is rejected."; + // TODO: this return must be uncommented when correct address is in use + //return; + } + if (!get_file(profile_archive_url, archive_path)) { + BOOST_LOG_TRIVIAL(error) << "Download of vedor profiles archive zip failed."; + return; + } + if (cancel) { return; } + + // Unzip archive to cache / vendor + mz_zip_archive archive; + mz_zip_zero_struct(&archive); + if (!open_zip_reader(&archive, archive_path.string())) { + BOOST_LOG_TRIVIAL(error) << "Couldn't open zipped bundle."; + return; + } else { + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); + // loop the entries + mz_zip_archive_file_stat stat; + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + if (stat.m_uncomp_size > 0) { + std::string buffer((size_t)stat.m_uncomp_size, 0); + mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0); + if (res == 0) { + BOOST_LOG_TRIVIAL(error) << "Failed to unzip " << stat.m_filename; + continue; + } + // create file from buffer + fs::path tmp_path(cache_vendor_path / (name + ".tmp")); + fs::path target_path(cache_vendor_path / name); + fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc); + file.write(buffer.c_str(), buffer.size()); + file.close(); + fs::rename(tmp_path, target_path); + } + } + } + close_zip_reader(&archive); + } + + // Update vendor preset bundles if in Vendor // Over all indices from the cache directory: for (auto &index : index_db) { if (cancel) { return; } const auto vendor_it = vendors.find(index.vendor()); if (vendor_it == vendors.end()) { - BOOST_LOG_TRIVIAL(warning) << "No such vendor: " << index.vendor(); + BOOST_LOG_TRIVIAL(info) << "No such vendor: " << index.vendor(); continue; } - const VendorProfile &vendor = vendor_it->second; - if (vendor.config_update_url.empty()) { - BOOST_LOG_TRIVIAL(info) << "Vendor has no config_update_url: " << vendor.name; - continue; - } - - // Download a fresh index - BOOST_LOG_TRIVIAL(info) << "Downloading index for vendor: " << vendor.name; - const auto idx_url = vendor.config_update_url + "/" + INDEX_FILENAME; const std::string idx_path = (cache_path / (vendor.id + ".idx")).string(); - const std::string idx_path_temp = idx_path + "-update"; - //check if idx_url is leading to our site - if (! boost::starts_with(idx_url, "http://files.prusa3d.com/wp-content/uploads/repository/") && - ! boost::starts_with(idx_url, "https://files.prusa3d.com/wp-content/uploads/repository/")) - { - BOOST_LOG_TRIVIAL(warning) << "unsafe url path for vendor \"" << vendor.name << "\" rejected: " << idx_url; - continue; - } - if (!get_file(idx_url, idx_path_temp)) { continue; } - if (cancel) { return; } - + const std::string idx_path_temp = (cache_vendor_path / (vendor.id + ".idx")).string(); + // Load the fresh index up { Index new_index; @@ -285,7 +326,8 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) BOOST_LOG_TRIVIAL(warning) << format("The downloaded index %1% for vendor %2% is older than the active one. Ignoring the downloaded index.", idx_path_temp, vendor.name); continue; } - Slic3r::rename_file(idx_path_temp, idx_path); + copy_file_fix(idx_path_temp, idx_path); + //if we rename path we need to change it in Index object too or create the object again //index = std::move(new_index); try { @@ -314,22 +356,24 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) recommended.to_string()); if (vendor.config_version >= recommended) { continue; } + + const auto path_in_archive = cache_vendor_path / (vendor.id + ".ini"); + const auto path_in_cache = cache_path / (vendor.id + ".ini"); + // Check version + if (!boost::filesystem::exists(path_in_archive)) + continue; + // vp is fully loaded to get all resources + auto vp = VendorProfile::from_ini(path_in_archive, true); + if (vp.config_version != recommended) + continue; + copy_file_fix(path_in_archive, path_in_cache); - // Download a fresh bundle - BOOST_LOG_TRIVIAL(info) << "Downloading new bundle for vendor: " << vendor.name; - const auto bundle_url = format("%1%/%2%.ini", vendor.config_update_url, recommended.to_string()); - const auto bundle_path = cache_path / (vendor.id + ".ini"); - if (! get_file(bundle_url, bundle_path)) { continue; } - if (cancel) { return; } - - // check the newly downloaded bundle for missing resources - // for that, the ini file must be parsed + // check the fresh bundle for missing resources + // for that, the ini file must be parsed (done above) auto check_and_get_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, const std::string& url, const fs::path& vendor_path, const fs::path& rsrc_path, const fs::path& cache_path){ if (!boost::filesystem::exists((vendor_path / (vendor + "/" + filename))) && !boost::filesystem::exists((rsrc_path / (vendor + "/" + filename)))) { BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; - // testing url (to be removed) - //const auto resource_url = format("https://raw.githubusercontent.com/prusa3d/PrusaSlicer/master/resources/profiles/%1%/%2%", vendor, filename); const auto resource_url = format("%1%/%2%/%3%", url, vendor, filename); const auto resource_path = (cache_path / (vendor + "/" + filename)); if (!fs::exists(resource_path.parent_path())) @@ -337,7 +381,6 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) self.get_file(resource_url, resource_path); } }; - auto vp = VendorProfile::from_ini(bundle_path, true); for (const auto& model : vp.models) { check_and_get_resource(vp.id, model.bed_texture, vendor.config_update_url, vendor_path, rsrc_path, cache_path); if (cancel) { return; } @@ -426,7 +469,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version bool current_not_supported = false; //if slcr is incompatible but situation is not downgrade, we do forced updated and this bool is information to do it if (ver_current_found && !ver_current->is_current_slic3r_supported()){ - if(ver_current->is_current_slic3r_downgrade()) { + if (ver_current->is_current_slic3r_downgrade()) { // "Reconfigure" situation. BOOST_LOG_TRIVIAL(warning) << "Current Slic3r incompatible with installed bundle: " << bundle_path.string(); updates.incompats.emplace_back(std::move(bundle_path), *ver_current, vp.name); @@ -694,8 +737,9 @@ void PresetUpdater::sync(PresetBundle *preset_bundle) // Unfortunatelly as of C++11, it needs to be copied again // into the closure (but perhaps the compiler can elide this). VendorMap vendors = preset_bundle->vendors; + std::string profile_archive_url =GUI::wxGetApp().app_config->profile_archive_url(); - p->thread = std::thread([this, vendors]() { + p->thread = std::thread([this, vendors, profile_archive_url]() { this->p->prune_tmps(); this->p->sync_config(std::move(vendors)); }); @@ -873,8 +917,26 @@ bool PresetUpdater::install_bundles_rsrc(std::vector bundles, bool for (const auto &bundle : bundles) { auto path_in_rsrc = (p->rsrc_path / bundle).replace_extension(".ini"); + auto path_in_cache_vendor = (p->cache_vendor_path / bundle).replace_extension(".ini"); auto path_in_vendors = (p->vendor_path / bundle).replace_extension(".ini"); - updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); + // find if in cache vendor is newer version than in resources + auto vp_cache = VendorProfile::from_ini(path_in_cache_vendor, false); + auto vp_rsrc = VendorProfile::from_ini(path_in_rsrc, false); + if (vp_cache.config_version > vp_rsrc.config_version) { + // in case we are installing from cache / vendor. we should also copy index to cache + // This needs to be done now bcs the current one would be missing this version on next start + auto path_idx_cache_vendor(path_in_cache_vendor); + path_idx_cache_vendor.replace_extension(".idx"); + auto path_idx_cache = (p->cache_path / bundle).replace_extension(".idx"); + // DK: do this during perform_updates() too? + if (fs::exists(path_idx_cache_vendor)) + copy_file_fix(path_idx_cache_vendor, path_idx_cache); + else // Should we dialog this? + BOOST_LOG_TRIVIAL(error) << GUI::format(_L("Couldn't locate idx file %1% when performing updates."), path_idx_cache_vendor.string()); + updates.updates.emplace_back(std::move(path_in_cache_vendor), std::move(path_in_vendors), Version(), "", ""); + + } else + updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", ""); } return p->perform_updates(std::move(updates), snapshot);