From ca8f7fbf805621d75579ece7b41db4c6e0150c2f Mon Sep 17 00:00:00 2001 From: David Kocik Date: Mon, 25 Oct 2021 14:19:22 +0200 Subject: [PATCH] Copying from other config folders: Find if there is more recent config in other folders (alpha / beta / release). If yes, ask user, make snapshot, copy files. if there is no current config, ask user and copy recent one. --- src/libslic3r/AppConfig.cpp | 20 ++--- src/libslic3r/AppConfig.hpp | 5 ++ src/libslic3r/PresetBundle.cpp | 55 ++++++++++++++ src/libslic3r/PresetBundle.hpp | 1 + src/slic3r/GUI/GUI_App.cpp | 133 +++++++++++++++++++++++++++++++++ src/slic3r/GUI/GUI_App.hpp | 6 ++ 6 files changed, 210 insertions(+), 10 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index d5444edc63..e5af1fa731 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -248,11 +248,11 @@ std::string AppConfig::load() bool recovered = false; try { - ifs.open(AppConfig::config_path()); + ifs.open(AppConfig::loading_path()); #ifdef WIN32 // Verify the checksum of the config file without taking just for debugging purpose. if (!verify_config_file_checksum(ifs)) - BOOST_LOG_TRIVIAL(info) << "The configuration file " << AppConfig::config_path() << + BOOST_LOG_TRIVIAL(info) << "The configuration file " << AppConfig::loading_path() << " has a wrong MD5 checksum or the checksum is missing. This may indicate a file corruption or a harmless user edit."; ifs.seekg(0, boost::nowide::ifstream::beg); @@ -262,32 +262,32 @@ std::string AppConfig::load() #ifdef WIN32 // The configuration file is corrupted, try replacing it with the backup configuration. ifs.close(); - std::string backup_path = (boost::format("%1%.bak") % AppConfig::config_path()).str(); + std::string backup_path = (boost::format("%1%.bak") % AppConfig::loading_path()).str(); if (boost::filesystem::exists(backup_path)) { // Compute checksum of the configuration backup file and try to load configuration from it when the checksum is correct. boost::nowide::ifstream backup_ifs(backup_path); if (!verify_config_file_checksum(backup_ifs)) { - BOOST_LOG_TRIVIAL(error) << format("Both \"%1%\" and \"%2%\" are corrupted. It isn't possible to restore configuration from the backup.", AppConfig::config_path(), backup_path); + BOOST_LOG_TRIVIAL(error) << format("Both \"%1%\" and \"%2%\" are corrupted. It isn't possible to restore configuration from the backup.", AppConfig::loading_path(), backup_path); backup_ifs.close(); boost::filesystem::remove(backup_path); - } else if (std::string error_message; copy_file(backup_path, AppConfig::config_path(), error_message, false) != SUCCESS) { - BOOST_LOG_TRIVIAL(error) << format("Configuration file \"%1%\" is corrupted. Failed to restore from backup \"%2%\": %3%", AppConfig::config_path(), backup_path, error_message); + } else if (std::string error_message; copy_file(backup_path, AppConfig::loading_path(), error_message, false) != SUCCESS) { + BOOST_LOG_TRIVIAL(error) << format("Configuration file \"%1%\" is corrupted. Failed to restore from backup \"%2%\": %3%", AppConfig::loading_path(), backup_path, error_message); backup_ifs.close(); boost::filesystem::remove(backup_path); } else { - BOOST_LOG_TRIVIAL(info) << format("Configuration file \"%1%\" was corrupted. It has been succesfully restored from the backup \"%2%\".", AppConfig::config_path(), backup_path); + BOOST_LOG_TRIVIAL(info) << format("Configuration file \"%1%\" was corrupted. It has been succesfully restored from the backup \"%2%\".", AppConfig::loading_path(), backup_path); // Try parse configuration file after restore from backup. try { - ifs.open(AppConfig::config_path()); + ifs.open(AppConfig::loading_path()); pt::read_ini(ifs, tree); recovered = true; } catch (pt::ptree_error& ex) { - BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\" after it has been restored from backup: %2%", AppConfig::config_path(), ex.what()); + BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\" after it has been restored from backup: %2%", AppConfig::loading_path(), ex.what()); } } } else #endif // WIN32 - BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\": %2%", AppConfig::config_path(), ex.what()); + BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\": %2%", AppConfig::loading_path(), ex.what()); if (! recovered) { // Report the initial error of parsing PrusaSlicer.ini. // Error while parsing config file. We'll customize the error message and rethrow to be displayed. diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index 03c35f35a3..5d9f32ab71 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -148,6 +148,9 @@ public: // Does the config file exist? bool exists(); + void set_loading_path(const std::string& path) { m_loading_path = path; } + std::string loading_path() { return (m_loading_path.empty() ? config_path() : m_loading_path); } + std::vector get_recent_projects() const; void set_recent_projects(const std::vector& recent_projects); @@ -196,6 +199,8 @@ private: Semver m_orig_version; // Whether the existing version is before system profiles & configuration updating bool m_legacy_datadir; + + std::string m_loading_path; }; } // namespace Slic3r diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index feaf0cac7d..56f01bb476 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -188,6 +188,61 @@ void PresetBundle::setup_directories() } } +// recursively copy all files and dirs in from_dir to to_dir +static void copy_dir(const boost::filesystem::path& from_dir, const boost::filesystem::path& to_dir) +{ + if(!boost::filesystem::is_directory(from_dir)) + return; + // i assume to_dir.parent surely exists + if (!boost::filesystem::is_directory(to_dir)) + boost::filesystem::create_directory(to_dir); + for (auto& dir_entry : boost::filesystem::directory_iterator(from_dir)) { + if (!boost::filesystem::is_directory(dir_entry.path())) { + std::string em; + CopyFileResult cfr = copy_file(dir_entry.path().string(), (to_dir / dir_entry.path().filename()).string(), em, false); + if (cfr != SUCCESS) { + BOOST_LOG_TRIVIAL(error) << "Error when copying files from " << from_dir << " to " << to_dir << ": " << em; + } + } else { + copy_dir(dir_entry.path(), to_dir / dir_entry.path().filename()); + } + } +} + +void PresetBundle::copy_files(const std::string& from) +{ + boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); + // list of searched paths based on current directory system in setup_directories() + // do not copy cache and snapshots + boost::filesystem::path from_data_dir = boost::filesystem::path(from); + std::initializer_list from_dirs= { + from_data_dir / "vendor", + from_data_dir / "shapes", +#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR + // Store the print/filament/printer presets into a "presets" directory. + data_dir / "presets", + data_dir / "presets" / "print", + data_dir / "presets" / "filament", + data_dir / "presets" / "sla_print", + data_dir / "presets" / "sla_material", + data_dir / "presets" / "printer", + data_dir / "presets" / "physical_printer" +#else + // Store the print/filament/printer presets at the same location as the upstream Slic3r. + from_data_dir / "print", + from_data_dir / "filament", + from_data_dir / "sla_print", + from_data_dir / "sla_material", + from_data_dir / "printer", + from_data_dir / "physical_printer" +#endif + }; + // copy recursively all files + for (const boost::filesystem::path& from_dir : from_dirs) { + copy_dir(from_dir, data_dir / from_dir.filename()); + } +} + PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule, const PresetPreferences& preferred_selection/* = PresetPreferences()*/) { diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index e5e49fb470..a975e37fe3 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -24,6 +24,7 @@ public: void reset(bool delete_files); void setup_directories(); + void copy_files(const std::string& from); struct PresetPreferences { std::string printer_model_id;// name of a preferred printer model diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 0b7bf45bca..cd286e45ae 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -743,6 +743,25 @@ bool GUI_App::init_opengl() #endif } +// gets path to PrusaSlicer.ini, returns semver from first line comment +static boost::optional parse_semver_from_ini(std::string path) +{ + std::ifstream stream(path); + std::stringstream buffer; + buffer << stream.rdbuf(); + std::string body = buffer.str(); + size_t end_line = body.find_first_of("\n\r"); + body.resize(end_line); + size_t start = body.find("PrusaSlicer "); + if (start == std::string::npos) + return boost::none; + body = body.substr(start + 12); + size_t end = body.find_first_of(" \n\r"); + if (end < body.size()) + body.resize(end); + return Semver::parse(body); +} + void GUI_App::init_app_config() { // Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release. @@ -791,9 +810,110 @@ void GUI_App::init_app_config() "\n\n" + app_config->config_path() + "\n\n" + error); } } + // Save orig_version here, so its empty if no app_config existed before this run. + m_last_config_version = app_config->orig_version();//parse_semver_from_ini(app_config->config_path()); } } +// returns true if found newer version and user agreed to use it +bool GUI_App::check_older_app_config(Semver current_version, bool backup) +{ + // find other version app config (alpha / beta / release) + std::string config_path = app_config->config_path(); + boost::filesystem::path parent_file_path(config_path); + std::string filename = parent_file_path.filename().string(); + parent_file_path.remove_filename().remove_filename(); + + std::vector candidates; + + if (SLIC3R_APP_KEY "-alpha" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-alpha" / filename); + if (SLIC3R_APP_KEY "-beta" != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY "-beta" / filename); + if (SLIC3R_APP_KEY != GetAppName()) candidates.emplace_back(parent_file_path / SLIC3R_APP_KEY / filename); + + Semver last_semver = current_version; + for (const auto& candidate : candidates) { + if (boost::filesystem::exists(candidate)) { + // parse + boost::optionalother_semver = parse_semver_from_ini(candidate.string()); + if (other_semver && *other_semver > last_semver) { + last_semver = *other_semver; + m_older_data_dir_path = candidate.parent_path().string(); + } + } + } + if (m_older_data_dir_path.empty()) + return false; + BOOST_LOG_TRIVIAL(info) << "last app config file used: " << m_older_data_dir_path; + // ask about using older data folder + wxRichMessageDialog msg(nullptr, backup ? + wxString::Format(_L("PrusaSlicer detected another configuration folder at %s." + "\nIts version is %s." + "\nLast version you used in current configuration folder is %s." + "\nPlease note that PrusaSlicer uses different folders to save configuration of alpha, beta and full release versions." + "\nWould you like to copy found configuration to your current configuration folder?" + + "\n\nIf you select yes, PrusaSlicer will copy all profiles and other files from found folder to the current one. Overwriting any existing file with matching name." + "\nIf you select no, you will continue with current configuration.") + , m_older_data_dir_path, last_semver.to_string(), current_version.to_string()) + : wxString::Format(_L("PrusaSlicer detected another configuration folder at %s." + "\nIts version is %s." + "\nThere is no configuration file in current configuration folder." + "\nPlease note that PrusaSlicer uses different folders to save configuration of alpha, beta and full release versions." + "\nWould you like to copy found configuration to your current configuration folder?" + + "\n\nIf you select yes, PrusaSlicer will copy all profiles and other files from found folder to the current one." + "\nIf you select no, you will start with clean installation with configuration wizard.") + , m_older_data_dir_path, last_semver.to_string()) + , _L("PrusaSlicer"), wxICON_QUESTION | wxYES_NO); + if (msg.ShowModal() == wxID_YES) { + std::string snapshot_id; + if (backup) { + // configuration snapshot + std::string comment; + if (const Config::Snapshot* snapshot = Config::take_config_snapshot_report_error( + *app_config, + Config::Snapshot::SNAPSHOT_USER, + comment); + snapshot != nullptr) + // Is thos correct? Save snapshot id for later, when new app config is loaded. + snapshot_id = snapshot->id; + else + BOOST_LOG_TRIVIAL(error) << "Failed to take congiguration snapshot: "; + } + + // This will tell later (when config folder structure is sure to exists) to copy files from m_older_data_dir_path + m_init_app_config_from_older = true; + // load app config from older file + app_config->set_loading_path((boost::filesystem::path(m_older_data_dir_path) / filename).string()); + std::string error = app_config->load(); + if (!error.empty()) { + // Error while parsing config file. We'll customize the error message and rethrow to be displayed. + if (is_editor()) { + throw Slic3r::RuntimeError( + _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + + "\n\n" + app_config->config_path() + "\n\n" + error); + } + else { + throw Slic3r::RuntimeError( + _u8L("Error parsing PrusaGCodeViewer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error.") + + "\n\n" + app_config->config_path() + "\n\n" + error); + } + } + if (!snapshot_id.empty()) + app_config->set("on_snapshot", snapshot_id); + m_app_conf_exists = true; + return true; + } + return false; +} + +void GUI_App::copy_older_config() +{ + preset_bundle->copy_files(m_older_data_dir_path); +} + void GUI_App::init_single_instance_checker(const std::string &name, const std::string &path) { BOOST_LOG_TRIVIAL(debug) << "init wx instance checker " << name << " "<< path; @@ -884,6 +1004,13 @@ bool GUI_App::on_init_inner() } } + if (m_last_config_version) { + if (*m_last_config_version < *Semver::parse(SLIC3R_VERSION)) + check_older_app_config(*m_last_config_version, true); + } else { + check_older_app_config(Semver(), false); + } + app_config->set("version", SLIC3R_VERSION); app_config->save(); @@ -922,12 +1049,18 @@ bool GUI_App::on_init_inner() scrn->SetText(_L("Loading configuration")+ dots); } + + preset_bundle = new PresetBundle(); // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory // supplied as argument to --datadir; in that case we should still run the wizard preset_bundle->setup_directories(); + + if (m_init_app_config_from_older) + copy_older_config(); + if (is_editor()) { #ifdef __WXMSW__ if (app_config->get("associate_3mf") == "1") diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index cbabd16e7d..90a8776a67 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -337,6 +337,8 @@ public: private: bool on_init_inner(); void init_app_config(); + bool check_older_app_config(Semver current_version, bool backup); + void copy_older_config(); void window_pos_save(wxTopLevelWindow* window, const std::string &name); void window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false); void window_pos_sanitize(wxTopLevelWindow* window); @@ -344,6 +346,10 @@ private: bool config_wizard_startup(); void check_updates(const bool verbose); + + bool m_init_app_config_from_older { false }; + std::string m_older_data_dir_path; + boost::optional m_last_config_version; }; DECLARE_APP(GUI_App)