diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 951cc7018d..e9d3b09ffd 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -205,6 +205,10 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem } model.bed_model = section.second.get("bed_model", ""); model.bed_texture = section.second.get("bed_texture", ""); + model.thumbnail = section.second.get("thumbnail", ""); + if (model.thumbnail.empty()) + model.thumbnail = model.id + "_thumbnail.png"; + if (! model.id.empty() && ! model.variants.empty()) res.models.push_back(std::move(model)); } @@ -2138,7 +2142,7 @@ namespace PresetUtils { std::string rsrc_folder = Slic3r::resources_dir() + "/profiles/" + vp.id + "/"; std::string cache_folder = Slic3r::data_dir() + "/cache/" + vp.id + "/"; for (const VendorProfile::PrinterModel& model : vp.models) { - for (const std::string& res : { model.bed_texture, model.bed_model, model.id + "_thumbnail.png" } ) { + for (const std::string& res : { model.bed_texture, model.bed_model, model.thumbnail } ) { if (! res.empty() && !fs::exists(fs::path(vendor_folder + res)) && !fs::exists(fs::path(rsrc_folder + res)) diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 234e9a0dc0..9a16a16a99 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -53,6 +53,7 @@ public: // Vendor & Printer Model specific print bed model & texture. std::string bed_model; std::string bed_texture; + std::string thumbnail; PrinterVariant* variant(const std::string &name) { for (auto &v : this->variants) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 2ecdca1ea0..2de72ad024 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -239,15 +239,23 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt } return false; }; - if (!load_bitmap(GUI::from_u8(Slic3r::data_dir() + "/vendor/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { - if (!load_bitmap(GUI::from_u8(Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png"), bitmap, bitmap_width)) { - BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead") - % (Slic3r::resources_dir() + "/profiles/" + vendor.id + "/" + model.id + "_thumbnail.png") - % vendor.id - % model.id; - load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width); + + bool found = false; + for (const std::string& res : { Slic3r::data_dir() + "/vendor/" + vendor.id + "/", Slic3r::resources_dir() + "/profiles/" + vendor.id + "/", Slic3r::data_dir() + "/cache/" + vendor.id + "/" } ) { + if (load_bitmap(GUI::from_u8(res + "/" + model.thumbnail), bitmap, bitmap_width)) { + found = true; + break; } } + + if (!found) { + BOOST_LOG_TRIVIAL(warning) << boost::format("Can't find bitmap file `%1%` for vendor `%2%`, printer `%3%`, using placeholder icon instead") + % model.thumbnail + % vendor.id + % model.id; + load_bitmap(Slic3r::var(PRINTER_PLACEHOLDER), bitmap, bitmap_width); + } + auto *title = new wxStaticText(this, wxID_ANY, from_u8(model.name), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); title->SetFont(font_name); const int wrap_width = std::max((int)MODEL_MIN_WRAP, bitmap_width); @@ -3053,7 +3061,9 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese if (install_bundles.size() > 0) { // Install bundles from resources or cache / vendor. // Don't create snapshot - we've already done that above if applicable. - if (! updater->install_bundles_rsrc_or_cache_vendor(std::move(install_bundles), false)) + + bool install_result = updater->install_bundles_rsrc_or_cache_vendor(std::move(install_bundles), false); + if (!install_result) return false; } else { BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources or cache / vendor"; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 3c561b91dd..6975006a62 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2999,6 +2999,8 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); if (reason == ConfigWizard::RR_USER) { + // Cancel sync before starting wizard to prevent two downloads at same time + preset_updater->cancel_sync(); if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED) return false; } diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 940d92c7f2..756fcb43ed 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -264,9 +264,12 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string BOOST_LOG_TRIVIAL(error) << "Download of vedor profiles archive zip failed."; return; } - if (cancel) { return; } + if (cancel) { + return; + } // Unzip archive to cache / vendor + std::vector vendors_only_in_archive; mz_zip_archive archive; mz_zip_zero_struct(&archive); if (!open_zip_reader(&archive, archive_path.string())) { @@ -293,22 +296,64 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string file.write(buffer.c_str(), buffer.size()); file.close(); fs::rename(tmp_path, target_path); + + if (name.substr(name.size() - 3) == "ini") + vendors_only_in_archive.push_back(name); } } } close_zip_reader(&archive); } + auto get_missing_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 (filename.empty() || vendor.empty()) + return; + + if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") && + !boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/")) + { + throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url)); + } + + const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename)); + const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename)); + const fs::path file_in_cache(cache_path / (vendor + "/" + filename)); + + if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything. + return; + } + if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything. + return; + } + + BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; + + const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", filename); // vendor should already be in url + + if (!fs::exists(file_in_cache.parent_path())) + fs::create_directory(file_in_cache.parent_path()); + + self.get_file(resource_url, file_in_cache); + return; + }; + // Update vendor preset bundles if in Vendor // Over all indices from the cache directory: for (auto &index : index_db) { - if (cancel) { return; } + if (cancel) { + return; + } const auto vendor_it = vendors.find(index.vendor()); if (vendor_it == vendors.end()) { - BOOST_LOG_TRIVIAL(info) << "No such vendor: " << index.vendor(); + // Not installed vendor yet we need to check missing thumbnails (of new printers) + BOOST_LOG_TRIVIAL(debug) << "No such vendor: " << index.vendor(); continue; } + const VendorProfile &vendor = vendor_it->second; const std::string idx_path = (cache_path / (vendor.id + ".idx")).string(); const std::string idx_path_temp = (cache_vendor_path / (vendor.id + ".idx")).string(); @@ -367,32 +412,59 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string if (vp.config_version != recommended) continue; copy_file_fix(path_in_archive, path_in_cache); + // vendors that are checked here, doesnt need to be checked again later + const auto archive_it = std::find(vendors_only_in_archive.begin(), vendors_only_in_archive.end(), index.vendor() + ".ini"); + if (archive_it != vendors_only_in_archive.end()) { + vendors_only_in_archive.erase(archive_it); + } // 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 (!fs::exists((vendor_path / (vendor + "/" + filename))) - && !fs::exists((rsrc_path / (vendor + "/" + filename)))) { - BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading."; - 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())) - fs::create_directory(resource_path.parent_path()); - self.get_file(resource_url, resource_path); - } - }; for (const auto& model : vp.models) { - for (const std::string& res : { model.bed_texture, model.bed_model, model.id +"_thumbnail.png"} ) { - if (! res.empty()) - check_and_get_resource(vp.id, res, vendor.config_update_url, vendor_path, rsrc_path, cache_path); + for (const std::string& res : { model.bed_texture, model.bed_model, model.thumbnail/*id +"_thumbnail.png"*/} ) { + if (! res.empty()) { + try + { + // for debug (wont pass check inside function) + //std::string fake_url = "https://github.com/kocikdav/PrusaSlicer-settings/raw/master/resources/" + vp.id; + get_missing_resource(vp.id, res, vendor.config_update_url, vendor_path, rsrc_path, cache_path); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to get " << res << " for " << vp.id << " " << model.id << ": " << e.what(); + } + + } if (cancel) return; } } } + // Download missing thumbnails for not-installed vendors. + for (const std::string& vendor : vendors_only_in_archive) + { + BOOST_LOG_TRIVIAL(error) << vendor; + const auto path_in_archive = cache_vendor_path / vendor; + assert(boost::filesystem::exists(path_in_archive)); + auto vp = VendorProfile::from_ini(path_in_archive, true); + for (const auto& model : vp.models) { + if (!model.thumbnail.empty()) { + try + { + // for debug (wont pass check inside function) + //std::string fake_url = "https://github.com/kocikdav/PrusaSlicer-settings/raw/master/resources/" + vp.id; + get_missing_resource(vp.id, model.thumbnail, vp.config_update_url, vendor_path, rsrc_path, cache_path); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to get " << model.thumbnail << " for " << vp.id << " " << model.id << ": " << e.what(); + } + } + if (cancel) + return; + + } + } } // Install indicies from resources. Only installs those that are either missing or older than in resources. @@ -643,6 +715,9 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons BOOST_LOG_TRIVIAL(info) << format("Performing %1% updates", updates.updates.size()); + wxProgressDialog progress_dialog(_L("Installing profiles"), _L("Installing profiles")); + progress_dialog.Pulse(); + for (const auto &update : updates.updates) { BOOST_LOG_TRIVIAL(info) << '\t' << update; @@ -681,32 +756,71 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons for (const auto &name : bundle.obsolete_presets.sla_materials/*filaments*/) { obsolete_remover("sla_material", name); } for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); } - auto copy_missing_resource = [](const std::string& vendor, const std::string& filename, + auto get_and_copy_missing_resource = [&self = std::as_const(*this)](const std::string& vendor, const std::string& filename, const fs::path& vendor_path, const fs::path& rsrc_path, - const fs::path& cache_path) + const fs::path& cache_path, const std::string& url) { - if ( !fs::exists((vendor_path / (vendor + "/" + filename))) - && !fs::exists((rsrc_path / (vendor + "/" + filename)))) { + if (filename.empty() || vendor.empty()) + return; - if (!fs::exists((cache_path / (vendor + "/" + filename)))) { - BOOST_LOG_TRIVIAL(error) << "Resources missing in cache directory: " << vendor << " / " << filename; - return; - } - if (!fs::exists((vendor_path / vendor))) - fs::create_directory((vendor_path / vendor)); + const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename)); + const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename)); + const fs::path file_in_cache(cache_path / (vendor + "/" + filename)); - copy_file_fix((cache_path / (vendor + "/" + filename)), (vendor_path / (vendor + "/" + filename))); + if (fs::exists(file_in_vendor)) { // Already in vendor. No need to do anything. + return; } + if (fs::exists(file_in_rsrc)) { // In resources dir since installation. No need to do anything. + return; + } + if (!fs::exists(file_in_cache)) { // No file to copy. Download it to straight to the vendor dir. + if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") && + !boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/")) + { + throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url)); + } + BOOST_LOG_TRIVIAL(info) << "Downloading resources missing in cache directory: " << vendor << " / " << filename; + + const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", filename); // vendor should already be in url + + if (!fs::exists(file_in_vendor.parent_path())) + fs::create_directory(file_in_vendor.parent_path()); + + self.get_file(resource_url, file_in_vendor); + return; + } + + if (!fs::exists(file_in_vendor.parent_path())) // create vendor_name dir in vendor + fs::create_directory(file_in_vendor.parent_path()); + + BOOST_LOG_TRIVIAL(debug) << "Copiing: " << file_in_cache << " to " << file_in_vendor; + copy_file_fix(file_in_cache, file_in_vendor); }; + // check if any resorces of installed bundle are missing. If so, new ones should be already downloaded at cache/vendor_id/ auto vp = VendorProfile::from_ini(update.target, true); + progress_dialog.Update(1, GUI::format_wxstr(_L("Downloading resources for %1%."),vp.id)); + progress_dialog.Pulse(); for (const auto& model : vp.models) { - copy_missing_resource(vp.id, model.bed_texture, vendor_path, rsrc_path, cache_path); - copy_missing_resource(vp.id, model.bed_model, vendor_path, rsrc_path, cache_path); - copy_missing_resource(vp.id, model.id + "_thumbnail.png", vendor_path, rsrc_path, cache_path); + for (const std::string& resource : { model.bed_texture, model.bed_model, model.thumbnail }) { + if (resource.empty()) + continue; + try + { + // for debug (wont pass check inside function) + //std::string fake_url = "https://github.com/kocikdav/PrusaSlicer-settings/raw/master/resources/" + vp.id; + get_and_copy_missing_resource(vp.id, resource, vendor_path, rsrc_path, cache_path, vp.config_update_url); + } + catch (const std::exception& e) + { + BOOST_LOG_TRIVIAL(error) << "Failed to prepare " << resource << " for " << vp.id << " " << model.id << ": " << e.what(); + } + } } } + + progress_dialog.Destroy(); } return true; @@ -752,6 +866,16 @@ void PresetUpdater::sync(const PresetBundle *preset_bundle) }); } +void PresetUpdater::cancel_sync() +{ + if (p && p->thread.joinable()) { + // This will stop transfers being done by the thread, if any. + // Cancelling takes some time, but should complete soon enough. + p->cancel = true; + p->thread.join(); + } +} + void PresetUpdater::slic3r_update_notify() { if (! p->enabled_version_check) @@ -932,10 +1056,10 @@ bool PresetUpdater::install_bundles_rsrc_or_cache_vendor(std::vector vp_rsrc.config_version) { + if (!is_in_rsrc || version_cache > version_rsrc) { // 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); diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index cf17bb685e..5bcba615bc 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -27,6 +27,7 @@ public: // If either version check or config updating is enabled, get the appropriate data in the background and cache it. void sync(const PresetBundle *preset_bundle); + void cancel_sync(); // If version check is enabled, check if chaced online slic3r version is newer, notify if so. void slic3r_update_notify();