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.
This commit is contained in:
David Kocik 2021-10-25 14:19:22 +02:00
parent 4c8e13947c
commit ca8f7fbf80
6 changed files with 210 additions and 10 deletions

View File

@ -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.

View File

@ -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<std::string> get_recent_projects() const;
void set_recent_projects(const std::vector<std::string>& 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

View File

@ -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<boost::filesystem::path> 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()*/)
{

View File

@ -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

View File

@ -743,6 +743,25 @@ bool GUI_App::init_opengl()
#endif
}
// gets path to PrusaSlicer.ini, returns semver from first line comment
static boost::optional<Semver> 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<boost::filesystem::path> 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::optional<Semver>other_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")

View File

@ -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<Semver> m_last_config_version;
};
DECLARE_APP(GUI_App)