Files
OrcaSlicer/src/slic3r/plugin/CloudPluginService.cpp
T
yw4z 0585836550 Fetch cloud plugin metadata faster (#16645)
When logged in to Orca Cloud, the plugin fetch at startup no longer refreshes the token a second time while start() is already refreshing it. It now waits for that refresh and checks the token again. The subscribed and owned plugin requests also run in parallel, so the fetch takes about half as long.
2026-10-19 15:56:52 -05:00

330 lines
11 KiB
C++

#include "CloudPluginService.hpp"
#include "OrcaCloudServiceAgent.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/Utils/Http.hpp"
#include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp>
#include <boost/nowide/fstream.hpp>
#include <cstddef>
#include <future>
#include <memory>
#include <ios>
#include <slic3r/plugin/PluginDescriptor.hpp>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
namespace Slic3r {
void CloudPluginService::set_cloud_agent(std::shared_ptr<OrcaCloudServiceAgent> agent) { m_orca_agent = std::move(agent); }
std::shared_ptr<OrcaCloudServiceAgent> CloudPluginService::get_cloud_agent() const { return m_orca_agent; }
bool CloudPluginService::can_fetch_cloud_plugins() const
{
if (!m_orca_agent) {
BOOST_LOG_TRIVIAL(warning) << "Orca service agent is null";
return false;
}
if (!m_orca_agent->is_user_login() || m_orca_agent->get_user_id().empty()) {
BOOST_LOG_TRIVIAL(info) << "User not logged in, no cloud directory";
return false;
}
return true;
}
bool CloudPluginService::fetch_manifests_into_descriptors(std::vector<PluginDescriptor>& descriptors,
std::vector<std::string>& not_found,
std::vector<std::string>& unauthorized) const
{
descriptors.clear();
not_found.clear();
unauthorized.clear();
if (m_orca_agent) {
// Refresh the token once, then run both requests in parallel.
m_orca_agent->ensure_token_fresh("fetch_plugin_manifests");
std::vector<PluginDescriptor> mine_descriptors;
auto mine = std::async(std::launch::async, [agent = m_orca_agent, &mine_descriptors] {
return agent->fetch_mine_manifests_into_descriptors(mine_descriptors);
});
int ret = m_orca_agent->fetch_subscribed_manifests_into_descriptors(descriptors, not_found, unauthorized);
const int mine_ret = mine.get();
if (ret != 0) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": Failed to get subscribed plugins.";
return false;
}
ret = mine_ret;
if (ret != 0) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": Failed to get owned plugins.";
return false;
}
descriptors.insert(descriptors.end(), mine_descriptors.begin(), mine_descriptors.end());
}
return true;
}
bool CloudPluginService::request_cloud_subscribe(const std::string& plugin_uuid, std::string& error) const
{
error.clear();
if (!m_orca_agent) {
error = "No cloud agent.";
return false;
}
if (plugin_uuid.empty()) {
error = _u8L("Cloud plugin key is missing UUID.");
return false;
}
if (m_orca_agent->subscribe_plugin(plugin_uuid) != 0) {
error = _u8L("Failed to subscribe to cloud plugin, see logs for more info.");
return false;
}
return true;
}
bool CloudPluginService::request_cloud_unsubscribe(const PluginDescriptor& plugin, std::string& error) const
{
if (!m_orca_agent) {
error = "No cloud agent.";
return false;
}
if (!plugin.is_cloud_plugin()) {
error = _u8L("Only cloud plugins can be unsubscribed.");
return false;
}
const std::string cloud_uuid = plugin.cloud_uuid();
if (cloud_uuid.empty()) {
error = _u8L("Cloud plugin key is missing UUID.");
return false;
}
int result = m_orca_agent->unsubscribe_plugins({cloud_uuid});
if (result != 0) {
error = _u8L("Failed to unsubscribe plugin, see logs for more info.");
return false;
}
return true;
}
bool CloudPluginService::download_cloud_plugin(PluginDescriptor& entry,
const std::string& requested_version,
CloudPluginDownload& download,
std::string& error) const
{
namespace fs = boost::filesystem;
error.clear();
download = {};
// Look up the cloud plugin metadata in the catalog.
std::string download_url;
if (!entry.is_cloud_plugin()) {
error = "Plugin is not a cloud plugin";
return false;
}
const std::string entry_uuid = entry.cloud_uuid();
if (!m_orca_agent) {
error = "Cloud service agent is null";
return false;
}
std::vector<PluginDownloadData> data;
std::vector<PluginDownloadNotFound> not_found;
std::vector<std::string> unauthorized;
int result = m_orca_agent->get_plugin_download_url(entry_uuid, requested_version, data, not_found, unauthorized);
if (result != 0) {
// TRN %1% is the numeric result code returned by the cloud service.
error = GUI::format(_u8L("Failed to fetch the plugin download URL (result %1%)."), result);
return false;
}
for (const std::string& plugin_uuid : unauthorized) {
if (plugin_uuid == entry_uuid) {
error = _u8L("You are not authorized to download this cloud plugin.");
return false;
}
}
for (const PluginDownloadNotFound& item : not_found) {
if (item.id == entry_uuid) {
if (item.reason == "requested os not found")
error = _u8L("No plugin package is available for this operating system.");
else if (item.reason == "requested version not found")
error = requested_version.empty() ? _u8L("The selected plugin version was not found.") :
GUI::format(_u8L("Plugin version %1% was not found."), requested_version);
else if (item.reason == "requested plugin not found")
error = _u8L("Cloud plugin was not found.");
else if (!item.reason.empty())
// TRN %1% is the reason reported by the cloud service, in English.
error = GUI::format(_u8L("Cloud plugin download was not found: %1%."), item.reason);
else
error = _u8L("Cloud plugin download was not found.");
return false;
}
}
for (const PluginDownloadData& item : data) {
if (item.plugin_id == entry_uuid) {
download_url = item.download_link;
break;
}
}
if (download_url.empty()) {
error = _u8L("No download URL is available for this plugin. Please check the logs for errors.");
return false;
}
// Download the plugin package.
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Downloading cloud plugin " << entry_uuid << " from " << download_url;
std::string body;
unsigned http_status = 0;
Http http = Http::get(download_url);
http.timeout_connect(30)
.timeout_max(300)
.tls_verify(true)
.on_complete([&body, &http_status](std::string response_body, unsigned status) {
body = std::move(response_body);
http_status = status;
})
.on_error([&error](std::string response_body, std::string err, unsigned status) {
error = std::move(err);
if (!response_body.empty())
error += " — " + response_body;
})
.perform_sync();
if (!error.empty()) {
error = GUI::format(_u8L("Failed to download plugin: %1%"), error);
return false;
}
if (http_status >= 400) {
error = GUI::format(_u8L("Plugin download failed with HTTP status %1%."), http_status);
return false;
}
if (body.empty()) {
error = _u8L("Plugin download returned empty data.");
return false;
}
// Detect file format from content: PK magic bytes = zip/wheel, otherwise .py.
const bool is_wheel = (body.size() >= 4 && body[0] == 'P' && body[1] == 'K' && body[2] == '\x03' && body[3] == '\x04');
const std::string ext = is_wheel ? ".whl" : ".py";
const fs::path tmp_path = fs::temp_directory_path() / (fs::unique_path("cloud_plugin-%%%%-%%%%-%%%%-%%%%%%%").string() + ext);
{
boost::nowide::ofstream file(tmp_path.string(), std::ios::binary | std::ios::trunc);
if (!file) {
error = _u8L("Failed to create temporary file for plugin download.");
return false;
}
file.write(body.data(), body.size());
if (!file) {
error = _u8L("Failed to write plugin data to temporary file.");
return false;
}
}
download.package_path = tmp_path;
if (entry.cloud.has_value())
entry.cloud->update_available = false;
return true;
}
bool CloudPluginService::fetch_plugin_changelog(const PluginDescriptor& descriptor,
std::vector<PluginChangelog>& changelog,
std::string& error) const
{
error.clear();
changelog.clear();
std::string plugin_uuid = descriptor.cloud_uuid();
std::vector<PluginDescriptor> descriptors{descriptor};
std::unordered_map<std::string, std::vector<PluginChangelog>> changelogs;
bool result = fetch_plugin_changelog(descriptors, changelogs, error);
bool found_changelog = changelogs.find(plugin_uuid) != changelogs.end();
if (changelogs.empty() || !found_changelog) {
if (!error.empty())
return false;
error = "Failed to fetch changelogs for plugin " + descriptor.cloud_uuid();
return false;
}
changelog = std::move(changelogs[plugin_uuid]);
return result;
}
bool CloudPluginService::fetch_plugin_changelog(const std::vector<PluginDescriptor>& descriptors,
std::unordered_map<std::string, std::vector<PluginChangelog>>& changelog,
std::string& error) const
{
error.clear();
changelog.clear();
if (!m_orca_agent) {
error = "Cloud service agent is null.";
return false;
}
size_t descriptor_count = descriptors.size();
if (descriptor_count <= 0) {
return true;
}
std::vector<std::string> uuids;
uuids.reserve(descriptor_count);
for (const PluginDescriptor& descriptor : descriptors) {
const std::string uuid = descriptor.cloud_uuid();
if (!uuid.empty())
uuids.push_back(uuid);
}
if (uuids.empty())
return true;
int result = m_orca_agent->fetch_plugin_changelogs(uuids, changelog);
if (result != 0) {
error = "Failed to fetch one or more plugin changelogs. result=" + std::to_string(result);
BOOST_LOG_TRIVIAL(warning) << error;
return false;
}
return true;
}
} // namespace Slic3r