mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-08 08:11:14 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f140a1208 | ||
|
|
cb7d2c698d | ||
|
|
a38e6c61f2 |
@@ -311,7 +311,7 @@ function CreatePrinterBlock(OneModel)
|
|||||||
|
|
||||||
return '<div class="PrinterBlock" onClick="ChooseModel(\''+vendor+'\',\''+OneModel['model']+'\')">'+
|
return '<div class="PrinterBlock" onClick="ChooseModel(\''+vendor+'\',\''+OneModel['model']+'\')">'+
|
||||||
' <div class="PImg">'+
|
' <div class="PImg">'+
|
||||||
' <img class="ModelThumbnail" src="' + OneModel['cover'] + '" />'+
|
' <img class="ModelThumbnail" src="' + OneModel['cover'] + '" onerror="this.onerror=null;this.src=\'../img/printer-dummy.png\';"/>'+
|
||||||
' </div>'+
|
' </div>'+
|
||||||
' <div class="PrinterInfoMark">?</div>'+
|
' <div class="PrinterInfoMark">?</div>'+
|
||||||
' <div class="PrinterInfo">'+
|
' <div class="PrinterInfo">'+
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -2337,7 +2337,7 @@ bool PresetCollection::reset_project_embedded_presets()
|
|||||||
return re_select;
|
return re_select;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PresetCollection::set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time, const std::string& user_id)
|
void PresetCollection::set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time)
|
||||||
{
|
{
|
||||||
lock();
|
lock();
|
||||||
const std::string canonical_name = this->canonical_preset_name(name);
|
const std::string canonical_name = this->canonical_preset_name(name);
|
||||||
@@ -2355,10 +2355,7 @@ void PresetCollection::set_sync_info_and_save(std::string name, std::string sett
|
|||||||
preset2.save_info();
|
preset2.save_info();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!setting_id.empty())
|
preset->setting_id = setting_id;
|
||||||
preset->setting_id = setting_id;
|
|
||||||
if (!user_id.empty())
|
|
||||||
preset->user_id = user_id;
|
|
||||||
if (update_time > 0)
|
if (update_time > 0)
|
||||||
preset->updated_time = update_time;
|
preset->updated_time = update_time;
|
||||||
if (preset->sync_info == "update")
|
if (preset->sync_info == "update")
|
||||||
@@ -2648,9 +2645,6 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
|
|||||||
iter->base_id = based_id;
|
iter->base_id = based_id;
|
||||||
iter->filament_id = cloud_filament_id;
|
iter->filament_id = cloud_filament_id;
|
||||||
update_alias(*iter);
|
update_alias(*iter);
|
||||||
// Persist the cloud-assigned identity to disk, mirroring the equal/newer branch
|
|
||||||
// above; otherwise the id stays only in memory and the next launch rewrites it.
|
|
||||||
iter->save_info();
|
|
||||||
//presets_loaded.emplace_back(*it->second);
|
//presets_loaded.emplace_back(*it->second);
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", update the user preset %1% from cloud, type %2%, setting_id %3%, base_id %4%, sync_info %5% inherits %6%, filament_id %7%")
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", update the user preset %1% from cloud, type %2%, setting_id %3%, base_id %4%, sync_info %5% inherits %6%, filament_id %7%")
|
||||||
% iter->name %Preset::get_type_string(m_type) %iter->setting_id %iter->base_id %iter->sync_info %iter->inherits() % iter->filament_id;
|
% iter->name %Preset::get_type_string(m_type) %iter->setting_id %iter->base_id %iter->sync_info %iter->inherits() % iter->filament_id;
|
||||||
|
|||||||
@@ -603,7 +603,7 @@ public:
|
|||||||
void update_after_user_presets_loaded();
|
void update_after_user_presets_loaded();
|
||||||
//BBS: get user presets
|
//BBS: get user presets
|
||||||
int get_user_presets(PresetBundle *preset_bundle, std::vector<Preset> &result_presets);
|
int get_user_presets(PresetBundle *preset_bundle, std::vector<Preset> &result_presets);
|
||||||
void set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time, const std::string& user_id);
|
void set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time);
|
||||||
bool need_sync(std::string name, std::string setting_id, long long update_time);
|
bool need_sync(std::string name, std::string setting_id, long long update_time);
|
||||||
|
|
||||||
//BBS: add function to generate differed preset for save
|
//BBS: add function to generate differed preset for save
|
||||||
|
|||||||
@@ -7204,11 +7204,11 @@ void GUI_App::sync_preset(Preset* preset, bool force)
|
|||||||
|
|
||||||
BOOST_LOG_TRIVIAL(trace) << "sync_preset: sync operation: " << preset->sync_info << " success! preset = " << preset->name;
|
BOOST_LOG_TRIVIAL(trace) << "sync_preset: sync operation: " << preset->sync_info << " success! preset = " << preset->name;
|
||||||
if (preset->type == Preset::Type::TYPE_FILAMENT) {
|
if (preset->type == Preset::Type::TYPE_FILAMENT) {
|
||||||
preset_bundle->filaments.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time, m_agent->get_user_id());
|
preset_bundle->filaments.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time);
|
||||||
} else if (preset->type == Preset::Type::TYPE_PRINT) {
|
} else if (preset->type == Preset::Type::TYPE_PRINT) {
|
||||||
preset_bundle->prints.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time, m_agent->get_user_id());
|
preset_bundle->prints.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time);
|
||||||
} else if (preset->type == Preset::Type::TYPE_PRINTER) {
|
} else if (preset->type == Preset::Type::TYPE_PRINTER) {
|
||||||
preset_bundle->printers.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time, m_agent->get_user_id());
|
preset_bundle->printers.set_sync_info_and_save(preset->name, setting_id, updated_info, update_time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7907,7 +7907,7 @@ void GUI_App::force_push_conflicting_preset(const std::string& setting_id)
|
|||||||
? OrcaCloudServiceAgent::generate_uuid_for_setting_id(preset.name, user_id)
|
? OrcaCloudServiceAgent::generate_uuid_for_setting_id(preset.name, user_id)
|
||||||
: preset.setting_id;
|
: preset.setting_id;
|
||||||
if (preset_id == setting_id) {
|
if (preset_id == setting_id) {
|
||||||
coll->set_sync_info_and_save(preset.name, setting_id, "update", 0, user_id);
|
coll->set_sync_info_and_save(preset.name, setting_id, "update", 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1129,19 +1129,6 @@ std::string OrcaCloudServiceAgent::request_setting_id(std::string name,
|
|||||||
if (http_code)
|
if (http_code)
|
||||||
*http_code = result.http_code;
|
*http_code = result.http_code;
|
||||||
|
|
||||||
// 409 duplicate_profile_uuid in the create path means the deterministic id we
|
|
||||||
// just generated already exists in this account: the earlier create succeeded.
|
|
||||||
// Adopt it instead of failing, so sync_preset persists the id and stops retrying.
|
|
||||||
if (result.http_code == 409 && result.conflict_code == -2
|
|
||||||
&& !result.server_version.id.empty() && result.server_version.id == new_id) {
|
|
||||||
if (values_map && result.server_version.updated_time != 0)
|
|
||||||
(*values_map)[IOT_JSON_KEY_UPDATED_TIME] = std::to_string(result.server_version.updated_time);
|
|
||||||
if (http_code)
|
|
||||||
*http_code = 200;
|
|
||||||
BOOST_LOG_TRIVIAL(info) << "OrcaCloudServiceAgent: request_setting_id adopted existing profile id " << new_id << " (409 duplicate_profile_uuid)";
|
|
||||||
return new_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
if (values_map && result.new_updated_time != 0) {
|
if (values_map && result.new_updated_time != 0) {
|
||||||
(*values_map)[IOT_JSON_KEY_UPDATED_TIME] = std::to_string(result.new_updated_time);
|
(*values_map)[IOT_JSON_KEY_UPDATED_TIME] = std::to_string(result.new_updated_time);
|
||||||
@@ -1407,7 +1394,6 @@ SyncPushResult OrcaCloudServiceAgent::sync_push(const std::string& profile_id,
|
|||||||
SyncPushResult result;
|
SyncPushResult result;
|
||||||
result.success = false;
|
result.success = false;
|
||||||
result.http_code = 0;
|
result.http_code = 0;
|
||||||
result.conflict_code = 0;
|
|
||||||
result.server_deleted = false;
|
result.server_deleted = false;
|
||||||
|
|
||||||
nlohmann::json body;
|
nlohmann::json body;
|
||||||
@@ -1443,30 +1429,20 @@ SyncPushResult OrcaCloudServiceAgent::sync_push(const std::string& profile_id,
|
|||||||
err_body = json;
|
err_body = json;
|
||||||
if (json.is_null()) {
|
if (json.is_null()) {
|
||||||
result.server_deleted = true;
|
result.server_deleted = true;
|
||||||
} else if (json.is_object()) {
|
} else {
|
||||||
result.conflict_code = json.value("code", 0);
|
auto& profile_data = json["server_profile"];
|
||||||
if (json.contains("server_profile") && !json["server_profile"].is_null()) {
|
result.server_version.id = profile_data.value("id", "");
|
||||||
auto& profile_data = json["server_profile"];
|
result.server_version.name = profile_data.value("name", "");
|
||||||
result.server_version.id = profile_data.value("id", "");
|
result.server_version.updated_time = profile_data.value(ORCA_JSON_KEY_UPDATE_TIME, 0);
|
||||||
result.server_version.name = profile_data.value("name", "");
|
|
||||||
result.server_version.updated_time = profile_data.value(ORCA_JSON_KEY_UPDATE_TIME, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (...) {}
|
} catch (...) {}
|
||||||
// Create-path duplicate_profile_uuid (-2) is an idempotent success: the deterministic id
|
// Surface the conflict via the http-error callback with the local preset name injected.
|
||||||
// already exists, so the caller adopts the returned id. Skip the conflict notification,
|
// The raw server body omits the name for tombstone (-3) conflicts (server_profile is null),
|
||||||
// otherwise every already-imported preset would raise a Pull/Force-push prompt on each launch.
|
// but the GUI needs it to regenerate the deterministic setting_id for a force push.
|
||||||
const bool is_create = original_updated_time.empty();
|
if (!err_body.is_object())
|
||||||
const bool auto_resolved_duplicate = (is_create && result.conflict_code == -2);
|
err_body = nlohmann::json::object();
|
||||||
if (!auto_resolved_duplicate) {
|
err_body["name"] = name;
|
||||||
// Surface the conflict via the http-error callback with the local preset name injected.
|
invoke_http_error_callback(409, err_body.dump());
|
||||||
// The raw server body omits the name for tombstone (-3) conflicts (server_profile is null),
|
|
||||||
// but the GUI needs it to regenerate the deterministic setting_id for a force push.
|
|
||||||
if (!err_body.is_object())
|
|
||||||
err_body = nlohmann::json::object();
|
|
||||||
err_body["name"] = name;
|
|
||||||
invoke_http_error_callback(409, err_body.dump());
|
|
||||||
}
|
|
||||||
result.error_message = response;
|
result.error_message = response;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ struct SyncPullResponse {
|
|||||||
struct SyncPushResult {
|
struct SyncPushResult {
|
||||||
bool success;
|
bool success;
|
||||||
int http_code;
|
int http_code;
|
||||||
int conflict_code;
|
|
||||||
long long new_updated_time;
|
long long new_updated_time;
|
||||||
ProfileUpsert server_version;
|
ProfileUpsert server_version;
|
||||||
bool server_deleted;
|
bool server_deleted;
|
||||||
|
|||||||
@@ -25,11 +25,19 @@ extern const char* const INSTALL_STATE_FILE;
|
|||||||
// Plugin config and orca.host.ui payloads both cross the boundary as plain JSON-compatible
|
// Plugin config and orca.host.ui payloads both cross the boundary as plain JSON-compatible
|
||||||
// values, so both go through these.
|
// values, so both go through these.
|
||||||
|
|
||||||
inline pybind11::object json_to_py(const nlohmann::json& j)
|
// Maximum nesting depth for JSON <-> Python conversion. A self-referential or pathologically
|
||||||
|
// deep value would otherwise recurse until the native C stack overflows, an uncatchable crash;
|
||||||
|
// past this bound we raise instead. 200 is far beyond any legitimate plugin config or UI payload.
|
||||||
|
inline constexpr int kMaxJsonConversionDepth = 200;
|
||||||
|
|
||||||
|
inline pybind11::object json_to_py(const nlohmann::json& j, int depth = 0)
|
||||||
{
|
{
|
||||||
namespace py = pybind11;
|
namespace py = pybind11;
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
if (depth > kMaxJsonConversionDepth)
|
||||||
|
throw py::value_error("Plugin JSON value nested too deeply");
|
||||||
|
|
||||||
switch (j.type()) {
|
switch (j.type()) {
|
||||||
case json::value_t::null: return py::none();
|
case json::value_t::null: return py::none();
|
||||||
case json::value_t::boolean: return py::bool_(j.get<bool>());
|
case json::value_t::boolean: return py::bool_(j.get<bool>());
|
||||||
@@ -40,24 +48,27 @@ inline pybind11::object json_to_py(const nlohmann::json& j)
|
|||||||
case json::value_t::array: {
|
case json::value_t::array: {
|
||||||
py::list lst;
|
py::list lst;
|
||||||
for (const auto& e : j)
|
for (const auto& e : j)
|
||||||
lst.append(json_to_py(e));
|
lst.append(json_to_py(e, depth + 1));
|
||||||
return lst;
|
return lst;
|
||||||
}
|
}
|
||||||
case json::value_t::object: {
|
case json::value_t::object: {
|
||||||
py::dict d;
|
py::dict d;
|
||||||
for (auto it = j.begin(); it != j.end(); ++it)
|
for (auto it = j.begin(); it != j.end(); ++it)
|
||||||
d[py::str(it.key())] = json_to_py(it.value());
|
d[py::str(it.key())] = json_to_py(it.value(), depth + 1);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
default: return py::none();
|
default: return py::none();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline nlohmann::json py_to_json(const pybind11::handle& o)
|
inline nlohmann::json py_to_json(const pybind11::handle& o, int depth = 0)
|
||||||
{
|
{
|
||||||
namespace py = pybind11;
|
namespace py = pybind11;
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
if (depth > kMaxJsonConversionDepth)
|
||||||
|
throw py::value_error("Plugin value nested too deeply (possible cycle)");
|
||||||
|
|
||||||
if (o.is_none())
|
if (o.is_none())
|
||||||
return json(nullptr);
|
return json(nullptr);
|
||||||
if (py::isinstance<py::bool_>(o)) // bool before int (bool subclasses int in Python)
|
if (py::isinstance<py::bool_>(o)) // bool before int (bool subclasses int in Python)
|
||||||
@@ -73,13 +84,13 @@ inline nlohmann::json py_to_json(const pybind11::handle& o)
|
|||||||
if (py::isinstance<py::dict>(o)) {
|
if (py::isinstance<py::dict>(o)) {
|
||||||
json obj = json::object();
|
json obj = json::object();
|
||||||
for (auto item : py::reinterpret_borrow<py::dict>(o))
|
for (auto item : py::reinterpret_borrow<py::dict>(o))
|
||||||
obj[py::str(item.first).cast<std::string>()] = py_to_json(item.second);
|
obj[py::str(item.first).cast<std::string>()] = py_to_json(item.second, depth + 1);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
if (py::isinstance<py::list>(o) || py::isinstance<py::tuple>(o)) {
|
if (py::isinstance<py::list>(o) || py::isinstance<py::tuple>(o)) {
|
||||||
json arr = json::array();
|
json arr = json::array();
|
||||||
for (auto e : o)
|
for (auto e : o)
|
||||||
arr.push_back(py_to_json(e));
|
arr.push_back(py_to_json(e, depth + 1));
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
return py::str(o).cast<std::string>(); // fallback: str()
|
return py::str(o).cast<std::string>(); // fallback: str()
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_plugin_sort.cpp
|
test_plugin_sort.cpp
|
||||||
test_plugin_cloud_metadata.cpp
|
test_plugin_cloud_metadata.cpp
|
||||||
test_plugin_audit.cpp
|
test_plugin_audit.cpp
|
||||||
|
test_plugin_json_depth.cpp
|
||||||
test_shortcuts.cpp
|
test_shortcuts.cpp
|
||||||
test_file_url.cpp
|
test_file_url.cpp
|
||||||
test_user_manager.cpp
|
test_user_manager.cpp
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
#include <catch2/catch_all.hpp>
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
#include <slic3r/plugin/PluginFsUtils.hpp>
|
||||||
|
#include <slic3r/plugin/PluginManager.hpp>
|
||||||
|
#include <slic3r/plugin/PythonInterpreter.hpp>
|
||||||
|
|
||||||
|
#include "plugin_test_utils.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <exception>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <pybind11/embed.h>
|
||||||
|
#include <pybind11/gil.h>
|
||||||
|
#include <pybind11/pytypes.h>
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Brings the embedded interpreter up for one test and tears it down before boost::log does,
|
||||||
|
// mirroring the ScopedPluginManager idiom in the other plugin tests.
|
||||||
|
struct ScopedPluginManager
|
||||||
|
{
|
||||||
|
ScopedDataDir python_data_dir{"plugin-json-depth"};
|
||||||
|
bool initialized = PluginManager::instance().initialize();
|
||||||
|
~ScopedPluginManager()
|
||||||
|
{
|
||||||
|
PluginManager::instance().shutdown();
|
||||||
|
PythonInterpreter::instance().shutdown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("py_to_json raises instead of overflowing on pathologically deep input", "[PluginHost][Python]")
|
||||||
|
{
|
||||||
|
ScopedPluginManager manager;
|
||||||
|
REQUIRE(manager.initialized);
|
||||||
|
namespace py = pybind11;
|
||||||
|
py::gil_scoped_acquire gil;
|
||||||
|
|
||||||
|
// [[[ ... 0 ... ]]] nested 300 deep: past the 200 conversion-depth cap, but shallow enough
|
||||||
|
// that the pre-fix code returns without crashing, so a regression fails cleanly rather than
|
||||||
|
// taking the process down. Built in C++ so the test does not depend on Python builtins.
|
||||||
|
py::object deep = py::int_(0);
|
||||||
|
for (int i = 0; i < 300; ++i) {
|
||||||
|
py::list wrapper;
|
||||||
|
wrapper.append(deep);
|
||||||
|
deep = std::move(wrapper);
|
||||||
|
}
|
||||||
|
CHECK_THROWS_AS(py_to_json(deep), std::exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("py_to_json still converts reasonably nested input", "[PluginHost][Python]")
|
||||||
|
{
|
||||||
|
ScopedPluginManager manager;
|
||||||
|
REQUIRE(manager.initialized);
|
||||||
|
namespace py = pybind11;
|
||||||
|
py::gil_scoped_acquire gil;
|
||||||
|
|
||||||
|
py::dict d;
|
||||||
|
d["a"] = py::int_(1);
|
||||||
|
py::list inner;
|
||||||
|
inner.append(py::str("x"));
|
||||||
|
inner.append(py::int_(2));
|
||||||
|
d["b"] = inner;
|
||||||
|
|
||||||
|
const nlohmann::json j = py_to_json(d);
|
||||||
|
CHECK(j.at("a").get<std::int64_t>() == 1);
|
||||||
|
CHECK(j.at("b").at(0).get<std::string>() == "x");
|
||||||
|
CHECK(j.at("b").at(1).get<std::int64_t>() == 2);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user