1 #include "Snapshot.hpp"
2 
3 #include <time.h>
4 
5 #include <boost/algorithm/string/predicate.hpp>
6 #include <boost/nowide/cstdio.hpp>
7 #include <boost/nowide/fstream.hpp>
8 #include <boost/property_tree/ini_parser.hpp>
9 #include <boost/property_tree/ptree_fwd.hpp>
10 #include <boost/filesystem/operations.hpp>
11 #include <boost/log/trivial.hpp>
12 
13 #include "libslic3r/PresetBundle.hpp"
14 #include "libslic3r/format.hpp"
15 #include "libslic3r/libslic3r.h"
16 #include "libslic3r/Time.hpp"
17 #include "libslic3r/Config.hpp"
18 #include "libslic3r/FileParserError.hpp"
19 #include "libslic3r/Utils.hpp"
20 
21 #include "../GUI/GUI.hpp"
22 #include "../GUI/GUI_App.hpp"
23 #include "../GUI/I18N.hpp"
24 #include "../GUI/MainFrame.hpp"
25 
26 #include <wx/richmsgdlg.h>
27 
28 #define SLIC3R_SNAPSHOTS_DIR "snapshots"
29 #define SLIC3R_SNAPSHOT_FILE "snapshot.ini"
30 
31 namespace Slic3r {
32 namespace GUI {
33 namespace Config {
34 
clear()35 void Snapshot::clear()
36 {
37 	this->id.clear();
38 	this->time_captured = 0;
39 	this->slic3r_version_captured = Semver::invalid();
40 	this->comment.clear();
41 	this->reason = SNAPSHOT_UNKNOWN;
42 	this->print.clear();
43     this->sla_print.clear();
44 	this->filaments.clear();
45     this->sla_material.clear();
46 	this->printer.clear();
47     this->physical_printer.clear();
48 }
49 
load_ini(const std::string & path)50 void Snapshot::load_ini(const std::string &path)
51 {
52 	this->clear();
53 
54 	auto throw_on_parse_error = [&path](const std::string &msg) {
55 		throw file_parser_error(std::string("Failed loading the snapshot file. Reason: ") + msg, path);
56 	};
57 
58 	// Load the snapshot.ini file.
59     boost::property_tree::ptree tree;
60     try {
61         boost::nowide::ifstream ifs(path);
62         boost::property_tree::read_ini(ifs, tree);
63     } catch (const std::ifstream::failure &err) {
64         throw file_parser_error(std::string("The snapshot file cannot be loaded. Reason: ") + err.what(), path);
65     } catch (const std::runtime_error &err) {
66         throw_on_parse_error(err.what());
67     }
68 
69     // Parse snapshot.ini
70     std::string group_name_vendor = "Vendor:";
71 	std::string key_filament = "filament";
72     std::string key_prefix_model = "model_";
73     for (auto &section : tree) {
74     	if (section.first == "snapshot") {
75     		// Parse the common section.
76             for (auto &kvp : section.second) {
77                 if (kvp.first == "id")
78                     this->id = kvp.second.data();
79                 else if (kvp.first == "time_captured") {
80                 	this->time_captured = Slic3r::Utils::parse_iso_utc_timestamp(kvp.second.data());
81 					if (this->time_captured == (time_t)-1)
82 				        throw_on_parse_error("invalid timestamp");
83                 } else if (kvp.first == "slic3r_version_captured") {
84                 	auto semver = Semver::parse(kvp.second.data());
85                 	if (! semver)
86                 		throw_on_parse_error("invalid slic3r_version_captured semver");
87                 	this->slic3r_version_captured = *semver;
88                 } else if (kvp.first == "comment") {
89                 	this->comment = kvp.second.data();
90                 } else if (kvp.first == "reason") {
91                 	std::string rsn = kvp.second.data();
92                 	if (rsn == "upgrade")
93                 		this->reason = SNAPSHOT_UPGRADE;
94                 	else if (rsn == "downgrade")
95                 		this->reason = SNAPSHOT_DOWNGRADE;
96                     else if (rsn == "before_rollback")
97                         this->reason = SNAPSHOT_BEFORE_ROLLBACK;
98                 	else if (rsn == "user")
99                 		this->reason = SNAPSHOT_USER;
100                 	else
101                 		this->reason = SNAPSHOT_UNKNOWN;
102                 }
103             }
104     	} else if (section.first == "presets") {
105             // Load the names of the active presets.
106             for (auto &kvp : section.second) {
107                 if (kvp.first == "print") {
108                     this->print = kvp.second.data();
109                 } else if (kvp.first == "sla_print") {
110                     this->sla_print = kvp.second.data();
111                 } else if (boost::starts_with(kvp.first, "filament")) {
112                     int idx = 0;
113                     if (kvp.first == "filament" || sscanf(kvp.first.c_str(), "filament_%d", &idx) == 1) {
114                         if (int(this->filaments.size()) <= idx)
115                             this->filaments.resize(idx + 1, std::string());
116                         this->filaments[idx] = kvp.second.data();
117                     }
118                 } else if (kvp.first == "sla_material") {
119                     this->sla_material = kvp.second.data();
120                 } else if (kvp.first == "printer") {
121                     this->printer = kvp.second.data();
122                 } else if (kvp.first == "physical_printer") {
123                     this->physical_printer = kvp.second.data();
124                 }
125             }
126     	} else if (boost::starts_with(section.first, group_name_vendor) && section.first.size() > group_name_vendor.size()) {
127     		// Vendor specific section.
128             VendorConfig vc;
129             vc.name = section.first.substr(group_name_vendor.size());
130             for (auto &kvp : section.second) {
131             	if (kvp.first == "version" || kvp.first == "min_slic3r_version" || kvp.first == "max_slic3r_version") {
132             		// Version of the vendor specific config bundle bundled with this snapshot.
133                 	auto semver = Semver::parse(kvp.second.data());
134                 	if (! semver)
135                 		throw_on_parse_error("invalid " + kvp.first + " format for " + section.first);
136 					if (kvp.first == "version")
137                 		vc.version.config_version = *semver;
138                 	else if (kvp.first == "min_slic3r_version")
139                 		vc.version.min_slic3r_version = *semver;
140                 	else
141                 		vc.version.max_slic3r_version = *semver;
142             	} else if (boost::starts_with(kvp.first, key_prefix_model) && kvp.first.size() > key_prefix_model.size()) {
143                     // Parse the printer variants installed for the current model.
144                     auto &set_variants = vc.models_variants_installed[kvp.first.substr(key_prefix_model.size())];
145                     std::vector<std::string> variants;
146                     if (unescape_strings_cstyle(kvp.second.data(), variants))
147                         for (auto &variant : variants)
148                             set_variants.insert(std::move(variant));
149                 }
150 			}
151 			this->vendor_configs.emplace_back(std::move(vc));
152         }
153     }
154     // Sort the vendors lexicographically.
155     std::sort(this->vendor_configs.begin(), this->vendor_configs.begin(),
156         [](const VendorConfig &cfg1, const VendorConfig &cfg2) { return cfg1.name < cfg2.name; });
157 }
158 
reason_string(const Snapshot::Reason reason)159 static std::string reason_string(const Snapshot::Reason reason)
160 {
161     switch (reason) {
162     case Snapshot::SNAPSHOT_UPGRADE:
163         return "upgrade";
164     case Snapshot::SNAPSHOT_DOWNGRADE:
165         return "downgrade";
166     case Snapshot::SNAPSHOT_BEFORE_ROLLBACK:
167         return "before_rollback";
168     case Snapshot::SNAPSHOT_USER:
169         return "user";
170     case Snapshot::SNAPSHOT_UNKNOWN:
171     default:
172         return "unknown";
173     }
174 }
175 
save_ini(const std::string & path)176 void Snapshot::save_ini(const std::string &path)
177 {
178 	boost::nowide::ofstream c;
179     c.open(path, std::ios::out | std::ios::trunc);
180     c << "# " << Slic3r::header_slic3r_generated() << std::endl;
181 
182     // Export the common "snapshot".
183 	c << std::endl << "[snapshot]" << std::endl;
184 	c << "id = " << this->id << std::endl;
185 	c << "time_captured = " << Slic3r::Utils::iso_utc_timestamp(this->time_captured) << std::endl;
186 	c << "slic3r_version_captured = " << this->slic3r_version_captured.to_string() << std::endl;
187 	c << "comment = " << this->comment << std::endl;
188 	c << "reason = " << reason_string(this->reason) << std::endl;
189 
190     // Export the active presets at the time of the snapshot.
191 	c << std::endl << "[presets]" << std::endl;
192 	c << "print = " << this->print << std::endl;
193     c << "sla_print = " << this->sla_print << std::endl;
194 	c << "filament = " << this->filaments.front() << std::endl;
195 	for (size_t i = 1; i < this->filaments.size(); ++ i)
196 		c << "filament_" << std::to_string(i) << " = " << this->filaments[i] << std::endl;
197     c << "sla_material = " << this->sla_material << std::endl;
198 	c << "printer = " << this->printer << std::endl;
199     c << "physical_printer = " << this->physical_printer << std::endl;
200 
201     // Export the vendor configs.
202     for (const VendorConfig &vc : this->vendor_configs) {
203 		c << std::endl << "[Vendor:" << vc.name << "]" << std::endl;
204 		c << "version = " << vc.version.config_version.to_string() << std::endl;
205 		c << "min_slic3r_version = " << vc.version.min_slic3r_version.to_string() << std::endl;
206 		c << "max_slic3r_version = " << vc.version.max_slic3r_version.to_string() << std::endl;
207         // Export installed printer models and their variants.
208         for (const auto &model : vc.models_variants_installed) {
209             if (model.second.size() == 0)
210                 continue;
211             const std::vector<std::string> variants(model.second.begin(), model.second.end());
212             const auto escaped = escape_strings_cstyle(variants);
213             c << "model_" << model.first << " = " << escaped << std::endl;
214         }
215     }
216     c.close();
217 }
218 
export_selections(AppConfig & config) const219 void Snapshot::export_selections(AppConfig &config) const
220 {
221     assert(filaments.size() >= 1);
222     config.clear_section("presets");
223     config.set("presets", "print",     print);
224     config.set("presets", "sla_print", sla_print);
225     config.set("presets", "filament",  filaments.front());
226     for (unsigned i = 1; i < filaments.size(); ++i) {
227         char name[64];
228         sprintf(name, "filament_%u", i);
229         config.set("presets", name, filaments[i]);
230     }
231     config.set("presets", "sla_material",     sla_material);
232     config.set("presets", "printer",          printer);
233     config.set("presets", "physical_printer", physical_printer);
234 }
235 
export_vendor_configs(AppConfig & config) const236 void Snapshot::export_vendor_configs(AppConfig &config) const
237 {
238     std::map<std::string, std::map<std::string, std::set<std::string>>> vendors;
239     for (const VendorConfig &vc : vendor_configs)
240         vendors[vc.name] = vc.models_variants_installed;
241     config.set_vendors(std::move(vendors));
242 }
243 
244 static constexpr auto snapshot_subdirs = { "print", "sla_print", "filament", "sla_material", "printer", "physical_printer", "vendor" };
245 
246 // Perform a deep compare of the active print / sla_print / filament / sla_material / printer / physical_printer / vendor directories.
247 // Return true if the content of the current print / sla_print / filament / sla_material / printer / physical_printer / vendor directories
248 // matches the state stored in this snapshot.
equal_to_active(const AppConfig & app_config) const249 bool Snapshot::equal_to_active(const AppConfig &app_config) const
250 {
251     // 1) Check, whether this snapshot contains the same set of active vendors, printer models and variants
252     // as app_config.
253     {
254         std::set<std::string> matched;
255         for (const VendorConfig &vc : this->vendor_configs) {
256             auto it_vendor_models_variants = app_config.vendors().find(vc.name);
257             if (it_vendor_models_variants == app_config.vendors().end() ||
258                 it_vendor_models_variants->second != vc.models_variants_installed)
259                 // There are more vendors enabled in the snapshot than currently installed.
260                 return false;
261             matched.insert(vc.name);
262         }
263         for (const std::pair<std::string, std::map<std::string, std::set<std::string>>> &v : app_config.vendors())
264             if (matched.find(v.first) == matched.end() && ! v.second.empty())
265                 // There are more vendors currently installed than enabled in the snapshot.
266                 return false;
267     }
268 
269     // 2) Check, whether this snapshot references the same set of ini files as the current state.
270     boost::filesystem::path data_dir     = boost::filesystem::path(Slic3r::data_dir());
271     boost::filesystem::path snapshot_dir = boost::filesystem::path(Slic3r::data_dir()) / SLIC3R_SNAPSHOTS_DIR / this->id;
272     for (const char *subdir : snapshot_subdirs) {
273         boost::filesystem::path path1 = data_dir / subdir;
274         boost::filesystem::path path2 = snapshot_dir / subdir;
275         std::vector<std::string> files1, files2;
276         if (boost::filesystem::is_directory(path1))
277             for (auto &dir_entry : boost::filesystem::directory_iterator(path1))
278                 if (Slic3r::is_ini_file(dir_entry))
279                     files1.emplace_back(dir_entry.path().filename().string());
280         if (boost::filesystem::is_directory(path2))
281             for (auto &dir_entry : boost::filesystem::directory_iterator(path2))
282                 if (Slic3r::is_ini_file(dir_entry))
283                     files2.emplace_back(dir_entry.path().filename().string());
284         std::sort(files1.begin(), files1.end());
285         std::sort(files2.begin(), files2.end());
286         if (files1 != files2)
287             return false;
288         for (const std::string &filename : files1) {
289             FILE *f1 = boost::nowide::fopen((path1 / filename).string().c_str(), "rb");
290             FILE *f2 = boost::nowide::fopen((path2 / filename).string().c_str(), "rb");
291             bool same = true;
292             if (f1 && f2) {
293                 char buf1[4096];
294                 char buf2[4096];
295                 do {
296                     size_t r1 = fread(buf1, 1, 4096, f1);
297                     size_t r2 = fread(buf2, 1, 4096, f2);
298                     if (r1 != r2 || memcmp(buf1, buf2, r1)) {
299                         same = false;
300                         break;
301                     }
302                 } while (! feof(f1) || ! feof(f2));
303             } else
304                 same = false;
305             if (f1)
306                 fclose(f1);
307             if (f2)
308                 fclose(f2);
309             if (! same)
310                 return false;
311         }
312     }
313     return true;
314 }
315 
load_db()316 size_t SnapshotDB::load_db()
317 {
318 	boost::filesystem::path snapshots_dir = SnapshotDB::create_db_dir();
319 
320 	m_snapshots.clear();
321 
322     // Walk over the snapshot directories and load their index.
323     std::string errors_cummulative;
324 	for (auto &dir_entry : boost::filesystem::directory_iterator(snapshots_dir))
325         if (boost::filesystem::is_directory(dir_entry.status())) {
326         	// Try to read "snapshot.ini".
327             boost::filesystem::path path_ini = dir_entry.path() / SLIC3R_SNAPSHOT_FILE;
328             Snapshot 			    snapshot;
329             try {
330             	snapshot.load_ini(path_ini.string());
331             } catch (const std::runtime_error &err) {
332                 errors_cummulative += err.what();
333                 errors_cummulative += "\n";
334                 continue;
335 			}
336             // Check that the name of the snapshot directory matches the snapshot id stored in the snapshot.ini file.
337             if (dir_entry.path().filename().string() != snapshot.id) {
338             	errors_cummulative += std::string("Snapshot ID ") + snapshot.id + " does not match the snapshot directory " + dir_entry.path().filename().string() + "\n";
339             	continue;
340             }
341             m_snapshots.emplace_back(std::move(snapshot));
342         }
343     // Sort the snapshots by their date/time.
344     std::sort(m_snapshots.begin(), m_snapshots.end(), [](const Snapshot &s1, const Snapshot &s2) { return s1.time_captured < s2.time_captured; });
345     if (! errors_cummulative.empty())
346         throw Slic3r::RuntimeError(errors_cummulative);
347     return m_snapshots.size();
348 }
349 
update_slic3r_versions(std::vector<Index> & index_db)350 void SnapshotDB::update_slic3r_versions(std::vector<Index> &index_db)
351 {
352 	for (Snapshot &snapshot : m_snapshots) {
353 		for (Snapshot::VendorConfig &vendor_config : snapshot.vendor_configs) {
354 			auto it = std::find_if(index_db.begin(), index_db.end(), [&vendor_config](const Index &idx) { return idx.vendor() == vendor_config.name; });
355 			if (it != index_db.end()) {
356 				Index::const_iterator it_version = it->find(vendor_config.version.config_version);
357 				if (it_version != it->end()) {
358 					vendor_config.version.min_slic3r_version = it_version->min_slic3r_version;
359 					vendor_config.version.max_slic3r_version = it_version->max_slic3r_version;
360 				}
361 			}
362 		}
363 	}
364 }
365 
copy_config_dir_single_level(const boost::filesystem::path & path_src,const boost::filesystem::path & path_dst)366 static void copy_config_dir_single_level(const boost::filesystem::path &path_src, const boost::filesystem::path &path_dst)
367 {
368     if (! boost::filesystem::is_directory(path_dst) &&
369         ! boost::filesystem::create_directory(path_dst))
370         throw Slic3r::RuntimeError(std::string("PrusaSlicer was unable to create a directory at ") + path_dst.string());
371 
372     for (auto &dir_entry : boost::filesystem::directory_iterator(path_src))
373         if (Slic3r::is_ini_file(dir_entry))
374             if (std::string error_message; copy_file(dir_entry.path().string(), (path_dst / dir_entry.path().filename()).string(), error_message, false) != SUCCESS)
375                 throw Slic3r::RuntimeError(format("Failed copying \"%1%\" to \"%2%\": %3%", path_src.string(), path_dst.string(), error_message));
376 }
377 
delete_existing_ini_files(const boost::filesystem::path & path)378 static void delete_existing_ini_files(const boost::filesystem::path &path)
379 {
380     if (! boost::filesystem::is_directory(path))
381     	return;
382     for (auto &dir_entry : boost::filesystem::directory_iterator(path))
383         if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
384 		    boost::filesystem::remove(dir_entry.path());
385 }
386 
take_snapshot(const AppConfig & app_config,Snapshot::Reason reason,const std::string & comment)387 const Snapshot&	SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment)
388 {
389 	boost::filesystem::path data_dir        = boost::filesystem::path(Slic3r::data_dir());
390 	boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir();
391 
392 	// 1) Prepare the snapshot structure.
393 	Snapshot snapshot;
394 	// Snapshot header.
395 	snapshot.time_captured 			 = Slic3r::Utils::get_current_time_utc();
396 	snapshot.id 					 = Slic3r::Utils::iso_utc_timestamp(snapshot.time_captured);
397 	snapshot.slic3r_version_captured = Slic3r::SEMVER;
398 	snapshot.comment 				 = comment;
399 	snapshot.reason 				 = reason;
400 	// Active presets at the time of the snapshot.
401     snapshot.print                   = app_config.get("presets", "print");
402     snapshot.sla_print               = app_config.get("presets", "sla_print");
403     snapshot.filaments.emplace_back(app_config.get("presets", "filament"));
404     snapshot.sla_material            = app_config.get("presets", "sla_material");
405     snapshot.printer                 = app_config.get("presets", "printer");
406     snapshot.physical_printer        = app_config.get("presets", "physical_printer");
407     for (unsigned i = 1; i < 1000; ++ i) {
408         char name[64];
409         sprintf(name, "filament_%u", i);
410         if (! app_config.has("presets", name))
411             break;
412         snapshot.filaments.emplace_back(app_config.get("presets", name));
413     }
414     // Vendor specific config bundles and installed printers.
415     for (const std::pair<std::string, std::map<std::string, std::set<std::string>>> &vendor : app_config.vendors()) {
416         Snapshot::VendorConfig cfg;
417         cfg.name = vendor.first;
418         cfg.models_variants_installed = vendor.second;
419         for (auto it = cfg.models_variants_installed.begin(); it != cfg.models_variants_installed.end();)
420             if (it->second.empty())
421                 cfg.models_variants_installed.erase(it ++);
422             else
423                 ++ it;
424         // Read the active config bundle, parse the config version.
425         PresetBundle bundle;
426         bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly, ForwardCompatibilitySubstitutionRule::EnableSilent);
427         for (const auto &vp : bundle.vendors)
428             if (vp.second.id == cfg.name)
429                 cfg.version.config_version = vp.second.config_version;
430         // Fill-in the min/max slic3r version from the config index, if possible.
431         try {
432             // Load the config index for the vendor.
433             Index index;
434             index.load(data_dir / "vendor" / (cfg.name + ".idx"));
435             auto it = index.find(cfg.version.config_version);
436             if (it != index.end()) {
437                 cfg.version.min_slic3r_version = it->min_slic3r_version;
438                 cfg.version.max_slic3r_version = it->max_slic3r_version;
439             }
440         } catch (const std::runtime_error & /* err */) {
441         }
442         snapshot.vendor_configs.emplace_back(std::move(cfg));
443     }
444 
445 	boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
446 
447     try {
448 	    boost::filesystem::create_directory(snapshot_dir);
449 
450         // Backup the presets.
451         for (const char *subdir : snapshot_subdirs)
452     	    copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
453         snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
454         assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
455         m_snapshots.emplace_back(std::move(snapshot));
456     } catch (...) {
457         if (boost::filesystem::is_directory(snapshot_dir)) {
458             try {
459                 // Clean up partially copied snapshot.
460                 boost::filesystem::remove_all(snapshot_dir);
461             } catch (...) {
462                 BOOST_LOG_TRIVIAL(error) << "Failed taking snapshot and failed removing the snapshot directory " << snapshot_dir;
463             }
464         }
465         throw;
466     }
467     return m_snapshots.back();
468 }
469 
restore_snapshot(const std::string & id,AppConfig & app_config)470 const Snapshot& SnapshotDB::restore_snapshot(const std::string &id, AppConfig &app_config)
471 {
472 	for (const Snapshot &snapshot : m_snapshots)
473 		if (snapshot.id == id) {
474 			this->restore_snapshot(snapshot, app_config);
475 			return snapshot;
476 		}
477 	throw Slic3r::RuntimeError(std::string("Snapshot with id " + id + " was not found."));
478 }
479 
restore_snapshot(const Snapshot & snapshot,AppConfig & app_config)480 void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_config)
481 {
482 	boost::filesystem::path data_dir        = boost::filesystem::path(Slic3r::data_dir());
483 	boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir();
484     boost::filesystem::path snapshot_dir 	= snapshot_db_dir / snapshot.id;
485     // Remove existing ini files and restore the ini files from the snapshot.
486     for (const char *subdir : snapshot_subdirs) {
487         boost::filesystem::path src = snapshot_dir / subdir;
488         boost::filesystem::path dst = data_dir / subdir;
489 		delete_existing_ini_files(dst);
490         if (boost::filesystem::is_directory(src))
491     	    copy_config_dir_single_level(src, dst);
492     }
493     // Update AppConfig with the selections of the print / sla_print / filament / sla_material / printer profiles
494     // and about the installed printer types and variants.
495     snapshot.export_selections(app_config);
496     snapshot.export_vendor_configs(app_config);
497 }
498 
is_on_snapshot(AppConfig & app_config) const499 bool SnapshotDB::is_on_snapshot(AppConfig &app_config) const
500 {
501     // Is the "on_snapshot" configuration value set?
502     std::string on_snapshot = app_config.get("on_snapshot");
503     if (on_snapshot.empty())
504         // No, we are not on a snapshot.
505         return false;
506     // Is the "on_snapshot" equal to the current configuration state?
507     auto it_snapshot = this->snapshot(on_snapshot);
508     if (it_snapshot != this->end() && it_snapshot->equal_to_active(app_config))
509         // Yes, we are on the snapshot.
510         return true;
511     // No, we are no more on a snapshot. Reset the state.
512     app_config.set("on_snapshot", "");
513     return false;
514 }
515 
snapshot_with_vendor_preset(const std::string & vendor_name,const Semver & config_version)516 SnapshotDB::const_iterator SnapshotDB::snapshot_with_vendor_preset(const std::string &vendor_name, const Semver &config_version)
517 {
518     auto it_found = m_snapshots.end();
519     Snapshot::VendorConfig key;
520     key.name = vendor_name;
521     for (auto it = m_snapshots.begin(); it != m_snapshots.end(); ++ it) {
522         const Snapshot &snapshot = *it;
523         auto it_vendor_config = std::lower_bound(snapshot.vendor_configs.begin(), snapshot.vendor_configs.end(),
524             key, [](const Snapshot::VendorConfig &cfg1, const Snapshot::VendorConfig &cfg2) { return cfg1.name < cfg2.name; });
525         if (it_vendor_config != snapshot.vendor_configs.end() && it_vendor_config->name == vendor_name &&
526             config_version == it_vendor_config->version.config_version) {
527             // Vendor config found with the correct version.
528             // Save it, but continue searching, as we want the newest snapshot.
529             it_found = it;
530         }
531     }
532     return it_found;
533 }
534 
snapshot(const std::string & id) const535 SnapshotDB::const_iterator SnapshotDB::snapshot(const std::string &id) const
536 {
537     for (const_iterator it = m_snapshots.begin(); it != m_snapshots.end(); ++ it)
538         if (it->id == id)
539             return it;
540     return m_snapshots.end();
541 }
542 
create_db_dir()543 boost::filesystem::path SnapshotDB::create_db_dir()
544 {
545     boost::filesystem::path data_dir 	  = boost::filesystem::path(Slic3r::data_dir());
546     boost::filesystem::path snapshots_dir = data_dir / SLIC3R_SNAPSHOTS_DIR;
547     for (const boost::filesystem::path &path : { data_dir, snapshots_dir }) {
548 		boost::filesystem::path subdir = path;
549         subdir.make_preferred();
550         if (! boost::filesystem::is_directory(subdir) &&
551             ! boost::filesystem::create_directory(subdir))
552             throw Slic3r::RuntimeError(std::string("Slic3r was unable to create a directory at ") + subdir.string());
553     }
554     return snapshots_dir;
555 }
556 
singleton()557 SnapshotDB& SnapshotDB::singleton()
558 {
559 	static SnapshotDB instance;
560 	static bool       loaded = false;
561 	if (! loaded) {
562 		try {
563 			loaded = true;
564 			// Load the snapshot database.
565 			instance.load_db();
566 			// Load the vendor specific configuration indices.
567 			std::vector<Index> index_db = Index::load_db();
568 			// Update the min / max slic3r versions compatible with the configurations stored inside the snapshots
569 			// based on the min / max slic3r versions defined by the vendor specific config indices.
570 			instance.update_slic3r_versions(index_db);
571 		} catch (std::exception & /* ex */) {
572 		}
573 	}
574 	return instance;
575 }
576 
take_config_snapshot_report_error(const AppConfig & app_config,Snapshot::Reason reason,const std::string & comment)577 const Snapshot* take_config_snapshot_report_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment)
578 {
579     try {
580         return &SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
581     } catch (std::exception &err) {
582         show_error(static_cast<wxWindow*>(wxGetApp().mainframe),
583             _L("Taking a configuration snapshot failed.") + "\n\n" + from_u8(err.what()));
584         return nullptr;
585     }
586 }
587 
take_config_snapshot_cancel_on_error(const AppConfig & app_config,Snapshot::Reason reason,const std::string & comment,const std::string & message)588 bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment, const std::string &message)
589 {
590     try {
591         SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
592         return true;
593     } catch (std::exception &err) {
594         wxRichMessageDialog dlg(static_cast<wxWindow*>(wxGetApp().mainframe),
595             _L("PrusaSlicer has encountered an error while taking a configuration snapshot.") + "\n\n" + from_u8(err.what()) + "\n\n" + from_u8(message),
596             _L("PrusaSlicer error"),
597             wxYES_NO);
598         dlg.SetYesNoLabels(_L("Continue"), _L("Abort"));
599         return dlg.ShowModal() == wxID_YES;
600     }
601 }
602 
603 } // namespace Config
604 } // namespace GUI
605 } // namespace Slic3r
606