mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-31 13:57:07 +00:00
Compare commits
3 Commits
feature/fi
...
fix/opc-su
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b585d9f3d9 | ||
|
|
c4fea8ad24 | ||
|
|
d9fa43f1d7 |
@@ -415,7 +415,9 @@ img.ModelThumbnail {
|
|||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: inherit;
|
border-color: var(--border-color);
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
color: var(--fg-color-text);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 200px;
|
top: 200px;
|
||||||
@@ -423,13 +425,17 @@ img.ModelThumbnail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#NoticeBar {
|
#NoticeBar {
|
||||||
background-color:#00f0d8;
|
background-color: var(--main-color);
|
||||||
height: 40px;
|
height: 40px;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#NoticeBar.notice-error {
|
||||||
|
background-color: var(--button-bg-alert);
|
||||||
|
}
|
||||||
|
|
||||||
#NoticeContent {
|
#NoticeContent {
|
||||||
padding: 4mm 10mm;
|
padding: 4mm 10mm;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,34 @@ function HandleStudio( pVal )
|
|||||||
{
|
{
|
||||||
HandleModelList(pVal['response']);
|
HandleModelList(pVal['response']);
|
||||||
}
|
}
|
||||||
|
else if(strCmd=='check_new_printers_result')
|
||||||
|
{
|
||||||
|
let button = document.getElementById("CheckNewPrintersBtn");
|
||||||
|
if (button) {
|
||||||
|
button.style.pointerEvents = "auto";
|
||||||
|
button.style.opacity = "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
let noticeBar = document.getElementById("NoticeBar");
|
||||||
|
let noticeText = document.getElementById("NoticeText");
|
||||||
|
let hasError = pVal.hasOwnProperty("error");
|
||||||
|
noticeBar.classList.toggle("notice-error", hasError);
|
||||||
|
if (hasError) {
|
||||||
|
noticeBar.textContent = "Error";
|
||||||
|
noticeText.textContent = pVal["error"];
|
||||||
|
} else if (pVal["vendors"] && pVal["vendors"].length > 0) {
|
||||||
|
noticeBar.textContent = "New printers found";
|
||||||
|
noticeText.textContent = "New printer vendors installed: " + pVal["vendors"].join(", ");
|
||||||
|
} else if (pVal["declined"]) {
|
||||||
|
noticeBar.textContent = "Information";
|
||||||
|
noticeText.textContent = "New printer vendors were found, but installation was cancelled.";
|
||||||
|
} else {
|
||||||
|
noticeBar.textContent = "Information";
|
||||||
|
noticeText.textContent = "No new printers found.";
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowNotice(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function HandleModelList( pVal )
|
function HandleModelList( pVal )
|
||||||
@@ -78,9 +106,13 @@ function HandleModelList( pVal )
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Update Nozzel Html Append
|
//Update Nozzel Html Append
|
||||||
|
// ORCA: HandleModelList can now be called more than once per dialog (e.g. after a new vendor
|
||||||
|
// is installed via "check for new printers"). pModel always holds the full, current list, so
|
||||||
|
// clear each vendor's printer area before repopulating instead of appending on top of a
|
||||||
|
// previous render.
|
||||||
for( let key in ModelHtml )
|
for( let key in ModelHtml )
|
||||||
{
|
{
|
||||||
$(".OneVendorBlock[vendor='"+key+"'] .PrinterArea").append( ModelHtml[key] );
|
$(".OneVendorBlock[vendor='"+key+"'] .PrinterArea").empty().append( ModelHtml[key] );
|
||||||
}
|
}
|
||||||
|
|
||||||
//Update Checkbox
|
//Update Checkbox
|
||||||
@@ -343,6 +375,9 @@ function OnExit()
|
|||||||
let nTotal=ModelSelect.length;
|
let nTotal=ModelSelect.length;
|
||||||
|
|
||||||
if( nTotal==0 ) {
|
if( nTotal==0 ) {
|
||||||
|
let noticeBar = document.getElementById("NoticeBar");
|
||||||
|
noticeBar.classList.add("notice-error");
|
||||||
|
noticeBar.textContent = "Error";
|
||||||
ShowNotice(1);
|
ShowNotice(1);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,22 @@ function ConfirmSelect()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CheckForNewPrinters()
|
||||||
|
{
|
||||||
|
var tSend={};
|
||||||
|
tSend['sequence_id']=Math.round(new Date() / 1000);
|
||||||
|
tSend['command']="check_for_new_printers";
|
||||||
|
tSend['data']={};
|
||||||
|
|
||||||
|
var button = document.getElementById("CheckNewPrintersBtn");
|
||||||
|
if (button) {
|
||||||
|
button.style.pointerEvents = "none";
|
||||||
|
button.style.opacity = "0.6";
|
||||||
|
}
|
||||||
|
|
||||||
|
SendWXMessage( JSON.stringify(tSend) );
|
||||||
|
}
|
||||||
|
|
||||||
function CreateNewPrinter()
|
function CreateNewPrinter()
|
||||||
{
|
{
|
||||||
var tSend={};
|
var tSend={};
|
||||||
|
|||||||
@@ -82,6 +82,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="AcceptArea">
|
<div id="AcceptArea">
|
||||||
<div class="ButtonStyleRegular ButtonTypeChoice trans" id="CreateBtn" onclick="CreateNewPrinter()">Create</div>
|
<div class="ButtonStyleRegular ButtonTypeChoice trans" id="CreateBtn" onclick="CreateNewPrinter()">Create</div>
|
||||||
|
<div class="ButtonStyleRegular ButtonTypeChoice" id="CheckNewPrintersBtn" onclick="CheckForNewPrinters()">Check for new printers</div>
|
||||||
<div class="ButtonStyleConfirm ButtonTypeChoice trans" tid="t39" id="AcceptBtn" onclick="ConfirmSelect()">Confirm</div>
|
<div class="ButtonStyleConfirm ButtonTypeChoice trans" tid="t39" id="AcceptBtn" onclick="ConfirmSelect()">Confirm</div>
|
||||||
<div class="ButtonStyleRegular ButtonTypeChoice trans" tid="t38" id="PreBtn" onclick="CancelSelect()">Cancel</div>
|
<div class="ButtonStyleRegular ButtonTypeChoice trans" tid="t38" id="PreBtn" onclick="CancelSelect()">Cancel</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ namespace Slic3r {
|
|||||||
|
|
||||||
static const std::string VERSION_CHECK_URL = "https://check-version.orcaslicer.com/latest";
|
static const std::string VERSION_CHECK_URL = "https://check-version.orcaslicer.com/latest";
|
||||||
static const std::string PROFILE_UPDATE_URL = "https://check-version.orcaslicer.com/profile";
|
static const std::string PROFILE_UPDATE_URL = "https://check-version.orcaslicer.com/profile";
|
||||||
|
|
||||||
|
constexpr const char* CONFIG_ORCA_UPDATER_URL = "orca_updater_url";
|
||||||
|
|
||||||
static const std::string MODELS_STR = "models";
|
static const std::string MODELS_STR = "models";
|
||||||
|
|
||||||
const std::string AppConfig::SECTION_FILAMENTS = "filaments";
|
const std::string AppConfig::SECTION_FILAMENTS = "filaments";
|
||||||
@@ -1814,7 +1817,10 @@ std::string AppConfig::version_check_url() const
|
|||||||
|
|
||||||
std::string AppConfig::profile_update_url() const
|
std::string AppConfig::profile_update_url() const
|
||||||
{
|
{
|
||||||
return PROFILE_UPDATE_URL;
|
std::string orca_updater_url = get(CONFIG_ORCA_UPDATER_URL);
|
||||||
|
if (orca_updater_url.empty())
|
||||||
|
return PROFILE_UPDATE_URL;
|
||||||
|
return orca_updater_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AppConfig::exists()
|
bool AppConfig::exists()
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <wx/textdlg.h>
|
#include <wx/textdlg.h>
|
||||||
|
|
||||||
#include <wx/wx.h>
|
#include <wx/wx.h>
|
||||||
|
#include <wx/weakref.h>
|
||||||
#include <wx/display.h>
|
#include <wx/display.h>
|
||||||
#include <wx/fileconf.h>
|
#include <wx/fileconf.h>
|
||||||
#include <wx/file.h>
|
#include <wx/file.h>
|
||||||
@@ -562,6 +563,78 @@ void GuideFrame::OnScriptMessage(wxWebViewEvent &evt)
|
|||||||
m_ProfileJson["filament"][fName]["selected"] = 1;
|
m_ProfileJson["filament"][fName]["selected"] = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (strCmd == "check_for_new_printers") {
|
||||||
|
json response = json::object();
|
||||||
|
response["command"] = "check_new_printers_result";
|
||||||
|
// Guide pages currently send sequence_id as a number, while older
|
||||||
|
// pages may send it as a string. Preserve the value without
|
||||||
|
// forcing either representation.
|
||||||
|
if (j.contains("sequence_id"))
|
||||||
|
response["sequence_id"] = j["sequence_id"];
|
||||||
|
else
|
||||||
|
response["sequence_id"] = "";
|
||||||
|
|
||||||
|
if (!m_MainPtr->preset_updater) {
|
||||||
|
response["error"] = "Printer update service is unavailable.";
|
||||||
|
wxString strJS = wxString::Format("HandleStudio(%s)", response.dump(-1, ' ', true));
|
||||||
|
wxGetApp().CallAfter([this, strJS] { RunScript(strJS); });
|
||||||
|
} else {
|
||||||
|
// Orca: enumerate vendors directly from disk rather than from m_ProfileJson["model"]
|
||||||
|
// — a vendor with no machine models (e.g. a filament-only bundle, or a test fixture
|
||||||
|
// like "test123" with an empty machine_model_list) never gets a "vendor" entry
|
||||||
|
// pushed into "model" by LoadProfileFamily(), so it would be invisible to the
|
||||||
|
// request body and get endlessly re-offered by the server. Scan both the system dir
|
||||||
|
// (already-installed vendors) and the bundled resources dir (shipped-but-not-yet-
|
||||||
|
// installed vendors), same as LoadProfileData() does when building loaded_vendors.
|
||||||
|
std::set<std::string> system_vendors;
|
||||||
|
for (const auto& dir : {vendor_dir, rsrc_vendor_dir}) {
|
||||||
|
if (!boost::filesystem::exists(dir))
|
||||||
|
continue;
|
||||||
|
for (const auto& entry : boost::filesystem::directory_iterator(dir)) {
|
||||||
|
if (!boost::filesystem::is_directory(entry) && boost::iequals(entry.path().extension().string(), ".json"))
|
||||||
|
system_vendors.insert(entry.path().stem().string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Orca: check_new_vendors() is async (network + confirmation dialog + download
|
||||||
|
// all happen off the calling thread apart from the dialog itself); guard against
|
||||||
|
// this dialog being closed before the callback fires.
|
||||||
|
wxWeakRef<GuideFrame> weak_this(this);
|
||||||
|
try {
|
||||||
|
m_MainPtr->preset_updater->check_new_vendors(
|
||||||
|
system_vendors, [weak_this, response](std::vector<std::string> installed_vendors, bool declined) mutable {
|
||||||
|
if (!weak_this)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Orca: append the newly installed vendor(s) into the in-memory
|
||||||
|
// profile data (instead of a full LoadProfileData() rescan of every
|
||||||
|
// vendor) and push the refreshed list to the webview, the same way
|
||||||
|
// request_userguide_profile does, so the printer list picks them up
|
||||||
|
// without needing to reopen the guide.
|
||||||
|
for (const auto& vendor_id : installed_vendors) {
|
||||||
|
weak_this->LoadProfileFamily(vendor_id, (weak_this->vendor_dir / (vendor_id + ".json")).string());
|
||||||
|
}
|
||||||
|
if (!installed_vendors.empty()) {
|
||||||
|
json profile_response = json::object();
|
||||||
|
profile_response["command"] = "response_userguide_profile";
|
||||||
|
profile_response["sequence_id"] = "10001";
|
||||||
|
profile_response["response"] = weak_this->m_ProfileJson;
|
||||||
|
wxString profileJS = wxString::Format("HandleStudio(%s)", profile_response.dump(-1, ' ', true));
|
||||||
|
weak_this->RunScript(profileJS);
|
||||||
|
}
|
||||||
|
|
||||||
|
response["vendors"] = installed_vendors;
|
||||||
|
response["declined"] = declined;
|
||||||
|
wxString strJS = wxString::Format("HandleStudio(%s)", response.dump(-1, ' ', true));
|
||||||
|
weak_this->RunScript(strJS);
|
||||||
|
});
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "Failed to check for new printers: " << e.what();
|
||||||
|
response["error"] = "Failed to check for new printers.";
|
||||||
|
wxString strJS = wxString::Format("HandleStudio(%s)", response.dump(-1, ' ', true));
|
||||||
|
wxGetApp().CallAfter([this, strJS] { RunScript(strJS); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (strCmd == "user_guide_finish") {
|
else if (strCmd == "user_guide_finish") {
|
||||||
SaveProfile();
|
SaveProfile();
|
||||||
|
|
||||||
@@ -1644,13 +1717,15 @@ int GuideFrame::LoadProfileFamily(std::string strVendor, std::string strFilePath
|
|||||||
OneModel["materials"] = pm["default_materials"];
|
OneModel["materials"] = pm["default_materials"];
|
||||||
|
|
||||||
// wxString strCoverPath = wxString::Format("%s\\%s\\%s_cover.png", strFolder, strVendor, std::string(s1.mb_str()));
|
// wxString strCoverPath = wxString::Format("%s\\%s\\%s_cover.png", strFolder, strVendor, std::string(s1.mb_str()));
|
||||||
std::string cover_file = s1 + "_cover.png";
|
std::string cover_file = s1 + "_cover.png";
|
||||||
boost::filesystem::path cover_path = boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/profiles/" / strVendor / cover_file).make_preferred();
|
boost::filesystem::path cover_path = boost::filesystem::absolute(vendor_dir / cover_file).make_preferred();
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "[WebGuideDialog] " << cover_path;
|
||||||
if (!boost::filesystem::exists(cover_path)) {
|
if (!boost::filesystem::exists(cover_path)) {
|
||||||
cover_path =
|
cover_path = boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/profiles/" / strVendor / cover_file)
|
||||||
(boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/web/image/printer/") /
|
.make_preferred();
|
||||||
cover_file)
|
if (!boost::filesystem::exists(cover_path))
|
||||||
.make_preferred();
|
cover_path = (boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/web/image/printer/") / cover_file)
|
||||||
|
.make_preferred();
|
||||||
}
|
}
|
||||||
OneModel["cover"] = cover_path.string();
|
OneModel["cover"] = cover_path.string();
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
#include "libslic3r/format.hpp"
|
#include "libslic3r/format.hpp"
|
||||||
#include "libslic3r/Utils.hpp"
|
#include "libslic3r/Utils.hpp"
|
||||||
#include "libslic3r/PresetBundle.hpp"
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
|
#include "libslic3r/PresetCacheFormat.hpp"
|
||||||
#include "libslic3r_version.h"
|
#include "libslic3r_version.h"
|
||||||
#include "slic3r/GUI/GUI.hpp"
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
#include "slic3r/GUI/GUI_App.hpp"
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
@@ -96,6 +98,8 @@ struct Update
|
|||||||
bool forced_update;
|
bool forced_update;
|
||||||
//BBS: add directory support
|
//BBS: add directory support
|
||||||
bool is_directory {false};
|
bool is_directory {false};
|
||||||
|
// Orca: a vendor update may be the cache-only form.
|
||||||
|
bool is_opc {false};
|
||||||
|
|
||||||
Update() {}
|
Update() {}
|
||||||
//BBS: add directory support
|
//BBS: add directory support
|
||||||
@@ -126,13 +130,24 @@ struct Update
|
|||||||
//BBS: add directory support
|
//BBS: add directory support
|
||||||
void install() const
|
void install() const
|
||||||
{
|
{
|
||||||
if (is_directory) {
|
if (is_directory) {
|
||||||
copy_directory_recursively(source, target, file_filter);
|
copy_directory_recursively(source, target, file_filter);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
copy_file_fix(source, target);
|
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)
|
friend std::ostream& operator<<(std::ostream& os, const Update &self)
|
||||||
{
|
{
|
||||||
@@ -180,6 +195,8 @@ struct Updates
|
|||||||
std::vector<Update> updates;
|
std::vector<Update> updates;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static bool reload_configs_update_gui();
|
||||||
|
|
||||||
|
|
||||||
wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
|
wxDEFINE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);
|
||||||
wxDEFINE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
|
wxDEFINE_EVENT(EVT_SLIC3R_EXPERIMENTAL_VERSION_ONLINE, wxCommandEvent);
|
||||||
@@ -207,6 +224,10 @@ struct PresetUpdater::priv
|
|||||||
|
|
||||||
// Per-vendor update checking
|
// Per-vendor update checking
|
||||||
std::set<std::string> checked_vendors;
|
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::vector<std::thread> vendor_check_threads;
|
||||||
std::atomic<bool> vendor_check_cancel{false};
|
std::atomic<bool> vendor_check_cancel{false};
|
||||||
|
|
||||||
@@ -231,6 +252,8 @@ struct PresetUpdater::priv
|
|||||||
void parse_version_string(const std::string& body) 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_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 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_tooltip(std::string http_url, std::string language);
|
||||||
void sync_plugins(std::string http_url, std::string plugin_version);
|
void sync_plugins(std::string http_url, std::string plugin_version);
|
||||||
void sync_printer_config(std::string http_url);
|
void sync_printer_config(std::string http_url);
|
||||||
@@ -268,7 +291,7 @@ void PresetUpdater::priv::set_download_prefs(AppConfig *app_config)
|
|||||||
version_check_url = app_config->version_check_url();
|
version_check_url = app_config->version_check_url();
|
||||||
|
|
||||||
auto profile_update_url = app_config->profile_update_url();
|
auto profile_update_url = app_config->profile_update_url();
|
||||||
if (!profile_update_url.empty())
|
if (!profile_update_url.empty() && app_config->get_bool("enable_ota"))
|
||||||
enabled_config_update = true;
|
enabled_config_update = true;
|
||||||
else
|
else
|
||||||
enabled_config_update = false;
|
enabled_config_update = false;
|
||||||
@@ -671,6 +694,9 @@ void PresetUpdater::priv::sync_vendor_config(const std::string& vendor_id)
|
|||||||
|
|
||||||
std::string online_version_str; // this represents the PROFILE VERSION, not ORCA VERSION
|
std::string online_version_str; // this represents the PROFILE VERSION, not ORCA VERSION
|
||||||
std::string download_url_str;
|
std::string download_url_str;
|
||||||
|
std::string changelog;
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] fetching vendor update status from " << url;
|
||||||
|
|
||||||
Http::get(url)
|
Http::get(url)
|
||||||
.timeout_connect(5)
|
.timeout_connect(5)
|
||||||
@@ -683,9 +709,12 @@ void PresetUpdater::priv::sync_vendor_config(const std::string& vendor_id)
|
|||||||
if (http_status != 200) return;
|
if (http_status != 200) return;
|
||||||
try {
|
try {
|
||||||
json j = json::parse(body);
|
json j = json::parse(body);
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] url: " << url << " returned:" << body;
|
||||||
|
|
||||||
if (j.contains("vendor_version") && j.contains("download_url")) {
|
if (j.contains("vendor_version") && j.contains("download_url")) {
|
||||||
online_version_str = j["vendor_version"].get<std::string>();
|
online_version_str = j["vendor_version"].get<std::string>();
|
||||||
download_url_str = j["download_url"].get<std::string>();
|
download_url_str = j["download_url"].get<std::string>();
|
||||||
|
changelog = j.value("changelog", std::string());
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] vendor check JSON parse failed: " << e.what();
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] vendor check JSON parse failed: " << e.what();
|
||||||
@@ -707,6 +736,10 @@ void PresetUpdater::priv::sync_vendor_config(const std::string& vendor_id)
|
|||||||
boost::system::error_code ec;
|
boost::system::error_code ec;
|
||||||
fs::remove_all(cache_profile_path / vendor_id, 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 + ".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);
|
fs::remove(cache_profile_path / (vendor_id + ".changelog"), ec);
|
||||||
|
|
||||||
// Download the zip
|
// Download the zip
|
||||||
@@ -735,7 +768,7 @@ void PresetUpdater::priv::sync_vendor_config(const std::string& vendor_id)
|
|||||||
if (!download_ok || cancel || vendor_check_cancel) return;
|
if (!download_ok || cancel || vendor_check_cancel) return;
|
||||||
|
|
||||||
// Extract vendor profile bundles under ota/profiles. The downloaded zip contains
|
// Extract vendor profile bundles under ota/profiles. The downloaded zip contains
|
||||||
// the vendor json/folder at its root.
|
// either the vendor json/folder or the vendor cache at its root.
|
||||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] extracting update for " << vendor_id;
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] extracting update for " << vendor_id;
|
||||||
if (!extract_file(download_file, cache_profile_path)) {
|
if (!extract_file(download_file, cache_profile_path)) {
|
||||||
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] extraction failed for " << vendor_id;
|
BOOST_LOG_TRIVIAL(warning) << "[Orca Updater] extraction failed for " << vendor_id;
|
||||||
@@ -745,6 +778,23 @@ void PresetUpdater::priv::sync_vendor_config(const std::string& vendor_id)
|
|||||||
|
|
||||||
if (cancel || vendor_check_cancel) return;
|
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;
|
||||||
|
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 update for " << 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);
|
||||||
|
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";
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater] vendor " << vendor_id << " update cached, notifying UI";
|
||||||
GUI::wxGetApp().CallAfter([] {
|
GUI::wxGetApp().CallAfter([] {
|
||||||
GUI::wxGetApp().check_config_updates_from_updater();
|
GUI::wxGetApp().check_config_updates_from_updater();
|
||||||
@@ -1039,6 +1089,10 @@ void PresetUpdater::priv::check_installed_vendor_profiles() const
|
|||||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:Checking whether the profile from resource is newer";
|
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:Checking whether the profile from resource is newer";
|
||||||
|
|
||||||
AppConfig *app_config = GUI::wxGetApp().app_config;
|
AppConfig *app_config = GUI::wxGetApp().app_config;
|
||||||
|
|
||||||
|
if (!app_config->get_bool("enable_ota"))
|
||||||
|
return;
|
||||||
|
|
||||||
const auto enabled_vendors = app_config->vendors();
|
const auto enabled_vendors = app_config->vendors();
|
||||||
|
|
||||||
std::set<std::string> bundles;
|
std::set<std::string> bundles;
|
||||||
@@ -1147,68 +1201,110 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
|
|||||||
if (!fs::exists(cache_profile_path))
|
if (!fs::exists(cache_profile_path))
|
||||||
return updates;
|
return updates;
|
||||||
|
|
||||||
for (auto &dir_entry : boost::filesystem::directory_iterator(cache_profile_path)) {
|
// Orca (PR #130): vendor changelogs are captured in memory during
|
||||||
const auto &path = dir_entry.path();
|
// sync_vendor_config()/check_new_vendors(), not written beside the cache.
|
||||||
std::string file_path = path.string();
|
std::unordered_map<std::string, std::string> changelogs;
|
||||||
if (is_json_file(file_path)) {
|
{
|
||||||
const auto path_in_vendor = vendor_path / path.filename();
|
std::lock_guard<std::mutex> lock(vendor_changelogs_mutex);
|
||||||
std::string vendor_name = path.filename().string();
|
changelogs = vendor_changelogs;
|
||||||
// Remove the .json suffix.
|
}
|
||||||
vendor_name.erase(vendor_name.size() - 5);
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (is_vendor_installed(vendor_name)
|
for (auto &dir_entry : boost::filesystem::directory_iterator(cache_profile_path)) {
|
||||||
|| fs::exists(print_in_cache)
|
const auto &path = dir_entry.path();
|
||||||
|| fs::exists(filament_in_cache)
|
std::string file_path = path.string();
|
||||||
|| fs::exists(machine_in_cache)) {
|
const bool is_opc_file = boost::iequals(path.extension().string(), ".opc");
|
||||||
// Orca: a vendor installed as a preset cache carries its version there.
|
if (!is_json_file(file_path) && !is_opc_file)
|
||||||
Semver vendor_ver = installed_vendor_version(vendor_name);
|
continue;
|
||||||
|
|
||||||
std::map<std::string, std::string> key_values;
|
const std::string vendor_name = path.stem().string();
|
||||||
std::vector<std::string> keys(3);
|
auto print_in_cache = (cache_profile_path / vendor_name / PRESET_PRINT_NAME);
|
||||||
Semver cache_ver;
|
auto filament_in_cache = (cache_profile_path / vendor_name / PRESET_FILAMENT_NAME);
|
||||||
keys[0] = BBL_JSON_KEY_VERSION;
|
auto machine_in_cache = (cache_profile_path / vendor_name / PRESET_PRINTER_NAME);
|
||||||
keys[1] = BBL_JSON_KEY_DESCRIPTION;
|
|
||||||
keys[2] = BBL_JSON_KEY_FORCE_UPDATE;
|
|
||||||
get_values_from_json(file_path, keys, key_values);
|
|
||||||
std::string description = key_values[BBL_JSON_KEY_DESCRIPTION];
|
|
||||||
bool force_update = false;
|
|
||||||
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;
|
|
||||||
|
|
||||||
std::string changelog;
|
// Orca (PR #130): a JSON cache entry is only meaningful next to a non-empty
|
||||||
std::string changelog_file = (cache_profile_path / (vendor_name + ".changelog")).string();
|
// <vendor>/ preset directory; a stray or half-downloaded <vendor>.json is
|
||||||
boost::nowide::ifstream ifs(changelog_file);
|
// skipped. An .opc cache is a single self-contained file (validated below),
|
||||||
if (ifs) {
|
// so this check does not apply to it.
|
||||||
std::ostringstream oss;
|
if (!is_opc_file) {
|
||||||
oss<< ifs.rdbuf();
|
const auto vendor_folder_in_cache = cache_profile_path / vendor_name;
|
||||||
changelog = oss.str();
|
if (!fs::is_regular_file(path) || !fs::is_directory(vendor_folder_in_cache) ||
|
||||||
ifs.close();
|
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 (vendor_ver < cache_ver) {
|
if (is_vendor_installed(vendor_name)
|
||||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:need to update settings from " << vendor_ver.to_string()
|
|| is_opc_file
|
||||||
<< " to newer version " << cache_ver.to_string() << ", app version " << SLIC3R_VERSION;
|
|| fs::exists(print_in_cache)
|
||||||
Version version;
|
|| fs::exists(filament_in_cache)
|
||||||
version.config_version = cache_ver;
|
|| fs::exists(machine_in_cache)) {
|
||||||
version.comment = description;
|
// Orca: a vendor installed as a preset cache carries its version there.
|
||||||
// Orca: update vendor.json
|
Semver vendor_ver = installed_vendor_version(vendor_name);
|
||||||
updates.updates.emplace_back(std::move(file_path), path_in_vendor.string(), std::move(version), vendor_name, changelog, "", force_update, false);
|
|
||||||
//Orca: update vendor folder
|
Semver cache_ver;
|
||||||
updates.updates.emplace_back(cache_profile_path / vendor_name, vendor_path / vendor_name, Version(), vendor_name, "", "", force_update, true);
|
std::string description;
|
||||||
} else {
|
bool force_update = false;
|
||||||
BOOST_LOG_TRIVIAL(info) << "[Orca Updater]:cached settings for " << vendor_name
|
if (is_opc_file) {
|
||||||
<< " are not newer than installed version, installed " << vendor_ver.to_string()
|
cache_ver = VendorCacheFile::usable_version(file_path, vendor_name);
|
||||||
<< ", cached " << cache_ver.to_string();
|
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;
|
return updates;
|
||||||
}
|
}
|
||||||
@@ -1317,6 +1413,9 @@ void PresetUpdater::sync(std::string http_url, std::string language, std::string
|
|||||||
// after the startup printer preset has been restored.
|
// after the startup printer preset has been restored.
|
||||||
this->p->sync_plugins(http_url, plugin_version);
|
this->p->sync_plugins(http_url, plugin_version);
|
||||||
this->p->sync_printer_config(http_url);
|
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)
|
//if (p->cancel)
|
||||||
// return;
|
// return;
|
||||||
//remove the tooltip currently
|
//remove the tooltip currently
|
||||||
@@ -1349,6 +1448,302 @@ void PresetUpdater::check_vendor_update(const std::string& vendor_id)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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, max_retries](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,
|
||||||
|
_u8L("Configuration package: ") + vendor_id + _u8L(" updated to ") + 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()
|
void PresetUpdater::slic3r_update_notify()
|
||||||
{
|
{
|
||||||
if (! p->enabled_version_check)
|
if (! p->enabled_version_check)
|
||||||
@@ -1370,10 +1765,9 @@ static bool reload_configs_update_gui()
|
|||||||
GUI::wxGetApp().load_current_presets();
|
GUI::wxGetApp().load_current_presets();
|
||||||
GUI::wxGetApp().plater()->set_bed_shape();
|
GUI::wxGetApp().plater()->set_bed_shape();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, UpdateParams params) const
|
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, UpdateParams params) const
|
||||||
{
|
{
|
||||||
if (! p->enabled_config_update) { return R_NOOP; }
|
if (! p->enabled_config_update) { return R_NOOP; }
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#ifndef slic3r_PresetUpdate_hpp_
|
#ifndef slic3r_PresetUpdate_hpp_
|
||||||
#define slic3r_PresetUpdate_hpp_
|
#define slic3r_PresetUpdate_hpp_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <wx/event.h>
|
#include <wx/event.h>
|
||||||
@@ -59,6 +61,12 @@ public:
|
|||||||
void on_update_notification_confirm();
|
void on_update_notification_confirm();
|
||||||
void do_printer_config_update();
|
void do_printer_config_update();
|
||||||
void check_vendor_update(const std::string& vendor_id);
|
void check_vendor_update(const std::string& vendor_id);
|
||||||
|
// Orca: async, mirrors check_vendor_update()/sync_vendor_config() — the network query and any
|
||||||
|
// download/install work happen on a background thread; only the confirmation dialog runs on
|
||||||
|
// the UI thread. `callback` is invoked on the UI thread with the ids of vendors that were
|
||||||
|
// installed (empty if none were found, or the user declined) and whether the user declined.
|
||||||
|
void check_new_vendors(const std::set<std::string>& system_vendors,
|
||||||
|
std::function<void(std::vector<std::string> installed_vendors, bool declined)> callback);
|
||||||
|
|
||||||
bool version_check_enabled() const;
|
bool version_check_enabled() const;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user