mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-01 00:11:57 +08:00
921 lines
32 KiB
C++
921 lines
32 KiB
C++
#include "PresetArchiveDatabase.hpp"
|
|
|
|
#include "slic3r/Utils/Http.hpp"
|
|
#include "slic3r/Utils/ServiceConfig.hpp"
|
|
#include "slic3r/GUI/format.hpp"
|
|
#include "slic3r/GUI/GUI_App.hpp"
|
|
#include "slic3r/GUI/Plater.hpp"
|
|
#include "slic3r/GUI/UserAccount.hpp"
|
|
#include "libslic3r/Utils.hpp"
|
|
#include "libslic3r/AppConfig.hpp"
|
|
#include "libslic3r/miniz_extension.hpp"
|
|
|
|
#include <boost/log/trivial.hpp>
|
|
#include <boost/filesystem/fstream.hpp>
|
|
#include <boost/nowide/fstream.hpp> // IWYU pragma: keep
|
|
#include <boost/property_tree/ptree.hpp>
|
|
#include <boost/property_tree/json_parser.hpp>
|
|
#include <boost/uuid/uuid.hpp>
|
|
#include <boost/uuid/uuid_io.hpp>
|
|
#include <cctype>
|
|
#include <curl/curl.h>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
|
|
namespace pt = boost::property_tree;
|
|
namespace fs = boost::filesystem;
|
|
namespace Slic3r {
|
|
namespace GUI {
|
|
|
|
static const char* TMP_EXTENSION = ".download";
|
|
|
|
namespace {
|
|
bool unzip_repository(const fs::path& source_path, const fs::path& target_path)
|
|
{
|
|
mz_zip_archive archive;
|
|
mz_zip_zero_struct(&archive);
|
|
if (!open_zip_reader(&archive, source_path.string())) {
|
|
BOOST_LOG_TRIVIAL(error) << "Couldn't open zipped Archive source. " << source_path;
|
|
return false;
|
|
}
|
|
size_t num_files = mz_zip_reader_get_num_files(&archive);
|
|
|
|
for (size_t i = 0; i < num_files; ++i) {
|
|
mz_zip_archive_file_stat file_stat;
|
|
if (!mz_zip_reader_file_stat(&archive, i, &file_stat)) {
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to get file stat for file #" << i << " in the zip archive. Ending Unzipping.";
|
|
close_zip_reader(&archive);
|
|
return false;
|
|
}
|
|
fs::path extracted_path = target_path / file_stat.m_filename;
|
|
if (file_stat.m_is_directory) {
|
|
// Create directory if it doesn't exist
|
|
fs::create_directories(extracted_path);
|
|
continue;
|
|
}
|
|
// Create parent directory if it doesn't exist
|
|
fs::create_directories(extracted_path.parent_path());
|
|
// Extract file
|
|
if (!mz_zip_reader_extract_to_file(&archive, i, extracted_path.string().c_str(), 0)) {
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to extract file #" << i << " from the zip archive. Ending Unzipping.";
|
|
close_zip_reader(&archive);
|
|
return false;
|
|
}
|
|
}
|
|
close_zip_reader(&archive);
|
|
return true;
|
|
}
|
|
|
|
bool extract_repository_header(const pt::ptree& ptree, ArchiveRepository::RepositoryManifest& data)
|
|
{
|
|
// mandatory atributes
|
|
if (const auto name = ptree.get_optional<std::string>("name"); name){
|
|
data.name = *name;
|
|
} else {
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to find \"name\" parameter in source manifest. Source is invalid.";
|
|
return false;
|
|
}
|
|
if (const auto id = ptree.get_optional<std::string>("id"); id) {
|
|
data.id = *id;
|
|
}
|
|
else {
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to find \"id\" parameter in source manifest. Source is invalid.";
|
|
return false;
|
|
}
|
|
if (const auto url = ptree.get_optional<std::string>("url"); url) {
|
|
data.url = *url;
|
|
}
|
|
else {
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to find \"url\" parameter in source manifest. Source is invalid.";
|
|
return false;
|
|
}
|
|
// optional atributes
|
|
if (const auto index_url = ptree.get_optional<std::string>("index_url"); index_url) {
|
|
data.index_url = *index_url;
|
|
}
|
|
if (const auto description = ptree.get_optional<std::string>("description"); description) {
|
|
data.description = *description;
|
|
}
|
|
if (const auto visibility = ptree.get_optional<std::string>("visibility"); visibility) {
|
|
data.visibility = *visibility;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void delete_path_recursive(const fs::path& path)
|
|
{
|
|
try {
|
|
boost::system::error_code ec;
|
|
if (fs::exists(path, ec) && !ec) {
|
|
for (fs::directory_iterator it(path); it != fs::directory_iterator(); ++it) {
|
|
const fs::path subpath = it->path();
|
|
if (fs::is_directory(subpath)) {
|
|
delete_path_recursive(subpath);
|
|
} else {
|
|
fs::remove(subpath);
|
|
}
|
|
}
|
|
fs::remove(path);
|
|
}
|
|
}
|
|
catch (const std::exception& e) {
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to delete files at: " << path;
|
|
}
|
|
}
|
|
|
|
bool extract_local_archive_repository( ArchiveRepository::RepositoryManifest& manifest_data)
|
|
{
|
|
assert(!manifest_data.tmp_path.empty());
|
|
assert(!manifest_data.source_path.empty());
|
|
// Delete previous data before unzip.
|
|
// We have unique path in temp set for whole run of slicer and in it folder for each repo.
|
|
delete_path_recursive(manifest_data.tmp_path);
|
|
fs::create_directories(manifest_data.tmp_path);
|
|
// Unzip repository zip to unique path in temp directory.
|
|
if (!unzip_repository(manifest_data.source_path, manifest_data.tmp_path)) {
|
|
return false;
|
|
}
|
|
// Read the manifest file.
|
|
fs::path manifest_path = manifest_data.tmp_path / "manifest.json";
|
|
try
|
|
{
|
|
pt::ptree ptree;
|
|
pt::read_json(manifest_path.string(), ptree);
|
|
if (!extract_repository_header(ptree, manifest_data)) {
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to load source " << manifest_data.tmp_path;
|
|
return false;
|
|
}
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to read source manifest JSON " << manifest_path << ". reason: " << e.what();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void deserialize_string(const std::string& opt, std::vector<std::string>& result)
|
|
{
|
|
std::string val;
|
|
for (size_t i = 0; i < opt.length(); i++) {
|
|
if (std::isspace(opt[i])) {
|
|
continue;
|
|
}
|
|
if (opt[i] != ';') {
|
|
val += opt[i];
|
|
}
|
|
else {
|
|
result.emplace_back(std::move(val));
|
|
}
|
|
}
|
|
if (!val.empty()) {
|
|
result.emplace_back(std::move(val));
|
|
}
|
|
}
|
|
|
|
std::string escape_string(const std::string& unescaped)
|
|
{
|
|
std::string ret_val;
|
|
CURL* curl = curl_easy_init();
|
|
if (curl) {
|
|
char* decoded = curl_easy_escape(curl, unescaped.c_str(), unescaped.size());
|
|
if (decoded) {
|
|
ret_val = std::string(decoded);
|
|
curl_free(decoded);
|
|
}
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
return ret_val;
|
|
}
|
|
std::string escape_path_by_element(const std::string& path_string)
|
|
{
|
|
const boost::filesystem::path path(path_string);
|
|
std::string ret_val = escape_string(path.filename().string());
|
|
boost::filesystem::path parent(path.parent_path());
|
|
while (!parent.empty() && parent.string() != "/") // "/" check is for case "/file.gcode" was inserted. Then boost takes "/" as parent_path.
|
|
{
|
|
ret_val = escape_string(parent.filename().string()) + "/" + ret_val;
|
|
parent = parent.parent_path();
|
|
}
|
|
return ret_val;
|
|
}
|
|
|
|
void add_authorization_header(Http& http)
|
|
{
|
|
const std::string access_token = GUI::wxGetApp().plater()->get_user_account()->get_access_token();
|
|
if (!access_token.empty()) {
|
|
http.header("Authorization", "Bearer " + access_token);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
bool OnlineArchiveRepository::get_file_inner(const std::string& url, const fs::path& target_path) const
|
|
{
|
|
|
|
bool res = false;
|
|
fs::path tmp_path = target_path;
|
|
tmp_path += format(".%1%%2%", get_current_pid(), TMP_EXTENSION);
|
|
BOOST_LOG_TRIVIAL(info) << format("Get: `%1%`\n\t-> `%2%`\n\tvia tmp path `%3%`",
|
|
url,
|
|
target_path.string(),
|
|
tmp_path.string());
|
|
|
|
auto http = Http::get(url);
|
|
add_authorization_header(http);
|
|
http
|
|
.timeout_max(30)
|
|
.on_progress([](Http::Progress, bool& cancel) {
|
|
//if (cancel) { cancel = true; }
|
|
})
|
|
.on_error([&](std::string body, std::string error, unsigned http_status) {
|
|
BOOST_LOG_TRIVIAL(error) << format("Error getting: `%1%`: HTTP %2%, %3%",
|
|
url,
|
|
http_status,
|
|
body);
|
|
})
|
|
.on_complete([&](std::string body, unsigned /* http_status */) {
|
|
if (body.empty()) {
|
|
return;
|
|
}
|
|
fs::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc);
|
|
file.write(body.c_str(), body.size());
|
|
file.close();
|
|
fs::rename(tmp_path, target_path);
|
|
res = true;
|
|
})
|
|
.perform_sync();
|
|
|
|
return res;
|
|
}
|
|
|
|
bool OnlineArchiveRepository::get_archive(const fs::path& target_path) const
|
|
{
|
|
return get_file_inner(m_data.index_url.empty() ? m_data.url + "vendor_indices.zip" : m_data.index_url, target_path);
|
|
}
|
|
|
|
bool OnlineArchiveRepository::get_file(const std::string& source_subpath, const fs::path& target_path, const std::string& repository_id) const
|
|
{
|
|
if (repository_id != m_data.id) {
|
|
BOOST_LOG_TRIVIAL(error) << "Error getting file " << source_subpath << ". The repository_id was not matching.";
|
|
return false;
|
|
}
|
|
const std::string escaped_source_subpath = escape_path_by_element(source_subpath);
|
|
return get_file_inner(m_data.url + escaped_source_subpath, target_path);
|
|
}
|
|
|
|
bool OnlineArchiveRepository::get_ini_no_id(const std::string& source_subpath, const fs::path& target_path) const
|
|
{
|
|
const std::string escaped_source_subpath = escape_path_by_element(source_subpath);
|
|
return get_file_inner(m_data.url + escaped_source_subpath, target_path);
|
|
}
|
|
|
|
bool LocalArchiveRepository::get_file_inner(const fs::path& source_path, const fs::path& target_path) const
|
|
{
|
|
BOOST_LOG_TRIVIAL(debug) << format("Copying %1% to %2%", source_path, target_path);
|
|
std::string error_message;
|
|
CopyFileResult cfr = Slic3r::copy_file(source_path.string(), target_path.string(), error_message, false);
|
|
if (cfr != CopyFileResult::SUCCESS) {
|
|
BOOST_LOG_TRIVIAL(error) << "Copying of " << source_path << " to " << target_path << " has failed (" << cfr << "): " << error_message;
|
|
// remove target file, even if it was there before
|
|
boost::system::error_code ec;
|
|
if (fs::exists(target_path, ec) && !ec) {
|
|
ec.clear();
|
|
fs::remove(target_path, ec);
|
|
if (ec) {
|
|
BOOST_LOG_TRIVIAL(error) << format("Failed to delete file: %1%", ec.message());
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
// Permissions should be copied from the source file by copy_file(). We are not sure about the source
|
|
// permissions, let's rewrite them with 644.
|
|
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
|
|
fs::permissions(target_path, perms);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LocalArchiveRepository::get_file(const std::string& source_subpath, const fs::path& target_path, const std::string& repository_id) const
|
|
{
|
|
if (repository_id != m_data.id) {
|
|
BOOST_LOG_TRIVIAL(error) << "Error getting file " << source_subpath << ". The repository_id was not matching.";
|
|
return false;
|
|
}
|
|
return get_file_inner(m_data.tmp_path / source_subpath, target_path);
|
|
}
|
|
bool LocalArchiveRepository::get_ini_no_id(const std::string& source_subpath, const fs::path& target_path) const
|
|
{
|
|
return get_file_inner(m_data.tmp_path / source_subpath, target_path);
|
|
}
|
|
bool LocalArchiveRepository::get_archive(const fs::path& target_path) const
|
|
{
|
|
fs::path source_path = fs::path(m_data.tmp_path) / "vendor_indices.zip";
|
|
return get_file_inner(std::move(source_path), target_path);
|
|
}
|
|
|
|
void LocalArchiveRepository::do_extract()
|
|
{
|
|
RepositoryManifest new_manifest;
|
|
new_manifest.source_path = this->get_manifest().source_path;
|
|
new_manifest.tmp_path = this->get_manifest().tmp_path;
|
|
m_extracted = extract_local_archive_repository(new_manifest);
|
|
set_manifest(std::move(new_manifest));
|
|
}
|
|
|
|
//-------------------------------------PresetArchiveDatabase-------------------------------------------------------------------------------------------------------------------------
|
|
|
|
PresetArchiveDatabase::PresetArchiveDatabase(AppConfig* app_config, wxEvtHandler* evt_handler)
|
|
: p_evt_handler(evt_handler)
|
|
{
|
|
//
|
|
boost::system::error_code ec;
|
|
m_unq_tmp_path = fs::temp_directory_path() / fs::unique_path();
|
|
fs::create_directories(m_unq_tmp_path, ec);
|
|
assert(!ec);
|
|
|
|
load_app_manifest_json();
|
|
}
|
|
|
|
bool PresetArchiveDatabase::set_selected_repositories(const std::vector<std::string>& selected_uuids, std::string& msg)
|
|
{
|
|
// First re-extract locals, this will set is_extracted flag
|
|
extract_local_archives();
|
|
// Check if some uuids leads to the same id (online vs local conflict)
|
|
std::map<std::string, std::string> used_set;
|
|
for (const std::string& uuid : selected_uuids) {
|
|
std::string id;
|
|
std::string name;
|
|
for (const auto& archive : m_archive_repositories) {
|
|
if (archive->get_uuid() != uuid) {
|
|
continue;
|
|
}
|
|
id = archive->get_manifest().id;
|
|
name = archive->get_manifest().name;
|
|
if (!archive->is_extracted()) {
|
|
// non existent local repo since start selected
|
|
msg = GUI::format(
|
|
_L("Cannot select local source from path: %1%. It was not extracted."),
|
|
archive->get_manifest().source_path
|
|
);
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
assert(!id.empty());
|
|
if (auto it = used_set.find(id); it != used_set.end()) {
|
|
msg = GUI::format(_L("Cannot select two sources with the same id: %1% and %2%"), it->second, name);
|
|
return false;
|
|
}
|
|
used_set.emplace(id, name);
|
|
}
|
|
// deselect all first
|
|
for (auto& pair : m_selected_repositories_uuid) {
|
|
pair.second = false;
|
|
}
|
|
for (const std::string& uuid : selected_uuids) {
|
|
m_selected_repositories_uuid[uuid] = true;
|
|
}
|
|
save_app_manifest_json();
|
|
return true;
|
|
}
|
|
bool PresetArchiveDatabase::extract_archives_with_check(std::string &msg)
|
|
{
|
|
extract_local_archives();
|
|
for (const std::pair<std::string, bool>& pair : m_selected_repositories_uuid) {
|
|
if (!pair.second) {
|
|
continue;
|
|
}
|
|
const std::string uuid = pair.first;
|
|
auto compare_repo = [&uuid](const std::unique_ptr<ArchiveRepository> &repo) {
|
|
return repo->get_uuid() == uuid;
|
|
};
|
|
|
|
const auto& archives_it =std::find_if(m_archive_repositories.begin(), m_archive_repositories.end(), compare_repo);
|
|
assert(archives_it != m_archive_repositories.end());
|
|
if (!archives_it->get()->is_extracted()) {
|
|
// non existent local repo since start selected
|
|
msg += std::string(msg.empty() ? "" : "\n") + archives_it->get()->get_manifest().source_path.string();
|
|
}
|
|
}
|
|
return msg.empty();
|
|
}
|
|
void PresetArchiveDatabase::set_installed_printer_repositories(const std::vector<std::string> &used_ids)
|
|
{
|
|
// set all uuids as not having installed printer
|
|
m_has_installed_printer_repositories_uuid.clear();
|
|
for (const auto &archive : m_archive_repositories) {
|
|
m_has_installed_printer_repositories_uuid.emplace(archive->get_uuid(), false);
|
|
}
|
|
// set correct repos as having installed printer
|
|
for (const std::string &used_id : used_ids) {
|
|
// find archive with id and is used
|
|
std::vector<std::string> selected_uuid;
|
|
std::vector<std::string> unselected_uuid;
|
|
for (const auto &archive : m_archive_repositories) {
|
|
if (archive->get_manifest().id != used_id) {
|
|
continue;
|
|
}
|
|
const std::string uuid = archive->get_uuid();
|
|
if (m_selected_repositories_uuid[uuid]) {
|
|
selected_uuid.emplace_back(uuid);
|
|
} else {
|
|
unselected_uuid.emplace_back(uuid);
|
|
}
|
|
}
|
|
|
|
if (selected_uuid.empty() && unselected_uuid.empty()) {
|
|
// there is id in used_ids that is not in m_archive_repositories - BAD
|
|
assert(true);
|
|
continue;
|
|
} else if (selected_uuid.size() == 1){
|
|
// regular case
|
|
m_has_installed_printer_repositories_uuid[selected_uuid.front()] = true;
|
|
} else if (selected_uuid.size() > 1) {
|
|
// this should not happen, only one repo of same id should be selected (online / local conflict)
|
|
assert(true);
|
|
// select first one to solve the conflict
|
|
m_has_installed_printer_repositories_uuid[selected_uuid.front()] = true;
|
|
// unselect the rest
|
|
for (size_t i = 1; i < selected_uuid.size(); i++) {
|
|
m_selected_repositories_uuid[selected_uuid[i]] = false;
|
|
}
|
|
} else if (selected_uuid.empty()) {
|
|
// This is a rare case, where there are no selected repos with matching id but id has installed printers
|
|
// Repro: install printer, unselect repo in the next run of wizard, next, cancel wizard, run wizard again and press finish.
|
|
// Solution: Select the first unselected
|
|
m_has_installed_printer_repositories_uuid[unselected_uuid.front()] = true;
|
|
m_selected_repositories_uuid[unselected_uuid.front()] = true;
|
|
}
|
|
|
|
}
|
|
save_app_manifest_json();
|
|
}
|
|
|
|
std::string PresetArchiveDatabase::add_local_archive(const boost::filesystem::path path, std::string& msg)
|
|
{
|
|
if (auto it = std::find_if(m_archive_repositories.begin(), m_archive_repositories.end(), [path](const std::unique_ptr<ArchiveRepository>& ptr) {
|
|
return ptr->get_manifest().source_path == path;
|
|
}); it != m_archive_repositories.end())
|
|
{
|
|
msg = GUI::format(_L("Failed to add local archive %1%. Path already used."), path);
|
|
BOOST_LOG_TRIVIAL(error) << msg;
|
|
return std::string();
|
|
}
|
|
std::string uuid = get_next_uuid();
|
|
ArchiveRepository::RepositoryManifest header_data;
|
|
header_data.source_path = path;
|
|
header_data.tmp_path = m_unq_tmp_path / uuid;
|
|
if (!extract_local_archive_repository(header_data)) {
|
|
msg = GUI::format(_L("Failed to extract local archive %1%."), path);
|
|
BOOST_LOG_TRIVIAL(error) << msg;
|
|
return std::string();
|
|
}
|
|
// Solve if it can be set true first.
|
|
m_selected_repositories_uuid[uuid] = false;
|
|
m_has_installed_printer_repositories_uuid[uuid] = false;
|
|
m_archive_repositories.emplace_back(std::make_unique<LocalArchiveRepository>(uuid, std::move(header_data), true));
|
|
|
|
save_app_manifest_json();
|
|
return uuid;
|
|
}
|
|
void PresetArchiveDatabase::remove_local_archive(const std::string& uuid)
|
|
{
|
|
auto compare_repo = [uuid](const std::unique_ptr<ArchiveRepository>& repo) {
|
|
return repo->get_uuid() == uuid;
|
|
};
|
|
|
|
auto archives_it = std::find_if(m_archive_repositories.begin(), m_archive_repositories.end(), compare_repo);
|
|
assert(archives_it != m_archive_repositories.end());
|
|
std::string removed_uuid = archives_it->get()->get_uuid();
|
|
m_archive_repositories.erase(archives_it);
|
|
|
|
auto used_it = m_selected_repositories_uuid.find(removed_uuid);
|
|
assert(used_it != m_selected_repositories_uuid.end());
|
|
m_selected_repositories_uuid.erase(used_it);
|
|
|
|
auto inst_it = m_has_installed_printer_repositories_uuid.find(removed_uuid);
|
|
assert(inst_it != m_has_installed_printer_repositories_uuid.end());
|
|
m_has_installed_printer_repositories_uuid.erase(inst_it);
|
|
|
|
save_app_manifest_json();
|
|
}
|
|
|
|
void PresetArchiveDatabase::extract_local_archives()
|
|
{
|
|
for (auto &archive : m_archive_repositories) {
|
|
archive->do_extract();
|
|
}
|
|
}
|
|
|
|
void PresetArchiveDatabase::load_app_manifest_json()
|
|
{
|
|
const fs::path path = get_stored_manifest_path();
|
|
boost::system::error_code ec;
|
|
if (!fs::exists(path, ec) || ec) {
|
|
copy_initial_manifest();
|
|
}
|
|
boost::nowide::ifstream file(path.string());
|
|
std::string data;
|
|
if (file.is_open()) {
|
|
std::string line;
|
|
while (getline(file, line)) {
|
|
data += line;
|
|
}
|
|
file.close();
|
|
}
|
|
else {
|
|
assert(true);
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to read Archive Source Manifest at " << path;
|
|
}
|
|
if (data.empty()) {
|
|
return;
|
|
}
|
|
|
|
m_archive_repositories.clear();
|
|
m_selected_repositories_uuid.clear();
|
|
m_has_installed_printer_repositories_uuid.clear();
|
|
try
|
|
{
|
|
std::stringstream ss(data);
|
|
pt::ptree ptree;
|
|
pt::read_json(ss, ptree);
|
|
for (const auto& subtree : ptree) {
|
|
// if has tmp_path its local repo else its online repo (manifest is written in its zip, not in our json)
|
|
if (const auto source_path = subtree.second.get_optional<std::string>("source_path"); source_path) {
|
|
ArchiveRepository::RepositoryManifest manifest;
|
|
std::string uuid = get_next_uuid();
|
|
manifest.source_path = boost::filesystem::path(*source_path);
|
|
manifest.tmp_path = m_unq_tmp_path / uuid;
|
|
bool extracted = extract_local_archive_repository(manifest);
|
|
// "selected" flag
|
|
if(const auto used = subtree.second.get_optional<bool>("selected"); used) {
|
|
m_selected_repositories_uuid[uuid] = extracted && *used;
|
|
} else {
|
|
assert(true);
|
|
m_selected_repositories_uuid[uuid] = extracted;
|
|
}
|
|
// "has_installed_printers" flag
|
|
if (const auto used = subtree.second.get_optional<bool>("has_installed_printers"); used) {
|
|
m_has_installed_printer_repositories_uuid[uuid] = extracted && *used;
|
|
} else {
|
|
assert(true);
|
|
m_has_installed_printer_repositories_uuid[uuid] = false;
|
|
}
|
|
m_archive_repositories.emplace_back(std::make_unique<LocalArchiveRepository>(std::move(uuid), std::move(manifest), extracted));
|
|
|
|
continue;
|
|
}
|
|
// online repo
|
|
ArchiveRepository::RepositoryManifest manifest;
|
|
std::string uuid = get_next_uuid();
|
|
if (!extract_repository_header(subtree.second, manifest)) {
|
|
assert(true);
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to read one of source headers.";
|
|
continue;
|
|
}
|
|
// "selected" flag
|
|
if (const auto used = subtree.second.get_optional<bool>("selected"); used) {
|
|
m_selected_repositories_uuid[uuid] = *used;
|
|
} else {
|
|
assert(true);
|
|
m_selected_repositories_uuid[uuid] = true;
|
|
}
|
|
// "has_installed_printers" flag
|
|
if (const auto used = subtree.second.get_optional<bool>("has_installed_printers"); used) {
|
|
m_has_installed_printer_repositories_uuid[uuid] = *used;
|
|
} else {
|
|
assert(true);
|
|
m_has_installed_printer_repositories_uuid[uuid] = false;
|
|
}
|
|
m_archive_repositories.emplace_back(std::make_unique<OnlineArchiveRepository>(std::move(uuid), std::move(manifest)));
|
|
}
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to read archives JSON. " << e.what();
|
|
}
|
|
}
|
|
|
|
void PresetArchiveDatabase::copy_initial_manifest()
|
|
{
|
|
const fs::path target_path = get_stored_manifest_path();
|
|
const fs::path source_path = fs::path(resources_dir()) / "profiles" / "ArchiveRepositoryManifest.json";
|
|
assert(fs::exists(source_path));
|
|
std::string error_message;
|
|
CopyFileResult cfr = Slic3r::copy_file(source_path.string(), target_path.string(), error_message, false);
|
|
assert(cfr == CopyFileResult::SUCCESS);
|
|
if (cfr != CopyFileResult::SUCCESS) {
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to copy ArchiveRepositoryManifest.json from resources.";
|
|
return;
|
|
}
|
|
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
|
|
fs::permissions(target_path, perms);
|
|
}
|
|
|
|
void PresetArchiveDatabase::save_app_manifest_json() const
|
|
{
|
|
/*
|
|
[{
|
|
"name": "Production",
|
|
"description": "Production repository",
|
|
"visibility": null,
|
|
"id": "prod",
|
|
"url": "http://10.24.3.3:8001/v1/repos/prod",
|
|
"index_url": "http://10.24.3.3:8001/v1/repos/prod/vendor_indices.zip"
|
|
"selected": 1
|
|
"has_installed_printers": 1
|
|
}, {
|
|
"name": "Development",
|
|
"description": "Production repository",
|
|
"visibility": "developers only",
|
|
"id": "dev",
|
|
"url": "http://10.24.3.3:8001/v1/repos/dev",
|
|
"index_url": "http://10.24.3.3:8001/v1/repos/dev/vendor_indices.zip"
|
|
"selected": 0
|
|
"has_installed_printers": 0
|
|
}]
|
|
*/
|
|
std::string data = "[";
|
|
|
|
for (const auto& archive : m_archive_repositories) {
|
|
// local writes only source_path and "selected". Rest is read from zip on source_path.
|
|
if (!archive->get_manifest().tmp_path.empty()) {
|
|
const ArchiveRepository::RepositoryManifest& man = archive->get_manifest();
|
|
std::string line = archive == m_archive_repositories.front() ? std::string() : ",";
|
|
line += GUI::format(
|
|
"{"
|
|
"\"source_path\": \"%1%\","
|
|
"\"selected\": %2%,"
|
|
"\"has_installed_printers\": %3%"
|
|
"}",
|
|
man.source_path.generic_string()
|
|
, is_selected(archive->get_uuid()) ? "1" : "0"
|
|
, has_installed_printers(archive->get_uuid()) ? "1" : "0"
|
|
);
|
|
data += line;
|
|
continue;
|
|
}
|
|
// online repo writes whole manifest - in case of offline run, this info is load from here
|
|
const ArchiveRepository::RepositoryManifest& man = archive->get_manifest();
|
|
std::string line = archive == m_archive_repositories.front() ? std::string() : ",";
|
|
line += GUI::format(
|
|
"{\"name\": \"%1%\","
|
|
"\"description\": \"%2%\","
|
|
"\"visibility\": \"%3%\","
|
|
"\"id\": \"%4%\","
|
|
"\"url\": \"%5%\","
|
|
"\"index_url\": \"%6%\","
|
|
"\"selected\": %7%,"
|
|
"\"has_installed_printers\": %8%"
|
|
"}"
|
|
, man.name, man.description
|
|
, man. visibility
|
|
, man.id
|
|
, man.url
|
|
, man.index_url
|
|
, is_selected(archive->get_uuid()) ? "1" : "0"
|
|
, has_installed_printers(archive->get_uuid()) ? "1" : "0"
|
|
);
|
|
data += line;
|
|
}
|
|
data += "]";
|
|
|
|
std::string path = get_stored_manifest_path().string();
|
|
boost::nowide::ofstream file(path);
|
|
if (file.is_open()) {
|
|
file << data;
|
|
file.close();
|
|
} else {
|
|
assert(true);
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to write Archive Repository Manifest to " << path;
|
|
}
|
|
}
|
|
|
|
fs::path PresetArchiveDatabase::get_stored_manifest_path() const
|
|
{
|
|
return (boost::filesystem::path(Slic3r::data_dir()) / "ArchiveRepositoryManifest.json").make_preferred();
|
|
}
|
|
|
|
bool PresetArchiveDatabase::is_selected(const std::string& uuid) const
|
|
{
|
|
auto search = m_selected_repositories_uuid.find(uuid);
|
|
assert(search != m_selected_repositories_uuid.end());
|
|
return search->second;
|
|
}
|
|
bool PresetArchiveDatabase::has_installed_printers(const std::string &uuid) const
|
|
{
|
|
auto search = m_has_installed_printer_repositories_uuid.find(uuid);
|
|
assert(search != m_has_installed_printer_repositories_uuid.end());
|
|
return search->second;
|
|
}
|
|
void PresetArchiveDatabase::clear_online_repos()
|
|
{
|
|
auto it = m_archive_repositories.begin();
|
|
while (it != m_archive_repositories.end()) {
|
|
// Do not clean repos with local path (local repo).
|
|
if ((*it)->get_manifest().tmp_path.empty()) {
|
|
it = m_archive_repositories.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PresetArchiveDatabase::read_server_manifest(const std::string& json_body)
|
|
{
|
|
pt::ptree ptree;
|
|
try
|
|
{
|
|
std::stringstream ss(json_body);
|
|
pt::read_json(ss, ptree);
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to read archives JSON. " << e.what();
|
|
|
|
}
|
|
// Online repo manifests are in json_body. We already have read local manifest and online manifest from last run.
|
|
// Keep the local ones and replace the online ones but keep uuid for same id so the selected map is correct.
|
|
// Solution: Create id - uuid translate table for online repos.
|
|
std::map<std::string, std::string> id_to_uuid;
|
|
for (const auto& repo_ptr : m_archive_repositories) {
|
|
if (repo_ptr->get_manifest().source_path.empty()){
|
|
id_to_uuid[repo_ptr->get_manifest().id] = repo_ptr->get_uuid();
|
|
}
|
|
}
|
|
|
|
// Make a stash of secret repos that are online and has installed printers.
|
|
// If some of these will be missing afer reading the json tree, it needs to be added back to main population.
|
|
PrivateArchiveRepositoryVector secret_online_used_repos_cache;
|
|
for (const auto &repo_ptr : m_archive_repositories) {
|
|
if (repo_ptr->get_manifest().visibility.empty() || !repo_ptr->get_manifest().tmp_path.empty()) {
|
|
continue;
|
|
}
|
|
const auto &it = m_has_installed_printer_repositories_uuid.find(repo_ptr->get_uuid());
|
|
assert(it != m_has_installed_printer_repositories_uuid.end());
|
|
if (it->second) {
|
|
ArchiveRepository::RepositoryManifest manifest(repo_ptr->get_manifest());
|
|
secret_online_used_repos_cache.emplace_back(std::make_unique<OnlineArchiveRepository>(repo_ptr->get_uuid(), std::move(manifest)));
|
|
}
|
|
}
|
|
|
|
clear_online_repos();
|
|
|
|
for (const auto& subtree : ptree) {
|
|
ArchiveRepository::RepositoryManifest manifest;
|
|
if (!extract_repository_header(subtree.second, manifest)) {
|
|
assert(true);
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to read one of repository headers.";
|
|
continue;
|
|
}
|
|
auto id_it = id_to_uuid.find(manifest.id);
|
|
std::string uuid = (id_it == id_to_uuid.end() ? get_next_uuid() : id_it->second);
|
|
// Set default selected value to true - its a never before seen repository
|
|
if (auto search = m_selected_repositories_uuid.find(uuid); search == m_selected_repositories_uuid.end()) {
|
|
m_selected_repositories_uuid[uuid] = true;
|
|
}
|
|
// Set default "has installed printers" value to false - its a never before seen repository
|
|
if (auto search = m_has_installed_printer_repositories_uuid.find(uuid);
|
|
search == m_has_installed_printer_repositories_uuid.end()) {
|
|
m_has_installed_printer_repositories_uuid[uuid] = false;
|
|
}
|
|
m_archive_repositories.emplace_back(std::make_unique<OnlineArchiveRepository>(uuid, std::move(manifest)));
|
|
}
|
|
|
|
// return missing secret online repos with installed printers to the vector
|
|
for (const auto &repo_ptr : secret_online_used_repos_cache) {
|
|
std::string uuid = repo_ptr->get_uuid();
|
|
if (std::find_if(
|
|
m_archive_repositories.begin(), m_archive_repositories.end(),
|
|
[uuid](const std::unique_ptr<ArchiveRepository> &ptr) {
|
|
return ptr->get_uuid() == uuid;
|
|
}
|
|
) == m_archive_repositories.end())
|
|
{
|
|
ArchiveRepository::RepositoryManifest manifest(repo_ptr->get_manifest());
|
|
m_archive_repositories.emplace_back(std::make_unique<OnlineArchiveRepository>(repo_ptr->get_uuid(), std::move(manifest)));
|
|
}
|
|
}
|
|
|
|
consolidate_uuid_maps();
|
|
save_app_manifest_json();
|
|
}
|
|
|
|
SharedArchiveRepositoryVector PresetArchiveDatabase::get_all_archive_repositories() const
|
|
{
|
|
SharedArchiveRepositoryVector result;
|
|
result.reserve(m_archive_repositories.size());
|
|
for (const auto &repo_ptr : m_archive_repositories)
|
|
{
|
|
result.emplace_back(repo_ptr.get());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
SharedArchiveRepositoryVector PresetArchiveDatabase::get_selected_archive_repositories() const
|
|
{
|
|
SharedArchiveRepositoryVector result;
|
|
result.reserve(m_archive_repositories.size());
|
|
for (const auto &repo_ptr : m_archive_repositories)
|
|
{
|
|
auto it = m_selected_repositories_uuid.find(repo_ptr->get_uuid());
|
|
assert(it != m_selected_repositories_uuid.end());
|
|
if (it->second) {
|
|
result.emplace_back(repo_ptr.get());
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool PresetArchiveDatabase::is_selected_repository_by_uuid(const std::string& uuid) const
|
|
{
|
|
auto selected_it = m_selected_repositories_uuid.find(uuid);
|
|
assert(selected_it != m_selected_repositories_uuid.end());
|
|
return selected_it->second;
|
|
}
|
|
bool PresetArchiveDatabase::is_selected_repository_by_id(const std::string& repo_id) const
|
|
{
|
|
assert(!repo_id.empty());
|
|
for (const auto& repo_ptr : m_archive_repositories) {
|
|
if (repo_ptr->get_manifest().id == repo_id) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
void PresetArchiveDatabase::consolidate_uuid_maps()
|
|
{
|
|
//std::vector<std::unique_ptr<ArchiveRepository>> m_archive_repositories;
|
|
//std::map<std::string, bool> m_selected_repositories_uuid;
|
|
auto selected_it = m_selected_repositories_uuid.begin();
|
|
while (selected_it != m_selected_repositories_uuid.end()) {
|
|
bool found = false;
|
|
for (const auto& repo_ptr : m_archive_repositories) {
|
|
if (repo_ptr->get_uuid() == selected_it->first) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
selected_it = m_selected_repositories_uuid.erase(selected_it);
|
|
} else {
|
|
++selected_it;
|
|
}
|
|
}
|
|
// Do the same for m_has_installed_printer_repositories_uuid
|
|
auto installed_it = m_has_installed_printer_repositories_uuid.begin();
|
|
while (installed_it != m_has_installed_printer_repositories_uuid.end()) {
|
|
bool found = false;
|
|
for (const auto &repo_ptr : m_archive_repositories) {
|
|
if (repo_ptr->get_uuid() == installed_it->first) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
installed_it = m_has_installed_printer_repositories_uuid.erase(installed_it);
|
|
} else {
|
|
++installed_it;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string PresetArchiveDatabase::get_next_uuid()
|
|
{
|
|
boost::uuids::uuid uuid = m_uuid_generator();
|
|
return boost::uuids::to_string(uuid);
|
|
}
|
|
|
|
namespace {
|
|
bool sync_inner(std::string& manifest)
|
|
{
|
|
bool ret = false;
|
|
std::string url = Utils::ServiceConfig::instance().preset_repo_repos_url();
|
|
auto http = Http::get(std::move(url));
|
|
add_authorization_header(http);
|
|
http
|
|
.timeout_max(30)
|
|
.on_error([&](std::string body, std::string error, unsigned http_status) {
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to get online archive source manifests: "<< body << " ; " << error << " ; " << http_status;
|
|
ret = false;
|
|
})
|
|
.on_complete([&](std::string body, unsigned /* http_status */) {
|
|
manifest = body;
|
|
ret = true;
|
|
})
|
|
.perform_sync();
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
void PresetArchiveDatabase::sync_blocking()
|
|
{
|
|
std::string manifest;
|
|
if (!sync_inner(manifest))
|
|
return;
|
|
read_server_manifest(std::move(manifest));
|
|
}
|
|
|
|
}} // Slic3r::GUI
|