mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-29 11:51:24 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0df62b804 | ||
|
|
6803e62949 | ||
|
|
08f086daf3 | ||
|
|
293aa3e0ed | ||
|
|
faeb84da72 | ||
|
|
c30c9beb09 | ||
|
|
77f8c64d37 |
@@ -1,5 +1,7 @@
|
||||
name: Daily OFL OTA Update
|
||||
|
||||
run-name: Daily OFL OTA Update [OFL barrier]
|
||||
|
||||
# This workflow is intended for creating and publishing the OrcaFilamentLibrary (OFL) OPC package to
|
||||
# https://github.com/OrcaSlicer/orcaslicer-profiles, which generates an OTA update.
|
||||
# This cronjob runs daily at 00:00 UTC every day and scans main plus every release/vX.Y.Z branch for
|
||||
@@ -12,9 +14,9 @@ name: Daily OFL OTA Update
|
||||
# vendor-dispatch path is also what makes post_merge_profiles.yml call the OTA auto-publish API after
|
||||
# uploading - see post_merge_profiles.yml for both sides of that contract.
|
||||
#
|
||||
# At the start of each run, the pending-publish table is cleared up to a captured
|
||||
# timestamp (POST /api/v1/ota/ofl/pending/clear?timestamp=...). Changes merged after
|
||||
# that timestamp remain pending for the next run.
|
||||
# Each run captures a timestamp, dispatches the needed OFL publishers, waits for
|
||||
# all of them to finish, then clears the pending-publish table once. Changes merged
|
||||
# after that timestamp remain pending for the next run.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -34,32 +36,14 @@ jobs:
|
||||
if: ${{ github.repository == 'OrcaSlicer/OrcaSlicer' }}
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Capture start timestamp and clear OFL pending queue
|
||||
- name: Capture start timestamp
|
||||
id: start
|
||||
shell: bash
|
||||
env:
|
||||
OTA_API_BASE_URL: ${{ vars.OTA_API_BASE_URL }}
|
||||
OTA_API_KEY: ${{ secrets.OFL_OTA_PUBLISH_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[ -n "$OTA_API_BASE_URL" ] || { echo "::error::vars.OTA_API_BASE_URL is not set"; exit 1; }
|
||||
[ -n "$OTA_API_KEY" ] || { echo "::error::secrets.OFL_OTA_PUBLISH_KEY is not set"; exit 1; }
|
||||
|
||||
timestamp="$(date -u +%s)"
|
||||
echo "timestamp=$timestamp" >> "$GITHUB_OUTPUT"
|
||||
|
||||
resp_file="$RUNNER_TEMP/ota-pending-clear-response.json"
|
||||
status="$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST \
|
||||
"${OTA_API_BASE_URL%/}/api/v1/ota/ofl/pending/clear?timestamp=$timestamp" \
|
||||
-H "Authorization: Bearer $OTA_API_KEY")"
|
||||
body="$(cat "$resp_file")"
|
||||
echo "$body"
|
||||
|
||||
if [ "$status" != "200" ]; then
|
||||
echo "::error::OTA pending-clear call failed with HTTP $status"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
@@ -84,27 +68,21 @@ jobs:
|
||||
| grep -E '^(main|release/v[0-9]+\.[0-9]+\.[0-9]+)$' | sort -u
|
||||
)
|
||||
|
||||
# The cron run is the checkpoint: a successful run means every
|
||||
# dispatched branch publisher completed and the pending queue was
|
||||
# cleared. Manual or push-triggered post_merge_profiles runs are not
|
||||
# checkpoints for this scan.
|
||||
successful_cron_runs="$(gh api --method GET \
|
||||
"repos/${{ github.repository }}/actions/workflows/ofl-ota-cronjob.yml/runs" \
|
||||
-f status=success -f branch=main -f per_page=100 --paginate \
|
||||
--jq '.workflow_runs[] | select((.display_title // "") | contains("[OFL barrier]"))')"
|
||||
since="$(jq -rs 'sort_by(.run_started_at) | last.run_started_at // empty' <<< "$successful_cron_runs")"
|
||||
|
||||
for branch in "${branches[@]}"; do
|
||||
echo "::group::$branch"
|
||||
|
||||
# post_merge_profiles.yml's own run history, not this workflow's: this
|
||||
# workflow only ever runs against main (schedule, or workflow_dispatch
|
||||
# --ref main), so its head branch never varies - filtering ITS history
|
||||
# by $branch would never match anything except main. post_merge_profiles.yml
|
||||
# genuinely runs per-branch (this dispatch below sets --ref "$branch"),
|
||||
# so its history is the real per-branch checkpoint. It also means a
|
||||
# failed publish naturally gets retried tomorrow: the checkpoint only
|
||||
# advances on a run that actually succeeded.
|
||||
# --method GET is required, not cosmetic: gh api defaults to POST
|
||||
# whenever -f fields are present unless a method is given
|
||||
# explicitly, and POST on this list-runs endpoint 404s - confirmed
|
||||
# on real Actions infrastructure, not just reasoned about.
|
||||
since="$(gh api --method GET "repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \
|
||||
-f status=success -f branch="$branch" -f per_page=1 \
|
||||
--jq '.workflow_runs[0].run_started_at // empty')"
|
||||
|
||||
if [ -z "$since" ]; then
|
||||
echo "No prior successful run for $branch; checking OFL changes up to $SCAN_UNTIL."
|
||||
echo "No prior successful OFL cron run; checking $branch through $SCAN_UNTIL."
|
||||
changed_files="$(git log --until="$SCAN_UNTIL" --name-only --pretty=format: "origin/$branch" -- \
|
||||
resources/profiles/OrcaFilamentLibrary resources/profiles/OrcaFilamentLibrary.json \
|
||||
| sed '/^$/d')"
|
||||
@@ -124,16 +102,98 @@ jobs:
|
||||
fi
|
||||
|
||||
if [ "$changed" = true ]; then
|
||||
# Tolerate a per-branch failure (e.g. a pre-existing release branch
|
||||
# whose post_merge_profiles.yml predates the vendor/auto_publish
|
||||
# inputs) rather than aborting the whole scan under set -e.
|
||||
if ! gh workflow run post_merge_profiles.yml \
|
||||
dispatch_id="${GITHUB_RUN_ID}-${branch//\//-}"
|
||||
# Record successful dispatches for the barrier step below.
|
||||
# Branches whose workflow predates workflow_dispatch are skipped
|
||||
# with a warning, as they were before the barrier was added.
|
||||
if gh workflow run post_merge_profiles.yml \
|
||||
--repo "${{ github.repository }}" \
|
||||
--ref "$branch" \
|
||||
-f vendor="$VENDOR" -f auto_publish=true; then
|
||||
echo "::warning::failed to dispatch post_merge_profiles.yml for $branch - its post_merge_profiles.yml at this ref may predate the vendor/auto_publish inputs"
|
||||
-f vendor="$VENDOR" -f auto_publish=true \
|
||||
-f ofl_cron_dispatch_id="$dispatch_id"; then
|
||||
printf '%s\t%s\n' "$branch" "$dispatch_id" >> "$RUNNER_TEMP/ofl-dispatches.tsv"
|
||||
else
|
||||
echo "::warning::skipping $branch because post_merge_profiles.yml could not be dispatched at that ref"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
- name: Wait for OFL publishers
|
||||
id: wait
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DISPATCHES_FILE: ${{ runner.temp }}/ofl-dispatches.tsv
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [ ! -s "$DISPATCHES_FILE" ]; then
|
||||
echo "No OFL publisher workflows were dispatched; pending queue will not be cleared."
|
||||
echo "publishers_dispatched=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
: > "$RUNNER_TEMP/ofl-run-ids.tsv"
|
||||
while IFS=$'\t' read -r branch dispatch_id; do
|
||||
[ -n "$branch" ] || continue
|
||||
echo "Waiting for OFL publisher on $branch ($dispatch_id)"
|
||||
|
||||
run_id=""
|
||||
for _ in {1..120}; do
|
||||
runs_json="$(gh api --method GET \
|
||||
"repos/${{ github.repository }}/actions/workflows/post_merge_profiles.yml/runs" \
|
||||
-f branch="$branch" -f event=workflow_dispatch -f per_page=100)"
|
||||
run_id="$(jq -r --arg marker "[OFL cron $dispatch_id]" \
|
||||
'[.workflow_runs[] | select((.display_title // "") | contains($marker))] \
|
||||
| sort_by(.created_at) | last | .id // empty' <<< "$runs_json")"
|
||||
[ -n "$run_id" ] && break
|
||||
sleep 5
|
||||
done
|
||||
|
||||
if [ -z "$run_id" ]; then
|
||||
echo "::error::could not find dispatched post_merge_profiles run for $branch ($dispatch_id)"
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\t%s\n' "$branch" "$run_id" >> "$RUNNER_TEMP/ofl-run-ids.tsv"
|
||||
done < "$DISPATCHES_FILE"
|
||||
|
||||
all_success=true
|
||||
while IFS=$'\t' read -r branch run_id; do
|
||||
[ -n "$run_id" ] || continue
|
||||
echo "Watching OFL publisher run $run_id for $branch"
|
||||
if ! gh run watch "$run_id" --repo "${{ github.repository }}" --exit-status; then
|
||||
all_success=false
|
||||
fi
|
||||
done < "$RUNNER_TEMP/ofl-run-ids.tsv"
|
||||
|
||||
if [ "$all_success" != true ]; then
|
||||
echo "::error::one or more OFL publisher workflows failed; pending queue will not be cleared"
|
||||
exit 1
|
||||
fi
|
||||
echo "publishers_dispatched=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Clear OFL pending queue
|
||||
if: steps.wait.outputs.publishers_dispatched == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
OTA_API_BASE_URL: ${{ vars.OTA_API_BASE_URL }}
|
||||
OTA_API_KEY: ${{ secrets.OFL_OTA_PUBLISH_KEY }}
|
||||
TIMESTAMP: ${{ steps.start.outputs.timestamp }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[ -n "$OTA_API_BASE_URL" ] || { echo "::error::vars.OTA_API_BASE_URL is not set"; exit 1; }
|
||||
[ -n "$OTA_API_KEY" ] || { echo "::error::secrets.OFL_OTA_PUBLISH_KEY is not set"; exit 1; }
|
||||
|
||||
resp_file="$RUNNER_TEMP/ota-pending-clear-response.json"
|
||||
status="$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST \
|
||||
"${OTA_API_BASE_URL%/}/api/v1/ota/ofl/pending/clear?timestamp=$TIMESTAMP" \
|
||||
-H "Authorization: Bearer $OTA_API_KEY")"
|
||||
body="$(cat "$resp_file")"
|
||||
echo "$body"
|
||||
|
||||
if [ "$status" != "200" ]; then
|
||||
echo "::error::OTA pending-clear call failed with HTTP $status"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
name: Post-merge profiles
|
||||
|
||||
run-name: >-
|
||||
Post-merge profiles${{
|
||||
inputs.ofl_cron_dispatch_id != '' &&
|
||||
inputs.vendor == 'OrcaFilamentLibrary' &&
|
||||
(inputs.auto_publish == true || inputs.auto_publish == 'true') &&
|
||||
format(' [OFL cron {0}]', inputs.ofl_cron_dispatch_id) ||
|
||||
''
|
||||
}}
|
||||
|
||||
# Push-triggered counterpart to check_profiles.yml (which only gates PRs). When a
|
||||
# profile change lands on main or a release branch, rebuild the affected vendors'
|
||||
# binary preset caches (<vendor>.opc) and publish each as a versioned ZIP asset on
|
||||
@@ -59,6 +68,12 @@ on:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
ofl_cron_dispatch_id:
|
||||
description: >-
|
||||
Unique marker supplied by the trusted OFL daily cron so it can find
|
||||
and wait for this dispatched workflow run.
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -1443,6 +1443,7 @@ void GCodeViewer::load_as_gcode(const GCodeProcessorResult& gcode_result, const
|
||||
libvgcode::EGCodeExtrusionRole::SupportTransition, libvgcode::EGCodeExtrusionRole::Mixed
|
||||
});
|
||||
m_paths_bounding_box = BoundingBoxf3(libvgcode::convert(bbox[0]).cast<double>(), libvgcode::convert(bbox[1]).cast<double>());
|
||||
m_max_bounding_box = m_paths_bounding_box;
|
||||
|
||||
if (wxGetApp().is_editor())
|
||||
m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(gcode_result, m_paths_bounding_box);
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/any.hpp>
|
||||
|
||||
#include <wx/filename.h>
|
||||
#include <wx/filesys.h>
|
||||
|
||||
#if __APPLE__
|
||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#elif _WIN32
|
||||
@@ -534,11 +531,6 @@ boost::filesystem::path into_path(const wxString &str)
|
||||
return boost::filesystem::path(str.wx_str());
|
||||
}
|
||||
|
||||
wxString file_url_from_path(const boost::filesystem::path &path)
|
||||
{
|
||||
return wxFileSystem::FileNameToURL(wxFileName(from_path(path)));
|
||||
}
|
||||
|
||||
void about()
|
||||
{
|
||||
AboutDialog dlg;
|
||||
|
||||
@@ -76,9 +76,6 @@ std::string into_u8(const wxString &str);
|
||||
wxString from_path(const boost::filesystem::path &path);
|
||||
// boost path from wxString
|
||||
boost::filesystem::path into_path(const wxString &str);
|
||||
// file:// URL of a local path, percent-encoded so characters such as '#', '%' and '?' stay part of the path.
|
||||
// Append any query or fragment to the result.
|
||||
wxString file_url_from_path(const boost::filesystem::path &path);
|
||||
|
||||
// Display an About dialog
|
||||
extern void about();
|
||||
|
||||
@@ -98,7 +98,10 @@ void MarkdownTip::LoadStyle()
|
||||
ph /= "tooltip/styled.html";
|
||||
_data_dir = false;
|
||||
}
|
||||
_tipView->LoadURL(file_url_from_path(ph));
|
||||
auto url = ph.string();
|
||||
std::replace(url.begin(), url.end(), '\\', '/');
|
||||
url = "file:///" + url;
|
||||
_tipView->LoadURL(from_u8(url));
|
||||
_lastTip.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -3483,7 +3483,7 @@ void Sidebar::update_all_preset_comboboxes()
|
||||
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
|
||||
wxString apikey;
|
||||
if(url.empty())
|
||||
url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/orca/missing_connection.html");
|
||||
url = wxString::Format("file://%s/web/orca/missing_connection.html", from_u8(resources_dir()));
|
||||
else {
|
||||
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
||||
if (cfg.has("printhost_apikey") && (host_type != htSimplyPrint))
|
||||
|
||||
@@ -57,7 +57,9 @@ PrivacyUpdateDialog::PrivacyUpdateDialog(wxWindow* parent, wxWindowID id, const
|
||||
|
||||
fs::path ph(resources_dir());
|
||||
ph /= "tooltip/privacyupdate.html";
|
||||
m_host_url = into_u8(file_url_from_path(ph));
|
||||
m_host_url = ph.string();
|
||||
std::replace(m_host_url.begin(), m_host_url.end(), '\\', '/');
|
||||
m_host_url = "file:///" + m_host_url;
|
||||
m_vebview_release_note->LoadURL(from_u8(m_host_url));
|
||||
m_sizer_right->Add(m_vebview_release_note, 0, wxEXPAND | wxRIGHT | wxLEFT, FromDIP(15));
|
||||
|
||||
|
||||
@@ -45,10 +45,10 @@ const std::vector<std::string> license_list = {
|
||||
ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, const wxSize &size, long style) : wxPanel(parent, id, pos, size, style)
|
||||
{
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
m_project_home_url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/model/index.html");
|
||||
m_project_home_url = wxString::Format("file://%s/web/model/index.html", from_u8(resources_dir()));
|
||||
wxString strlang = wxGetApp().current_language_code_safe();
|
||||
if (strlang != "")
|
||||
m_project_home_url += "?lang=" + strlang;
|
||||
m_project_home_url = wxString::Format("file://%s/web/model/index.html?lang=%s", from_u8(resources_dir()), strlang);
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
|
||||
@@ -510,7 +510,7 @@ void UpdateVersionDialog::update_version_info(wxString release_note, wxString ve
|
||||
out_buf->append(text, size);
|
||||
}, (void*) &html_source, MD_DIALECT_GITHUB | MD_FLAG_STRIKETHROUGH | MD_FLAG_WIKILINKS, 0);
|
||||
html_source.append("</body></html>");
|
||||
m_vebview_release_note->LoadURL(file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/0/index.html"));
|
||||
m_vebview_release_note->LoadURL("file://" + (boost::filesystem::path (resources_dir()) / "web/guide/0/index.html").string());
|
||||
|
||||
SetMinSize(GetSize());
|
||||
SetMaxSize(GetSize());
|
||||
|
||||
@@ -38,7 +38,9 @@ DownPluginFrame::DownPluginFrame(GUI_App *pGUI) : wxDialog((wxWindow *) (pGUI->m
|
||||
|
||||
// set the frame icon
|
||||
wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
|
||||
wxString TargetUrl = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/6/index.html");
|
||||
wxString TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/6/index.html").make_preferred().string());
|
||||
|
||||
TargetUrl = "file://" + TargetUrl;
|
||||
|
||||
// Create the webview
|
||||
m_browser = WebView::CreateWebView(this, TargetUrl);
|
||||
|
||||
@@ -218,38 +218,37 @@ wxString GuideFrame::SetStartPage(GuidePage startpage, bool load)
|
||||
m_page = startpage;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(" enter, load=%1%, start_page=%2%")%load%int(startpage);
|
||||
//wxLogMessage("GUIDE: webpage_1 %s", (boost::filesystem::path(resources_dir()) / "web\\guide\\1\\index.html").make_preferred().string().c_str() );
|
||||
const wxString guide_url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/guide/0/index.html");
|
||||
wxString TargetUrl = guide_url + "?target=1";
|
||||
wxString TargetUrl = from_u8( (boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=1").make_preferred().string() );
|
||||
//wxLogMessage("GUIDE: webpage_2 %s", TargetUrl.mb_str());
|
||||
|
||||
if (startpage == BBL_WELCOME){
|
||||
SetTitle(_L("Setup Wizard"));
|
||||
TargetUrl = guide_url + "?target=1";
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=1").make_preferred().string());
|
||||
} else if (startpage == BBL_REGION) {
|
||||
SetTitle(_L("Setup Wizard"));
|
||||
TargetUrl = guide_url + "?target=11";
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=11").make_preferred().string());
|
||||
} else if (startpage == BBL_MODELS) {
|
||||
SetTitle(_L("Setup Wizard"));
|
||||
TargetUrl = guide_url + "?target=21";
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
|
||||
} else if (startpage == BBL_FILAMENTS) {
|
||||
SetTitle(_L("Setup Wizard"));
|
||||
|
||||
int nSize = m_ProfileJson["model"].size();
|
||||
|
||||
if (nSize>0)
|
||||
TargetUrl = guide_url + "?target=22";
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=22").make_preferred().string());
|
||||
else
|
||||
TargetUrl = guide_url + "?target=21";
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
|
||||
} else if (startpage == BBL_FILAMENT_ONLY) {
|
||||
SetTitle("");
|
||||
TargetUrl = guide_url + "?target=23";
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=23").make_preferred().string());
|
||||
} else if (startpage == BBL_MODELS_ONLY) {
|
||||
SetTitle("");
|
||||
TargetUrl = guide_url + "?target=24";
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=24").make_preferred().string());
|
||||
}
|
||||
else {
|
||||
SetTitle(_L("Setup Wizard"));
|
||||
TargetUrl = guide_url + "?target=21";
|
||||
TargetUrl = from_u8((boost::filesystem::path(resources_dir()) / "web/guide/0/index.html?target=21").make_preferred().string());
|
||||
}
|
||||
|
||||
wxString strlang = wxGetApp().current_language_code_safe();
|
||||
@@ -257,6 +256,7 @@ wxString GuideFrame::SetStartPage(GuidePage startpage, bool load)
|
||||
if (strlang != "")
|
||||
TargetUrl = wxString::Format("%s&lang=%s", w2s(TargetUrl), strlang);
|
||||
|
||||
TargetUrl = "file://" + TargetUrl;
|
||||
if (load)
|
||||
load_url(TargetUrl);
|
||||
|
||||
@@ -1284,7 +1284,7 @@ bool GuideFrame::BuildProfileJson(const PresetBundle& bundle, bool require_all_r
|
||||
entry["vendor"] = vp.id;
|
||||
entry["nozzle_diameter"] = nozzle_str;
|
||||
entry["materials"] = materials_str;
|
||||
entry["cover"] = into_u8(file_url_from_path(cover_path));
|
||||
entry["cover"] = cover_path.string();
|
||||
entry["nozzle_selected"] = "";
|
||||
entry["sub_path"] = "";
|
||||
m_ProfileJson["model"].push_back(entry);
|
||||
@@ -1732,7 +1732,7 @@ int GuideFrame::LoadProfileFamily(std::string strVendor, std::string strFilePath
|
||||
cover_path = (boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "/web/image/printer/") / cover_file)
|
||||
.make_preferred();
|
||||
}
|
||||
OneModel["cover"] = into_u8(file_url_from_path(cover_path));
|
||||
OneModel["cover"] = cover_path.string();
|
||||
|
||||
OneModel["nozzle_selected"] = "";
|
||||
|
||||
|
||||
@@ -599,7 +599,7 @@ void ZUserLogin::OnScriptResponseMessage(wxCommandEvent &WXUNUSED(evt))
|
||||
|
||||
bool ZUserLogin::ShowErrorPage()
|
||||
{
|
||||
wxString ErrortUrl = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/login/error.html");
|
||||
wxString ErrortUrl = from_u8((boost::filesystem::path(resources_dir()) / "web\\login\\error.html").make_preferred().string());
|
||||
load_url(ErrortUrl);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -36,10 +36,10 @@ namespace GUI {
|
||||
WebViewPanel::WebViewPanel(wxWindow *parent)
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
|
||||
{
|
||||
wxString url = file_url_from_path(boost::filesystem::path(resources_dir()) / "web/homepage/index.html");
|
||||
wxString url = wxString::Format("file://%s/web/homepage/index.html", from_u8(resources_dir()));
|
||||
wxString strlang = wxGetApp().current_language_code_safe();
|
||||
if (strlang != "")
|
||||
url += "?lang=" + strlang;
|
||||
url = wxString::Format("file://%s/web/homepage/index.html?lang=%s", from_u8(resources_dir()), strlang);
|
||||
|
||||
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "MultiNozzleSync.hpp"
|
||||
|
||||
#include "../GUI.hpp"
|
||||
#include "../GUI_App.hpp"
|
||||
#include "../I18N.hpp"
|
||||
#include "../Plater.hpp"
|
||||
@@ -22,6 +21,8 @@
|
||||
#include <set>
|
||||
|
||||
#include <wx/choice.h>
|
||||
#include <wx/filename.h>
|
||||
#include <wx/filesys.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
|
||||
@@ -640,7 +641,8 @@ NozzleListTable::NozzleListTable(wxWindow* parent) : wxPanel(parent,wxID_ANY,wxD
|
||||
m_web_view->AddScriptMessageHandler("nozzleListTable");
|
||||
m_web_view->EnableContextMenu(false);
|
||||
fs::path filepath = fs::path(resources_dir()) / "web/flush/NozzleListTable.html";
|
||||
wxString url = file_url_from_path(filepath);
|
||||
wxFileName fn(wxString::FromUTF8(filepath.string()));
|
||||
wxString url = wxFileSystem::FileNameToURL(fn);
|
||||
m_web_view->LoadURL(url);
|
||||
|
||||
auto sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
@@ -48,19 +48,20 @@ constexpr char ORCA_BRIDGE_JS[] = R"JS(
|
||||
|
||||
wxString bootstrap_url()
|
||||
{
|
||||
return file_url_from_path(boost::filesystem::path(resources_dir()) / BOOTSTRAP_PAGE);
|
||||
return wxString("file://") + from_u8((boost::filesystem::path(resources_dir()) / BOOTSTRAP_PAGE).make_preferred().string());
|
||||
}
|
||||
|
||||
wxString content_base_url()
|
||||
{
|
||||
return file_url_from_path(boost::filesystem::path(resources_dir()) / "web") + "/";
|
||||
const std::string dir = (boost::filesystem::path(resources_dir()) / "web").make_preferred().string();
|
||||
return wxString("file://") + from_u8(dir) + "/";
|
||||
}
|
||||
|
||||
bool is_content_url(const wxString& url)
|
||||
{
|
||||
// The web view reports the URL it parsed, which may escape the resources path differently
|
||||
// from content_base_url().
|
||||
return wxURI::Unescape(url.BeforeFirst('#')) == wxURI::Unescape(content_base_url());
|
||||
// The web view reports the URL it parsed, which escapes anything the resources path holds
|
||||
// (a space, a non-ASCII character), while content_base_url() is the raw path.
|
||||
return wxURI::Unescape(url.BeforeFirst('#')) == content_base_url();
|
||||
}
|
||||
|
||||
const char* orca_bridge_script() { return ORCA_BRIDGE_JS; }
|
||||
|
||||
@@ -192,7 +192,7 @@ bool WebViewHostDialog::create_webview(const std::string& resource_path,
|
||||
|
||||
wxString WebViewHostDialog::build_resource_url(const std::string& resource_path) const
|
||||
{
|
||||
wxString target_url = file_url_from_path(boost::filesystem::path(resources_dir()) / resource_path);
|
||||
wxString target_url = from_u8((boost::filesystem::path(resources_dir()) / resource_path).make_preferred().string());
|
||||
|
||||
if (append_language_to_url()) {
|
||||
const wxString lang = wxGetApp().current_language_code_safe();
|
||||
@@ -202,7 +202,7 @@ wxString WebViewHostDialog::build_resource_url(const std::string& resource_path)
|
||||
}
|
||||
}
|
||||
|
||||
return target_url;
|
||||
return wxString("file://") + target_url;
|
||||
}
|
||||
|
||||
void WebViewHostDialog::load_url(const wxString& url)
|
||||
|
||||
@@ -464,7 +464,7 @@ WipingDialog::WipingDialog(wxWindow* parent, const int max_flush_volume) :
|
||||
wxString filepath_str = from_path(filepath);
|
||||
wxFileName fn(filepath_str);
|
||||
if(fn.FileExists()) {
|
||||
wxString url = file_url_from_path(filepath);
|
||||
wxString url = wxFileSystem::FileNameToURL(fn);
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "File exists and load url " << url.ToStdString();
|
||||
m_webview->LoadURL(url);
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__<< "Successfully loaded url: " << url.ToStdString();
|
||||
|
||||
@@ -340,7 +340,9 @@ namespace Slic3r {
|
||||
if (classify_printer_model(config->opt_string("printer_model")) != ElegooPrinterType::CC2)
|
||||
return fallback_webui;
|
||||
|
||||
std::string web_path = GUI::into_u8(GUI::file_url_from_path(boost::filesystem::path(resources_dir()) / "web/elegoolink/lan_service_web/index.html"));
|
||||
std::string web_path = resources_dir() + "/web/elegoolink/lan_service_web/index.html";
|
||||
std::replace(web_path.begin(), web_path.end(), '\\', '/');
|
||||
web_path = "file://" + web_path;
|
||||
const std::string token = get_cc2_token(config->opt_string("printhost_apikey"));
|
||||
const std::string host_ip = Http::get_host_header_value(host);
|
||||
|
||||
|
||||
@@ -818,7 +818,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;
|
||||
}
|
||||
|
||||
@@ -1603,7 +1605,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)
|
||||
@@ -1643,6 +1647,7 @@ bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
|
||||
}
|
||||
|
||||
if (integrity_ok && aes256gcm_decrypt(encoded_payload, key, plain) && !plain.empty()) {
|
||||
secret_stored = true;
|
||||
out_secret = plain;
|
||||
// Upgrade legacy payloads to signed format
|
||||
if (payload.rfind("v2:", 0) != 0) {
|
||||
@@ -1660,6 +1665,7 @@ bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
|
||||
if (store.Load(SECRET_STORE_SERVICE, username, secret) && secret.IsOk()) {
|
||||
out_secret.assign(static_cast<const char*>(secret.GetData()), secret.GetSize());
|
||||
if (!out_secret.empty()) {
|
||||
secret_stored = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1669,11 +1675,20 @@ bool OrcaCloudServiceAgent::load_user_secret(std::string& out_secret)
|
||||
return false;
|
||||
}
|
||||
|
||||
void OrcaCloudServiceAgent::clear_user_secret()
|
||||
void OrcaCloudServiceAgent::clear_user_secret(bool all_backends)
|
||||
{
|
||||
wxSecretStore store = wxSecretStore::GetDefault();
|
||||
if (store.IsOk()) {
|
||||
store.Delete(SECRET_STORE_SERVICE);
|
||||
// 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();
|
||||
if (store.IsOk()) {
|
||||
store.Delete(SECRET_STORE_SERVICE);
|
||||
}
|
||||
}
|
||||
|
||||
compute_fallback_path();
|
||||
@@ -2022,13 +2037,13 @@ bool OrcaCloudServiceAgent::set_user_session(const json& session_json, bool noti
|
||||
return success;
|
||||
}
|
||||
|
||||
void OrcaCloudServiceAgent::clear_session()
|
||||
void OrcaCloudServiceAgent::clear_session(bool all_backends)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(session_mutex);
|
||||
session = SessionInfo{};
|
||||
}
|
||||
clear_user_secret();
|
||||
clear_user_secret(all_backends);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -324,7 +324,7 @@ public:
|
||||
|
||||
void persist_user_secret(const std::string& secret);
|
||||
bool load_user_secret(std::string& out_secret);
|
||||
void clear_user_secret();
|
||||
void clear_user_secret(bool all_backends = false);
|
||||
|
||||
// Token refresh helpers
|
||||
bool refresh_if_expiring(std::chrono::seconds skew, const std::string& reason);
|
||||
@@ -342,7 +342,7 @@ public:
|
||||
bool persist = true);
|
||||
// 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);
|
||||
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 = "");
|
||||
|
||||
@@ -411,6 +411,11 @@ private:
|
||||
// Member variables - auth state
|
||||
PkceBundle pkce_bundle;
|
||||
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;
|
||||
OnLoginCompleteHandler on_login_complete_handler;
|
||||
SessionInfo session;
|
||||
|
||||
@@ -10,6 +10,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_prebuild_queue.cpp
|
||||
test_staged_build.cpp
|
||||
test_network_versions.cpp
|
||||
test_orca_cloud_agent.cpp
|
||||
test_action_source.cpp
|
||||
test_plugin_host_api.cpp
|
||||
test_plugin_capability_config.cpp
|
||||
@@ -25,7 +26,6 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_plugin_cloud_metadata.cpp
|
||||
test_plugin_audit.cpp
|
||||
test_shortcuts.cpp
|
||||
test_file_url.cpp
|
||||
../fff_print/test_helpers.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -1,39 +1,6 @@
|
||||
#include <catch2/catch_all.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]") {
|
||||
|
||||
@@ -53,62 +20,6 @@ TEST_CASE("Check SSL certificates paths", "[Http][NotWorking]") {
|
||||
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]") {
|
||||
Slic3r::Http g = Slic3r::Http::get("https://httpbingo.org/digest-auth/auth/guest/guest");
|
||||
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include <wx/uri.h>
|
||||
|
||||
using namespace Slic3r::GUI;
|
||||
|
||||
#ifndef _WIN32
|
||||
TEST_CASE("A file URL keeps characters special to URLs in its path", "[FileUrl]")
|
||||
{
|
||||
const std::string path = GENERATE(as<std::string>{},
|
||||
"/opt/test#dir/resources/web/homepage/index.html",
|
||||
"/opt/test%20x/resources/web/homepage/index.html",
|
||||
"/opt/Orca Slicer/resources/web/homepage/index.html",
|
||||
"/opt/what?/resources/web/homepage/index.html",
|
||||
"/home/Jos\xC3\xA9/\xE8\xB5\x84\xE6\xBA\x90/resources/web/homepage/index.html");
|
||||
|
||||
const wxString url = file_url_from_path(boost::filesystem::path(path));
|
||||
CAPTURE(path, url.utf8_string());
|
||||
|
||||
// WebView::CreateWebView() and WebView::LoadUrl() re-parse the URL before loading it.
|
||||
const wxURI uri(wxURI(url).BuildURI());
|
||||
CHECK(uri.GetScheme() == "file");
|
||||
CHECK(!uri.HasQuery());
|
||||
CHECK(!uri.HasFragment());
|
||||
CHECK(wxURI::Unescape(uri.GetPath()).utf8_string() == path);
|
||||
}
|
||||
|
||||
TEST_CASE("A file URL of a plain path is the path behind file://", "[FileUrl]")
|
||||
{
|
||||
const std::string path = "/opt/OrcaSlicer/resources/web/homepage/index.html";
|
||||
CHECK(file_url_from_path(boost::filesystem::path(path)) == "file://" + path);
|
||||
}
|
||||
|
||||
TEST_CASE("A query appended to a file URL stays separate from its path", "[FileUrl]")
|
||||
{
|
||||
const std::string path = "/opt/test#dir%20x/resources/web/guide/0/index.html";
|
||||
const wxURI uri(file_url_from_path(boost::filesystem::path(path)) + "?target=21&lang=de");
|
||||
CHECK(wxURI::Unescape(uri.GetPath()).utf8_string() == path);
|
||||
CHECK(uri.GetQuery() == "target=21&lang=de");
|
||||
CHECK(!uri.HasFragment());
|
||||
}
|
||||
#else
|
||||
TEST_CASE("A file URL of a Windows path has a drive letter and forward slashes", "[FileUrl]")
|
||||
{
|
||||
const auto [path, url] = GENERATE(table<std::wstring, std::string>({
|
||||
{ L"C:\\Program Files\\OrcaSlicer\\resources\\web\\homepage\\index.html",
|
||||
"file:///C:/Program%20Files/OrcaSlicer/resources/web/homepage/index.html" },
|
||||
{ L"D:\\#OneDrive\\OrcaSlicer\\resources\\web\\guide\\0\\index.html",
|
||||
"file:///D:/%23OneDrive/OrcaSlicer/resources/web/guide/0/index.html" },
|
||||
{ L"D:\\100%\\OrcaSlicer\\resources\\web\\homepage\\index.html",
|
||||
"file:///D:/100%25/OrcaSlicer/resources/web/homepage/index.html" },
|
||||
// Callers join the resources directory with a forward-slash relative path.
|
||||
{ L"D:\\#OneDrive\\OrcaSlicer\\resources/web/homepage/index.html",
|
||||
"file:///D:/%23OneDrive/OrcaSlicer/resources/web/homepage/index.html" },
|
||||
{ L"\\\\server\\share\\OrcaSlicer\\resources\\web\\homepage\\index.html",
|
||||
"file://server/share/OrcaSlicer/resources/web/homepage/index.html" },
|
||||
}));
|
||||
CHECK(file_url_from_path(boost::filesystem::path(path)).utf8_string() == url);
|
||||
}
|
||||
|
||||
TEST_CASE("A query appended to a Windows file URL stays separate from its path", "[FileUrl]")
|
||||
{
|
||||
const wxURI uri(file_url_from_path(boost::filesystem::path(L"D:\\#OneDrive\\OrcaSlicer\\resources\\web\\guide\\0\\index.html")) +
|
||||
"?target=21&lang=de");
|
||||
CHECK(wxURI::Unescape(uri.GetPath()) == "/D:/#OneDrive/OrcaSlicer/resources/web/guide/0/index.html");
|
||||
CHECK(uri.GetQuery() == "target=21&lang=de");
|
||||
CHECK(!uri.HasFragment());
|
||||
}
|
||||
#endif
|
||||
@@ -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");
|
||||
}
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <wx/uri.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace {
|
||||
@@ -212,33 +210,23 @@ TEST_CASE("A reloaded plugin page is recognised by its base URL, fragment aside"
|
||||
{
|
||||
using namespace Slic3r::GUI::web_hosting;
|
||||
|
||||
// A resources path holding a space, which the web view may report escaped differently.
|
||||
// A resources path holding a space, which the web view reports escaped.
|
||||
const Slic3r::ScopedResourcesDir resources("web content check");
|
||||
|
||||
// The swapped-in page, then after an in-page anchor and a reload.
|
||||
CHECK(is_content_url(content_base_url()));
|
||||
CHECK(is_content_url(content_base_url() + "#tab2"));
|
||||
const wxString unescaped = wxURI::Unescape(content_base_url());
|
||||
REQUIRE(unescaped != content_base_url());
|
||||
CHECK(is_content_url(unescaped));
|
||||
CHECK(is_content_url(unescaped + "#tab2"));
|
||||
wxString escaped = content_base_url();
|
||||
escaped.Replace(" ", "%20");
|
||||
REQUIRE(escaped != content_base_url());
|
||||
CHECK(is_content_url(escaped));
|
||||
CHECK(is_content_url(escaped + "#tab2"));
|
||||
// A page the plugin linked to keeps its own URL and must be left alone.
|
||||
CHECK_FALSE(is_content_url(content_base_url() + "guide.html"));
|
||||
CHECK_FALSE(is_content_url("https://example.com/"));
|
||||
CHECK_FALSE(is_content_url(""));
|
||||
}
|
||||
|
||||
TEST_CASE("A reloaded plugin page is recognised when the resources path holds a '#'", "[PluginHost]")
|
||||
{
|
||||
using namespace Slic3r::GUI::web_hosting;
|
||||
|
||||
const Slic3r::ScopedResourcesDir resources("web#content check");
|
||||
|
||||
CHECK(is_content_url(content_base_url()));
|
||||
CHECK(is_content_url(content_base_url() + "#tab2"));
|
||||
CHECK_FALSE(is_content_url(content_base_url() + "guide.html"));
|
||||
}
|
||||
|
||||
TEST_CASE("Plugin host API exposes model geometry and structure to Python", "[PluginHost][Python]")
|
||||
{
|
||||
using Catch::Matchers::WithinAbs;
|
||||
|
||||
Reference in New Issue
Block a user