mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-25 09:50:59 +00:00
* preset updater: refresh installed vendors from resources regardless of enable_ota Sincec4fea8ad24the resources check in check_installed_vendor_profiles() sat behind enabled_config_update, which now follows enable_ota, a hidden flag that defaults to off. The hotfixa93c6ea67bthen made that gate skip installed vendors entirely, so a new build's newer vendor profiles were never installed over an existing vendor unless OTA had been turned on. Before the gating the update URL always had a default, so the comparison effectively always ran. The resources shipped with a build are not an over-the-air update. Judge installed vendors against them, and drop the ones no longer enabled, regardless of the flag; enable_ota keeps gating the online sync. * preset updater: stop reinstalling the filament library on every launch check_installed_vendor_profiles() put OrcaFilamentLibrary on the install list unconditionally, so every launch recopied the whole vendor from resources and, in a build that ships the profile JSONs rather than a preset cache, then re-parsed and re-cached it: about 0.3 s of a dev build's startup, and a 3 MB copy in a release build, for a vendor that had not changed. The library was special-cased because it is never in the enabled-vendor list. Treat it like the default bundle instead: always wanted, and reinstalled only when the resources carry a newer version.
1919 lines
81 KiB
C++
1919 lines
81 KiB
C++
#include "PresetUpdater.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <boost/filesystem/directory.hpp>
|
|
#include <boost/filesystem/operations.hpp>
|
|
#include <boost/filesystem/path.hpp>
|
|
#include <boost/nowide/fstream.hpp>
|
|
#include <functional>
|
|
#include <atomic>
|
|
#include <set>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <mutex>
|
|
#include <unordered_map>
|
|
#include <ostream>
|
|
#include <utility>
|
|
#include <stdexcept>
|
|
#include <boost/format.hpp>
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <boost/filesystem.hpp>
|
|
#include <boost/filesystem/fstream.hpp>
|
|
#include <boost/lexical_cast.hpp>
|
|
#include <boost/log/trivial.hpp>
|
|
|
|
#include <vector>
|
|
#include <wx/app.h>
|
|
#include <wx/msgdlg.h>
|
|
|
|
#include "libslic3r/libslic3r.h"
|
|
#include "libslic3r/format.hpp"
|
|
#include "libslic3r/Utils.hpp"
|
|
#include "libslic3r/PresetBundle.hpp"
|
|
#include "libslic3r/PresetCacheFormat.hpp"
|
|
#include "libslic3r_version.h"
|
|
#include "slic3r/GUI/GUI.hpp"
|
|
#include "slic3r/GUI/GUI_App.hpp"
|
|
#include "slic3r/GUI/I18N.hpp"
|
|
#include "slic3r/GUI/UpdateDialogs.hpp"
|
|
#include "slic3r/GUI/ConfigWizard.hpp"
|
|
#include "slic3r/GUI/GUI_App.hpp"
|
|
#include "slic3r/GUI/Plater.hpp"
|
|
#include "slic3r/GUI/format.hpp"
|
|
#include "slic3r/GUI/NotificationManager.hpp"
|
|
#include "slic3r/Utils/Http.hpp"
|
|
#include "slic3r/Utils/bambu_networking.hpp"
|
|
#include "slic3r/Config/Version.hpp"
|
|
#include "slic3r/Config/Snapshot.hpp"
|
|
#include "slic3r/GUI/MarkdownTip.hpp"
|
|
#include "libslic3r/miniz_extension.hpp"
|
|
#include "slic3r/GUI/GUI_Utils.hpp"
|
|
|
|
namespace fs = boost::filesystem;
|
|
using Slic3r::GUI::Config::Index;
|
|
using Slic3r::GUI::Config::Version;
|
|
using Slic3r::GUI::Config::Snapshot;
|
|
using Slic3r::GUI::Config::SnapshotDB;
|
|
|
|
|
|
// FIXME: Incompat bundle resolution doesn't deal with inherited user presets
|
|
|
|
namespace Slic3r {
|
|
|
|
|
|
static const char *INDEX_FILENAME = "index.idx";
|
|
static const char *TMP_EXTENSION = ".data";
|
|
|
|
|
|
void copy_file_fix(const fs::path &source, const fs::path &target)
|
|
{
|
|
BOOST_LOG_TRIVIAL(debug) << format("PresetUpdater: Copying %1% -> %2%", source, target);
|
|
std::string error_message;
|
|
//CopyFileResult cfr = Slic3r::GUI::copy_file_gui(source.string(), target.string(), error_message, false);
|
|
CopyFileResult cfr = copy_file(source.string(), target.string(), error_message, false);
|
|
if (cfr != CopyFileResult::SUCCESS) {
|
|
BOOST_LOG_TRIVIAL(error) << "Copying failed(" << cfr << "): " << error_message;
|
|
throw Slic3r::CriticalException(GUI::format(
|
|
_L("Copying of file %1% to %2% failed: %3%"),
|
|
source, target, error_message));
|
|
}
|
|
// Permissions should be copied from the source file by copy_file(). We are not sure about the source
|
|
// permissions, let's rewrite them with 644.
|
|
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
|
|
fs::permissions(target, perms);
|
|
}
|
|
|
|
struct Update
|
|
{
|
|
fs::path source;
|
|
fs::path target;
|
|
|
|
Version version;
|
|
std::string vendor;
|
|
//BBS: use changelog string instead of url
|
|
std::string change_log;
|
|
std::string descriptions;
|
|
// Orca: add file filter support
|
|
std::function<bool(const std::string)> file_filter;
|
|
|
|
bool forced_update;
|
|
//BBS: add directory support
|
|
bool is_directory {false};
|
|
// Orca: a vendor update may be the cache-only form.
|
|
bool is_opc {false};
|
|
|
|
Update() {}
|
|
//BBS: add directory support
|
|
//BBS: use changelog string instead of url
|
|
Update(fs::path &&source, fs::path &&target, const Version &version, std::string vendor, std::string changelog, std::string description, bool forced = false, bool is_dir = false)
|
|
: source(std::move(source))
|
|
, target(std::move(target))
|
|
, version(version)
|
|
, vendor(std::move(vendor))
|
|
, change_log(std::move(changelog))
|
|
, descriptions(std::move(description))
|
|
, forced_update(forced)
|
|
, is_directory(is_dir)
|
|
{}
|
|
|
|
Update(fs::path &&source, fs::path &&target, const Version &version, std::string vendor, std::string changelog, std::string description, std::function<bool(const std::string)> file_filter, bool forced = false, bool is_dir = false)
|
|
: source(std::move(source))
|
|
, target(std::move(target))
|
|
, version(version)
|
|
, vendor(std::move(vendor))
|
|
, change_log(std::move(changelog))
|
|
, descriptions(std::move(description))
|
|
, file_filter(file_filter)
|
|
, forced_update(forced)
|
|
, is_directory(is_dir)
|
|
{}
|
|
|
|
//BBS: add directory support
|
|
void install() const
|
|
{
|
|
if (is_directory) {
|
|
copy_directory_recursively(source, target, file_filter);
|
|
} else {
|
|
copy_file_fix(source, target);
|
|
|
|
// A vendor must be installed in exactly one form. Remove the
|
|
// representation that would otherwise be stale or shadow this one.
|
|
boost::system::error_code ec;
|
|
if (is_opc) {
|
|
fs::remove(target.parent_path() / (vendor + ".json"), ec);
|
|
ec.clear();
|
|
fs::remove_all(target.parent_path() / vendor, ec);
|
|
}
|
|
else {
|
|
fs::remove(target.parent_path() / (vendor + ".opc"), ec);
|
|
}
|
|
}
|
|
}
|
|
|
|
friend std::ostream& operator<<(std::ostream& os, const Update &self)
|
|
{
|
|
os << "Update(" << self.source.string() << " -> " << self.target.string() << ')';
|
|
return os;
|
|
}
|
|
};
|
|
|
|
struct Incompat
|
|
{
|
|
fs::path bundle;
|
|
Version version;
|
|
std::string vendor;
|
|
//BBS: add directory support
|
|
bool is_directory {false};
|
|
|
|
Incompat(fs::path &&bundle, const Version &version, std::string vendor, bool is_dir = false)
|
|
: bundle(std::move(bundle))
|
|
, version(version)
|
|
, vendor(std::move(vendor))
|
|
, is_directory(is_dir)
|
|
{}
|
|
|
|
void remove() {
|
|
// Remove the bundle file
|
|
if (is_directory) {
|
|
if (fs::exists(bundle))
|
|
fs::remove_all(bundle);
|
|
}
|
|
else {
|
|
if (fs::exists(bundle))
|
|
fs::remove(bundle);
|
|
}
|
|
}
|
|
|
|
friend std::ostream& operator<<(std::ostream& os , const Incompat &self) {
|
|
os << "Incompat(" << self.bundle.string() << ')';
|
|
return os;
|
|
}
|
|
};
|
|
|
|
struct Updates
|
|
{
|
|
std::vector<Incompat> incompats;
|
|
std::vector<Update> updates;
|
|
};
|
|
|
|
static bool reload_configs_update_gui();
|
|
|
|
|
|
wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
|
|
wxDEFINE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
|
|
|
|
|
|
struct PresetUpdater::priv
|
|
{
|
|
std::vector<Index> index_db;
|
|
|
|
bool enabled_version_check;
|
|
bool enabled_config_update;
|
|
std::string version_check_url;
|
|
|
|
fs::path cache_path;
|
|
fs::path rsrc_path;
|
|
fs::path vendor_path;
|
|
|
|
bool cancel;
|
|
std::thread thread;
|
|
|
|
bool has_waiting_updates { false };
|
|
Updates waiting_updates;
|
|
bool has_waiting_printer_updates { false };
|
|
Updates waiting_printer_updates;
|
|
|
|
// Per-vendor update checking
|
|
std::set<std::string> checked_vendors;
|
|
// Orca (PR #130): changelog text for each vendor, captured in memory during
|
|
// sync_vendor_config()/check_new_vendors() instead of written beside the cache.
|
|
std::unordered_map<std::string, std::string> vendor_changelogs;
|
|
mutable std::mutex vendor_changelogs_mutex;
|
|
std::vector<std::thread> vendor_check_threads;
|
|
std::atomic<bool> vendor_check_cancel{false};
|
|
|
|
struct Resource
|
|
{
|
|
std::string version;
|
|
std::string description;
|
|
std::string url;
|
|
bool force{false};
|
|
std::string cache_root;
|
|
std::vector<std::string> sub_caches;
|
|
};
|
|
|
|
priv();
|
|
|
|
void set_download_prefs(AppConfig *app_config);
|
|
bool get_file(const std::string &url, const fs::path &target_path) const;
|
|
//BBS: refine preset update logic
|
|
bool extract_file(const fs::path &source_path, const fs::path &dest_path = {});
|
|
void prune_tmp(const std::string& vendor_id) const;
|
|
void sync_version() const;
|
|
void parse_version_string(const std::string& body) const;
|
|
void sync_resources(std::string http_url, std::map<std::string, Resource> &resources, bool check_patch = false, std::string current_version="", std::string changelog_file="");
|
|
void sync_vendor_config(const std::string& vendor_id);
|
|
void check_new_vendors(const std::set<std::string>& system_vendors,
|
|
std::function<void(std::vector<std::string>, bool)> callback);
|
|
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);
|
|
bool get_cached_plugins_version(std::string &cached_version, bool& force);
|
|
|
|
//BBS: refine preset update logic
|
|
bool install_bundles_rsrc(const std::vector<std::string>& bundles, bool snapshot) const;
|
|
void check_installed_vendor_profiles() const;
|
|
Updates get_printer_config_updates(bool update = false) const;
|
|
Updates get_config_updates(const Semver& old_slic3r_version) const;
|
|
bool perform_updates(Updates &&updates, bool snapshot = true) const;
|
|
void set_waiting_updates(Updates u);
|
|
};
|
|
|
|
//BBS: change directories by design
|
|
PresetUpdater::priv::priv()
|
|
: cache_path(fs::path(Slic3r::data_dir()) / "ota")
|
|
, rsrc_path(fs::path(resources_dir()) / "profiles")
|
|
, vendor_path(fs::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR)
|
|
, cancel(false)
|
|
{
|
|
//BBS: refine preset updater logic
|
|
enabled_version_check = true;
|
|
set_download_prefs(GUI::wxGetApp().app_config);
|
|
// Install indicies from resources. Only installs those that are either missing or older than in resources.
|
|
check_installed_vendor_profiles();
|
|
perform_updates(get_printer_config_updates(), false);
|
|
// Load indices from the cache directory.
|
|
//index_db = Index::load_db();
|
|
}
|
|
|
|
// Pull relevant preferences from AppConfig
|
|
void PresetUpdater::priv::set_download_prefs(AppConfig *app_config)
|
|
{
|
|
version_check_url = app_config->version_check_url();
|
|
|
|
auto profile_update_url = app_config->profile_update_url();
|
|
if (!profile_update_url.empty() && app_config->get_bool("enable_ota"))
|
|
enabled_config_update = true;
|
|
else
|
|
enabled_config_update = false;
|
|
}
|
|
|
|
//BBS: refine the Preset Updater logic
|
|
// Downloads a file (http get operation). Cancels if the Updater is being destroyed.
|
|
bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &target_path) const
|
|
{
|
|
bool res = false;
|
|
fs::path tmp_path = target_path;
|
|
tmp_path += format(".%1%%2%", get_current_pid(), TMP_EXTENSION);
|
|
|
|
BOOST_LOG_TRIVIAL(info) << format("[BBS Updater]download file `%1%`, stored to `%2%`, tmp path `%3%`",
|
|
url,
|
|
target_path.string(),
|
|
tmp_path.string());
|
|
|
|
Slic3r::Http::get(url)
|
|
.on_progress([this](Slic3r::Http::Progress, bool &cancel_http) {
|
|
if (cancel) {
|
|
cancel_http = true;
|
|
}
|
|
})
|
|
.on_error([&](std::string body, std::string error, unsigned http_status) {
|
|
(void)body;
|
|
BOOST_LOG_TRIVIAL(error) << format("[BBS Updater]getting: `%1%`: http status %2%, %3%",
|
|
url,
|
|
http_status,
|
|
error);
|
|
})
|
|
.on_complete([&](std::string body, unsigned /* http_status */) {
|
|
fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc);
|
|
file.write(body.c_str(), body.size());
|
|
file.close();
|
|
fs::rename(tmp_path, target_path);
|
|
res = true;
|
|
})
|
|
.perform_sync();
|
|
|
|
return res;
|
|
}
|
|
|
|
//BBS: refine preset update logic
|
|
bool PresetUpdater::priv::extract_file(const fs::path &source_path, const fs::path &dest_path)
|
|
{
|
|
bool res = true;
|
|
std::string file_path = source_path.string();
|
|
std::string parent_path = (!dest_path.empty() ? dest_path : source_path.parent_path()).string();
|
|
mz_zip_archive archive;
|
|
mz_zip_zero_struct(&archive);
|
|
|
|
if (!open_zip_reader(&archive, file_path))
|
|
{
|
|
BOOST_LOG_TRIVIAL(error) << "Unable to open zip reader for "<<file_path;
|
|
return false;
|
|
}
|
|
|
|
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
|
|
|
|
mz_zip_archive_file_stat stat;
|
|
// we first loop the entries to read from the archive the .amf file only, in order to extract the version from it
|
|
for (mz_uint i = 0; i < num_entries; ++i)
|
|
{
|
|
if (mz_zip_reader_file_stat(&archive, i, &stat))
|
|
{
|
|
std::string dest_file = parent_path+"/"+stat.m_filename;
|
|
if (stat.m_is_directory) {
|
|
fs::path dest_path(dest_file);
|
|
if (!fs::exists(dest_path))
|
|
fs::create_directories(dest_path);
|
|
continue;
|
|
}
|
|
else if (stat.m_uncomp_size == 0) {
|
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]Unzip: invalid size for file "<<stat.m_filename;
|
|
continue;
|
|
}
|
|
try
|
|
{
|
|
res = mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_file.c_str(), 0);
|
|
if (!res) {
|
|
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]extract file "<<stat.m_filename<<" to dest "<<dest_file<<" failed";
|
|
close_zip_reader(&archive);
|
|
return res;
|
|
}
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]successfully extract file " << stat.m_file_index << " to "<<dest_file;
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
// ensure the zip archive is closed and rethrow the exception
|
|
close_zip_reader(&archive);
|
|
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]Archive read exception:"<<e.what();
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]Unzip: read file stat failed";
|
|
}
|
|
}
|
|
close_zip_reader(&archive);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Remove a leftover partial archive for the vendor about to be synchronized.
|
|
void PresetUpdater::priv::prune_tmp(const std::string& vendor_id) const
|
|
{
|
|
boost::system::error_code ec;
|
|
const fs::path tmp_path = cache_path / (vendor_id + TMP_EXTENSION);
|
|
fs::remove(tmp_path, ec);
|
|
if (ec)
|
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]failed to remove " << tmp_path.string() << ": " << ec.message();
|
|
}
|
|
|
|
//BBS: refine the Preset Updater logic
|
|
// Get Slic3rPE version available online, save in AppConfig.
|
|
void PresetUpdater::priv::sync_version() const
|
|
{
|
|
if (! enabled_version_check) { return; }
|
|
|
|
#if 0
|
|
Http::get(version_check_url)
|
|
.size_limit(SLIC3R_VERSION_BODY_MAX)
|
|
.on_progress([this](Http::Progress, bool &cancel) {
|
|
cancel = this->cancel;
|
|
})
|
|
.on_error([&](std::string body, std::string error, unsigned http_status) {
|
|
(void)body;
|
|
BOOST_LOG_TRIVIAL(error) << format("Error getting: `%1%`: HTTP %2%, %3%",
|
|
version_check_url,
|
|
http_status,
|
|
error);
|
|
})
|
|
.on_complete([&](std::string body, unsigned /* http_status */) {
|
|
boost::trim(body);
|
|
parse_version_string(body);
|
|
})
|
|
.perform_sync();
|
|
#endif
|
|
}
|
|
|
|
// Parses version string obtained in sync_version() and sends events to UI thread.
|
|
// Version string must contain release version on first line. Follows non-mandatory alpha / beta releases on following lines (alpha=2.0.0-alpha1).
|
|
void PresetUpdater::priv::parse_version_string(const std::string& body) const
|
|
{
|
|
#if 0
|
|
// release version
|
|
std::string version;
|
|
const auto first_nl_pos = body.find_first_of("\n\r");
|
|
if (first_nl_pos != std::string::npos)
|
|
version = body.substr(0, first_nl_pos);
|
|
else
|
|
version = body;
|
|
boost::optional<Semver> release_version = Semver::parse(version);
|
|
if (!release_version) {
|
|
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
|
|
return;
|
|
}
|
|
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
|
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_VERSION_ONLINE);
|
|
evt->SetString(GUI::from_u8(version));
|
|
GUI::wxGetApp().QueueEvent(evt);
|
|
|
|
// alpha / beta version
|
|
std::vector<std::string> prerelease_versions;
|
|
size_t nexn_nl_pos = first_nl_pos;
|
|
while (nexn_nl_pos != std::string::npos && body.size() > nexn_nl_pos + 1) {
|
|
const auto last_nl_pos = nexn_nl_pos;
|
|
nexn_nl_pos = body.find_first_of("\n\r", last_nl_pos + 1);
|
|
std::string line;
|
|
if (nexn_nl_pos == std::string::npos)
|
|
line = body.substr(last_nl_pos + 1);
|
|
else
|
|
line = body.substr(last_nl_pos + 1, nexn_nl_pos - last_nl_pos - 1);
|
|
|
|
// alpha
|
|
if (line.substr(0, 6) == "alpha=") {
|
|
version = line.substr(6);
|
|
if (!Semver::parse(version)) {
|
|
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents for alpha release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
|
|
return;
|
|
}
|
|
prerelease_versions.emplace_back(version);
|
|
// beta
|
|
}
|
|
else if (line.substr(0, 5) == "beta=") {
|
|
version = line.substr(5);
|
|
if (!Semver::parse(version)) {
|
|
BOOST_LOG_TRIVIAL(error) << format("Received invalid contents for beta release from `%1%`: Not a correct semver: `%2%`", SLIC3R_APP_NAME, version);
|
|
return;
|
|
}
|
|
prerelease_versions.emplace_back(version);
|
|
}
|
|
}
|
|
// find recent version that is newer than last full release.
|
|
boost::optional<Semver> recent_version;
|
|
for (const std::string& ver_string : prerelease_versions) {
|
|
boost::optional<Semver> ver = Semver::parse(ver_string);
|
|
if (ver && *release_version < *ver && ((recent_version && *recent_version < *ver) || !recent_version)) {
|
|
recent_version = ver;
|
|
version = ver_string;
|
|
}
|
|
}
|
|
if (recent_version) {
|
|
BOOST_LOG_TRIVIAL(info) << format("Got %1% online version: `%2%`. Sending to GUI thread...", SLIC3R_APP_NAME, version);
|
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE);
|
|
evt->SetString(GUI::from_u8(version));
|
|
GUI::wxGetApp().QueueEvent(evt);
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
//BBS: refine the Preset Updater logic
|
|
// 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_resources(std::string http_url, std::map<std::string, Resource> &resources, bool check_patch, std::string current_version_str, std::string changelog_file)
|
|
{
|
|
std::map<std::string, Resource> resource_list;
|
|
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("[Orca Updater]: sync_resources get preferred setting version for app version %1%, url: %2%, current_version_str %3%, check_patch %4%")%SLIC3R_APP_NAME%http_url%current_version_str%check_patch;
|
|
|
|
std::string query_params = "?";
|
|
bool first = true;
|
|
for (auto resource_it : resources) {
|
|
if (cancel) { return; }
|
|
auto resource_name = resource_it.first;
|
|
boost::to_lower(resource_name);
|
|
std::string query_resource = (boost::format("%1%=%2%")
|
|
% resource_name % resource_it.second.version).str();
|
|
if (!first) query_params += "&";
|
|
query_params += query_resource;
|
|
first = false;
|
|
}
|
|
|
|
std::string url = http_url;
|
|
url += query_params;
|
|
Slic3r::Http http = Slic3r::Http::get(url);
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("[Orca Updater]: sync_resources request_url: %1%")%url;
|
|
http.on_progress([this](Slic3r::Http::Progress, bool &cancel_http) {
|
|
if (cancel) {
|
|
cancel_http = true;
|
|
}
|
|
})
|
|
.on_complete([&resource_list, resources](std::string body, unsigned) {
|
|
try {
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]: request_resources, body=" << body;
|
|
|
|
json j = json::parse(body);
|
|
std::string message = j["message"].get<std::string>();
|
|
|
|
if (message == "success") {
|
|
json resource = j.at("resources");
|
|
if (resource.is_array()) {
|
|
for (auto iter = resource.begin(); iter != resource.end(); iter++) {
|
|
std::string version;
|
|
std::string url;
|
|
std::string resource;
|
|
std::string description;
|
|
bool force_upgrade = false;
|
|
for (auto sub_iter = iter.value().begin(); sub_iter != iter.value().end(); sub_iter++) {
|
|
if (boost::iequals(sub_iter.key(), "type")) {
|
|
resource = sub_iter.value();
|
|
BOOST_LOG_TRIVIAL(trace) << "[Orca Updater]: get version of settings's type, " << sub_iter.value();
|
|
} else if (boost::iequals(sub_iter.key(), "version")) {
|
|
version = sub_iter.value();
|
|
} else if (boost::iequals(sub_iter.key(), "description")) {
|
|
description = sub_iter.value();
|
|
} else if (boost::iequals(sub_iter.key(), "url")) {
|
|
url = sub_iter.value();
|
|
}
|
|
else if (boost::iequals(sub_iter.key(), "force_update")) {
|
|
force_upgrade = sub_iter.value();
|
|
}
|
|
}
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]: get type " << resource << ", version " << version << ", url " << url<<", force_update "<<force_upgrade;
|
|
|
|
resource_list.emplace(resource, Resource{version, description, url, force_upgrade});
|
|
}
|
|
}
|
|
} else {
|
|
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]: get version of settings failed, body=" << body;
|
|
}
|
|
} catch (std::exception &e) {
|
|
BOOST_LOG_TRIVIAL(error) << (boost::format("[Orca Updater]: get version of settings failed, exception=%1% body=%2%") % e.what() % body).str();
|
|
} catch (...) {
|
|
BOOST_LOG_TRIVIAL(error) << "[Orca Updater]: get version of settings failed, body=" << body;
|
|
}
|
|
})
|
|
.on_error([&](std::string body, std::string error, unsigned status) {
|
|
BOOST_LOG_TRIVIAL(error) << boost::format("[Orca Updater]: status=%1%, error=%2%, body=%3%") % status % error % body;
|
|
})
|
|
.perform_sync();
|
|
|
|
for (auto & resource_it : resources) {
|
|
if (cancel) { return; }
|
|
|
|
auto resource = resource_it.second;
|
|
std::string resource_name = resource_it.first;
|
|
boost::to_lower(resource_name);
|
|
auto resource_update = resource_list.find(resource_name);
|
|
if (resource_update == resource_list.end()) {
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]Vendor " << resource_name << " can not get setting versions online";
|
|
continue;
|
|
}
|
|
Semver online_version = resource_update->second.version;
|
|
// Semver current_version = get_version_from_json(vendor_root_config.string());
|
|
Semver current_version = current_version_str.empty()?resource.version:current_version_str;
|
|
bool version_match = ((online_version.maj() == current_version.maj()) && (online_version.min() == current_version.min()));
|
|
if (version_match && check_patch) {
|
|
int online_cc_patch = online_version.patch()/100;
|
|
int current_cc_patch = current_version.patch()/100;
|
|
if (online_cc_patch != current_cc_patch) {
|
|
version_match = false;
|
|
BOOST_LOG_TRIVIAL(warning) << boost::format("[Orca Updater]: online patch CC not match: online_cc_patch=%1%, current_cc_patch=%2%") % online_cc_patch % current_cc_patch;
|
|
}
|
|
}
|
|
if (version_match && (current_version < online_version)) {
|
|
if (cancel) { return; }
|
|
|
|
// need to download the online files
|
|
fs::path cache_path(resource.cache_root);
|
|
std::string online_url = resource_update->second.url;
|
|
std::string cache_file_path = (fs::temp_directory_path() / (fs::unique_path().string() + TMP_EXTENSION)).string();
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]Downloading resource: " << resource_name << ", version " << online_version.to_string();
|
|
if (!get_file(online_url, cache_file_path)) {
|
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]download resource " << resource_name << " failed, url: " << online_url;
|
|
continue;
|
|
}
|
|
if (cancel) { return; }
|
|
|
|
// remove previous files before
|
|
if (resource.sub_caches.empty()) {
|
|
if (fs::exists(cache_path)) {
|
|
fs::remove_all(cache_path);
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]remove cache path " << cache_path.string();
|
|
}
|
|
} else {
|
|
for (auto sub : resource.sub_caches) {
|
|
if (fs::exists(cache_path / sub)) {
|
|
fs::remove_all(cache_path / sub);
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]remove cache path " << (cache_path / sub).string();
|
|
}
|
|
}
|
|
}
|
|
// extract the file downloaded
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]start to unzip the downloaded file " << cache_file_path << " to "<<cache_path;
|
|
fs::create_directories(cache_path);
|
|
if (!extract_file(cache_file_path, cache_path)) {
|
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]extract resource " << resource_it.first << " failed, path: " << cache_file_path;
|
|
continue;
|
|
}
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]finished unzip the downloaded file " << cache_file_path;
|
|
|
|
// save the description to disk
|
|
if (changelog_file.empty())
|
|
changelog_file = (cache_path / "changelog.json").string();
|
|
else
|
|
changelog_file = (cache_path / changelog_file).string();
|
|
|
|
try {
|
|
json j;
|
|
//record the headers
|
|
j["version"] = resource_update->second.version;
|
|
j["description"] = resource_update->second.description;
|
|
j["force"] = resource_update->second.force;
|
|
|
|
boost::nowide::ofstream c;
|
|
c.open(changelog_file, std::ios::out | std::ios::trunc);
|
|
c << j.dump(1, '\t') << std::endl;
|
|
c.close();
|
|
}
|
|
catch(std::exception &err) {
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": save to "<<changelog_file<<" got a generic exception, reason = " << err.what();
|
|
}
|
|
|
|
resource_it.second = resource_update->second;
|
|
}
|
|
else {
|
|
BOOST_LOG_TRIVIAL(warning) << boost::format("[Orca Updater]: online version=%1%, current_version=%2%, no need to download") % online_version.to_string() % current_version.to_string();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Orca: per-vendor config update check
|
|
void PresetUpdater::priv::sync_vendor_config(const std::string& vendor_id)
|
|
{
|
|
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);
|
|
|
|
std::string online_version_str; // this represents the PROFILE VERSION, not ORCA VERSION
|
|
std::string download_url_str;
|
|
std::string changelog;
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] fetching vendor update status from " << url;
|
|
|
|
Http::get(url)
|
|
.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] 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);
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] url: " << url << " returned:" << body;
|
|
|
|
if (j.contains("vendor_version") && j.contains("download_url")) {
|
|
online_version_str = j["vendor_version"].get<std::string>();
|
|
download_url_str = j["download_url"].get<std::string>();
|
|
changelog = j.value("changelog", std::string());
|
|
}
|
|
} 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);
|
|
// Orca: the OPC cache is the vendor's whole installation in one file; clear it too.
|
|
fs::remove(cache_profile_path / (vendor_id + ".opc"), ec);
|
|
// Best-effort cleanup of the legacy on-disk changelog written by older builds
|
|
// (changelogs are now kept in memory - see vendor_changelogs).
|
|
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
|
|
// either the vendor json/folder or the vendor cache 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;
|
|
|
|
const fs::path cached_vendor_json = cache_profile_path / (vendor_id + ".json");
|
|
const fs::path cached_vendor_folder = cache_profile_path / vendor_id;
|
|
|
|
const fs::path cached_vendor_opc = cache_profile_path / (vendor_id + ".opc");
|
|
|
|
bool is_json_update = fs::is_regular_file(cached_vendor_json) && fs::is_directory(cached_vendor_folder) && !fs::is_empty(cached_vendor_folder);
|
|
bool is_opc_update = fs::is_regular_file(cached_vendor_opc);
|
|
if (!is_json_update && !is_opc_update) {
|
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] rejected update for " << vendor_id
|
|
<< ": expected " << vendor_id << ".json and a non-empty "
|
|
<< vendor_id << " directory, or OPC update format.";
|
|
fs::remove_all(cached_vendor_folder, ec);
|
|
fs::remove(cached_vendor_json, ec);
|
|
return;
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(vendor_changelogs_mutex);
|
|
vendor_changelogs[vendor_id] = std::move(changelog);
|
|
}
|
|
|
|
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)
|
|
{
|
|
try {
|
|
std::string common_version = "00.00.00.00";
|
|
std::string language_version = "00.00.00.00";
|
|
fs::path cache_root = fs::path(data_dir()) / "resources/tooltip";
|
|
try {
|
|
auto vf = cache_root / "common" / "version";
|
|
if (fs::exists(vf)) Slic3r::load_string_file(vf, common_version);
|
|
vf = cache_root / language / "version";
|
|
if (fs::exists(vf)) Slic3r::load_string_file(vf, language_version);
|
|
} catch (...) {}
|
|
std::map<std::string, Resource> resources
|
|
{
|
|
{"slicer/tooltip/common", { common_version, "", "", false, (cache_root / "common").string() }},
|
|
{"slicer/tooltip/" + language, { language_version, "", "", false, (cache_root / language).string() }}
|
|
};
|
|
sync_resources(http_url, resources);
|
|
for (auto &r : resources) {
|
|
if (!r.second.url.empty()) {
|
|
GUI::MarkdownTip::Reload();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch (std::exception& e) {
|
|
BOOST_LOG_TRIVIAL(warning) << format("[Orca Updater] sync_tooltip: %1%", e.what());
|
|
}
|
|
}
|
|
|
|
// return true means there are plugins files
|
|
bool PresetUpdater::priv::get_cached_plugins_version(std::string& cached_version, bool &force)
|
|
{
|
|
// The OTA plugin cache lives in its own ota/plugins subfolder; the update dialog
|
|
// (Plater::priv::update_plugin_when_launch) reads the changelog from the same place.
|
|
auto cache_folder = cache_path / "plugins";
|
|
std::string network_library, player_library, live555_library;
|
|
bool has_plugins = false;
|
|
|
|
#if defined(_MSC_VER) || defined(_WIN32)
|
|
network_library = cache_folder.string() + "/bambu_networking.dll";
|
|
player_library = cache_folder.string() + "/BambuSource.dll";
|
|
live555_library = cache_folder.string() + "/live555.dll";
|
|
#elif defined(__WXMAC__)
|
|
network_library = cache_folder.string() + "/libbambu_networking.dylib";
|
|
player_library = cache_folder.string() + "/libBambuSource.dylib";
|
|
live555_library = cache_folder.string() + "/liblive555.dylib";
|
|
#else
|
|
network_library = cache_folder.string() + "/libbambu_networking.so";
|
|
player_library = cache_folder.string() + "/libBambuSource.so";
|
|
live555_library = cache_folder.string() + "/liblive555.so";
|
|
#endif
|
|
|
|
std::string changelog_file = cache_folder.string() + "/network_plugins.json";
|
|
if (boost::filesystem::exists(network_library)
|
|
&& boost::filesystem::exists(player_library)
|
|
&& boost::filesystem::exists(live555_library)
|
|
&& boost::filesystem::exists(changelog_file))
|
|
{
|
|
has_plugins = true;
|
|
try {
|
|
boost::nowide::ifstream ifs(changelog_file);
|
|
json j;
|
|
ifs >> j;
|
|
|
|
if (j.contains("version"))
|
|
cached_version = j["version"];
|
|
if (j.contains("force"))
|
|
force = j["force"];
|
|
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< ": cached_version = "<<cached_version<<", force = " << force;
|
|
}
|
|
catch(nlohmann::detail::parse_error &err) {
|
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse "<<changelog_file<<" got a nlohmann::detail::parse_error, reason = " << err.what();
|
|
//throw ConfigurationError(format("Failed loading json file \"%1%\": %2%", file_path, err.what()));
|
|
}
|
|
}
|
|
|
|
return has_plugins;
|
|
}
|
|
|
|
void PresetUpdater::priv::sync_plugins(std::string http_url, std::string plugin_version)
|
|
{
|
|
if (plugin_version == "00.00.00.00") {
|
|
BOOST_LOG_TRIVIAL(info) << "non need to sync plugins for there is no plugins currently.";
|
|
return;
|
|
}
|
|
std::string curr_version = GUI::wxGetApp().use_legacy_network_plugin() ? BAMBU_NETWORK_AGENT_VERSION_LEGACY : get_latest_network_version();
|
|
std::string using_version = curr_version.substr(0, 9) + "00";
|
|
auto cache_plugin_folder = cache_path / "plugins";
|
|
|
|
// Orca: drop leftovers from the old flat ota/ cache layout (pre ota/plugins) so the
|
|
// stale files cannot linger forever after this layout migration.
|
|
{
|
|
#if defined(_MSC_VER) || defined(_WIN32)
|
|
const char* legacy_names[] = {"bambu_networking.dll", "BambuSource.dll", "live555.dll", "network_plugins.json"};
|
|
#elif defined(__WXMAC__)
|
|
const char* legacy_names[] = {"libbambu_networking.dylib", "libBambuSource.dylib", "liblive555.dylib", "network_plugins.json"};
|
|
#else
|
|
const char* legacy_names[] = {"libbambu_networking.so", "libBambuSource.so", "liblive555.so", "network_plugins.json"};
|
|
#endif
|
|
for (const char* name : legacy_names) {
|
|
boost::system::error_code ec;
|
|
auto legacy_file = cache_path / name;
|
|
if (boost::filesystem::exists(legacy_file, ec))
|
|
boost::filesystem::remove(legacy_file, ec);
|
|
}
|
|
}
|
|
|
|
std::string cached_version;
|
|
bool force_upgrade = false;
|
|
get_cached_plugins_version(cached_version, force_upgrade);
|
|
if (!cached_version.empty()) {
|
|
bool need_delete_cache = false;
|
|
Semver current_semver = curr_version;
|
|
Semver cached_semver = cached_version;
|
|
|
|
int curent_patch_cc = current_semver.patch()/100;
|
|
int cached_patch_cc = cached_semver.patch()/100;
|
|
int curent_patch_dd = current_semver.patch()%100;
|
|
int cached_patch_dd = cached_semver.patch()%100;
|
|
if ((cached_semver.maj() != current_semver.maj())
|
|
|| (cached_semver.min() != current_semver.min())
|
|
|| (curent_patch_cc != cached_patch_cc))
|
|
{
|
|
need_delete_cache = true;
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("cached plugins version %1% not match with current %2%")%cached_version%curr_version;
|
|
}
|
|
else if (cached_patch_dd <= curent_patch_dd) {
|
|
need_delete_cache = true;
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("cached plugins version %1% not newer than current %2%")%cached_version%curr_version;
|
|
}
|
|
else {
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("cached plugins version %1% newer than current %2%")%cached_version%curr_version;
|
|
plugin_version = cached_version;
|
|
}
|
|
|
|
if (need_delete_cache) {
|
|
BOOST_LOG_TRIVIAL(info) << "[remove_old_networking_plugins] remove the plugins directory " << cache_plugin_folder.string();
|
|
try {
|
|
fs::remove_all(cache_plugin_folder);
|
|
} catch (...) {
|
|
BOOST_LOG_TRIVIAL(error) << "Failed removing the plugins directory " << cache_plugin_folder.string();
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(__WINDOWS__)
|
|
if (GUI::wxGetApp().is_running_on_arm64() && !GUI::wxGetApp().use_legacy_network_plugin()) {
|
|
//set to arm64 for plugins
|
|
std::map<std::string, std::string> current_headers = Slic3r::Http::get_extra_headers();
|
|
current_headers["X-BBL-OS-Type"] = "windows_arm";
|
|
|
|
Slic3r::Http::set_extra_headers(current_headers);
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("set X-BBL-OS-Type to windows_arm");
|
|
}
|
|
#endif
|
|
try {
|
|
std::map<std::string, Resource> resources
|
|
{
|
|
{"slicer/plugins/cloud", { using_version, "", "", false, cache_plugin_folder.string()}}
|
|
};
|
|
sync_resources(http_url, resources, true, plugin_version, "network_plugins.json");
|
|
}
|
|
catch (std::exception& e) {
|
|
BOOST_LOG_TRIVIAL(warning) << format("[Orca Updater] sync_plugins: %1%", e.what());
|
|
}
|
|
#if defined(__WINDOWS__)
|
|
if (GUI::wxGetApp().is_running_on_arm64() && !GUI::wxGetApp().use_legacy_network_plugin()) {
|
|
//set back
|
|
std::map<std::string, std::string> current_headers = Slic3r::Http::get_extra_headers();
|
|
current_headers["X-BBL-OS-Type"] = "windows";
|
|
|
|
Slic3r::Http::set_extra_headers(current_headers);
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("set X-BBL-OS-Type back to windows");
|
|
}
|
|
#endif
|
|
|
|
bool result = get_cached_plugins_version(cached_version, force_upgrade);
|
|
if (result) {
|
|
BOOST_LOG_TRIVIAL(info) << format("[Orca Updater] found new plugins: %1%, prompt to update, force_upgrade %2%", cached_version, force_upgrade);
|
|
if (force_upgrade) {
|
|
auto app_config = GUI::wxGetApp().app_config;
|
|
if (!app_config)
|
|
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::BBLPluginUpdateAvailable);
|
|
else
|
|
app_config->set("update_network_plugin", "true");
|
|
}
|
|
else
|
|
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::BBLPluginUpdateAvailable);
|
|
}
|
|
}
|
|
|
|
void PresetUpdater::priv::sync_printer_config(std::string http_url)
|
|
{
|
|
std::string curr_version = SLIC3R_VERSION;
|
|
std::string using_version = curr_version.substr(0, 6) + "00.00";
|
|
|
|
std::string cached_version;
|
|
std::string data_dir_str = data_dir();
|
|
boost::filesystem::path data_dir_path(data_dir_str);
|
|
auto config_folder = data_dir_path / "printers";
|
|
auto cache_folder = data_dir_path / "ota" / "printers";
|
|
|
|
try {
|
|
auto version_file = config_folder / "version.txt";
|
|
if (fs::exists(version_file)) {
|
|
Slic3r::load_string_file(version_file, curr_version);
|
|
boost::algorithm::trim(curr_version);
|
|
}
|
|
} catch (...) {}
|
|
try {
|
|
auto version_file = cache_folder / "version.txt";
|
|
if (fs::exists(version_file)) {
|
|
Slic3r::load_string_file(version_file, cached_version);
|
|
boost::algorithm::trim(cached_version);
|
|
}
|
|
} catch (...) {}
|
|
if (!cached_version.empty()) {
|
|
bool need_delete_cache = false;
|
|
Semver current_semver = curr_version;
|
|
Semver cached_semver = cached_version;
|
|
|
|
if ((cached_semver.maj() != current_semver.maj()) || (cached_semver.min() != current_semver.min())) {
|
|
need_delete_cache = true;
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("cached printer config version %1% not match with current %2%") % cached_version % curr_version;
|
|
} else if (cached_semver.patch() <= current_semver.patch()) {
|
|
need_delete_cache = true;
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("cached printer config version %1% not newer than current %2%") % cached_version % curr_version;
|
|
} else {
|
|
using_version = cached_version;
|
|
}
|
|
|
|
if (need_delete_cache) {
|
|
boost::system::error_code ec;
|
|
boost::filesystem::remove_all(cache_folder, ec);
|
|
cached_version = curr_version;
|
|
}
|
|
}
|
|
|
|
try {
|
|
std::map<std::string, Resource> resources{{"slicer/printer/bbl", {using_version, "", "", false, cache_folder.string()}}};
|
|
sync_resources(http_url, resources, false, cached_version, "printer.json");
|
|
} catch (std::exception &e) {
|
|
BOOST_LOG_TRIVIAL(warning) << format("[Orca Updater] sync_printer_config: %1%", e.what());
|
|
}
|
|
|
|
bool result = false;
|
|
try {
|
|
auto version_file = cache_folder / "version.txt";
|
|
if (fs::exists(version_file)) {
|
|
Slic3r::load_string_file(version_file, cached_version);
|
|
boost::algorithm::trim(cached_version);
|
|
result = true;
|
|
}
|
|
} catch (...) {}
|
|
if (result) {
|
|
BOOST_LOG_TRIVIAL(info) << format("[Orca Updater] found new printer config: %1%, prompt to update", cached_version);
|
|
waiting_printer_updates = get_printer_config_updates(true);
|
|
if (waiting_printer_updates.updates.size() > 0) {
|
|
has_waiting_printer_updates = true;
|
|
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::BBLPrinterConfigUpdateAvailable);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool PresetUpdater::priv::install_bundles_rsrc(const std::vector<std::string>& bundles, bool snapshot) const
|
|
{
|
|
// Use the Core library function to install bundles
|
|
// This function is now in libslic3r so both Core and GUI can use it
|
|
if (!Slic3r::install_vendor_bundles_from_resources(bundles)) {
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to install bundles from resources";
|
|
return false;
|
|
}
|
|
|
|
// Snapshot logic is currently commented out in perform_updates, so we don't need to handle it here
|
|
// If snapshot logic is needed in the future, it can be added here
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Orca: copy/update the vendor profiles from resource to system folder
|
|
void PresetUpdater::priv::check_installed_vendor_profiles() const
|
|
{
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:Checking whether the profile from resource is newer";
|
|
|
|
AppConfig *app_config = GUI::wxGetApp().app_config;
|
|
|
|
const auto enabled_vendors = app_config->vendors();
|
|
|
|
std::set<std::string> bundles;
|
|
// A vendor is named by its profile or, where the build ships preset caches
|
|
// instead of the raw profile JSONs, by its cache alone.
|
|
for (const std::string &vendor_name : vendor_names_in(rsrc_path)) {
|
|
// enabled_vendors lists the vendors whose printer models the user picked, and
|
|
// neither of these two is ever in it.
|
|
const auto is_vendor_enabled = (vendor_name == PresetBundle::ORCA_DEFAULT_BUNDLE)
|
|
|| (vendor_name == PresetBundle::ORCA_FILAMENT_LIBRARY)
|
|
|| (enabled_vendors.find(vendor_name) != enabled_vendors.end());
|
|
if (is_vendor_installed(vendor_name)) {
|
|
if (is_vendor_enabled) {
|
|
// Orca: whichever form of the vendor resources ships at the newer
|
|
// version is the one installing lays down, and the one to judge
|
|
// what is installed against.
|
|
Semver resource_ver = resource_vendor_version(vendor_name);
|
|
// Orca: a vendor installed as a preset cache has no profile
|
|
// beside it; the version it was installed at is in the cache.
|
|
Semver vendor_ver = installed_vendor_version(vendor_name);
|
|
|
|
if (vendor_ver < resource_ver) {
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:found vendor " << vendor_name << " newer version "
|
|
<< resource_ver.to_string() << " from resource, old version " << vendor_ver.to_string();
|
|
bundles.insert(vendor_name);
|
|
}
|
|
} else {
|
|
// need to be removed because not installed
|
|
remove_installed_vendor(vendor_name);
|
|
}
|
|
} else if (is_vendor_enabled) {
|
|
bundles.insert(vendor_name);
|
|
}
|
|
}
|
|
|
|
if (bundles.size() > 0) {
|
|
install_bundles_rsrc(std::vector(bundles.begin(), bundles.end()), false);
|
|
}
|
|
}
|
|
|
|
Updates PresetUpdater::priv::get_printer_config_updates(bool update) const
|
|
{
|
|
std::string data_dir_str = data_dir();
|
|
boost::filesystem::path data_dir_path(data_dir_str);
|
|
boost::filesystem::path resc_dir_path(resources_dir());
|
|
auto config_folder = data_dir_path / "printers";
|
|
auto resc_folder = (update ? cache_path : resc_dir_path) / "printers";
|
|
std::string curr_version;
|
|
std::string resc_version;
|
|
try {
|
|
Slic3r::load_string_file(resc_folder / "version.txt", resc_version);
|
|
boost::algorithm::trim(resc_version);
|
|
} catch (...) {}
|
|
try {
|
|
Slic3r::load_string_file(config_folder / "version.txt", curr_version);
|
|
boost::algorithm::trim(curr_version);
|
|
} catch (...) {}
|
|
|
|
if (!curr_version.empty()) {
|
|
Semver curr_ver = curr_version;
|
|
Semver resc_ver = resc_version;
|
|
|
|
bool version_match = ((resc_ver.maj() == curr_ver.maj()) && (resc_ver.min() == curr_ver.min()));
|
|
|
|
if (!version_match || (curr_ver < resc_ver)) {
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:found newer version " << resc_version << " from resource, old version " << curr_version;
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
Updates updates;
|
|
Version version;
|
|
version.config_version = resc_version;
|
|
std::string change_log;
|
|
if (update) {
|
|
std::string changelog_file = (resc_folder / "printer.json").string();
|
|
try {
|
|
boost::nowide::ifstream ifs(changelog_file);
|
|
json j;
|
|
ifs >> j;
|
|
version.comment = j["description"];
|
|
} catch (...) {}
|
|
}
|
|
updates.updates.emplace_back(std::move(resc_folder), std::move(config_folder), version, "bbl", change_log, version.comment, false, true);
|
|
return updates;
|
|
}
|
|
|
|
// Generates a list of bundle updates that are to be performed.
|
|
// Version of slic3r that was running the last time and which was read out from PrusaSlicer.ini is provided
|
|
// as a parameter.
|
|
// Orca: OTA profile updates should be loacated in ota/profiles folder
|
|
Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version) const
|
|
{
|
|
Updates updates;
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:Checking for cached configuration updates...";
|
|
auto cache_profile_path = cache_path / "profiles";
|
|
if (!fs::exists(cache_profile_path))
|
|
return updates;
|
|
|
|
// Orca (PR #130): vendor changelogs are captured in memory during
|
|
// sync_vendor_config()/check_new_vendors(), not written beside the cache.
|
|
std::unordered_map<std::string, std::string> changelogs;
|
|
{
|
|
std::lock_guard<std::mutex> lock(vendor_changelogs_mutex);
|
|
changelogs = vendor_changelogs;
|
|
}
|
|
|
|
for (auto &dir_entry : boost::filesystem::directory_iterator(cache_profile_path)) {
|
|
const auto &path = dir_entry.path();
|
|
std::string file_path = path.string();
|
|
const bool is_opc_file = boost::iequals(path.extension().string(), ".opc");
|
|
if (!is_json_file(file_path) && !is_opc_file)
|
|
continue;
|
|
|
|
const std::string vendor_name = path.stem().string();
|
|
auto print_in_cache = (cache_profile_path / vendor_name / PRESET_PRINT_NAME);
|
|
auto filament_in_cache = (cache_profile_path / vendor_name / PRESET_FILAMENT_NAME);
|
|
auto machine_in_cache = (cache_profile_path / vendor_name / PRESET_PRINTER_NAME);
|
|
|
|
// Orca (PR #130): a JSON cache entry is only meaningful next to a non-empty
|
|
// <vendor>/ preset directory; a stray or half-downloaded <vendor>.json is
|
|
// skipped. An .opc cache is a single self-contained file (validated below),
|
|
// so this check does not apply to it.
|
|
if (!is_opc_file) {
|
|
const auto vendor_folder_in_cache = cache_profile_path / vendor_name;
|
|
if (!fs::is_regular_file(path) || !fs::is_directory(vendor_folder_in_cache) ||
|
|
fs::is_empty(vendor_folder_in_cache)) {
|
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]:ignoring invalid cached update for "
|
|
<< vendor_name << ": expected " << vendor_name
|
|
<< ".json and a non-empty " << vendor_name << " directory";
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (is_vendor_installed(vendor_name)
|
|
|| is_opc_file
|
|
|| fs::exists(print_in_cache)
|
|
|| fs::exists(filament_in_cache)
|
|
|| fs::exists(machine_in_cache)) {
|
|
// Orca: a vendor installed as a preset cache carries its version there.
|
|
Semver vendor_ver = installed_vendor_version(vendor_name);
|
|
|
|
Semver cache_ver;
|
|
std::string description;
|
|
bool force_update = false;
|
|
if (is_opc_file) {
|
|
cache_ver = VendorCacheFile::usable_version(file_path, vendor_name);
|
|
if (!cache_ver.valid()) {
|
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater]:ignoring unreadable vendor cache " << file_path;
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
std::map<std::string, std::string> key_values;
|
|
std::vector<std::string> keys(3);
|
|
keys[0] = BBL_JSON_KEY_VERSION;
|
|
keys[1] = BBL_JSON_KEY_DESCRIPTION;
|
|
keys[2] = BBL_JSON_KEY_FORCE_UPDATE;
|
|
get_values_from_json(file_path, keys, key_values);
|
|
description = key_values[BBL_JSON_KEY_DESCRIPTION];
|
|
if (key_values.find(BBL_JSON_KEY_FORCE_UPDATE) != key_values.end())
|
|
force_update = (key_values[BBL_JSON_KEY_FORCE_UPDATE] == "1")?true:false;
|
|
auto config_version = Semver::parse(key_values[BBL_JSON_KEY_VERSION]);
|
|
if (config_version)
|
|
cache_ver = *config_version;
|
|
}
|
|
|
|
// Orca (PR #130): changelog for this vendor was captured in memory at sync time.
|
|
const auto changelog_it = changelogs.find(vendor_name);
|
|
std::string changelog = changelog_it != changelogs.end() ? changelog_it->second : std::string();
|
|
|
|
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;
|
|
version.config_version = cache_ver;
|
|
version.comment = description;
|
|
if (is_opc_file) {
|
|
// A cache contains the vendor profile and all presets.
|
|
// Install it directly; Update::install removes any
|
|
// superseded JSON representation.
|
|
auto &update = updates.updates.emplace_back(
|
|
std::move(file_path), vendor_path / (vendor_name + ".opc"),
|
|
std::move(version), vendor_name, changelog, "", force_update, false);
|
|
update.is_opc = true;
|
|
}
|
|
else {
|
|
// JSON profile and its preset directory are installed
|
|
// separately, as before.
|
|
updates.updates.emplace_back(std::move(file_path),
|
|
vendor_path / (vendor_name + ".json"), std::move(version),
|
|
vendor_name, changelog, "", force_update, false);
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
return updates;
|
|
}
|
|
|
|
//BBS: switch to new BBL.json configs
|
|
bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const
|
|
{
|
|
//std::string vendor_path;
|
|
//std::string vendor_name;
|
|
if (updates.incompats.size() > 0) {
|
|
//if (snapshot) {
|
|
// BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
|
|
// if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_DOWNGRADE, "",
|
|
// _u8L("Continue and install configuration updates?")))
|
|
// return false;
|
|
//}
|
|
BOOST_LOG_TRIVIAL(info) << format("[Orca Updater]:Deleting %1% incompatible bundles", updates.incompats.size());
|
|
|
|
for (auto &incompat : updates.incompats) {
|
|
BOOST_LOG_TRIVIAL(info) << '\t' << incompat;
|
|
incompat.remove();
|
|
}
|
|
} else if (updates.updates.size() > 0) {
|
|
//if (snapshot) {
|
|
// BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
|
|
// if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE, "",
|
|
// _u8L("Continue and install configuration updates?")))
|
|
// return false;
|
|
//}
|
|
|
|
BOOST_LOG_TRIVIAL(info) << format("[Orca Updater]:Performing %1% updates", updates.updates.size());
|
|
|
|
for (const auto &update : updates.updates) {
|
|
BOOST_LOG_TRIVIAL(info) << '\t' << update;
|
|
|
|
update.install();
|
|
//if (!update.is_directory) {
|
|
// vendor_path = update.source.parent_path().string();
|
|
// vendor_name = update.vendor;
|
|
//}
|
|
}
|
|
|
|
//if (!vendor_path.empty()) {
|
|
// PresetBundle bundle;
|
|
// // Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
|
|
// bundle.load_vendor_configs_from_json(vendor_path, vendor_name, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
|
|
|
|
// BOOST_LOG_TRIVIAL(info) << format("Deleting %1% conflicting presets", bundle.prints.size() + bundle.filaments.size() + bundle.printers.size());
|
|
|
|
// auto preset_remover = [](const Preset& preset) {
|
|
// BOOST_LOG_TRIVIAL(info) << '\t' << preset.file;
|
|
// fs::remove(preset.file);
|
|
// };
|
|
|
|
// for (const auto &preset : bundle.prints) { preset_remover(preset); }
|
|
// for (const auto &preset : bundle.filaments) { preset_remover(preset); }
|
|
// for (const auto &preset : bundle.printers) { preset_remover(preset); }
|
|
//}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void PresetUpdater::priv::set_waiting_updates(Updates u)
|
|
{
|
|
waiting_updates = u;
|
|
has_waiting_updates = true;
|
|
}
|
|
|
|
PresetUpdater::PresetUpdater() :
|
|
p(new priv())
|
|
{}
|
|
|
|
|
|
// Public
|
|
|
|
PresetUpdater::~PresetUpdater()
|
|
{
|
|
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();
|
|
}
|
|
if (p) {
|
|
p->vendor_check_cancel = true;
|
|
for (auto& t : p->vendor_check_threads)
|
|
if (t.joinable())
|
|
t.join();
|
|
}
|
|
}
|
|
|
|
//BBS: change directories by design
|
|
//BBS: refine the preset updater logic
|
|
void PresetUpdater::sync(std::string http_url, std::string language, std::string plugin_version, PresetBundle * /*preset_bundle*/)
|
|
{
|
|
//p->set_download_prefs(GUI::wxGetApp().app_config);
|
|
if (!p->enabled_version_check && !p->enabled_config_update) { return; }
|
|
|
|
p->thread = std::thread([this, http_url, language, plugin_version]() {
|
|
try {
|
|
this->p->sync_version();
|
|
if (p->cancel)
|
|
return;
|
|
// Vendor profile updates are triggered by check_vendor_update()
|
|
// after the startup printer preset has been restored.
|
|
this->p->sync_plugins(http_url, plugin_version);
|
|
this->p->sync_printer_config(http_url);
|
|
// Orca (PR #130): the filament library is always installed, so refresh it
|
|
// from the updater on every startup sync rather than deferring to check_vendor_update().
|
|
this->p->sync_vendor_config(PresetBundle::ORCA_FILAMENT_LIBRARY);
|
|
//if (p->cancel)
|
|
// return;
|
|
//remove the tooltip currently
|
|
//this->p->sync_tooltip(http_url, language);
|
|
} catch (const std::exception &e) {
|
|
BOOST_LOG_TRIVIAL(error) << "[Orca Updater] background sync failed: " << e.what();
|
|
} catch (...) {
|
|
BOOST_LOG_TRIVIAL(error) << "[Orca Updater] background sync failed with an unknown exception";
|
|
}
|
|
});
|
|
}
|
|
|
|
void PresetUpdater::check_vendor_update(const std::string& vendor_id)
|
|
{
|
|
if (!p->enabled_config_update) return;
|
|
if (vendor_id.empty()) return;
|
|
|
|
if (!p->checked_vendors.insert(vendor_id).second)
|
|
return;
|
|
|
|
p->vendor_check_threads.emplace_back([this, vendor_id]() {
|
|
try {
|
|
this->p->prune_tmp(vendor_id);
|
|
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();
|
|
} catch (...) {
|
|
BOOST_LOG_TRIVIAL(error) << "[Orca Updater] vendor update failed for " << vendor_id << " with an unknown exception";
|
|
}
|
|
});
|
|
}
|
|
|
|
// Orca: ask the server which vendors from `system_vendors` have a profile bundle available that
|
|
// isn't installed yet (or is newer than what's installed). Request body maps vendor id -> currently
|
|
// installed profile version (unknown/not-yet-installed vendors report "0.0.0"). Response maps
|
|
// vendor id -> {version, download_url, changelog} for each vendor the server has an update for.
|
|
// Any such vendor is downloaded and cached under ota/profiles the same way sync_vendor_config()
|
|
// does; the caller's check_config_updates_from_updater() -> get_config_updates()/perform_updates()
|
|
// flow then installs the cached profiles into data_dir()/system.
|
|
//
|
|
// Mirrors check_vendor_update()/sync_vendor_config(): the network query and the download/extract
|
|
// work run on a background thread (vendor_check_threads), never on the calling (UI) thread. Only
|
|
// the confirmation dialog (which must run on the UI thread) and the final callback are marshaled
|
|
// back via CallAfter().
|
|
void PresetUpdater::priv::check_new_vendors(const std::set<std::string>& system_vendors,
|
|
std::function<void(std::vector<std::string>, bool)> callback)
|
|
{
|
|
vendor_check_threads.emplace_back([this, system_vendors, callback]() {
|
|
AppConfig* app_config = GUI::wxGetApp().app_config;
|
|
std::string url = app_config->profile_update_url() + "/new?orcaslicer_version=" + Http::url_encode(SoftFever_VERSION);
|
|
|
|
auto check_cancel = [this](Http::Progress, bool& cancel_http) {
|
|
if (cancel || vendor_check_cancel)
|
|
cancel_http = true;
|
|
};
|
|
|
|
json request_body = json::object();
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] checking new vendors for:";
|
|
for (const auto& vendor_id : system_vendors) {
|
|
// Orca: installed_vendor_version() reads whichever form the vendor is
|
|
// installed as - the .json profile or the .opc preset cache stamp -
|
|
// so a cache-only vendor is not reported as version 0.0.0 and then
|
|
// endlessly re-offered by the server.
|
|
Semver installed_ver = installed_vendor_version(vendor_id);
|
|
request_body[vendor_id] = installed_ver.to_string();
|
|
BOOST_LOG_TRIVIAL(info) << vendor_id << " (installed version " << installed_ver.to_string() << ")";
|
|
}
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] new vendor check request url: " << url;
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] new vendor check request body: " << request_body.dump(2);
|
|
|
|
json response_json;
|
|
bool got_response = false;
|
|
|
|
auto post = Http::post(url);
|
|
|
|
post.timeout_connect(5);
|
|
post.on_progress(check_cancel);
|
|
post.header("Content-Type", "application/json");
|
|
post.on_error([](std::string body, std::string error, unsigned http_status) {
|
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] new vendor check HTTP error: " << error;
|
|
})
|
|
.on_complete([&response_json, &got_response](std::string body, unsigned http_status) {
|
|
if (http_status != 200)
|
|
return;
|
|
try {
|
|
json j = json::parse(body);
|
|
if (j.is_object()) {
|
|
response_json = std::move(j);
|
|
got_response = true;
|
|
}
|
|
} catch (const std::exception& e) {
|
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] new vendor check JSON parse failed: " << e.what();
|
|
}
|
|
});
|
|
|
|
post.set_post_body(request_body.dump());
|
|
post.perform_sync();
|
|
|
|
if (cancel || vendor_check_cancel)
|
|
return;
|
|
|
|
if (!got_response) {
|
|
GUI::wxGetApp().CallAfter([callback]() { callback({}, false); });
|
|
return;
|
|
}
|
|
|
|
// Collect candidates before touching the filesystem or network again.
|
|
struct NewVendorCandidate
|
|
{
|
|
std::string vendor_id;
|
|
Semver version;
|
|
std::string download_url;
|
|
std::string changelog;
|
|
};
|
|
std::vector<NewVendorCandidate> candidates;
|
|
for (auto it = response_json.begin(); it != response_json.end(); ++it) {
|
|
const json& entry = it.value();
|
|
if (!entry.is_object())
|
|
continue;
|
|
std::string download_url_str = entry.value("download_url", std::string());
|
|
if (download_url_str.empty())
|
|
continue;
|
|
|
|
NewVendorCandidate candidate;
|
|
candidate.vendor_id = it.key();
|
|
auto parsed_ver = Semver::parse(entry.value("version", std::string()));
|
|
candidate.version = parsed_ver ? *parsed_ver : Semver();
|
|
candidate.download_url = std::move(download_url_str);
|
|
candidate.changelog = entry.value("changelog", std::string());
|
|
candidates.push_back(std::move(candidate));
|
|
}
|
|
|
|
if (candidates.empty()) {
|
|
GUI::wxGetApp().CallAfter([callback]() { callback({}, false); });
|
|
return;
|
|
}
|
|
|
|
// Orca: the confirmation dialog must run on the UI thread; if confirmed, the actual
|
|
// download/install work is dispatched back onto a new background thread from there,
|
|
// same as check_vendor_update() does for a single vendor.
|
|
GUI::wxGetApp().CallAfter([this, candidates, callback]() {
|
|
std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
|
|
for (const auto& candidate : candidates)
|
|
updates_msg.emplace_back(candidate.vendor_id, candidate.version, std::string(), candidate.changelog);
|
|
|
|
GUI::MsgUpdateConfig dlg(updates_msg);
|
|
if (dlg.ShowModal() != wxID_OK) {
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] user declined installing new vendors";
|
|
callback({}, true);
|
|
return;
|
|
}
|
|
|
|
// Orca: the actual download runs on a background thread below (so it doesn't block the
|
|
// UI), but that also means nothing visibly happens for the several seconds it can take
|
|
// (longer still if a retry kicks in) — push a notification so it's clear work is
|
|
// ongoing rather than looking hung.
|
|
{
|
|
std::string vendor_list;
|
|
for (const auto& candidate : candidates) {
|
|
if (!vendor_list.empty())
|
|
vendor_list += ", ";
|
|
vendor_list += candidate.vendor_id;
|
|
}
|
|
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(
|
|
_u8L("Downloading new vendor profile(s): ") + vendor_list + _u8L("..."));
|
|
}
|
|
|
|
vendor_check_threads.emplace_back([this, candidates, callback]() {
|
|
auto check_cancel = [this](Http::Progress, bool& cancel_http) {
|
|
if (cancel || vendor_check_cancel)
|
|
cancel_http = true;
|
|
};
|
|
|
|
std::vector<std::string> new_vendor_ids;
|
|
std::vector<std::string> failed_vendor_ids;
|
|
auto cache_profile_path = cache_path / "profiles";
|
|
fs::create_directories(cache_profile_path);
|
|
boost::system::error_code ec;
|
|
|
|
for (const auto& candidate : candidates) {
|
|
if (cancel || vendor_check_cancel)
|
|
break;
|
|
|
|
const std::string& vendor_id = candidate.vendor_id;
|
|
const std::string& download_url_str = candidate.download_url;
|
|
std::string changelog = candidate.changelog;
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] downloading new vendor " << vendor_id << " version " << candidate.version.to_string();
|
|
|
|
// Clear only this vendor's cached data, same as sync_vendor_config().
|
|
fs::remove_all(cache_profile_path / vendor_id, ec);
|
|
fs::remove(cache_profile_path / (vendor_id + ".json"), ec);
|
|
|
|
fs::path download_file = cache_path / (vendor_id + TMP_EXTENSION);
|
|
bool download_ok = false;
|
|
|
|
// Orca: same retry pattern as Plater.cpp's project download — a single-shot
|
|
// 5s connect timeout against GitHub's redirect chain is prone to transient
|
|
// failures (DNS/connect hiccups) that succeed a moment later, so retry a few
|
|
// times before giving up rather than failing the whole vendor on one blip.
|
|
int retry_count = 0;
|
|
const int max_retries = 3;
|
|
bool keep_trying = true;
|
|
while (keep_trying && retry_count < max_retries) {
|
|
retry_count++;
|
|
Http::get(download_url_str)
|
|
.timeout_connect(5)
|
|
.on_progress(check_cancel)
|
|
.on_error([&vendor_id, &retry_count](std::string body, std::string error, unsigned http_status) {
|
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] download failed for new vendor " << vendor_id
|
|
<< " (attempt " << retry_count << "/" << max_retries << "): " << 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();
|
|
|
|
keep_trying = !download_ok && !(cancel || vendor_check_cancel);
|
|
}
|
|
|
|
if (!download_ok || cancel || vendor_check_cancel) {
|
|
if (!download_ok)
|
|
failed_vendor_ids.push_back(vendor_id);
|
|
continue;
|
|
}
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] extracting new vendor " << vendor_id;
|
|
if (!extract_file(download_file, cache_profile_path)) {
|
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] extraction failed for new vendor " << vendor_id;
|
|
fs::remove(download_file, ec);
|
|
failed_vendor_ids.push_back(vendor_id);
|
|
continue;
|
|
}
|
|
fs::remove(download_file, ec);
|
|
|
|
const fs::path cached_vendor_json = cache_profile_path / (vendor_id + ".json");
|
|
const fs::path cached_vendor_folder = cache_profile_path / vendor_id;
|
|
if (!fs::is_regular_file(cached_vendor_json) || !fs::is_directory(cached_vendor_folder) ||
|
|
fs::is_empty(cached_vendor_folder)) {
|
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] rejected new vendor " << vendor_id << ": expected " << vendor_id
|
|
<< ".json and a non-empty " << vendor_id << " directory";
|
|
fs::remove_all(cached_vendor_folder, ec);
|
|
fs::remove(cached_vendor_json, ec);
|
|
failed_vendor_ids.push_back(vendor_id);
|
|
continue;
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(vendor_changelogs_mutex);
|
|
vendor_changelogs[vendor_id] = std::move(changelog);
|
|
}
|
|
|
|
new_vendor_ids.push_back(vendor_id);
|
|
}
|
|
|
|
if (!new_vendor_ids.empty()) {
|
|
// Orca: the user already confirmed via the dialog above, so install right away
|
|
// instead of routing through check_config_updates_from_updater(), which only
|
|
// queues a passive notification (meant for the silent background per-vendor
|
|
// check) requiring yet another click + confirmation before anything is copied
|
|
// into data_dir()/system.
|
|
GUI::wxGetApp().CallAfter([this, new_vendor_ids] {
|
|
AppConfig* app_config = GUI::wxGetApp().app_config;
|
|
Updates updates = get_config_updates(app_config->orig_version());
|
|
|
|
// Only install the vendors just confirmed; leave any other unrelated
|
|
// pending cached update (from a background sync_vendor_config()) alone,
|
|
// still gated behind its own notification/confirmation.
|
|
std::set<std::string> confirmed(new_vendor_ids.begin(), new_vendor_ids.end());
|
|
Updates filtered;
|
|
for (auto& update : updates.updates)
|
|
if (confirmed.count(update.vendor))
|
|
filtered.updates.push_back(std::move(update));
|
|
|
|
if (filtered.updates.empty()) {
|
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] new vendors cached but no updates detected";
|
|
return;
|
|
}
|
|
|
|
if (!perform_updates(std::move(filtered)) || !reload_configs_update_gui()) {
|
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] failed to install new vendors";
|
|
return;
|
|
}
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] new vendors installed";
|
|
for (const auto& vendor_id : new_vendor_ids) {
|
|
Semver cur_ver = GUI::wxGetApp().preset_bundle->get_vendor_profile_version(vendor_id);
|
|
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(
|
|
GUI::NotificationType::PresetUpdateFinished,
|
|
GUI::NotificationManager::NotificationLevel::ImportantNotificationLevel,
|
|
Slic3r::format(_u8L("Configuration package: %1% updated to %2%"), vendor_id, cur_ver.to_string()));
|
|
}
|
|
});
|
|
}
|
|
|
|
if (!failed_vendor_ids.empty()) {
|
|
GUI::wxGetApp().CallAfter([failed_vendor_ids] {
|
|
std::string vendor_list;
|
|
for (const auto& vendor_id : failed_vendor_ids) {
|
|
if (!vendor_list.empty())
|
|
vendor_list += ", ";
|
|
vendor_list += vendor_id;
|
|
}
|
|
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(
|
|
_u8L("Failed to download vendor profile(s): ") + vendor_list);
|
|
});
|
|
}
|
|
|
|
GUI::wxGetApp().CallAfter([callback, new_vendor_ids]() { callback(new_vendor_ids, false); });
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
void PresetUpdater::check_new_vendors(const std::set<std::string>& system_vendors,
|
|
std::function<void(std::vector<std::string>, bool)> callback)
|
|
{
|
|
p->check_new_vendors(system_vendors, std::move(callback));
|
|
}
|
|
|
|
void PresetUpdater::slic3r_update_notify()
|
|
{
|
|
if (! p->enabled_version_check)
|
|
return;
|
|
}
|
|
|
|
static bool reload_configs_update_gui()
|
|
{
|
|
wxString header = _L("Please check any unsaved changes before updating the configuration.");
|
|
if (!GUI::wxGetApp().check_and_save_current_preset_changes(_L("Configuration updates"), header, false ))
|
|
return false;
|
|
|
|
// Reload global configuration
|
|
auto* app_config = GUI::wxGetApp().app_config;
|
|
// System profiles should not trigger any substitutions, user profiles may trigger substitutions, but these substitutions
|
|
// were already presented to the user on application start up. Just do substitutions now and keep quiet about it.
|
|
// However throw on substitutions in system profiles, those shall never happen with system profiles installed over the air.
|
|
GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem);
|
|
GUI::wxGetApp().load_current_presets();
|
|
GUI::wxGetApp().plater()->set_bed_shape();
|
|
|
|
return true;
|
|
}
|
|
|
|
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, UpdateParams params) const
|
|
{
|
|
if (! p->enabled_config_update) { return R_NOOP; }
|
|
|
|
auto updates = p->get_config_updates(old_slic3r_version);
|
|
|
|
if (updates.updates.size() > 0) {
|
|
|
|
bool force_update = false;
|
|
for (const auto& update : updates.updates) {
|
|
force_update = (update.forced_update ? true : force_update);
|
|
}
|
|
|
|
//forced update
|
|
if (force_update)
|
|
{
|
|
BOOST_LOG_TRIVIAL(info) << format("[Orca Updater]:Force updating will start, size %1% ", updates.updates.size());
|
|
std::vector<std::string> bundles;
|
|
for (const auto& update : updates.updates) {
|
|
if (update.is_directory)
|
|
continue;
|
|
bundles.push_back(update.vendor);
|
|
}
|
|
bool ret = p->perform_updates(std::move(updates));
|
|
if (!ret) {
|
|
BOOST_LOG_TRIVIAL(warning) << format("[Orca Updater]:perform_updates failed");
|
|
return R_INCOMPAT_EXIT;
|
|
}
|
|
|
|
ret = reload_configs_update_gui();
|
|
if (!ret) {
|
|
BOOST_LOG_TRIVIAL(warning) << format("[Orca Updater]:reload_configs_update_gui failed");
|
|
return R_INCOMPAT_EXIT;
|
|
}
|
|
for(auto b : bundles){
|
|
Semver cur_ver = GUI::wxGetApp().preset_bundle->get_vendor_profile_version(b);
|
|
GUI::wxGetApp()
|
|
.plater()
|
|
->get_notification_manager()
|
|
->push_notification(GUI::NotificationType::PresetUpdateFinished,
|
|
GUI::NotificationManager::NotificationLevel::ImportantNotificationLevel,
|
|
Slic3r::format(_u8L("Configuration package: %1% updated to %2%"), b, cur_ver.to_string()));
|
|
}
|
|
return R_UPDATE_INSTALLED;
|
|
}
|
|
|
|
// regular update
|
|
if (params == UpdateParams::SHOW_NOTIFICATION) {
|
|
p->set_waiting_updates(updates);
|
|
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAvailable);
|
|
}
|
|
else {
|
|
BOOST_LOG_TRIVIAL(info) << format("[Orca Updater]:Configuration package available. size %1%, need to confirm...", p->waiting_updates.updates.size());
|
|
|
|
std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
|
|
for (const auto& update : updates.updates) {
|
|
if (update.is_directory)
|
|
continue;
|
|
std::string changelog = update.change_log;
|
|
updates_msg.emplace_back(update.vendor, update.version.config_version, update.descriptions, std::move(changelog));
|
|
}
|
|
|
|
GUI::MsgUpdateConfig dlg(updates_msg, params == UpdateParams::FORCED_BEFORE_WIZARD);
|
|
|
|
const auto res = dlg.ShowModal();
|
|
if (res == wxID_OK) {
|
|
BOOST_LOG_TRIVIAL(debug) << "[Orca Updater]:selected yes to update";
|
|
if (! p->perform_updates(std::move(updates)) ||
|
|
! reload_configs_update_gui())
|
|
return R_ALL_CANCELED;
|
|
return R_UPDATE_INSTALLED;
|
|
}
|
|
else {
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:selected no for updating";
|
|
if (params == UpdateParams::FORCED_BEFORE_WIZARD && res == wxID_CANCEL)
|
|
return R_ALL_CANCELED;
|
|
return R_UPDATE_REJECT;
|
|
}
|
|
}
|
|
|
|
// MsgUpdateConfig will show after the notificaation is clicked
|
|
} else {
|
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:No configuration updates available.";
|
|
}
|
|
|
|
return R_NOOP;
|
|
}
|
|
|
|
//BBS: add json related logic
|
|
bool PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const
|
|
{
|
|
return p->install_bundles_rsrc(bundles, snapshot);
|
|
}
|
|
|
|
void PresetUpdater::on_update_notification_confirm()
|
|
{
|
|
if (!p->has_waiting_updates)
|
|
return;
|
|
BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size());
|
|
|
|
std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
|
|
for (const auto& update : p->waiting_updates.updates) {
|
|
//BBS: skip directory
|
|
if (update.is_directory)
|
|
continue;
|
|
std::string changelog = update.change_log;
|
|
updates_msg.emplace_back(update.vendor, update.version.config_version, update.descriptions, std::move(changelog));
|
|
}
|
|
|
|
GUI::MsgUpdateConfig dlg(updates_msg);
|
|
|
|
const auto res = dlg.ShowModal();
|
|
if (res == wxID_OK) {
|
|
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
|
|
if (p->perform_updates(std::move(p->waiting_updates)) &&
|
|
reload_configs_update_gui()) {
|
|
p->has_waiting_updates = false;
|
|
}
|
|
}
|
|
else {
|
|
BOOST_LOG_TRIVIAL(info) << "User refused the update";
|
|
}
|
|
}
|
|
|
|
void PresetUpdater::do_printer_config_update()
|
|
{
|
|
if (!p->has_waiting_printer_updates)
|
|
return;
|
|
BOOST_LOG_TRIVIAL(info) << "Update of printer configs available. Asking for confirmation ...";
|
|
|
|
std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
|
|
for (const auto &update : p->waiting_printer_updates.updates) {
|
|
std::string changelog = update.change_log;
|
|
updates_msg.emplace_back(update.vendor, update.version.config_version, update.descriptions, std::move(changelog));
|
|
}
|
|
|
|
GUI::MsgUpdateConfig dlg(updates_msg);
|
|
|
|
const auto res = dlg.ShowModal();
|
|
if (res == wxID_OK) {
|
|
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
|
|
if (p->perform_updates(std::move(p->waiting_printer_updates)))
|
|
p->has_waiting_printer_updates = false;
|
|
} else {
|
|
BOOST_LOG_TRIVIAL(info) << "User refused the update";
|
|
}
|
|
}
|
|
|
|
bool PresetUpdater::version_check_enabled() const
|
|
{
|
|
return p->enabled_version_check;
|
|
}
|
|
|
|
}
|