mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-03 22:01:03 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a493c3c80f | ||
|
|
6842d9c778 | ||
|
|
1a5bc8982d |
@@ -809,20 +809,13 @@ void priv::set_skip_for_out_of_aoi(std::vector<bool> &skip_indicies,
|
|||||||
}); // END parallel for
|
}); // END parallel for
|
||||||
|
|
||||||
// inspect all triangles, when it is out of bounding box
|
// inspect all triangles, when it is out of bounding box
|
||||||
// NOTE: std::vector<bool> is bit packed, thus setting its items from multiple threads is a
|
|
||||||
// read-modify-write race on the shared words and silently loses flags. Collect the flags into
|
|
||||||
// a byte per triangle, where the chunks do not share memory, and merge them afterwards.
|
|
||||||
std::vector<unsigned char> skip_triangle(its.indices.size(), 0);
|
|
||||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
|
||||||
[&its, &is_on_sides, &skip_triangle](const tbb::blocked_range<size_t> &range) {
|
[&its, &is_on_sides, &skip_indicies](const tbb::blocked_range<size_t> &range) {
|
||||||
for (size_t i = range.begin(); i < range.end(); ++i) {
|
for (size_t i = range.begin(); i < range.end(); ++i) {
|
||||||
if (is_all_on_one_side(its.indices[i], is_on_sides))
|
if (is_all_on_one_side(its.indices[i], is_on_sides))
|
||||||
skip_triangle[i] = 1;
|
skip_indicies[i] = true;
|
||||||
}
|
}
|
||||||
}); // END parallel for
|
}); // END parallel for
|
||||||
for (size_t i = 0; i < skip_triangle.size(); ++i)
|
|
||||||
if (skip_triangle[i])
|
|
||||||
skip_indicies[i] = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
indexed_triangle_set Slic3r::its_mask(const indexed_triangle_set &its,
|
indexed_triangle_set Slic3r::its_mask(const indexed_triangle_set &its,
|
||||||
|
|||||||
@@ -1378,22 +1378,12 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
|||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The layers are processed in groups of "granularity" layers. A layer projects its shells up to "granularity"
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers, granularity), [&granularity, &num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top,
|
||||||
// layers away, thus a group may write into the slots of its neighbor groups. The even and the odd groups
|
|
||||||
// therefore write into two disjoint halves of the output vectors (the 2nd half is offset by num_layers) and
|
|
||||||
// both halves are merged below. The group index has to be derived from the layer index and not from the extent
|
|
||||||
// of the TBB sub-range: tbb::blocked_range bisects at midpoints, thus a sub-range neither starts at a multiple
|
|
||||||
// of the grain size nor covers a whole group, and two sub-ranges of one group would append into a single
|
|
||||||
// ExPolygons concurrently. Iterating over the groups keeps every group on a single thread, in ascending order.
|
|
||||||
const size_t num_groups = (num_layers + size_t(granularity) - 1) / size_t(granularity);
|
|
||||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_groups, 1), [&granularity, &num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top,
|
|
||||||
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom,
|
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom,
|
||||||
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
|
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
|
||||||
for (size_t group_idx = range.begin(); group_idx < range.end(); ++ group_idx) {
|
size_t group_idx = range.begin() / granularity;
|
||||||
const size_t layer_idx_offset = (group_idx & 1) * num_layers;
|
size_t layer_idx_offset = (group_idx & 1) * num_layers;
|
||||||
const size_t layer_idx_begin = group_idx * size_t(granularity);
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||||
const size_t layer_idx_end = std::min(num_layers, layer_idx_begin + size_t(granularity));
|
|
||||||
for (size_t layer_idx = layer_idx_begin; layer_idx < layer_idx_end; ++ layer_idx) {
|
|
||||||
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) {
|
for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) {
|
||||||
throw_on_cancel_callback();
|
throw_on_cancel_callback();
|
||||||
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
|
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
|
||||||
@@ -1439,7 +1429,6 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_facets_states);
|
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_facets_states);
|
||||||
|
|||||||
@@ -424,16 +424,8 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
|
|||||||
[this](size_t i, size_t j) { return m_layer_outlines[i].second.size() < m_layer_outlines[j].second.size(); });
|
[this](size_t i, size_t j) { return m_layer_outlines[i].second.size() < m_layer_outlines[j].second.size(); });
|
||||||
|
|
||||||
// Layer range for which the collisions will be calculated.
|
// Layer range for which the collisions will be calculated.
|
||||||
// Another thread may have advanced getMaxCalculatedLayer() past max_layer_idx after this calculation
|
|
||||||
// was requested. Bail out in that case, otherwise the layer range would be negative and allocating
|
|
||||||
// it would throw std::length_error out of a parallel task.
|
|
||||||
const LayerIndex start_layer = 1 + m_collision_cache.getMaxCalculatedLayer(radius);
|
|
||||||
if (start_layer > max_layer_idx) {
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LayerPolygonCache data;
|
LayerPolygonCache data;
|
||||||
data.allocate(start_layer, max_layer_idx + 1);
|
data.allocate(m_collision_cache.getMaxCalculatedLayer(radius) + 1, max_layer_idx + 1);
|
||||||
|
|
||||||
const bool calculate_placable = m_support_rests_on_model && radius == 0;
|
const bool calculate_placable = m_support_rests_on_model && radius == 0;
|
||||||
LayerPolygonCache data_placeable;
|
LayerPolygonCache data_placeable;
|
||||||
@@ -812,12 +804,6 @@ void TreeModelVolumes::calculateWallRestrictions(const std::vector<RadiusLayerPa
|
|||||||
const coord_t radius = keys[key_idx].first;
|
const coord_t radius = keys[key_idx].first;
|
||||||
const LayerIndex max_required_layer = keys[key_idx].second;
|
const LayerIndex max_required_layer = keys[key_idx].second;
|
||||||
const coord_t min_layer_bottom = std::max(1, m_wall_restrictions_cache.getMaxCalculatedLayer(radius));
|
const coord_t min_layer_bottom = std::max(1, m_wall_restrictions_cache.getMaxCalculatedLayer(radius));
|
||||||
if (min_layer_bottom > max_required_layer) {
|
|
||||||
// Another thread has calculated this range in the meantime. Continuing would make
|
|
||||||
// buffer_size negative and allocating it would throw std::length_error.
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Requested calculation for value already calculated ?";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const size_t buffer_size = max_required_layer + 1 - min_layer_bottom;
|
const size_t buffer_size = max_required_layer + 1 - min_layer_bottom;
|
||||||
std::vector<Polygons> data(buffer_size, Polygons{});
|
std::vector<Polygons> data(buffer_size, Polygons{});
|
||||||
std::vector<Polygons> data_min;
|
std::vector<Polygons> data_min;
|
||||||
|
|||||||
+46
-48
@@ -12,7 +12,6 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <tbb/parallel_for.h>
|
#include <tbb/parallel_for.h>
|
||||||
#include <tbb/task_arena.h>
|
#include <tbb/task_arena.h>
|
||||||
#include <tbb/task_scheduler_observer.h>
|
|
||||||
|
|
||||||
#include "Thread.hpp"
|
#include "Thread.hpp"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
@@ -213,14 +212,54 @@ bool is_main_thread_active()
|
|||||||
return get_main_thread_id() == boost::this_thread::get_id();
|
return get_main_thread_id() == boost::this_thread::get_id();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name the current TBB worker thread and set its locale to "C", so that the G-code generator
|
// Spawn (n - 1) worker threads on Intel TBB thread pool and name them by an index and a system thread ID.
|
||||||
// produces "." as a decimal separator. Called once per worker thread, before it runs its first task.
|
// Also it sets locale of the worker threads to "C" for the G-code generator to produce "." as a decimal separator.
|
||||||
static void setup_tbb_worker_thread()
|
void name_tbb_thread_pool_threads_set_locale()
|
||||||
{
|
{
|
||||||
static std::atomic<size_t> s_worker_idx{ 0 };
|
static bool initialized = false;
|
||||||
|
if (initialized)
|
||||||
|
return;
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
// see GH issue #5661 PrusaSlicer hangs on Linux when run with non standard task affinity
|
||||||
|
// TBB will respect the task affinity mask on Linux and spawn less threads than std::thread::hardware_concurrency().
|
||||||
|
// const size_t nthreads_hw = std::thread::hardware_concurrency();
|
||||||
|
const size_t nthreads_hw = tbb::this_task_arena::max_concurrency();
|
||||||
|
size_t nthreads = nthreads_hw;
|
||||||
|
|
||||||
|
#ifdef SLIC3R_PROFILE
|
||||||
|
// Shiny profiler is not thread safe, thus disable parallelization.
|
||||||
|
disable_multi_threading();
|
||||||
|
nthreads = 1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
size_t nthreads_running(0);
|
||||||
|
std::condition_variable cv;
|
||||||
|
std::mutex cv_m;
|
||||||
|
auto master_thread_id = std::this_thread::get_id();
|
||||||
|
tbb::parallel_for(
|
||||||
|
tbb::blocked_range<size_t>(0, nthreads, 1),
|
||||||
|
[&nthreads_running, nthreads, &master_thread_id, &cv, &cv_m](const tbb::blocked_range<size_t> &range) {
|
||||||
|
assert(range.begin() + 1 == range.end());
|
||||||
|
if (std::unique_lock<std::mutex> lk(cv_m); ++nthreads_running == nthreads) {
|
||||||
|
lk.unlock();
|
||||||
|
// All threads are spinning.
|
||||||
|
// Wake them up.
|
||||||
|
cv.notify_all();
|
||||||
|
} else {
|
||||||
|
// Wait for the last thread to wake the others.
|
||||||
|
cv.wait(lk, [&nthreads_running, nthreads]{return nthreads_running == nthreads;});
|
||||||
|
}
|
||||||
|
auto thread_id = std::this_thread::get_id();
|
||||||
|
if (thread_id == master_thread_id) {
|
||||||
|
// The calling thread runs the 0'th task.
|
||||||
|
assert(range.begin() == 0);
|
||||||
|
} else {
|
||||||
|
assert(range.begin() > 0);
|
||||||
std::ostringstream name;
|
std::ostringstream name;
|
||||||
name << "slic3r_tbb_" << (1 + s_worker_idx.fetch_add(1, std::memory_order_relaxed));
|
name << "slic3r_tbb_" << range.begin();
|
||||||
set_current_thread_name(name.str().c_str());
|
set_current_thread_name(name.str().c_str());
|
||||||
|
// Set locales of the worker thread to "C".
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
_configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
|
_configthreadlocale(_ENABLE_PER_THREAD_LOCALE);
|
||||||
std::setlocale(LC_ALL, "C");
|
std::setlocale(LC_ALL, "C");
|
||||||
@@ -236,48 +275,7 @@ static void setup_tbb_worker_thread()
|
|||||||
, "C", nullptr));
|
, "C", nullptr));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
});
|
||||||
// Sets up the TBB worker threads of the arena of the thread, which activated the observation.
|
|
||||||
// A worker sets itself up on entry to the arena, before it executes its first task, thus unlike a barrier
|
|
||||||
// inside a parallel_for, this does not depend on TBB running any number of tasks simultaneously.
|
|
||||||
class TBBWorkerThreadSetupObserver : public tbb::task_scheduler_observer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
TBBWorkerThreadSetupObserver() { this->observe(true); }
|
|
||||||
|
|
||||||
void on_scheduler_entry(bool is_worker) override
|
|
||||||
{
|
|
||||||
// Leave the external threads (the calling / UI thread) alone, their name and locale must not be modified here.
|
|
||||||
if (! is_worker)
|
|
||||||
return;
|
|
||||||
// A worker thread enters an arena many times, while its name and locale have to be set just once.
|
|
||||||
static thread_local bool initialized = false;
|
|
||||||
if (initialized)
|
|
||||||
return;
|
|
||||||
initialized = true;
|
|
||||||
setup_tbb_worker_thread();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Name the threads of the Intel TBB thread pool by an index and set their locale to "C"
|
|
||||||
// for the G-code generator to produce "." as a decimal separator.
|
|
||||||
// Formerly all the worker threads were caught inside a single parallel_for, which was held on a condition
|
|
||||||
// variable barrier until max_concurrency() of its chunks were running. TBB guarantees no such simultaneity,
|
|
||||||
// thus the barrier was able to block the slicing threads indefinitely. The TBB scheduler observer below
|
|
||||||
// sets each worker up on its own, thus no two chunks have to run at the same time.
|
|
||||||
void name_tbb_thread_pool_threads_set_locale()
|
|
||||||
{
|
|
||||||
#ifdef SLIC3R_PROFILE
|
|
||||||
// Shiny profiler is not thread safe, thus disable parallelization.
|
|
||||||
disable_multi_threading();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// An observer is local to the arena of the thread which activates it, thus one observer is registered
|
|
||||||
// per calling thread. Being function local and thread local, it is also initialized exactly once per
|
|
||||||
// thread without a race. It is intentionally never destroyed, as it has to stay alive as long as the
|
|
||||||
// TBB scheduler may notify it, which includes the shutdown of the process.
|
|
||||||
static thread_local tbb::task_scheduler_observer *observer = new TBBWorkerThreadSetupObserver();
|
|
||||||
(void)observer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,10 +28,6 @@ TriangleSetSamples sample_its_uniform_parallel(size_t samples_count, const index
|
|||||||
area_sum_to_triangle_idx[area_sum] = t_idx;
|
area_sum_to_triangle_idx[area_sum] = t_idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (area_sum_to_triangle_idx.empty())
|
|
||||||
// No triangle to sample from.
|
|
||||||
return {};
|
|
||||||
|
|
||||||
std::mt19937_64 mersenne_engine { 27644437 };
|
std::mt19937_64 mersenne_engine { 27644437 };
|
||||||
// random numbers on interval [0, 1)
|
// random numbers on interval [0, 1)
|
||||||
std::uniform_real_distribution<double> fdistribution;
|
std::uniform_real_distribution<double> fdistribution;
|
||||||
@@ -54,10 +50,7 @@ TriangleSetSamples sample_its_uniform_parallel(size_t samples_count, const index
|
|||||||
tbb::blocked_range<size_t> r) {
|
tbb::blocked_range<size_t> r) {
|
||||||
for (size_t s_idx = r.begin(); s_idx < r.end(); ++s_idx) {
|
for (size_t s_idx = r.begin(); s_idx < r.end(); ++s_idx) {
|
||||||
double t_sample = random_samples[s_idx].x() * area_sum;
|
double t_sample = random_samples[s_idx].x() * area_sum;
|
||||||
// The keys of area_sum_to_triangle_idx are accumulated areas in double precision, while area_sum
|
size_t t_idx = area_sum_to_triangle_idx.upper_bound(t_sample)->second;
|
||||||
// is a float, thus t_sample may reach or exceed the largest key and upper_bound() may return end().
|
|
||||||
auto t_it = area_sum_to_triangle_idx.upper_bound(t_sample);
|
|
||||||
size_t t_idx = (t_it == area_sum_to_triangle_idx.end() ? std::prev(t_it) : t_it)->second;
|
|
||||||
|
|
||||||
double sq_u = std::sqrt(random_samples[s_idx].y());
|
double sq_u = std::sqrt(random_samples[s_idx].y());
|
||||||
double v = random_samples[s_idx].z();
|
double v = random_samples[s_idx].z();
|
||||||
|
|||||||
@@ -819,7 +819,9 @@ int OrcaCloudServiceAgent::user_logout(bool request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clear_session();
|
// An explicit logout also wipes the backend the token storage option is not using, so a token
|
||||||
|
// stranded by switching that option cannot sign the account back in later.
|
||||||
|
clear_session(/*all_backends=*/request);
|
||||||
return BAMBU_NETWORK_SUCCESS;
|
return BAMBU_NETWORK_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1604,7 +1606,9 @@ void OrcaCloudServiceAgent::persist_user_secret(const std::string& secret)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(void) stored;
|
if (stored) {
|
||||||
|
secret_stored = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
|
bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
|
||||||
@@ -1644,6 +1648,7 @@ bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (integrity_ok && aes256gcm_decrypt(encoded_payload, key, plain) && !plain.empty()) {
|
if (integrity_ok && aes256gcm_decrypt(encoded_payload, key, plain) && !plain.empty()) {
|
||||||
|
secret_stored = true;
|
||||||
out_secret = plain;
|
out_secret = plain;
|
||||||
// Upgrade legacy payloads to signed format
|
// Upgrade legacy payloads to signed format
|
||||||
if (payload.rfind("v2:", 0) != 0) {
|
if (payload.rfind("v2:", 0) != 0) {
|
||||||
@@ -1661,6 +1666,7 @@ bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
|
|||||||
if (store.Load(SECRET_STORE_SERVICE, username, secret) && secret.IsOk()) {
|
if (store.Load(SECRET_STORE_SERVICE, username, secret) && secret.IsOk()) {
|
||||||
out_secret.assign(static_cast<const char*>(secret.GetData()), secret.GetSize());
|
out_secret.assign(static_cast<const char*>(secret.GetData()), secret.GetSize());
|
||||||
if (!out_secret.empty()) {
|
if (!out_secret.empty()) {
|
||||||
|
secret_stored = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1670,12 +1676,21 @@ bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OrcaCloudServiceAgent::clear_user_secret()
|
void OrcaCloudServiceAgent::clear_user_secret(bool all_backends)
|
||||||
{
|
{
|
||||||
|
// Nothing this process loaded or saved: leave the store alone. Deleting would only cost a
|
||||||
|
// keychain round trip (or a hang while the keychain is unresponsive) and could remove a
|
||||||
|
// login another instance just saved.
|
||||||
|
if (!secret_stored.exchange(false) && !all_backends) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (all_backends || !m_use_encrypted_token_file) {
|
||||||
wxSecretStore store = wxSecretStore::GetDefault();
|
wxSecretStore store = wxSecretStore::GetDefault();
|
||||||
if (store.IsOk()) {
|
if (store.IsOk()) {
|
||||||
store.Delete(SECRET_STORE_SERVICE);
|
store.Delete(SECRET_STORE_SERVICE);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
compute_fallback_path();
|
compute_fallback_path();
|
||||||
if (!secret_fallback_path.empty() && wxFileExists(wxString::FromUTF8(secret_fallback_path.c_str()))) {
|
if (!secret_fallback_path.empty() && wxFileExists(wxString::FromUTF8(secret_fallback_path.c_str()))) {
|
||||||
@@ -2023,13 +2038,13 @@ bool OrcaCloudServiceAgent::set_user_session(const json& session_json, bool noti
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OrcaCloudServiceAgent::clear_session()
|
void OrcaCloudServiceAgent::clear_session(bool all_backends)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(session_mutex);
|
std::lock_guard<std::mutex> lock(session_mutex);
|
||||||
session = SessionInfo{};
|
session = SessionInfo{};
|
||||||
}
|
}
|
||||||
clear_user_secret();
|
clear_user_secret(all_backends);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ public:
|
|||||||
|
|
||||||
void persist_user_secret(const std::string& secret);
|
void persist_user_secret(const std::string& secret);
|
||||||
bool load_user_secret(std::string& out_secret);
|
bool load_user_secret(std::string& out_secret);
|
||||||
void clear_user_secret();
|
void clear_user_secret(bool all_backends = false);
|
||||||
|
|
||||||
// Token refresh helpers
|
// Token refresh helpers
|
||||||
bool refresh_if_expiring(std::chrono::seconds skew, const std::string& reason);
|
bool refresh_if_expiring(std::chrono::seconds skew, const std::string& reason);
|
||||||
@@ -344,7 +344,7 @@ public:
|
|||||||
bool persist = true);
|
bool persist = true);
|
||||||
// Accepts either nested Orca cloud / GoTrue session JSON or flat WebView token JSON.
|
// Accepts either nested Orca cloud / GoTrue session JSON or flat WebView token JSON.
|
||||||
bool set_user_session(const nlohmann::json& session_json, bool notify_login = true);
|
bool set_user_session(const nlohmann::json& session_json, bool notify_login = true);
|
||||||
void clear_session();
|
void clear_session(bool all_backends = false);
|
||||||
|
|
||||||
static std::string generate_uuid_for_setting_id(const std::string& name, const std::string& user_id = "");
|
static std::string generate_uuid_for_setting_id(const std::string& name, const std::string& user_id = "");
|
||||||
|
|
||||||
@@ -413,6 +413,11 @@ private:
|
|||||||
// Member variables - auth state
|
// Member variables - auth state
|
||||||
PkceBundle pkce_bundle;
|
PkceBundle pkce_bundle;
|
||||||
std::string secret_fallback_path;
|
std::string secret_fallback_path;
|
||||||
|
// Set once this process has read a secret from the store or written one. Unless the user logs
|
||||||
|
// out explicitly, clear_user_secret() only touches the store while it is set, so a logged-out
|
||||||
|
// instance (the GUI polls the login status every 2 s) makes no keychain calls and cannot wipe
|
||||||
|
// a login another instance saved.
|
||||||
|
std::atomic_bool secret_stored{false};
|
||||||
SessionHandler session_handler;
|
SessionHandler session_handler;
|
||||||
OnLoginCompleteHandler on_login_complete_handler;
|
OnLoginCompleteHandler on_login_complete_handler;
|
||||||
SessionInfo session;
|
SessionInfo session;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_prebuild_queue.cpp
|
test_prebuild_queue.cpp
|
||||||
test_staged_build.cpp
|
test_staged_build.cpp
|
||||||
test_network_versions.cpp
|
test_network_versions.cpp
|
||||||
|
test_orca_cloud_agent.cpp
|
||||||
test_action_source.cpp
|
test_action_source.cpp
|
||||||
test_plugin_host_api.cpp
|
test_plugin_host_api.cpp
|
||||||
# Exercise seam enums and predicates through the embedded Python host API.
|
# Exercise seam enums and predicates through the embedded Python host API.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
#include <memory.h>
|
#include <memory.h>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
#include <pybind11/embed.h>
|
#include <pybind11/embed.h>
|
||||||
#include <pybind11/pybind11.h>
|
#include <pybind11/pybind11.h>
|
||||||
|
|
||||||
@@ -25,8 +26,15 @@ void ensure_python_initialized()
|
|||||||
config.parse_argv = 0;
|
config.parse_argv = 0;
|
||||||
|
|
||||||
const auto python_home = boost::dll::program_location().parent_path() / "python";
|
const auto python_home = boost::dll::program_location().parent_path() / "python";
|
||||||
|
#ifdef _WIN32
|
||||||
|
const auto stdlib = python_home / "Lib";
|
||||||
|
#else
|
||||||
|
const auto stdlib = python_home / "lib" /
|
||||||
|
("python" + std::to_string(PY_MAJOR_VERSION) + "." + std::to_string(PY_MINOR_VERSION));
|
||||||
|
#endif
|
||||||
|
|
||||||
if (boost::filesystem::exists(python_home)) {
|
// Only a real runtime: a stray python/ folder (packages a test left behind) is not a home.
|
||||||
|
if (boost::filesystem::exists(stdlib / "encodings")) {
|
||||||
const std::string home = python_home.string();
|
const std::string home = python_home.string();
|
||||||
const PyStatus status = PyConfig_SetBytesString(&config, &config.home, home.c_str());
|
const PyStatus status = PyConfig_SetBytesString(&config, &config.home, home.c_str());
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +1,6 @@
|
|||||||
#include <catch2/catch_all.hpp>
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
#include "slic3r/Utils/Http.hpp"
|
#include "slic3r/Utils/Http.hpp"
|
||||||
#include "slic3r/Utils/OrcaCloudServiceAgent.hpp"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
nlohmann::json flat_session_json(const nlohmann::json& fields)
|
|
||||||
{
|
|
||||||
nlohmann::json session = {
|
|
||||||
{"access_token", "test-token"},
|
|
||||||
{"user_id", "test-user-id"}
|
|
||||||
};
|
|
||||||
session.update(fields);
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
nlohmann::json nested_session_json(const nlohmann::json& metadata)
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
{"access_token", "test-token"},
|
|
||||||
{"user", {
|
|
||||||
{"id", "test-user-id"},
|
|
||||||
{"user_metadata", metadata}
|
|
||||||
}}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string resolved_display_name(const nlohmann::json& session)
|
|
||||||
{
|
|
||||||
Slic3r::OrcaCloudServiceAgent agent("");
|
|
||||||
REQUIRE(agent.set_user_session(session, false));
|
|
||||||
return agent.get_user_nickname();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("Check SSL certificates paths", "[Http][NotWorking]") {
|
TEST_CASE("Check SSL certificates paths", "[Http][NotWorking]") {
|
||||||
|
|
||||||
@@ -53,62 +20,6 @@ TEST_CASE("Check SSL certificates paths", "[Http][NotWorking]") {
|
|||||||
REQUIRE(status == 200);
|
REQUIRE(status == 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Orca cloud flat session resolves display name consistently", "[OrcaCloudServiceAgent]")
|
|
||||||
{
|
|
||||||
CHECK(resolved_display_name(flat_session_json({
|
|
||||||
{"username", "orca_username"},
|
|
||||||
{"display_name", "Display Name"},
|
|
||||||
{"nickname", "Nickname"}
|
|
||||||
})) == "Display Name");
|
|
||||||
|
|
||||||
CHECK(resolved_display_name(flat_session_json({
|
|
||||||
{"username", "orca_username"},
|
|
||||||
{"nickname", "Nickname"}
|
|
||||||
})) == "Nickname");
|
|
||||||
|
|
||||||
CHECK(resolved_display_name(flat_session_json({
|
|
||||||
{"username", "orca_username"},
|
|
||||||
{"full_name", "Full Name"}
|
|
||||||
})) == "Full Name");
|
|
||||||
|
|
||||||
CHECK(resolved_display_name(flat_session_json({
|
|
||||||
{"username", "orca_username"},
|
|
||||||
{"name", "Provider Name"}
|
|
||||||
})) == "Provider Name");
|
|
||||||
|
|
||||||
CHECK(resolved_display_name(flat_session_json({
|
|
||||||
{"username", "orca_username"}
|
|
||||||
})) == "orca_username");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Orca cloud nested session resolves display name consistently", "[OrcaCloudServiceAgent]")
|
|
||||||
{
|
|
||||||
CHECK(resolved_display_name(nested_session_json({
|
|
||||||
{"username", "orca_username"},
|
|
||||||
{"display_name", "Display Name"},
|
|
||||||
{"nickname", "Nickname"}
|
|
||||||
})) == "Display Name");
|
|
||||||
|
|
||||||
CHECK(resolved_display_name(nested_session_json({
|
|
||||||
{"username", "orca_username"},
|
|
||||||
{"nickname", "Nickname"}
|
|
||||||
})) == "Nickname");
|
|
||||||
|
|
||||||
CHECK(resolved_display_name(nested_session_json({
|
|
||||||
{"username", "orca_username"},
|
|
||||||
{"full_name", "Full Name"}
|
|
||||||
})) == "Full Name");
|
|
||||||
|
|
||||||
CHECK(resolved_display_name(nested_session_json({
|
|
||||||
{"username", "orca_username"},
|
|
||||||
{"name", "Provider Name"}
|
|
||||||
})) == "Provider Name");
|
|
||||||
|
|
||||||
CHECK(resolved_display_name(nested_session_json({
|
|
||||||
{"username", "orca_username"}
|
|
||||||
})) == "orca_username");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Http digest authentication", "[Http][NotWorking]") {
|
TEST_CASE("Http digest authentication", "[Http][NotWorking]") {
|
||||||
Slic3r::Http g = Slic3r::Http::get("https://httpbingo.org/digest-auth/auth/guest/guest");
|
Slic3r::Http g = Slic3r::Http::get("https://httpbingo.org/digest-auth/auth/guest/guest");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <boost/filesystem/fstream.hpp>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "slic3r/Utils/OrcaCloudServiceAgent.hpp"
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// The encrypted token file is the one secret backend a test can observe without a system
|
||||||
|
// keychain. Every agent pointed at the same directory shares it, like separate app instances
|
||||||
|
// share the keychain entry.
|
||||||
|
std::unique_ptr<OrcaCloudServiceAgent> make_file_backed_agent(const fs::path& dir)
|
||||||
|
{
|
||||||
|
auto agent = std::make_unique<OrcaCloudServiceAgent>(dir.string());
|
||||||
|
agent->set_use_encrypted_token_file(true);
|
||||||
|
agent->set_config_dir(dir.string());
|
||||||
|
return agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path secret_file(const fs::path& dir) { return dir / secret_constants::USER_SECRET_FILENAME; }
|
||||||
|
|
||||||
|
nlohmann::json flat_session_json(const nlohmann::json& fields)
|
||||||
|
{
|
||||||
|
nlohmann::json session = {
|
||||||
|
{"access_token", "test-token"},
|
||||||
|
{"user_id", "test-user-id"}
|
||||||
|
};
|
||||||
|
session.update(fields);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json nested_session_json(const nlohmann::json& metadata)
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{"access_token", "test-token"},
|
||||||
|
{"user", {
|
||||||
|
{"id", "test-user-id"},
|
||||||
|
{"user_metadata", metadata}
|
||||||
|
}}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// set_user_session() persists the session, so it goes to a throwaway token file rather than the
|
||||||
|
// system keychain of whoever runs the tests.
|
||||||
|
std::string resolved_display_name(const nlohmann::json& session)
|
||||||
|
{
|
||||||
|
ScopedTemporaryDir dir("orca-secret");
|
||||||
|
auto agent = make_file_backed_agent(dir.path());
|
||||||
|
REQUIRE(agent->set_user_session(session, false));
|
||||||
|
return agent->get_user_nickname();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("Logging out removes the secret this instance saved", "[OrcaCloudServiceAgent]")
|
||||||
|
{
|
||||||
|
ScopedTemporaryDir dir("orca-secret");
|
||||||
|
auto agent = make_file_backed_agent(dir.path());
|
||||||
|
|
||||||
|
agent->persist_user_secret("refresh-token");
|
||||||
|
REQUIRE(fs::exists(secret_file(dir.path())));
|
||||||
|
|
||||||
|
agent->user_logout(false);
|
||||||
|
CHECK_FALSE(fs::exists(secret_file(dir.path())));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Logging out removes a secret this instance loaded from the store", "[OrcaCloudServiceAgent]")
|
||||||
|
{
|
||||||
|
ScopedTemporaryDir dir("orca-secret");
|
||||||
|
make_file_backed_agent(dir.path())->persist_user_secret("refresh-token");
|
||||||
|
|
||||||
|
auto agent = make_file_backed_agent(dir.path());
|
||||||
|
std::string secret;
|
||||||
|
REQUIRE(agent->load_user_secret(secret));
|
||||||
|
CHECK(secret == "refresh-token");
|
||||||
|
|
||||||
|
agent->user_logout(false);
|
||||||
|
CHECK_FALSE(fs::exists(secret_file(dir.path())));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Logging out leaves a secret this instance never loaded or saved alone", "[OrcaCloudServiceAgent]")
|
||||||
|
{
|
||||||
|
ScopedTemporaryDir dir("orca-secret");
|
||||||
|
make_file_backed_agent(dir.path())->persist_user_secret("refresh-token");
|
||||||
|
|
||||||
|
// A logged-out instance is asked to log out on every login-status poll.
|
||||||
|
auto other = make_file_backed_agent(dir.path());
|
||||||
|
other->user_logout(false);
|
||||||
|
other->user_logout(false);
|
||||||
|
CHECK(fs::exists(secret_file(dir.path())));
|
||||||
|
|
||||||
|
std::string secret;
|
||||||
|
REQUIRE(make_file_backed_agent(dir.path())->load_user_secret(secret));
|
||||||
|
CHECK(secret == "refresh-token");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Logging out leaves a secret this instance could not read alone", "[OrcaCloudServiceAgent]")
|
||||||
|
{
|
||||||
|
ScopedTemporaryDir dir("orca-secret");
|
||||||
|
// Written under another encryption key, e.g. by another OS user sharing the data directory.
|
||||||
|
fs::ofstream(secret_file(dir.path())) << "v2:0000:not-a-payload-this-user-can-decrypt";
|
||||||
|
|
||||||
|
auto agent = make_file_backed_agent(dir.path());
|
||||||
|
std::string secret;
|
||||||
|
REQUIRE_FALSE(agent->load_user_secret(secret));
|
||||||
|
|
||||||
|
agent->user_logout(false);
|
||||||
|
CHECK(fs::exists(secret_file(dir.path())));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Orca cloud flat session resolves display name consistently", "[OrcaCloudServiceAgent]")
|
||||||
|
{
|
||||||
|
CHECK(resolved_display_name(flat_session_json({
|
||||||
|
{"username", "orca_username"},
|
||||||
|
{"display_name", "Display Name"},
|
||||||
|
{"nickname", "Nickname"}
|
||||||
|
})) == "Display Name");
|
||||||
|
|
||||||
|
CHECK(resolved_display_name(flat_session_json({
|
||||||
|
{"username", "orca_username"},
|
||||||
|
{"nickname", "Nickname"}
|
||||||
|
})) == "Nickname");
|
||||||
|
|
||||||
|
CHECK(resolved_display_name(flat_session_json({
|
||||||
|
{"username", "orca_username"},
|
||||||
|
{"full_name", "Full Name"}
|
||||||
|
})) == "Full Name");
|
||||||
|
|
||||||
|
CHECK(resolved_display_name(flat_session_json({
|
||||||
|
{"username", "orca_username"},
|
||||||
|
{"name", "Provider Name"}
|
||||||
|
})) == "Provider Name");
|
||||||
|
|
||||||
|
CHECK(resolved_display_name(flat_session_json({
|
||||||
|
{"username", "orca_username"}
|
||||||
|
})) == "orca_username");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Orca cloud nested session resolves display name consistently", "[OrcaCloudServiceAgent]")
|
||||||
|
{
|
||||||
|
CHECK(resolved_display_name(nested_session_json({
|
||||||
|
{"username", "orca_username"},
|
||||||
|
{"display_name", "Display Name"},
|
||||||
|
{"nickname", "Nickname"}
|
||||||
|
})) == "Display Name");
|
||||||
|
|
||||||
|
CHECK(resolved_display_name(nested_session_json({
|
||||||
|
{"username", "orca_username"},
|
||||||
|
{"nickname", "Nickname"}
|
||||||
|
})) == "Nickname");
|
||||||
|
|
||||||
|
CHECK(resolved_display_name(nested_session_json({
|
||||||
|
{"username", "orca_username"},
|
||||||
|
{"full_name", "Full Name"}
|
||||||
|
})) == "Full Name");
|
||||||
|
|
||||||
|
CHECK(resolved_display_name(nested_session_json({
|
||||||
|
{"username", "orca_username"},
|
||||||
|
{"name", "Provider Name"}
|
||||||
|
})) == "Provider Name");
|
||||||
|
|
||||||
|
CHECK(resolved_display_name(nested_session_json({
|
||||||
|
{"username", "orca_username"}
|
||||||
|
})) == "orca_username");
|
||||||
|
}
|
||||||
@@ -38,6 +38,9 @@ namespace {
|
|||||||
// before this destructor's shutdown() runs.
|
// before this destructor's shutdown() runs.
|
||||||
struct ScopedPluginManager
|
struct ScopedPluginManager
|
||||||
{
|
{
|
||||||
|
// Before initialize(): the interpreter creates {data_dir}/python/packages and {data_dir}/log,
|
||||||
|
// which would otherwise land in the working directory.
|
||||||
|
ScopedDataDir python_data_dir{"plugin-python"};
|
||||||
bool initialized = PluginManager::instance().initialize();
|
bool initialized = PluginManager::instance().initialize();
|
||||||
|
|
||||||
~ScopedPluginManager()
|
~ScopedPluginManager()
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ namespace {
|
|||||||
// same as any other plugin.
|
// same as any other plugin.
|
||||||
struct ScopedManagerShutdown
|
struct ScopedManagerShutdown
|
||||||
{
|
{
|
||||||
|
// Before initialize(): the interpreter creates {data_dir}/python/packages and {data_dir}/log,
|
||||||
|
// which would otherwise land in the working directory.
|
||||||
|
ScopedDataDir python_data_dir{"plugin-python"};
|
||||||
bool initialized = PluginManager::instance().initialize();
|
bool initialized = PluginManager::instance().initialize();
|
||||||
|
|
||||||
~ScopedManagerShutdown()
|
~ScopedManagerShutdown()
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ namespace {
|
|||||||
// Declare this FIRST in a test so it is destroyed last.
|
// Declare this FIRST in a test so it is destroyed last.
|
||||||
struct ScopedPluginManager
|
struct ScopedPluginManager
|
||||||
{
|
{
|
||||||
|
// Before initialize(): the interpreter creates {data_dir}/python/packages and {data_dir}/log,
|
||||||
|
// which would otherwise land in the working directory.
|
||||||
|
ScopedDataDir python_data_dir{"plugin-python"};
|
||||||
bool initialized = false;
|
bool initialized = false;
|
||||||
|
|
||||||
ScopedPluginManager() { initialized = PluginManager::instance().initialize(); }
|
ScopedPluginManager() { initialized = PluginManager::instance().initialize(); }
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "plugin_test_utils.hpp"
|
||||||
|
|
||||||
namespace py = pybind11;
|
namespace py = pybind11;
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
|
|
||||||
@@ -21,6 +23,9 @@ namespace {
|
|||||||
// into Python unless PythonInterpreter::instance() reports initialized.
|
// into Python unless PythonInterpreter::instance() reports initialized.
|
||||||
struct ScopedPluginManager
|
struct ScopedPluginManager
|
||||||
{
|
{
|
||||||
|
// Before initialize(): the interpreter creates {data_dir}/python/packages and {data_dir}/log,
|
||||||
|
// which would otherwise land in the working directory.
|
||||||
|
ScopedDataDir python_data_dir{"plugin-python"};
|
||||||
bool initialized = PluginManager::instance().initialize();
|
bool initialized = PluginManager::instance().initialize();
|
||||||
|
|
||||||
~ScopedPluginManager()
|
~ScopedPluginManager()
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ namespace {
|
|||||||
|
|
||||||
struct ScopedPluginManager
|
struct ScopedPluginManager
|
||||||
{
|
{
|
||||||
|
// Before initialize(): the interpreter creates {data_dir}/python/packages and {data_dir}/log,
|
||||||
|
// which would otherwise land in the working directory.
|
||||||
|
ScopedDataDir python_data_dir{"plugin-python"};
|
||||||
bool initialized = false;
|
bool initialized = false;
|
||||||
|
|
||||||
ScopedPluginManager() { initialized = PluginManager::instance().initialize(); }
|
ScopedPluginManager() { initialized = PluginManager::instance().initialize(); }
|
||||||
|
|||||||
Reference in New Issue
Block a user