Preset Archive Database

testing version of multiple archives with indicies

WIP Archive db

Getting archives from web mockup

WIP paste archives to updater

Preset Updater sync_config with Online Archive Repository

UserAccount -> PresetArchiveDatabase -> PresetUpdater dataflow

LocalArchiveRepository get_file by copying file

Local Archive Repository

fix of thread save multiple call of preset_updater sync

Testing JSON string with repos

WIP offline archive repo unzip before use

Local repository unzipping to temp. get_file() refactored.

todo: checking and writing repository ids correctly.

Refactoring of loading data for archive repository

Comments

WIP download repo manifest

Preset Archive Database sync in thread

delete testing code

escape subpath before download

post rebase fix

resources getting fix

vendor profile id -> name fix
This commit is contained in:
David Kocik 2024-04-11 16:08:16 +02:00
parent f889bf470c
commit ece5207783
14 changed files with 795 additions and 158 deletions

View File

@ -156,6 +156,11 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem
res.templates_profile = templates_profile->second.data() == "1";
}
const auto repo_id = vendor_section.find("repo_id");
if (repo_id != vendor_section.not_found()) {
res.repo_id = repo_id->second.data();
}
if (! load_all) {
return res;
}

View File

@ -39,6 +39,7 @@ public:
Semver config_version;
std::string config_update_url;
std::string changelog_url;
std::string repo_id;
bool templates_profile { false };
struct PrinterVariant {

View File

@ -315,6 +315,8 @@ set(SLIC3R_GUI_SOURCES
GUI/DownloaderFileGet.hpp
GUI/LoginDialog.cpp
GUI/LoginDialog.hpp
GUI/PresetArchiveDatabase.cpp
GUI/PresetArchiveDatabase.hpp
Utils/AppUpdater.cpp
Utils/AppUpdater.hpp
Utils/Http.cpp

View File

@ -102,6 +102,7 @@
#include "UserAccount.hpp"
#include "WebViewDialog.hpp"
#include "LoginDialog.hpp"
#include "PresetArchiveDatabase.hpp"
#include "BitmapCache.hpp"
//#include "Notebook.hpp"
@ -833,8 +834,8 @@ void GUI_App::post_init()
#endif
CallAfter([this] {
// preset_updater->sync downloads profile updates on background so it must begin after config wizard finished.
// its call was moved to start_preset_updater method
bool cw_showed = this->config_wizard_startup();
this->preset_updater->sync(preset_bundle, this);
if (! cw_showed) {
// The CallAfter is needed as well, without it, GL extensions did not show.
// Also, we only want to show this when the wizard does not, so the new user
@ -3127,19 +3128,29 @@ bool GUI_App::may_switch_to_SLA_preset(const wxString& caption)
bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page)
{
wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
// Cancel sync before starting wizard to prevent two downloads at same time.
preset_updater->cancel_sync();
// Show login dialog before wizard.
#if 0
if (!plater()->get_user_account()->is_logged()) {
bool user_was_logged = plater()->get_user_account()->is_logged();
if (!user_was_logged) {
m_login_dialog = std::make_unique<LoginDialog>(mainframe, plater()->get_user_account());
m_login_dialog->ShowModal();
mainframe->RemoveChild(m_login_dialog.get());
m_login_dialog->Destroy();
// Destructor does not call Destroy
// Destructor does not call Destroy.
m_login_dialog.reset();
}
// Update archive db if login status changed, otherwise we expect to have archive db on date.
if (user_was_logged != plater()->get_user_account()->is_logged()) {
plater()->get_preset_archive_database()->sync_blocking();
}
#endif // 0
if (reason == ConfigWizard::RR_USER) {
// Cancel sync before starting wizard to prevent two downloads at same time
preset_updater->cancel_sync();
// Do blocking sync on every start of wizard, so user is always offered recent profiles.
preset_updater->sync_blocking(preset_bundle, this, plater()->get_preset_archive_database()->get_archives());
// Offer update installation (of already installed profiles) only when run by user.
if (reason == ConfigWizard::RR_USER) {
preset_updater->update_index_db();
if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED)
return false;
@ -3843,5 +3854,14 @@ void GUI_App::show_printer_webview_tab()
mainframe->show_printer_webview_tab(preset_bundle->physical_printers.get_selected_printer_config());
}
void GUI_App::start_preset_updater(bool forced)
{
if (m_started_preset_updater && !forced) {
return;
}
this->preset_updater->cancel_sync();
this->preset_updater->sync(preset_bundle, this, plater()->get_preset_archive_database()->get_archives());
m_started_preset_updater = true;
}
} // GUI
} //Slic3r

View File

@ -60,7 +60,7 @@ class Downloader;
struct GUI_InitParams;
class GalleryDialog;
class LoginDialog;
class PresetArchiveDatabase;
enum FileType
{
@ -170,14 +170,14 @@ private:
OpenGLManager m_opengl_mgr;
std::unique_ptr<RemovableDriveManager> m_removable_drive_manager;
std::unique_ptr<ImGuiWrapper> m_imgui;
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
std::unique_ptr <OtherInstanceMessageHandler> m_other_instance_message_handler;
std::unique_ptr <AppUpdater> m_app_updater;
std::unique_ptr <wxSingleInstanceChecker> m_single_instance_checker;
std::unique_ptr <Downloader> m_downloader;
std::unique_ptr<RemovableDriveManager> m_removable_drive_manager;
std::unique_ptr<ImGuiWrapper> m_imgui;
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
std::unique_ptr<OtherInstanceMessageHandler> m_other_instance_message_handler;
std::unique_ptr<AppUpdater> m_app_updater;
std::unique_ptr<wxSingleInstanceChecker> m_single_instance_checker;
std::unique_ptr<Downloader> m_downloader;
std::string m_instance_hash_string;
size_t m_instance_hash_int;
@ -427,6 +427,8 @@ public:
void request_open_project(std::string project_id) {}
void request_remove_project(std::string project_id) {}
void start_preset_updater(bool forced);
private:
bool on_init_inner();
void init_app_config();
@ -450,6 +452,7 @@ private:
bool m_wifi_config_dialog_shown { false };
bool m_wifi_config_dialog_was_declined { false };
bool m_started_preset_updater { false };
// change to vector of items when adding more items that require update
//wxMenuItem* m_login_config_menu_item { nullptr };
std::map< ConfigMenuIDs, wxMenuItem*> m_config_menu_updatable_items;

View File

@ -124,6 +124,7 @@
#include "UserAccount.hpp"
#include "DesktopIntegrationDialog.hpp"
#include "WebViewDialog.hpp"
#include "PresetArchiveDatabase.hpp"
#ifdef __APPLE__
#include "Gizmos/GLGizmosManager.hpp"
@ -268,6 +269,7 @@ struct Plater::priv
Preview *preview;
std::unique_ptr<NotificationManager> notification_manager;
std::unique_ptr<UserAccount> user_account;
std::unique_ptr<PresetArchiveDatabase> preset_archive_database;
ProjectDirtyStateManager dirty_state;
@ -615,6 +617,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
, sidebar(new Sidebar(q))
, notification_manager(std::make_unique<NotificationManager>(q))
, user_account(std::make_unique<UserAccount>(q, wxGetApp().app_config, wxGetApp().get_instance_hash_string()))
, preset_archive_database(std::make_unique<PresetArchiveDatabase>(wxGetApp().app_config, q))
, m_worker{q, std::make_unique<NotificationProgressIndicator>(notification_manager.get()), "ui_worker"}
, m_sla_import_dlg{new SLAImportDialog{q}}
, delayed_scene_refresh(false)
@ -891,11 +894,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
this->q->Bind(EVT_UA_ID_USER_SUCCESS, [this](UserAccountSuccessEvent& evt) {
std::string username;
if (user_account->on_user_id_success(evt.data, username)) {
// login notification
std::string text = format(_u8L("Logged to Prusa Account as %1%."), username);
// login notification
this->notification_manager->close_notification_of_type(NotificationType::UserAccountID);
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text);
// show connect tab
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text);
this->main_frame->add_connect_webview_tab();
// Update User name in TopBar
this->main_frame->refresh_account_menu();
@ -903,6 +906,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
wxGetApp().update_login_dialog();
#endif // 0
this->show_action_buttons(this->ready_to_slice);
preset_archive_database->set_token("ABCD");
preset_archive_database->sync();
} else {
// data were corrupt and username was not retrieved
// procced as if EVT_UA_RESET was recieved
@ -928,6 +933,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
this->main_frame->refresh_account_menu(true);
// Update sidebar printer status
sidebar->update_printer_presets_combobox();
preset_archive_database->set_token({});
preset_archive_database->sync();
});
this->q->Bind(EVT_UA_FAIL, [this](UserAccountFailEvent& evt) {
BOOST_LOG_TRIVIAL(error) << "Failed communication with Prusa Account: " << evt.data;
@ -986,6 +993,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
});
}
this->q->Bind(EVT_PRESET_ARCHIVE_DATABASE_SYNC_DONE, [this](Event<ArchiveRepositorySyncData>& evt) {
preset_archive_database->set_archives(evt.data.json);
wxGetApp().start_preset_updater(evt.data.force_updater);
});
wxGetApp().other_instance_message_handler()->init(this->q);
// collapse sidebar according to saved value
@ -6805,6 +6817,16 @@ const NotificationManager * Plater::get_notification_manager() const
return p->notification_manager.get();
}
PresetArchiveDatabase* Plater::get_preset_archive_database()
{
return p->preset_archive_database.get();
}
const PresetArchiveDatabase* Plater::get_preset_archive_database() const
{
return p->preset_archive_database.get();
}
UserAccount* Plater::get_user_account()
{
return p->user_account.get();

View File

@ -63,6 +63,7 @@ class NotificationManager;
struct Camera;
class GLToolbar;
class UserAccount;
class PresetArchiveDatabase;
class Plater: public wxPanel
{
@ -355,6 +356,9 @@ public:
NotificationManager* get_notification_manager();
const NotificationManager* get_notification_manager() const;
PresetArchiveDatabase* get_preset_archive_database();
const PresetArchiveDatabase* get_preset_archive_database() const;
UserAccount* get_user_account();
const UserAccount* get_user_account() const;

View File

@ -0,0 +1,403 @@
#include "PresetArchiveDatabase.hpp"
#include "slic3r/Utils/Http.hpp"
#include "slic3r/GUI/format.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/miniz_extension.hpp"
#include <boost/log/trivial.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <cctype>
#include <curl/curl.h>
namespace pt = boost::property_tree;
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
wxDEFINE_EVENT(EVT_PRESET_ARCHIVE_DATABASE_SYNC_DONE, Event<ArchiveRepositorySyncData>);
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 Repository. " << 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.";
mz_zip_reader_end(&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.";
mz_zip_reader_end(&archive);
return false;
}
}
mz_zip_reader_end(&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 repository header. Repository 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 repository header. Repository 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 repository header. Repository 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;
data.m_secret = data.visibility.empty();
}
return true;
}
void delete_path_recursive(const fs::path& path)
{
try {
if (fs::exists(path)) {
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(const fs::path& zip_path, fs::path& unq_tmp_path, ArchiveRepository::RepositoryManifest& header_data)
{
// Path where data will be unzipped.
header_data.local_path = unq_tmp_path / zip_path.stem();
// 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(header_data.local_path);
fs::create_directories(header_data.local_path);
// Unzip repository zip to unique path in temp directory.
if (!unzip_repository(zip_path, header_data.local_path)) {
return false;
}
// Read the header file.
fs::path header_path = header_data.local_path / "header.json";
try
{
pt::ptree ptree;
pt::read_json(header_path.string(), ptree);
if (!extract_repository_header(ptree, header_data)) {
BOOST_LOG_TRIVIAL(error) << "Failed to load repository: " << zip_path;
return false;
}
}
catch (const std::exception& e)
{
BOOST_LOG_TRIVIAL(error) << "Failed to read repository header JSON " << header_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;
}
}
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());
Http::get(url)
.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
if (fs::exists(target_path)) {
boost::system::error_code ec;
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.local_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.local_path / source_subpath, target_path);
}
bool LocalArchiveRepository::get_archive(const fs::path& target_path) const
{
fs::path source_path = fs::path(m_data.local_path) / "vendor_indices.zip";
return get_file_inner(std::move(source_path), target_path);
}
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);
set_local_archives(app_config);
}
void PresetArchiveDatabase::set_archives(const std::string& json_body)
{
m_archives.clear();
// Online repo headers are in json_body.
try
{
std::stringstream ss(json_body);
pt::ptree ptree;
pt::read_json(ss, ptree);
for (const auto& subtree : ptree) {
ArchiveRepository::RepositoryManifest header;
if (extract_repository_header(subtree.second, header)) {
m_archives.emplace_back(std::make_unique<OnlineArchiveRepository>(std::move(header)));
} else {
BOOST_LOG_TRIVIAL(error) << "Failed to read one of repository headers.";
}
}
}
catch (const std::exception& e)
{
BOOST_LOG_TRIVIAL(error) << "Failed to read archives JSON. " << e.what();
}
// Local archives are stored as paths to zip.
// PresetArchiveDatabase has its folder in temp, local archives are extracted there
for (const std::string& archive_opt : m_local_archive_adresses) {
ArchiveRepository::RepositoryManifest header_data;
if (extract_local_archive_repository(archive_opt, m_unq_tmp_path, header_data)) {
m_archives.emplace_back(std::make_unique<LocalArchiveRepository>(std::move(header_data)));
}
}
}
void PresetArchiveDatabase::set_local_archives(AppConfig* app_config)
{
m_local_archive_adresses.clear();
std::string opt = app_config->get("local_archives");
std::vector<std::string> options;
deserialize_string(opt, m_local_archive_adresses);
}
// test_json is only for testing
namespace {
std::string test_json(bool secret)
{
std::string test = "["
"{\"name\": \"Prusa Research\", \"id\": \"PrusaResearch\", \"url\": \"https://github.com/kocikdav/PrusaSlicer-settings/raw/master/archive_repos/PrusaResearch\", \"description\": \"Prusa Research\", \"visibility\":\"\"}, "
"{\"name\": \"Other Vendors\", \"id\": \"OtherVendors\", \"url\": \"https://github.com/kocikdav/PrusaSlicer-settings/raw/master/archive_repos/OtherVendors\",\"description\": \"Other Vendors\", \"visibility\":\"\"} ";
//if (secret) {
// test += ", {\"name\": \"Davit\", \"id\": \"Davit\", \"archive_url\": \"https://github.com/kocikdav/PrusaSlicer-settings/raw/master/other_source\", \"secret\": true}";
//}
test += "]";
return std::move(test);
}
bool sync_inner(std::string& manifest)
{
bool ret = false;
std::string url = "http://10.24.3.3:8001/v1/repos";
Http::get(std::move(url))
.on_error([&](std::string body, std::string error, unsigned http_status) {
BOOST_LOG_TRIVIAL(error) << "Failed to get online archive repository 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()
{
std::thread thread([this]() {
std::string manifest;
if (!sync_inner(manifest))
return;
// Force update when logged in (token not empty).
wxQueueEvent(this->p_evt_handler, new Event<ArchiveRepositorySyncData>(EVT_PRESET_ARCHIVE_DATABASE_SYNC_DONE, {std::move(manifest), !m_token.empty()}));
});
thread.join();
}
void PresetArchiveDatabase::sync_blocking()
{
std::string manifest;
if (!sync_inner(manifest))
return;
set_archives(std::move(manifest));
}
}} // Slic3r::GUI

View File

@ -0,0 +1,120 @@
#ifndef slic3r_PresetArchiveDatabase_hpp_
#define slic3r_PresetArchiveDatabase_hpp_
#include "Event.hpp"
#include <string>
#include <vector>
#include <memory>
class boost::filesystem::path;
namespace Slic3r {
class AppConfig;
namespace GUI {
struct ArchiveRepositorySyncData
{
std::string json;
bool force_updater;
};
wxDECLARE_EVENT(EVT_PRESET_ARCHIVE_DATABASE_SYNC_DONE, Event<ArchiveRepositorySyncData>);
struct ArchiveRepositoryGetFileArgs {
boost::filesystem::path target_path;
std::string repository_id;
};
class ArchiveRepository
{
public:
struct RepositoryManifest {
// mandatory
std::string id;
std::string name;
std::string url;
// optional
std::string index_url;
std::string description;
std::string visibility;
// not read from manifest json
boost::filesystem::path local_path;
bool m_secret { false };
};
// Use std::move when calling constructor.
ArchiveRepository(RepositoryManifest&& data) : m_data(std::move(data)) {}
~ArchiveRepository() {}
// Gets vendor_indices.zip to target_path
virtual bool get_archive(const boost::filesystem::path& target_path) const = 0;
// Gets file if repository_id arg matches m_id.
// Should be used to get the most recent ini file and every missing resource.
virtual bool get_file(const std::string& source_subpath, const boost::filesystem::path& target_path, const std::string& repository_id) const = 0;
// Gets file without id check - for not yet encountered vendors only!
virtual bool get_ini_no_id(const std::string& source_subpath, const boost::filesystem::path& target_path) const = 0;
protected:
RepositoryManifest m_data;
};
typedef std::vector<std::unique_ptr<const ArchiveRepository>> ArchiveRepositoryVector;
class OnlineArchiveRepository : public ArchiveRepository
{
public:
OnlineArchiveRepository(RepositoryManifest&& data) : ArchiveRepository(std::move(data))
{
if (m_data.url.back() != '/') {
m_data.url += "/";
}
}
// Gets vendor_indices.zip to target_path.
bool get_archive(const boost::filesystem::path& target_path) const override;
// Gets file if repository_id arg matches m_id.
// Should be used to get the most recent ini file and every missing resource.
bool get_file(const std::string& source_subpath, const boost::filesystem::path& target_path, const std::string& repository_id) const override;
// Gets file without checking id.
// Should be used only if no previous ini file exists.
bool get_ini_no_id(const std::string& source_subpath, const boost::filesystem::path& target_path) const override;
private:
bool get_file_inner(const std::string& url, const boost::filesystem::path& target_path) const;
};
class LocalArchiveRepository : public ArchiveRepository
{
public:
LocalArchiveRepository(RepositoryManifest&& data) : ArchiveRepository(std::move(data)) {}
// Gets vendor_indices.zip to target_path.
bool get_archive(const boost::filesystem::path& target_path) const override;
// Gets file if repository_id arg matches m_id.
// Should be used to get the most recent ini file and every missing resource.
bool get_file(const std::string& source_subpath, const boost::filesystem::path& target_path, const std::string& repository_id) const override;
// Gets file without checking id.
// Should be used only if no previous ini file exists.
bool get_ini_no_id(const std::string& source_subpath, const boost::filesystem::path& target_path) const override;
private:
bool get_file_inner(const boost::filesystem::path& source_path, const boost::filesystem::path& target_path) const;
};
class PresetArchiveDatabase
{
public:
PresetArchiveDatabase(AppConfig* app_config, wxEvtHandler* evt_handler);
~PresetArchiveDatabase() {}
const ArchiveRepositoryVector& get_archives() const { return m_archives; }
void sync();
void sync_blocking();
void set_token(const std::string token) { m_token = token; }
void set_local_archives(AppConfig* app_config);
void set_archives(const std::string& json_body);
private:
wxEvtHandler* p_evt_handler;
boost::filesystem::path m_unq_tmp_path;
ArchiveRepositoryVector m_archives;
std::vector<std::string> m_local_archive_adresses;
std::string m_token;
};
}} // Slic3r::GUI
#endif // PresetArchiveDatabase

View File

@ -20,7 +20,8 @@ namespace GUI {
UserAccount::UserAccount(wxEvtHandler* evt_handler, AppConfig* app_config, const std::string& instance_hash)
: m_communication(std::make_unique<UserAccountCommunication>(evt_handler, app_config))
, m_instance_hash(instance_hash)
{}
{
}
UserAccount::~UserAccount()
{}
@ -135,7 +136,7 @@ bool UserAccount::on_user_id_success(const std::string data, std::string& out_us
std::string public_username = m_account_user_data["public_username"];
set_username(public_username);
out_username = public_username;
// equeue GET with avatar url
// enqueue GET with avatar url
if (m_account_user_data.find("avatar") != m_account_user_data.end()) {
const boost::filesystem::path server_file(m_account_user_data["avatar"]);
m_avatar_extension = server_file.extension().string();

View File

@ -45,6 +45,9 @@ namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
wxDEFINE_EVENT(EVT_UA_NO_TOKENS, UserAccountFailEvent);
namespace {
std::string get_code_from_message(const std::string& url_message)
@ -151,8 +154,12 @@ UserAccountCommunication::UserAccountCommunication(wxEvtHandler* evt_handler, Ap
m_session = std::make_unique<UserAccountSession>(evt_handler, access_token, refresh_token, shared_session_key, m_app_config->get_bool("connect_polling"));
init_session_thread();
// perform login at the start, but only with tokens
if (has_token)
if (has_token) {
do_login();
} else {
// send evt so preset archive database knows it can sync
wxQueueEvent(evt_handler, new UserAccountFailEvent(EVT_UA_NO_TOKENS, {}));
}
}
UserAccountCommunication::~UserAccountCommunication()

View File

@ -27,6 +27,7 @@ wxDECLARE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_SUCCESS, UserAccountSuccessEven
wxDECLARE_EVENT(EVT_UA_FAIL, UserAccountFailEvent); // Soft fail - clears only after some number of fails
wxDECLARE_EVENT(EVT_UA_RESET, UserAccountFailEvent); // Hard fail - clears all
wxDECLARE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_FAIL, UserAccountFailEvent); // Failed to get data for printer to select, soft fail, action does not repeat
wxDECLARE_EVENT(EVT_UA_NO_TOKENS, UserAccountFailEvent); // when login wont be performed on startup
typedef std::function<void(const std::string& body)> UserActionSuccessFn;
@ -90,7 +91,7 @@ public:
struct ActionQueueData
{
UserAccountActionID action_id;
UserAccountActionID action_id;
UserActionSuccessFn success_callback;
UserActionFailFn fail_callback;
std::string input;
@ -173,8 +174,8 @@ private:
std::string m_refresh_token;
std::string m_shared_session_key;
std::queue<ActionQueueData> m_action_queue;
std::queue<ActionQueueData> m_priority_action_queue;
std::queue<ActionQueueData> m_action_queue;
std::queue<ActionQueueData> m_priority_action_queue;
std::map<UserAccountActionID, std::unique_ptr<UserAction>> m_actions;
wxEvtHandler* p_evt_handler;

View File

@ -184,18 +184,20 @@ struct PresetUpdater::priv
priv();
void set_download_prefs(const AppConfig *app_config);
bool get_file(const std::string &url, const fs::path &target_path) const;
//bool get_file(const std::string &url, const fs::path &target_path) const;
void prune_tmps() const;
void sync_config(const VendorMap vendors, const std::string& index_archive_url);
void sync_config(const VendorMap& vendors, const GUI::ArchiveRepository& archive);
void check_install_indices() const;
Updates get_config_updates(const Semver& old_slic3r_version) const;
bool perform_updates(Updates &&updates, bool snapshot = true) const;
void set_waiting_updates(Updates u);
// checks existence and downloads resource to cache
void get_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const;
void get_missing_resource(const GUI::ArchiveRepository& archive, const std::string& vendor, const std::string& filename, const std::string& repository_id_from_ini) const;
// checks existence and downloads resource to vendor or copy from cache to vendor
void get_or_copy_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const;
void get_or_copy_missing_resource(const GUI::ArchiveRepository& archive, const std::string& vendor, const std::string& filename, const std::string& repository_id_from_ini) const;
// checks existence and copies resource to vendor from cache to vendor
void copy_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const;
void update_index_db();
};
@ -227,39 +229,39 @@ void PresetUpdater::priv::set_download_prefs(const AppConfig *app_config)
}
// Downloads a file (http get operation). Cancels if the Updater is being destroyed.
bool PresetUpdater::priv::get_file(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());
Http::get(url)
.on_progress([](Http::Progress, bool &cancel) {
if (cancel) { cancel = true; }
})
.on_error([&](std::string body, std::string error, unsigned http_status) {
(void)body;
BOOST_LOG_TRIVIAL(error) << format("Error getting: `%1%`: HTTP %2%, %3%",
url,
http_status,
error);
})
.on_complete([&](std::string body, unsigned /* http_status */) {
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 PresetUpdater::priv::get_file(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());
//
// Http::get(url)
// .on_progress([](Http::Progress, bool &cancel) {
// if (cancel) { cancel = true; }
// })
// .on_error([&](std::string body, std::string error, unsigned http_status) {
// (void)body;
// BOOST_LOG_TRIVIAL(error) << format("Error getting: `%1%`: HTTP %2%, %3%",
// url,
// http_status,
// error);
// })
// .on_complete([&](std::string body, unsigned /* http_status */) {
// 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;
//}
// Remove leftover paritally downloaded files, if any.
void PresetUpdater::priv::prune_tmps() const
@ -271,18 +273,15 @@ void PresetUpdater::priv::prune_tmps() const
}
}
void PresetUpdater::priv::get_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const
// gets resource to cache/<vendor_name>/
void PresetUpdater::priv::get_missing_resource(const GUI::ArchiveRepository& archive, const std::string& vendor, const std::string& filename, const std::string& repository_id_from_ini) const
{
if (filename.empty() || vendor.empty())
return;
assert(!filename.empty() && !vendor.empty());
//if (filename.empty() || vendor.empty()) {
// BOOST_LOG_TRIVIAL(error) << "PresetUpdater::get_missing_resource - wrong input. vendor: "<< vendor << " filename: " << filename;
// return;
//}
if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") &&
!boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/"))
{
throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url));
}
std::string escaped_filename = escape_string_url(filename);
const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename));
const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename));
const fs::path file_in_cache(cache_path / (vendor + "/" + filename));
@ -299,24 +298,60 @@ void PresetUpdater::priv::get_missing_resource(const std::string& vendor, const
BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in cache folder. No need to download.";
return;
}
BOOST_LOG_TRIVIAL(info) << "Resources check could not find " << vendor << " / " << filename << " bed texture. Downloading.";
const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", escaped_filename); // vendor should already be in url
if (!fs::exists(file_in_cache.parent_path()))
fs::create_directory(file_in_cache.parent_path());
get_file(resource_url, file_in_cache);
//std::string escaped_filename = escape_string_url(filename);
const std::string resource_subpath = GUI::format("%1%/%2%",vendor, filename);
archive.get_file(resource_subpath, file_in_cache, repository_id_from_ini);
return;
}
// gets resource to vendor/<vendor_name>/
void PresetUpdater::priv::get_or_copy_missing_resource(const GUI::ArchiveRepository& archive, const std::string& vendor, const std::string& filename, const std::string& repository_id_from_ini) const
{
assert(!filename.empty() && !vendor.empty() /*&& !repository_id_from_ini.empty()*/);
//if (filename.empty() || vendor.empty()) {
// BOOST_LOG_TRIVIAL(error) << "PresetUpdater::get_or_copy_missing_resource - wrong input. vendor: " << vendor << " filename: " << filename;
// return;
//}
void PresetUpdater::priv::get_or_copy_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const
const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename));
const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename));
const fs::path file_in_cache(cache_path / (vendor + "/" + filename));
// Already in vendor. No need to do anything.
if (fs::exists(file_in_vendor)) {
BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in vendor folder. No need to download.";
return;
}
// In resources dir since installation. No need to do anything.
if (fs::exists(file_in_rsrc)) {
BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in resources folder. No need to download.";
return;
}
// Create vendor_name dir in vendor.
if (!fs::exists(file_in_vendor.parent_path())) {
fs::create_directory(file_in_vendor.parent_path());
}
// No file to copy. Download it to straight to the vendor dir.
if (!fs::exists(file_in_cache)) {
BOOST_LOG_TRIVIAL(info) << "Downloading resources missing in cache directory: " << vendor << " / " << filename;
//std::string escaped_filename = escape_string_url(filename);
const std::string resource_subpath = GUI::format("%1%/%2%", vendor, filename);
archive.get_file(resource_subpath, file_in_vendor, repository_id_from_ini);
return;
}
BOOST_LOG_TRIVIAL(debug) << "Copiing: " << file_in_cache << " to " << file_in_vendor;
copy_file_fix(file_in_cache, file_in_vendor);
}
// gets resource to vendor/<vendor_name>/
void PresetUpdater::priv::copy_missing_resource(const std::string& vendor, const std::string& filename, const std::string& url) const
{
if (filename.empty() || vendor.empty())
return;
std::string escaped_filename = escape_string_url(filename);
const fs::path file_in_vendor(vendor_path / (vendor + "/" + filename));
const fs::path file_in_rsrc(rsrc_path / (vendor + "/" + filename));
const fs::path file_in_cache(cache_path / (vendor + "/" + filename));
@ -329,20 +364,8 @@ void PresetUpdater::priv::get_or_copy_missing_resource(const std::string& vendor
BOOST_LOG_TRIVIAL(info) << "Resource " << vendor << " / " << filename << " found in resources folder. No need to download.";
return;
}
if (!fs::exists(file_in_cache)) { // No file to copy. Download it to straight to the vendor dir.
if (!boost::starts_with(url, "http://files.prusa3d.com/wp-content/uploads/repository/") &&
!boost::starts_with(url, "https://files.prusa3d.com/wp-content/uploads/repository/"))
{
throw Slic3r::CriticalException(GUI::format("URL outside prusa3d.com network: %1%", url));
}
BOOST_LOG_TRIVIAL(info) << "Downloading resources missing in cache directory: " << vendor << " / " << filename;
const auto resource_url = format("%1%%2%%3%", url, url.back() == '/' ? "" : "/", escaped_filename); // vendor should already be in url
if (!fs::exists(file_in_vendor.parent_path()))
fs::create_directory(file_in_vendor.parent_path());
get_file(resource_url, file_in_vendor);
if (!fs::exists(file_in_cache)) { // No file to copy. Bad!
BOOST_LOG_TRIVIAL(error) << "Resource " << vendor << " / " << filename << " not found!";
return;
}
@ -355,29 +378,15 @@ void PresetUpdater::priv::get_or_copy_missing_resource(const std::string& vendor
// Download vendor indices. Also download new bundles if an index indicates there's a new one available.
// Both are saved in cache.
void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string& index_archive_url)
void PresetUpdater::priv::sync_config(const VendorMap& vendors, const GUI::ArchiveRepository& archive_repository)
{
BOOST_LOG_TRIVIAL(info) << "Syncing configuration cache";
if (!enabled_config_update) { return; }
// Download profiles archive zip
// dk: Do we want to return here on error? Or skip archive dwnld and unzip and work with previous run state cache / vendor? I think return.
// Any error here also doesnt show any info in UI. Do we want maybe notification?
fs::path archive_path(cache_path / "vendor_indices.zip");
if (index_archive_url.empty()) {
BOOST_LOG_TRIVIAL(error) << "Downloading profile archive failed - url has no value.";
return;
}
BOOST_LOG_TRIVIAL(info) << "Downloading vedor profiles archive zip from " << index_archive_url;
//check if idx_url is leading to our site
if (!boost::starts_with(index_archive_url, "http://files.prusa3d.com/wp-content/uploads/repository/") &&
!boost::starts_with(index_archive_url, "https://files.prusa3d.com/wp-content/uploads/repository/"))
{
BOOST_LOG_TRIVIAL(error) << "Unsafe url path for vedor profiles archive zip. Download is rejected.";
return;
}
if (!get_file(index_archive_url, archive_path)) {
if (!archive_repository.get_archive(archive_path)) {
BOOST_LOG_TRIVIAL(error) << "Download of vedor profiles archive zip failed.";
return;
}
@ -387,10 +396,11 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string
enum class VendorStatus
{
IN_ARCHIVE,
IN_CACHE,
NEW_VERSION,
INSTALLED
IN_ARCHIVE, // index was unzipped from archive to /cache/vendors/
IN_CACHE, // vendor does exists in index_db, probably bc its ini is in resources and idx was copied to /cache/. It is not istalled, so new version of ini would be in /cache/vendors/
INSTALLED, // vendor is installed, ini is in /vendors/ folder, no new version is available.
NEW_VERSION, // vendor is installed, in /cache/ is new ini version waiting for installation.
};
std::vector<std::pair<std::string, VendorStatus>> vendors_with_status;
@ -455,19 +465,18 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string
}
auto archive_it = std::find_if(vendors_with_status.begin(), vendors_with_status.end(),
[&index](const std::pair<std::string, VendorStatus>& element) { return element.first == index.vendor(); });
//assert(archive_it != vendors_with_status.end()); // this would mean there is a index for vendor that is missing in recently downloaded archive
const auto vendor_it = vendors.find(index.vendor());
if (vendor_it == vendors.end()) {
// Not installed vendor yet we need to check missing thumbnails (of new printers)
BOOST_LOG_TRIVIAL(debug) << "No such vendor: " << index.vendor();
if (archive_it != vendors_with_status.end())
archive_it->second = VendorStatus::IN_CACHE;
if (archive_it == vendors_with_status.end()) {
// index for vendor that is missing in recently downloaded archive
continue;
}
if (archive_it != vendors_with_status.end())
archive_it->second = VendorStatus::INSTALLED;
const auto vendor_it = vendors.find(index.vendor());
if (vendor_it == vendors.end()) {
// Not installed vendor yet - later new version and missing resources will be checked.
BOOST_LOG_TRIVIAL(debug) << "No such vendor: " << index.vendor();
archive_it->second = VendorStatus::IN_CACHE;
continue;
}
archive_it->second = VendorStatus::INSTALLED;
const VendorProfile &vendor = vendor_it->second;
const std::string idx_path = (cache_path / (vendor.id + ".idx")).string();
@ -518,15 +527,14 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string
if (vendor.config_version >= recommended) { continue; }
// vendors that are checked here, doesnt need to be checked again later
if (archive_it != vendors_with_status.end())
archive_it->second = VendorStatus::NEW_VERSION;
archive_it->second = VendorStatus::NEW_VERSION;
// Download recomended ini to cache
const auto path_in_cache = cache_path / (vendor.id + ".ini");
BOOST_LOG_TRIVIAL(info) << "Downloading new bundle for vendor: " << vendor.name;
const auto bundle_url = format("%1%/%2%.ini", vendor.config_update_url, recommended.to_string());
const auto bundle_path = cache_path / (vendor.id + ".ini");
if (!get_file(bundle_url, bundle_path))
const std::string source_subpath = GUI::format("%1%/%2%.ini", vendor.id, recommended.to_string());
const fs::path bundle_path = cache_path / (vendor.id + ".ini");
if (!archive_repository.get_file(source_subpath, bundle_path, vendor.repo_id))
continue;
if (cancel)
return;
@ -545,7 +553,7 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string
if (! res.empty()) {
try
{
get_missing_resource(vp.id, res, vendor.config_update_url);
get_missing_resource(archive_repository, vp.id, res, vendor.repo_id);
}
catch (const std::exception& e)
{
@ -558,8 +566,8 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string
}
}
}
// Download missing thumbnails for not-installed vendors.
//for (const std::string& vendor : vendors_only_in_archive)
// Now status of each vendor is already decided.
// Download missing for non-installed vendors.
for (const std::pair<std::string, VendorStatus >& vendor : vendors_with_status) {
if (vendor.second == VendorStatus::IN_ARCHIVE) {
// index in archive and not in cache and not installed vendor
@ -583,25 +591,30 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string
}
const auto recommended = recommended_it->config_version;
if (!fs::exists(ini_path_in_archive)){
// Download recommneded to vendor - we do not have any existing ini file so we have to use hardcoded url.
const std::string fixed_url = GUI::wxGetApp().app_config->profile_folder_url();
const auto bundle_url = format("%1%/%2%/%3%.ini", fixed_url, vendor.first, recommended.to_string());
if (!get_file(bundle_url, ini_path_in_archive))
// Download recommneded to vendor - we do not have any existing ini file so we have to use archive url.
const std::string source_subpath = GUI::format("%1%/%2%.ini", vendor.first, recommended.to_string());
if (!archive_repository.get_ini_no_id(source_subpath, ini_path_in_archive))
continue;
} else {
// check existing ini version
// then download recommneded to vendor if needed
// then download recommended to vendor if needed
VendorProfile vp;
try {
vp = VendorProfile::from_ini(ini_path_in_archive, true);
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_archive, e.what());
// Delete the file
boost::system::error_code ec;
fs::remove(ini_path_in_archive, ec);
if (ec) {
BOOST_LOG_TRIVIAL(error) << format("Failed to delete file: %1%", ec.message());
}
continue;
}
if (vp.config_version != recommended) {
const std::string fixed_url = GUI::wxGetApp().app_config->profile_folder_url();
const auto bundle_url = format("%1%/%2%/%3%.ini", fixed_url, vendor.first, recommended.to_string());
if (!get_file(bundle_url, ini_path_in_archive))
// Take url from existing ini. This way we prevent downloading files from multiple sources.
const std::string source_subpath = GUI::format("%1%/%2%.ini", vp.id, recommended.to_string());
if (!archive_repository.get_file(source_subpath, ini_path_in_archive, vp.repo_id))
continue;
}
}
@ -618,7 +631,7 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string
if (!model.thumbnail.empty()) {
try
{
get_missing_resource(vp.id, model.thumbnail, vp.config_update_url);
get_missing_resource(archive_repository, vp.id, model.thumbnail, vp.repo_id);
}
catch (const std::exception& e)
{
@ -690,11 +703,12 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << format("Corrupted profile file for vendor %1% at %2%, message: %3%", vendor.first, ini_path_in_rsrc, e.what());
// This means resources are corrupted.
continue;
}
const auto bundle_url = format("%1%/%2%.ini", vp.config_update_url, recommended_archive.to_string());
if (!get_file(bundle_url, ini_path_in_archive)) {
BOOST_LOG_TRIVIAL(error) << format("Failed to open vendor .ini file when checking missing resources: %1%", ini_path_in_rsrc);
const std::string source_subpath = GUI::format("%1%/%2%.ini", vp.id, recommended_archive.to_string());
if (!archive_repository.get_file(source_subpath, ini_path_in_archive, vp.repo_id)) {
BOOST_LOG_TRIVIAL(error) << format("Failed to get new vendor .ini file when checking missing resources: %1%", ini_path_in_archive.string());
continue;
}
} else {
@ -709,8 +723,8 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string
continue;
}
if (vp.config_version != recommended_archive) {
const auto bundle_url = format("%1%/%2%.ini", vp.config_update_url, recommended_archive.to_string());
if (!get_file(bundle_url, ini_path_in_archive)) {
const std::string source_subpath = GUI::format("%1%/%2%.ini", vp.id, recommended_archive.to_string());
if (!archive_repository.get_file(source_subpath, ini_path_in_archive, vp.repo_id)) {
BOOST_LOG_TRIVIAL(error) << format("Failed to open vendor .ini file when checking missing resources: %1%", ini_path_in_archive);
continue;
}
@ -734,7 +748,7 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string
if (!model.thumbnail.empty()) {
try
{
get_missing_resource(vp.id, model.thumbnail, vp.config_update_url);
get_missing_resource(archive_repository, vp.id, model.thumbnail, vp.repo_id);
}
catch (const std::exception& e)
{
@ -764,7 +778,7 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors, const std::string
if (!model.thumbnail.empty()) {
try
{
get_or_copy_missing_resource(vp.id, res, vp.config_update_url);
get_or_copy_missing_resource(archive_repository, vp.id, res, vp.repo_id);
}
catch (const std::exception& e)
{
@ -1089,7 +1103,7 @@ bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
continue;
try
{
get_or_copy_missing_resource(vp.id, resource, vp.config_update_url);
copy_missing_resource(vp.id, resource, vp.config_update_url);
}
catch (const std::exception& e)
{
@ -1128,20 +1142,16 @@ PresetUpdater::~PresetUpdater()
}
}
void PresetUpdater::sync(const PresetBundle *preset_bundle, wxEvtHandler* evt_handler)
void PresetUpdater::sync(const PresetBundle *preset_bundle, wxEvtHandler* evt_handler, const ArchiveRepositoryVector& repositories)
{
p->set_download_prefs(GUI::wxGetApp().app_config);
if (!p->enabled_version_check && !p->enabled_config_update) { return; }
// Copy the whole vendors data for use in the background thread
// Unfortunatelly as of C++11, it needs to be copied again
// into the closure (but perhaps the compiler can elide this).
VendorMap vendors = preset_bundle->vendors;
std::string index_archive_url = GUI::wxGetApp().app_config->index_archive_url();
p->thread = std::thread([this, vendors, index_archive_url, evt_handler]() {
if (!p->enabled_config_update) { return; }
p->thread = std::thread([this, &vendors = preset_bundle->vendors, &repositories, evt_handler]() {
this->p->prune_tmps();
this->p->sync_config(std::move(vendors), index_archive_url);
for(const auto& archive : repositories) {
this->p->sync_config(vendors, *archive);
}
wxCommandEvent* evt = new wxCommandEvent(EVT_CONFIG_UPDATER_SYNC_DONE);
evt_handler->QueueEvent(evt);
});
@ -1155,6 +1165,20 @@ void PresetUpdater::cancel_sync()
p->cancel = true;
p->thread.join();
}
p->cancel = false;
}
void PresetUpdater::sync_blocking(const PresetBundle* preset_bundle, wxEvtHandler* evt_handler, const ArchiveRepositoryVector& repositories)
{
p->set_download_prefs(GUI::wxGetApp().app_config);
if (!p->enabled_config_update) { return; }
this->p->prune_tmps();
for (const auto& archive : repositories) {
this->p->sync_config(preset_bundle->vendors, *archive);
}
wxCommandEvent* evt = new wxCommandEvent(EVT_CONFIG_UPDATER_SYNC_DONE);
evt_handler->QueueEvent(evt);
}
void PresetUpdater::slic3r_update_notify()
@ -1488,5 +1512,19 @@ void PresetUpdater::update_index_db()
{
p->update_index_db();
}
void PresetUpdater::add_additional_archive(const std::string& archive_url, const std::string& download_url)
{
if (std::find_if(m_additional_archives.begin(), m_additional_archives.end(), [archive_url](const std::pair<std::string, std::string>& it) { return it.first == archive_url; }) == m_additional_archives.end()) {
m_additional_archives.emplace_back(archive_url, download_url);
}
}
void PresetUpdater::add_additional_archives(const std::vector<std::pair<std::string, std::string>>& archives)
{
for (const auto& pair : archives) {
add_additional_archive(pair.first, pair.second);
}
}
}

View File

@ -5,6 +5,8 @@
#ifndef slic3r_PresetUpdate_hpp_
#define slic3r_PresetUpdate_hpp_
#include "slic3r/GUI/PresetArchiveDatabase.hpp"
#include <memory>
#include <vector>
@ -17,6 +19,8 @@ class AppConfig;
class PresetBundle;
class Semver;
typedef std::vector<std::unique_ptr<const GUI::ArchiveRepository>> ArchiveRepositoryVector;
static constexpr const int SLIC3R_VERSION_BODY_MAX = 256;
class PresetUpdater
@ -30,9 +34,11 @@ public:
~PresetUpdater();
// If either version check or config updating is enabled, get the appropriate data in the background and cache it.
void sync(const PresetBundle *preset_bundle, wxEvtHandler* evt_handler);
void sync(const PresetBundle *preset_bundle, wxEvtHandler* evt_handler, const ArchiveRepositoryVector& repositories);
void cancel_sync();
void sync_blocking(const PresetBundle* preset_bundle, wxEvtHandler* evt_handler, const ArchiveRepositoryVector& repositories);
// If version check is enabled, check if chaced online slic3r version is newer, notify if so.
void slic3r_update_notify();
@ -67,9 +73,13 @@ public:
bool version_check_enabled() const;
void add_additional_archive(const std::string& archive_url, const std::string& download_url);
void add_additional_archives(const std::vector<std::pair<std::string, std::string>>& archives);
private:
struct priv;
std::unique_ptr<priv> p;
std::vector<std::pair<std::string, std::string>> m_additional_archives;
};
//wxDECLARE_EVENT(EVT_SLIC3R_VERSION_ONLINE, wxCommandEvent);