diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index d9fa9392a7..6da825b090 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -41,7 +41,7 @@ using namespace nlohmann; namespace Slic3r { static const std::string VERSION_CHECK_URL = "https://check-version.orcaslicer.com/latest"; -static const std::string PROFILE_UPDATE_URL = "https://api.github.com/repos/OrcaSlicer/orcaslicer-profiles/releases/tags"; +static const std::string PROFILE_UPDATE_URL = "https://check-version.orcaslicer.com/profile"; static const std::string MODELS_STR = "models"; const std::string AppConfig::SECTION_FILAMENTS = "filaments"; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 22ef28f215..6c263d6b54 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -33,6 +33,7 @@ #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" +#include "slic3r/Utils/PresetUpdater.hpp" #include "Plater.hpp" #include "MainFrame.hpp" #include "format.hpp" @@ -2061,6 +2062,12 @@ void Tab::on_presets_changed() // Check if printer agent needs switching if (m_type == Preset::TYPE_PRINTER) { wxGetApp().switch_printer_agent(); + + // Trigger per-vendor preset update check + const Preset& printer_preset = m_preset_bundle->printers.get_edited_preset(); + if (printer_preset.vendor) { + wxGetApp().get_preset_updater()->check_vendor_update(printer_preset.vendor->id); + } } bool is_bbl_vendor_preset = m_preset_bundle->is_bbl_vendor(); diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 75d42367f5..62c4dc6ad5 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -199,6 +202,12 @@ struct PresetUpdater::priv bool has_waiting_printer_updates { false }; Updates waiting_printer_updates; + // Per-vendor update checking + std::set checked_vendors; + std::mutex vendor_check_mutex; + std::vector vendor_check_threads; + std::atomic vendor_check_cancel{false}; + struct Resource { std::string version; @@ -219,7 +228,7 @@ struct PresetUpdater::priv void sync_version() const; void parse_version_string(const std::string& body) const; void sync_resources(std::string http_url, std::map &resources, bool check_patch = false, std::string current_version="", std::string changelog_file=""); - void sync_config(); + void sync_vendor_config(const std::string& vendor_id); void sync_tooltip(std::string http_url, std::string language); void sync_plugins(std::string http_url, std::string plugin_version); void sync_printer_config(std::string http_url); @@ -642,124 +651,102 @@ void PresetUpdater::priv::sync_resources(std::string http_url, std::map(); - f.close(); - } catch (const std::exception& ex) { - BOOST_LOG_TRIVIAL(error) << "[Orca Updater]: failed to read profiles_update.json when sync_config: " << ex.what() << std::endl; - } catch (...) { - // catch any other errors (that we have no information about) - BOOST_LOG_TRIVIAL(error) << "[Orca Updater]: unknown failure when reading profiles_update.json in sync_config" << std::endl; - } - } + if (!enabled_config_update) return; + + BOOST_LOG_TRIVIAL(info) << "[Orca Updater] checking vendor update for " << vendor_id; + + auto check_cancel = [this](Http::Progress, bool &cancel_http) { + if (cancel || vendor_check_cancel) cancel_http = true; + }; + AppConfig *app_config = GUI::wxGetApp().app_config; + std::string url = app_config->profile_update_url() + + "?vendor=" + Http::url_encode(vendor_id) + + "&orca_version=" + Http::url_encode(SoftFever_VERSION); - auto profile_update_url = app_config->profile_update_url() + "/" + SoftFever_VERSION; - // parse the assets section and get the latest asset by comparing the name + std::string online_version_str; // this represents the PROFILE VERSION, not ORCA VERSION + std::string download_url_str; - Http::get(profile_update_url) - .on_error([cache_profile_path, cache_profile_update_file](std::string body, std::string error, unsigned http_status) { - // Orca: we check the response body to see if it's "Not Found", if so, it means for the current Orca version we don't have OTA - // updates, we can delete the cache file - if (!body.empty()) { - try { - json j = json::parse(body); - if (j.contains("message") && j["message"].get() == "Not Found") { - // The current Orca version does not have any OTA updates, delete the cache file - if (fs::exists(cache_profile_path / "profiles")) - fs::remove_all(cache_profile_path / "profiles"); - if (fs::exists(cache_profile_update_file)) - fs::remove(cache_profile_update_file); - } - } catch (...) {} - } - BOOST_LOG_TRIVIAL(info) << format("Error getting: `%1%`: HTTP %2%, %3%", "sync_config_orca", http_status, error); - }) + Http::get(url) .timeout_connect(5) - .on_complete([this, asset_name, cache_profile_path, cache_profile_update_file](std::string body, unsigned http_status) { - // Http response OK - if (http_status != 200) - return; + .on_progress(check_cancel) + .on_error([&vendor_id](std::string body, std::string error, unsigned http_status) { + BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] vendor check HTTP error for " + << vendor_id << ": " << error; + }) + .on_complete([&](std::string body, unsigned http_status) { + if (http_status != 200) return; try { json j = json::parse(body); - - struct update - { - std::string url; - std::string name; - int ver = -9999; - } latest_update; - - if (!(j.contains("message") && j["message"].get() == "Not Found")) { - json assets = j.at("assets"); - if (assets.is_array()) { - for (auto asset : assets) { - std::string name = asset["name"].get(); - int versionNumber = -1; - std::regex regexPattern("orcaslicer-profiles_ota_.*\\.([0-9]+)\\.zip$"); - std::smatch matches; - if (std::regex_search(name, matches, regexPattern) && matches.size() > 1) { - versionNumber = std::stoi(matches[1].str()); - } - if (versionNumber > 0 && versionNumber > latest_update.ver) { - latest_update.url = asset["browser_download_url"].get(); - latest_update.name = name; - latest_update.ver = versionNumber; - } - } - } + if (j.contains("vendor_version") && j.contains("download_url")) { + online_version_str = j["vendor_version"].get(); + download_url_str = j["download_url"].get(); } - - if (cancel) - return; - - if (latest_update.ver > 0) { - if (latest_update.name == asset_name) - return; - if (fs::exists(cache_profile_path / "profiles")) - fs::remove_all(cache_profile_path / "profiles"); - fs::create_directories(cache_profile_path / "profiles"); - // download the file - std::string download_url = latest_update.url; - std::string download_file = (cache_path / (latest_update.name + TMP_EXTENSION)).string(); - if (!get_file(download_url, download_file)) { - return; - } - - // extract the file downloaded - BOOST_LOG_TRIVIAL(info) << "[Orca Updater]start to unzip the downloaded file " << download_file; - if (!extract_file(download_file, cache_profile_path)) { - BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]extract downloaded file" - << " failed, path: " << download_file; - return; - } - BOOST_LOG_TRIVIAL(info) << "[Orca Updater]finished unzip the downloaded file " << download_file; - boost::nowide::ofstream f(cache_profile_update_file.string()); - json data; - data["name"] = latest_update.name; - f << data << std::endl; - f.close(); - } else { - // The current Orca version does not have any OTA updates, delete the cache file - if (fs::exists(cache_profile_path / "profiles")) - fs::remove_all(cache_profile_path / "profiles"); - if (fs::exists(cache_profile_update_file)) - fs::remove(cache_profile_update_file); - } - - } catch (...) {} + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] vendor check JSON parse failed: " << e.what(); + } }) .perform_sync(); + + if (cancel || vendor_check_cancel) return; + if (online_version_str.empty() || download_url_str.empty()) { + BOOST_LOG_TRIVIAL(info) << "[Orca Updater] no update available for vendor " << vendor_id; + return; + } + + if (cancel || vendor_check_cancel) return; + + // Clear only this vendor's cached data + auto cache_profile_path = cache_path / "profiles"; + fs::create_directories(cache_profile_path); + boost::system::error_code ec; + fs::remove_all(cache_profile_path / vendor_id, ec); + fs::remove(cache_profile_path / (vendor_id + ".json"), ec); + fs::remove(cache_profile_path / (vendor_id + ".changelog"), ec); + + // Download the zip + BOOST_LOG_TRIVIAL(info) << "[Orca Updater] downloading update for " << vendor_id + << " version " << online_version_str; + fs::path download_file = cache_path / (vendor_id + TMP_EXTENSION); + bool download_ok = false; + + Http::get(download_url_str) + .timeout_connect(5) + .on_progress(check_cancel) + .on_error([&vendor_id](std::string body, std::string error, unsigned http_status) { + BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] download failed for " << vendor_id << ": " << error; + }) + .on_complete([&](std::string body, unsigned http_status) { + if (http_status != 200) return; + fs::fstream file(download_file, std::ios::out | std::ios::binary | std::ios::trunc); + if (!file.good()) return; + file.write(body.c_str(), body.size()); + file.close(); + if (file.good()) + download_ok = true; + }) + .perform_sync(); + + if (!download_ok || cancel || vendor_check_cancel) return; + + // Extract vendor profile bundles under ota/profiles. The downloaded zip contains + // the vendor json/folder at its root. + BOOST_LOG_TRIVIAL(info) << "[Orca Updater] extracting update for " << vendor_id; + if (!extract_file(download_file, cache_profile_path)) { + BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] extraction failed for " << vendor_id; + return; + } + fs::remove(download_file, ec); + + if (cancel || vendor_check_cancel) return; + + BOOST_LOG_TRIVIAL(info) << "[Orca Updater] vendor " << vendor_id << " update cached, notifying UI"; + GUI::wxGetApp().CallAfter([] { + GUI::wxGetApp().check_config_updates_from_updater(); + }); } void PresetUpdater::priv::sync_tooltip(std::string http_url, std::string language) @@ -1240,8 +1227,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version ifs.close(); } - bool version_match = ((vendor_ver.maj() == cache_ver.maj()) && (vendor_ver.min() == cache_ver.min())); - if (version_match && (vendor_ver < cache_ver)) { + if (vendor_ver < cache_ver) { BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:need to update settings from " << vendor_ver.to_string() << " to newer version " << cache_ver.to_string() << ", app version " << SLIC3R_VERSION; Version version; @@ -1251,6 +1237,10 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version updates.updates.emplace_back(std::move(file_path), std::move(path_in_vendor.string()), std::move(version), vendor_name, changelog, "", force_update, false); //Orca: update vendor folder updates.updates.emplace_back(cache_profile_path / vendor_name, vendor_path / vendor_name, Version(), vendor_name, "", "", force_update, true); + } else { + BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:cached settings for " << vendor_name + << " are not newer than installed version, installed " << vendor_ver.to_string() + << ", cached " << cache_ver.to_string(); } } } @@ -1339,6 +1329,12 @@ PresetUpdater::~PresetUpdater() p->cancel = true; p->thread.join(); } + if (p) { + p->vendor_check_cancel = true; + for (auto& t : p->vendor_check_threads) + if (t.joinable()) + t.join(); + } } //BBS: change directories by design @@ -1353,20 +1349,30 @@ void PresetUpdater::sync(std::string http_url, std::string language, std::string // into the closure (but perhaps the compiler can elide this). VendorMap vendors = preset_bundle ? preset_bundle->vendors : VendorMap{}; - p->thread = std::thread([this, vendors, http_url, language, plugin_version]() { + // Determine active vendor before entering the thread + std::string active_vendor; + if (preset_bundle) { + const Preset& printer = preset_bundle->printers.get_edited_preset(); + if (printer.vendor) + active_vendor = printer.vendor->id; + } + + p->thread = std::thread([this, vendors, active_vendor, http_url, language, plugin_version]() { this->p->prune_tmps(); if (p->cancel) return; this->p->sync_version(); if (p->cancel) return; - if (!vendors.empty()) { - this->p->sync_config(); - if (p->cancel) - return; - GUI::wxGetApp().CallAfter([] { - GUI::wxGetApp().check_config_updates_from_updater(); - }); + // Per-vendor config check for the active vendor at startup + if (!active_vendor.empty() && !vendors.empty()) { + this->p->sync_vendor_config(active_vendor); + if (p->cancel) + return; + { + std::lock_guard lock(this->p->vendor_check_mutex); + this->p->checked_vendors.insert(active_vendor); + } } if (p->cancel) return; @@ -1379,6 +1385,25 @@ void PresetUpdater::sync(std::string http_url, std::string language, std::string }); } +void PresetUpdater::check_vendor_update(const std::string& vendor_id) +{ + if (!p->enabled_config_update) return; + if (vendor_id.empty()) return; + + std::lock_guard lock(p->vendor_check_mutex); + + if (!p->checked_vendors.insert(vendor_id).second) + return; + + p->vendor_check_threads.emplace_back([this, vendor_id]() { + try { + this->p->sync_vendor_config(vendor_id); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "[Orca Updater] vendor update failed for " << vendor_id << ": " << e.what(); + } + }); +} + void PresetUpdater::slic3r_update_notify() { if (! p->enabled_version_check) diff --git a/src/slic3r/Utils/PresetUpdater.hpp b/src/slic3r/Utils/PresetUpdater.hpp index 6d6e4cd94d..27ec6748f2 100644 --- a/src/slic3r/Utils/PresetUpdater.hpp +++ b/src/slic3r/Utils/PresetUpdater.hpp @@ -58,6 +58,7 @@ public: void on_update_notification_confirm(); void do_printer_config_update(); + void check_vendor_update(const std::string& vendor_id); bool version_check_enabled() const;