Initial implementation of configuration snapshotting.
This commit is contained in:
parent
2b8da333ef
commit
670061ac33
@ -54,6 +54,7 @@ add_library(libslic3r STATIC
|
|||||||
${LIBDIR}/libslic3r/ExtrusionEntityCollection.hpp
|
${LIBDIR}/libslic3r/ExtrusionEntityCollection.hpp
|
||||||
${LIBDIR}/libslic3r/ExtrusionSimulator.cpp
|
${LIBDIR}/libslic3r/ExtrusionSimulator.cpp
|
||||||
${LIBDIR}/libslic3r/ExtrusionSimulator.hpp
|
${LIBDIR}/libslic3r/ExtrusionSimulator.hpp
|
||||||
|
${LIBDIR}/libslic3r/FileParserError.hpp
|
||||||
${LIBDIR}/libslic3r/Fill/Fill.cpp
|
${LIBDIR}/libslic3r/Fill/Fill.cpp
|
||||||
${LIBDIR}/libslic3r/Fill/Fill.hpp
|
${LIBDIR}/libslic3r/Fill/Fill.hpp
|
||||||
${LIBDIR}/libslic3r/Fill/Fill3DHoneycomb.cpp
|
${LIBDIR}/libslic3r/Fill/Fill3DHoneycomb.cpp
|
||||||
@ -201,6 +202,10 @@ add_library(libslic3r_gui STATIC
|
|||||||
${LIBDIR}/slic3r/GUI/wxExtensions.hpp
|
${LIBDIR}/slic3r/GUI/wxExtensions.hpp
|
||||||
${LIBDIR}/slic3r/GUI/BonjourDialog.cpp
|
${LIBDIR}/slic3r/GUI/BonjourDialog.cpp
|
||||||
${LIBDIR}/slic3r/GUI/BonjourDialog.hpp
|
${LIBDIR}/slic3r/GUI/BonjourDialog.hpp
|
||||||
|
${LIBDIR}/slic3r/Config/Snapshot.cpp
|
||||||
|
${LIBDIR}/slic3r/Config/Snapshot.hpp
|
||||||
|
${LIBDIR}/slic3r/Config/Version.cpp
|
||||||
|
${LIBDIR}/slic3r/Config/Version.hpp
|
||||||
${LIBDIR}/slic3r/Utils/ASCIIFolding.cpp
|
${LIBDIR}/slic3r/Utils/ASCIIFolding.cpp
|
||||||
${LIBDIR}/slic3r/Utils/ASCIIFolding.hpp
|
${LIBDIR}/slic3r/Utils/ASCIIFolding.hpp
|
||||||
${LIBDIR}/slic3r/Utils/Http.cpp
|
${LIBDIR}/slic3r/Utils/Http.cpp
|
||||||
|
48
xs/src/libslic3r/FileParserError.hpp
Normal file
48
xs/src/libslic3r/FileParserError.hpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#ifndef slic3r_FileParserError_hpp_
|
||||||
|
#define slic3r_FileParserError_hpp_
|
||||||
|
|
||||||
|
#include "libslic3r.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// Generic file parser error, mostly copied from boost::property_tree::file_parser_error
|
||||||
|
class file_parser_error: public std::runtime_error
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
file_parser_error(const std::string &msg, const std::string &file, unsigned long line = 0) :
|
||||||
|
std::runtime_error(format_what(msg, file, line)),
|
||||||
|
m_message(msg), m_filename(file), m_line(line) {}
|
||||||
|
// gcc 3.4.2 complains about lack of throw specifier on compiler
|
||||||
|
// generated dtor
|
||||||
|
~file_parser_error() throw() {}
|
||||||
|
|
||||||
|
// Get error message (without line and file - use what() to get full message)
|
||||||
|
std::string message() const { return m_message; }
|
||||||
|
// Get error filename
|
||||||
|
std::string filename() const { return m_filename; }
|
||||||
|
// Get error line number
|
||||||
|
unsigned long line() const { return m_line; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_message;
|
||||||
|
std::string m_filename;
|
||||||
|
unsigned long m_line;
|
||||||
|
|
||||||
|
// Format error message to be returned by std::runtime_error::what()
|
||||||
|
static std::string format_what(const std::string &msg, const std::string &file, unsigned long l)
|
||||||
|
{
|
||||||
|
std::stringstream stream;
|
||||||
|
stream << (file.empty() ? "<unspecified file>" : file.c_str());
|
||||||
|
if (l > 0)
|
||||||
|
stream << '(' << l << ')';
|
||||||
|
stream << ": " << msg;
|
||||||
|
return stream.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}; // Slic3r
|
||||||
|
|
||||||
|
#endif // slic3r_FileParserError_hpp_
|
@ -60,9 +60,6 @@ extern std::string timestamp_str();
|
|||||||
// to be placed at the top of Slic3r generated files.
|
// to be placed at the top of Slic3r generated files.
|
||||||
inline std::string header_slic3r_generated() { return std::string("generated by " SLIC3R_FORK_NAME " " SLIC3R_VERSION " " ) + timestamp_str(); }
|
inline std::string header_slic3r_generated() { return std::string("generated by " SLIC3R_FORK_NAME " " SLIC3R_VERSION " " ) + timestamp_str(); }
|
||||||
|
|
||||||
// Encode a file into a multi-part HTTP response with a given boundary.
|
|
||||||
std::string octoprint_encode_file_send_request_content(const char *path, bool select, bool print, const char *boundary);
|
|
||||||
|
|
||||||
// Compute the next highest power of 2 of 32-bit v
|
// Compute the next highest power of 2 of 32-bit v
|
||||||
// http://graphics.stanford.edu/~seander/bithacks.html
|
// http://graphics.stanford.edu/~seander/bithacks.html
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
@ -263,7 +263,6 @@ namespace PerlUtils {
|
|||||||
std::string timestamp_str()
|
std::string timestamp_str()
|
||||||
{
|
{
|
||||||
const auto now = boost::posix_time::second_clock::local_time();
|
const auto now = boost::posix_time::second_clock::local_time();
|
||||||
const auto date = now.date();
|
|
||||||
char buf[2048];
|
char buf[2048];
|
||||||
sprintf(buf, "on %04d-%02d-%02d at %02d:%02d:%02d",
|
sprintf(buf, "on %04d-%02d-%02d at %02d:%02d:%02d",
|
||||||
// Local date in an ANSII format.
|
// Local date in an ANSII format.
|
||||||
@ -272,31 +271,4 @@ std::string timestamp_str()
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string octoprint_encode_file_send_request_content(const char *cpath, bool select, bool print, const char *boundary)
|
|
||||||
{
|
|
||||||
// Read the complete G-code string into a string buffer.
|
|
||||||
// It will throw if the file cannot be open or read.
|
|
||||||
std::stringstream str_stream;
|
|
||||||
{
|
|
||||||
boost::nowide::ifstream ifs(cpath);
|
|
||||||
str_stream << ifs.rdbuf();
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::filesystem::path path(cpath);
|
|
||||||
std::string request = boundary + '\n';
|
|
||||||
request += "Content-Disposition: form-data; name=\"";
|
|
||||||
request += path.stem().string() + "\"; filename=\"" + path.filename().string() + "\"\n";
|
|
||||||
request += "Content-Type: application/octet-stream\n\n";
|
|
||||||
request += str_stream.str();
|
|
||||||
request += boundary + '\n';
|
|
||||||
request += "Content-Disposition: form-data; name=\"select\"\n\n";
|
|
||||||
request += select ? "true\n" : "false\n";
|
|
||||||
request += boundary + '\n';
|
|
||||||
request += "Content-Disposition: form-data; name=\"print\"\n\n";
|
|
||||||
request += print ? "true\n" : "false\n";
|
|
||||||
request += boundary + '\n';
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
}; // namespace Slic3r
|
}; // namespace Slic3r
|
||||||
|
308
xs/src/slic3r/Config/Snapshot.cpp
Normal file
308
xs/src/slic3r/Config/Snapshot.cpp
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
#include "Snapshot.hpp"
|
||||||
|
#include "../GUI/AppConfig.hpp"
|
||||||
|
#include "../Utils/Time.hpp"
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
#include <boost/algorithm/string/trim.hpp>
|
||||||
|
#include <boost/nowide/fstream.hpp>
|
||||||
|
#include <boost/property_tree/ini_parser.hpp>
|
||||||
|
#include <boost/property_tree/ptree.hpp>
|
||||||
|
|
||||||
|
#include "../../libslic3r/libslic3r.h"
|
||||||
|
#include "../../libslic3r/Config.hpp"
|
||||||
|
#include "../../libslic3r/FileParserError.hpp"
|
||||||
|
#include "../../libslic3r/Utils.hpp"
|
||||||
|
|
||||||
|
#define SLIC3R_SNAPSHOTS_DIR "snapshots"
|
||||||
|
#define SLIC3R_SNAPSHOT_FILE "snapshot.ini"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace GUI {
|
||||||
|
namespace Config {
|
||||||
|
|
||||||
|
void Snapshot::clear()
|
||||||
|
{
|
||||||
|
this->id.clear();
|
||||||
|
this->time_captured = 0;
|
||||||
|
this->slic3r_version_captured = Semver::invalid();
|
||||||
|
this->comment.clear();
|
||||||
|
this->reason = SNAPSHOT_UNKNOWN;
|
||||||
|
this->print.clear();
|
||||||
|
this->filaments.clear();
|
||||||
|
this->printer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snapshot::load_ini(const std::string &path)
|
||||||
|
{
|
||||||
|
this->clear();
|
||||||
|
|
||||||
|
auto throw_on_parse_error = [&path](const std::string &msg) {
|
||||||
|
throw file_parser_error(std::string("Failed loading the snapshot file. Reason: ") + msg, path);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load the snapshot.ini file.
|
||||||
|
boost::property_tree::ptree tree;
|
||||||
|
try {
|
||||||
|
boost::nowide::ifstream ifs(path);
|
||||||
|
boost::property_tree::read_ini(ifs, tree);
|
||||||
|
} catch (const std::ifstream::failure &err) {
|
||||||
|
throw file_parser_error(std::string("The snapshot file cannot be loaded. Reason: ") + err.what(), path);
|
||||||
|
} catch (const std::runtime_error &err) {
|
||||||
|
throw_on_parse_error(err.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse snapshot.ini
|
||||||
|
std::string group_name_vendor = "Vendor:";
|
||||||
|
std::string key_filament = "filament";
|
||||||
|
for (auto §ion : tree) {
|
||||||
|
if (section.first == "snapshot") {
|
||||||
|
// Parse the common section.
|
||||||
|
for (auto &kvp : section.second) {
|
||||||
|
if (kvp.first == "id")
|
||||||
|
this->id = kvp.second.data();
|
||||||
|
else if (kvp.first == "time_captured") {
|
||||||
|
this->time_captured = Slic3r::Utils::parse_time_ISO8601Z(kvp.second.data());
|
||||||
|
if (this->time_captured == (time_t)-1)
|
||||||
|
throw_on_parse_error("invalid timestamp");
|
||||||
|
} else if (kvp.first == "slic3r_version_captured") {
|
||||||
|
auto semver = Semver::parse(kvp.second.data());
|
||||||
|
if (! semver)
|
||||||
|
throw_on_parse_error("invalid slic3r_version_captured semver");
|
||||||
|
this->slic3r_version_captured = *semver;
|
||||||
|
} else if (kvp.first == "comment") {
|
||||||
|
this->comment = kvp.second.data();
|
||||||
|
} else if (kvp.first == "reason") {
|
||||||
|
std::string rsn = kvp.second.data();
|
||||||
|
if (rsn == "upgrade")
|
||||||
|
this->reason = SNAPSHOT_UPGRADE;
|
||||||
|
else if (rsn == "downgrade")
|
||||||
|
this->reason = SNAPSHOT_DOWNGRADE;
|
||||||
|
else if (rsn == "user")
|
||||||
|
this->reason = SNAPSHOT_USER;
|
||||||
|
else
|
||||||
|
this->reason = SNAPSHOT_UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (section.first == "presets") {
|
||||||
|
// Load the names of the active presets.
|
||||||
|
for (auto &kvp : section.second) {
|
||||||
|
if (kvp.first == "print") {
|
||||||
|
this->print = kvp.second.data();
|
||||||
|
} else if (boost::starts_with(kvp.first, "filament")) {
|
||||||
|
int idx = 0;
|
||||||
|
if (kvp.first == "filament" || sscanf(kvp.first.c_str(), "filament_%d", &idx) == 1) {
|
||||||
|
if (int(this->filaments.size()) <= idx)
|
||||||
|
this->filaments.resize(idx + 1, std::string());
|
||||||
|
this->filaments[idx] = kvp.second.data();
|
||||||
|
}
|
||||||
|
} else if (kvp.first == "printer") {
|
||||||
|
this->printer = kvp.second.data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (boost::starts_with(section.first, group_name_vendor) && section.first.size() > group_name_vendor.size()) {
|
||||||
|
// Vendor specific section.
|
||||||
|
VendorConfig vc;
|
||||||
|
vc.name = section.first.substr(group_name_vendor.size());
|
||||||
|
for (auto &kvp : section.second) {
|
||||||
|
if (boost::starts_with(kvp.first, "model_")) {
|
||||||
|
//model:MK2S = 0.4;xxx
|
||||||
|
//model:MK3 = 0.4;xxx
|
||||||
|
} else if (kvp.first == "version" || kvp.first == "min_slic3r_version" || kvp.first == "max_slic3r_version") {
|
||||||
|
// Version of the vendor specific config bundle bundled with this snapshot.
|
||||||
|
auto semver = Semver::parse(kvp.second.data());
|
||||||
|
if (! semver)
|
||||||
|
throw_on_parse_error("invalid " + kvp.first + " format for " + section.first);
|
||||||
|
if (kvp.first == "version")
|
||||||
|
vc.version = *semver;
|
||||||
|
else if (kvp.first == "min_slic3r_version")
|
||||||
|
vc.min_slic3r_version = *semver;
|
||||||
|
else
|
||||||
|
vc.max_slic3r_version = *semver;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snapshot::save_ini(const std::string &path)
|
||||||
|
{
|
||||||
|
boost::nowide::ofstream c;
|
||||||
|
c.open(path, std::ios::out | std::ios::trunc);
|
||||||
|
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
|
||||||
|
|
||||||
|
// Export the common "snapshot".
|
||||||
|
c << std::endl << "[snapshot]" << std::endl;
|
||||||
|
c << "id = " << this->id << std::endl;
|
||||||
|
c << "time_captured = " << Slic3r::Utils::format_time_ISO8601Z(this->time_captured) << std::endl;
|
||||||
|
c << "slic3r_version_captured = " << this->slic3r_version_captured.to_string() << std::endl;
|
||||||
|
c << "comment = " << this->comment << std::endl;
|
||||||
|
c << "reason = " << this->reason << std::endl;
|
||||||
|
|
||||||
|
// Export the active presets at the time of the snapshot.
|
||||||
|
c << std::endl << "[presets]" << std::endl;
|
||||||
|
c << "print = " << this->print << std::endl;
|
||||||
|
c << "filament = " << this->filaments.front() << std::endl;
|
||||||
|
for (size_t i = 1; i < this->filaments.size(); ++ i)
|
||||||
|
c << "filament_" << std::to_string(i) << " = " << this->filaments[i] << std::endl;
|
||||||
|
c << "printer = " << this->printer << std::endl;
|
||||||
|
|
||||||
|
// Export the vendor configs.
|
||||||
|
for (const VendorConfig &vc : this->vendor_configs) {
|
||||||
|
c << std::endl << "[Vendor:" << vc.name << "]" << std::endl;
|
||||||
|
c << "version = " << vc.version.to_string() << std::endl;
|
||||||
|
c << "min_slic3r_version = " << vc.min_slic3r_version.to_string() << std::endl;
|
||||||
|
c << "max_slic3r_version = " << vc.max_slic3r_version.to_string() << std::endl;
|
||||||
|
}
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snapshot::export_selections(AppConfig &config) const
|
||||||
|
{
|
||||||
|
assert(filaments.size() >= 1);
|
||||||
|
config.clear_section("presets");
|
||||||
|
config.set("presets", "print", print);
|
||||||
|
config.set("presets", "filament", filaments.front());
|
||||||
|
for (int i = 1; i < filaments.size(); ++i) {
|
||||||
|
char name[64];
|
||||||
|
sprintf(name, "filament_%d", i);
|
||||||
|
config.set("presets", name, filaments[i]);
|
||||||
|
}
|
||||||
|
config.set("presets", "printer", printer);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SnapshotDB::load_db()
|
||||||
|
{
|
||||||
|
boost::filesystem::path snapshots_dir = SnapshotDB::create_db_dir();
|
||||||
|
|
||||||
|
m_snapshots.clear();
|
||||||
|
|
||||||
|
// Walk over the snapshot directories and load their index.
|
||||||
|
std::string errors_cummulative;
|
||||||
|
for (auto &dir_entry : boost::filesystem::directory_iterator(snapshots_dir))
|
||||||
|
if (boost::filesystem::is_directory(dir_entry.status())) {
|
||||||
|
// Try to read "snapshot.ini".
|
||||||
|
boost::filesystem::path path_ini = dir_entry.path() / SLIC3R_SNAPSHOT_FILE;
|
||||||
|
Snapshot snapshot;
|
||||||
|
try {
|
||||||
|
snapshot.load_ini(path_ini.string());
|
||||||
|
} catch (const std::runtime_error &err) {
|
||||||
|
errors_cummulative += err.what();
|
||||||
|
errors_cummulative += "\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Check that the name of the snapshot directory matches the snapshot id stored in the snapshot.ini file.
|
||||||
|
if (dir_entry.path().filename().string() != snapshot.id) {
|
||||||
|
errors_cummulative += std::string("Snapshot ID ") + snapshot.id + " does not match the snapshot directory " + dir_entry.path().filename().string() + "\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m_snapshots.emplace_back(std::move(snapshot));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! errors_cummulative.empty())
|
||||||
|
throw std::runtime_error(errors_cummulative);
|
||||||
|
return m_snapshots.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copy_config_dir_single_level(const boost::filesystem::path &path_src, const boost::filesystem::path &path_dst)
|
||||||
|
{
|
||||||
|
if (! boost::filesystem::is_directory(path_dst) &&
|
||||||
|
! boost::filesystem::create_directory(path_dst))
|
||||||
|
throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + path_dst.string());
|
||||||
|
|
||||||
|
for (auto &dir_entry : boost::filesystem::directory_iterator(path_src))
|
||||||
|
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
|
||||||
|
boost::filesystem::copy_file(dir_entry.path(), path_dst / dir_entry.path().filename(), boost::filesystem::copy_option::overwrite_if_exists);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void delete_existing_ini_files(const boost::filesystem::path &path)
|
||||||
|
{
|
||||||
|
if (! boost::filesystem::is_directory(path))
|
||||||
|
return;
|
||||||
|
for (auto &dir_entry : boost::filesystem::directory_iterator(path))
|
||||||
|
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
|
||||||
|
boost::filesystem::remove(dir_entry.path());
|
||||||
|
}
|
||||||
|
|
||||||
|
const Snapshot& SnapshotDB::make_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment)
|
||||||
|
{
|
||||||
|
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
|
||||||
|
boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir();
|
||||||
|
|
||||||
|
// 1) Prepare the snapshot structure.
|
||||||
|
Snapshot snapshot;
|
||||||
|
// Snapshot header.
|
||||||
|
snapshot.time_captured = Slic3r::Utils::get_current_time_utc();
|
||||||
|
snapshot.id = Slic3r::Utils::format_time_ISO8601Z(snapshot.time_captured);
|
||||||
|
snapshot.slic3r_version_captured = *Semver::parse(SLIC3R_VERSION);
|
||||||
|
snapshot.comment = comment;
|
||||||
|
snapshot.reason = reason;
|
||||||
|
// Active presets at the time of the snapshot.
|
||||||
|
snapshot.print = app_config.get("presets", "print");
|
||||||
|
snapshot.filaments.emplace_back(app_config.get("presets", "filament"));
|
||||||
|
snapshot.printer = app_config.get("presets", "printer");
|
||||||
|
for (unsigned int i = 1; i < 1000; ++ i) {
|
||||||
|
char name[64];
|
||||||
|
sprintf(name, "filament_%d", i);
|
||||||
|
if (! app_config.has("presets", name))
|
||||||
|
break;
|
||||||
|
snapshot.filaments.emplace_back(app_config.get("presets", name));
|
||||||
|
}
|
||||||
|
// Vendor specific config bundles and installed printers.
|
||||||
|
|
||||||
|
// Backup the presets.
|
||||||
|
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
|
||||||
|
for (const char *subdir : { "print", "filament", "printer", "vendor" })
|
||||||
|
copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
|
||||||
|
snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
|
||||||
|
m_snapshots.emplace_back(std::move(snapshot));
|
||||||
|
return m_snapshots.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SnapshotDB::restore_snapshot(const std::string &id, AppConfig &app_config)
|
||||||
|
{
|
||||||
|
for (const Snapshot &snapshot : m_snapshots)
|
||||||
|
if (snapshot.id == id) {
|
||||||
|
this->restore_snapshot(snapshot, app_config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw std::runtime_error(std::string("Snapshot with id " + id + " was not found."));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_config)
|
||||||
|
{
|
||||||
|
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
|
||||||
|
boost::filesystem::path snapshot_db_dir = SnapshotDB::create_db_dir();
|
||||||
|
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
|
||||||
|
|
||||||
|
// Remove existing ini files and restore the ini files from the snapshot.
|
||||||
|
for (const char *subdir : { "print", "filament", "printer", "vendor" }) {
|
||||||
|
delete_existing_ini_files(data_dir / subdir);
|
||||||
|
copy_config_dir_single_level(snapshot_dir / subdir, data_dir / subdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update app_config from the snapshot.
|
||||||
|
snapshot.export_selections(app_config);
|
||||||
|
|
||||||
|
// Store information about the snapshot.
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::filesystem::path SnapshotDB::create_db_dir()
|
||||||
|
{
|
||||||
|
boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir());
|
||||||
|
boost::filesystem::path snapshots_dir = data_dir / SLIC3R_SNAPSHOTS_DIR;
|
||||||
|
for (const boost::filesystem::path &path : { data_dir, snapshots_dir }) {
|
||||||
|
boost::filesystem::path subdir = path;
|
||||||
|
subdir.make_preferred();
|
||||||
|
if (! boost::filesystem::is_directory(subdir) &&
|
||||||
|
! boost::filesystem::create_directory(subdir))
|
||||||
|
throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + subdir.string());
|
||||||
|
}
|
||||||
|
return snapshots_dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Config
|
||||||
|
} // namespace GUI
|
||||||
|
} // namespace Slic3r
|
106
xs/src/slic3r/Config/Snapshot.hpp
Normal file
106
xs/src/slic3r/Config/Snapshot.hpp
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
#ifndef slic3r_GUI_Snapshot_
|
||||||
|
#define slic3r_GUI_Snapshot_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
|
#include "../Utils/Semver.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class AppConfig;
|
||||||
|
|
||||||
|
namespace GUI {
|
||||||
|
namespace Config {
|
||||||
|
|
||||||
|
// A snapshot contains:
|
||||||
|
// Slic3r.ini
|
||||||
|
// vendor/
|
||||||
|
// print/
|
||||||
|
// filament/
|
||||||
|
// printer/
|
||||||
|
class Snapshot
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Reason {
|
||||||
|
SNAPSHOT_UNKNOWN,
|
||||||
|
SNAPSHOT_UPGRADE,
|
||||||
|
SNAPSHOT_DOWNGRADE,
|
||||||
|
SNAPSHOT_USER,
|
||||||
|
};
|
||||||
|
|
||||||
|
Snapshot() { clear(); }
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
void load_ini(const std::string &path);
|
||||||
|
void save_ini(const std::string &path);
|
||||||
|
|
||||||
|
// Export the print / filament / printer selections to be activated into the AppConfig.
|
||||||
|
void export_selections(AppConfig &config) const;
|
||||||
|
|
||||||
|
// ID of a snapshot should equal to the name of the snapshot directory.
|
||||||
|
// The ID contains the date/time, reason and comment to be human readable.
|
||||||
|
std::string id;
|
||||||
|
std::time_t time_captured;
|
||||||
|
// Which Slic3r version captured this snapshot?
|
||||||
|
Semver slic3r_version_captured = Semver::invalid();
|
||||||
|
// Comment entered by the user at the start of the snapshot capture.
|
||||||
|
std::string comment;
|
||||||
|
Reason reason;
|
||||||
|
|
||||||
|
// Active presets at the time of the snapshot.
|
||||||
|
std::string print;
|
||||||
|
std::vector<std::string> filaments;
|
||||||
|
std::string printer;
|
||||||
|
|
||||||
|
// Annotation of the vendor configuration stored in the snapshot.
|
||||||
|
// This information is displayed to the user and used to decide compatibility
|
||||||
|
// of the configuration stored in the snapshot with the running Slic3r version.
|
||||||
|
struct VendorConfig {
|
||||||
|
// Name of the vendor contained in this snapshot.
|
||||||
|
std::string name;
|
||||||
|
// Version of the vendor config contained in this snapshot.
|
||||||
|
Semver version = Semver::invalid();
|
||||||
|
// Minimum Slic3r version compatible with this vendor configuration.
|
||||||
|
Semver min_slic3r_version = Semver::zero();
|
||||||
|
// Maximum Slic3r version compatible with this vendor configuration, or empty.
|
||||||
|
Semver max_slic3r_version = Semver::inf();
|
||||||
|
};
|
||||||
|
// List of vendor configs contained in this snapshot.
|
||||||
|
std::vector<VendorConfig> vendor_configs;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SnapshotDB
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef std::vector<Snapshot>::const_iterator const_iterator;
|
||||||
|
|
||||||
|
// Load the snapshot database from the snapshots directory.
|
||||||
|
// If the snapshot directory or its parent does not exist yet, it will be created.
|
||||||
|
// Returns a number of snapshots loaded.
|
||||||
|
size_t load_db();
|
||||||
|
|
||||||
|
// Create a snapshot directory, copy the vendor config bundles, user print/filament/printer profiles,
|
||||||
|
// create an index.
|
||||||
|
const Snapshot& make_snapshot(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment);
|
||||||
|
void restore_snapshot(const std::string &id, AppConfig &app_config);
|
||||||
|
void restore_snapshot(const Snapshot &snapshot, AppConfig &app_config);
|
||||||
|
|
||||||
|
const_iterator begin() const { return m_snapshots.begin(); }
|
||||||
|
const_iterator end() const { return m_snapshots.end(); }
|
||||||
|
const std::vector<Snapshot>& snapshots() const { return m_snapshots; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Create the snapshots directory if it does not exist yet.
|
||||||
|
static boost::filesystem::path create_db_dir();
|
||||||
|
|
||||||
|
std::vector<Snapshot> m_snapshots;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Config
|
||||||
|
} // namespace GUI
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif /* slic3r_GUI_Snapshot_ */
|
136
xs/src/slic3r/Config/Version.cpp
Normal file
136
xs/src/slic3r/Config/Version.cpp
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
#include "Version.hpp"
|
||||||
|
|
||||||
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
#include <boost/algorithm/string/trim.hpp>
|
||||||
|
#include <boost/nowide/fstream.hpp>
|
||||||
|
|
||||||
|
#include "../../libslic3r/libslic3r.h"
|
||||||
|
#include "../../libslic3r/Config.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace GUI {
|
||||||
|
namespace Config {
|
||||||
|
|
||||||
|
static boost::optional<Semver> s_current_slic3r_semver = Semver::parse(SLIC3R_VERSION);
|
||||||
|
|
||||||
|
bool Version::is_current_slic3r_supported() const
|
||||||
|
{
|
||||||
|
return this->is_slic3r_supported(*s_current_slic3r_semver);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline char* left_trim(char *c)
|
||||||
|
{
|
||||||
|
for (; *c == ' ' || *c == '\t'; ++ c);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline char* right_trim(char *start)
|
||||||
|
{
|
||||||
|
char *end = start + strlen(start) - 1;
|
||||||
|
for (; end >= start && (*end == ' ' || *end == '\t'); -- end);
|
||||||
|
*(++ end) = 0;
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string unquote_value(char *value, char *end, const std::string &path, int idx_line)
|
||||||
|
{
|
||||||
|
std::string svalue;
|
||||||
|
if (value == end) {
|
||||||
|
// Empty string is a valid string.
|
||||||
|
} else if (*value == '"') {
|
||||||
|
if (++ value < -- end || *end != '"')
|
||||||
|
throw file_parser_error("String not enquoted correctly", path, idx_line);
|
||||||
|
*end = 0;
|
||||||
|
if (! unescape_string_cstyle(value, svalue))
|
||||||
|
throw file_parser_error("Invalid escape sequence inside a quoted string", path, idx_line);
|
||||||
|
}
|
||||||
|
return svalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string unquote_version_comment(char *value, char *end, const std::string &path, int idx_line)
|
||||||
|
{
|
||||||
|
std::string svalue;
|
||||||
|
if (value == end) {
|
||||||
|
// Empty string is a valid string.
|
||||||
|
} else if (*value == '"') {
|
||||||
|
if (++ value < -- end || *end != '"')
|
||||||
|
throw file_parser_error("Version comment not enquoted correctly", path, idx_line);
|
||||||
|
*end = 0;
|
||||||
|
if (! unescape_string_cstyle(value, svalue))
|
||||||
|
throw file_parser_error("Invalid escape sequence inside a quoted version comment", path, idx_line);
|
||||||
|
}
|
||||||
|
return svalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Index::load(const std::string &path)
|
||||||
|
{
|
||||||
|
m_configs.clear();
|
||||||
|
|
||||||
|
boost::nowide::ifstream ifs(path);
|
||||||
|
std::string line;
|
||||||
|
size_t idx_line = 0;
|
||||||
|
Version ver;
|
||||||
|
while (std::getline(ifs, line)) {
|
||||||
|
++ idx_line;
|
||||||
|
// Skip the initial white spaces.
|
||||||
|
char *key = left_trim(const_cast<char*>(line.data()));
|
||||||
|
// Right trim the line.
|
||||||
|
char *end = right_trim(key);
|
||||||
|
// Keyword may only contain alphanumeric characters. Semantic version may in addition contain "+.-".
|
||||||
|
char *key_end = key;
|
||||||
|
bool maybe_semver = false;
|
||||||
|
for (;; ++ key) {
|
||||||
|
if (strchr("+.-", *key) != nullptr)
|
||||||
|
maybe_semver = true;
|
||||||
|
else if (! std::isalnum(*key))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (*key != 0 && *key != ' ' && *key != '\t' && *key != '=')
|
||||||
|
throw file_parser_error("Invalid keyword or semantic version", path, idx_line);
|
||||||
|
*key_end = 0;
|
||||||
|
boost::optional<Semver> semver;
|
||||||
|
if (maybe_semver)
|
||||||
|
semver = Semver::parse(key);
|
||||||
|
char *value = left_trim(key_end);
|
||||||
|
if (*value == '=') {
|
||||||
|
if (semver)
|
||||||
|
throw file_parser_error("Key cannot be a semantic version", path, idx_line);
|
||||||
|
// Verify validity of the key / value pair.
|
||||||
|
std::string svalue = unquote_value(left_trim(++ value), end, path, idx_line);
|
||||||
|
if (key == "min_sic3r_version" || key == "max_slic3r_version") {
|
||||||
|
if (! svalue.empty())
|
||||||
|
semver = Semver::parse(key);
|
||||||
|
if (! semver)
|
||||||
|
throw file_parser_error(std::string(key) + " must referece a valid semantic version", path, idx_line);
|
||||||
|
if (key == "min_sic3r_version")
|
||||||
|
ver.min_slic3r_version = *semver;
|
||||||
|
else
|
||||||
|
ver.max_slic3r_version = *semver;
|
||||||
|
} else {
|
||||||
|
// Ignore unknown keys, as there may come new keys in the future.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! semver)
|
||||||
|
throw file_parser_error("Invalid semantic version", path, idx_line);
|
||||||
|
ver.config_version = *semver;
|
||||||
|
ver.comment = (end <= key_end) ? "" : unquote_version_comment(value, end, path, idx_line);
|
||||||
|
m_configs.emplace_back(ver);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_configs.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
Index::const_iterator Index::recommended() const
|
||||||
|
{
|
||||||
|
int idx = -1;
|
||||||
|
const_iterator highest = m_configs.end();
|
||||||
|
for (const_iterator it = this->begin(); it != this->end(); ++ it)
|
||||||
|
if (it->is_current_slic3r_supported() &&
|
||||||
|
(highest == this->end() || highest->max_slic3r_version < it->max_slic3r_version))
|
||||||
|
highest = it;
|
||||||
|
return highest;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Config
|
||||||
|
} // namespace GUI
|
||||||
|
} // namespace Slic3r
|
75
xs/src/slic3r/Config/Version.hpp
Normal file
75
xs/src/slic3r/Config/Version.hpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
#ifndef slic3r_GUI_ConfigIndex_
|
||||||
|
#define slic3r_GUI_ConfigIndex_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../../libslic3r/FileParserError.hpp"
|
||||||
|
#include "../Utils/Semver.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace GUI {
|
||||||
|
namespace Config {
|
||||||
|
|
||||||
|
// Configuration bundle version.
|
||||||
|
struct Version
|
||||||
|
{
|
||||||
|
// Version of this config.
|
||||||
|
Semver config_version = Semver::invalid();
|
||||||
|
// Minimum Slic3r version, for which this config is applicable.
|
||||||
|
Semver min_slic3r_version = Semver::zero();
|
||||||
|
// Maximum Slic3r version, for which this config is recommended.
|
||||||
|
// Slic3r should read older configuration and upgrade to a newer format,
|
||||||
|
// but likely there has been a better configuration published, using the new features.
|
||||||
|
Semver max_slic3r_version = Semver::inf();
|
||||||
|
// Single comment line.
|
||||||
|
std::string comment;
|
||||||
|
|
||||||
|
bool is_slic3r_supported(const Semver &slicer_version) const { return slicer_version.in_range(min_slic3r_version, max_slic3r_version); }
|
||||||
|
bool is_current_slic3r_supported() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Index of vendor specific config bundle versions and Slic3r compatibilities.
|
||||||
|
// The index is being downloaded from the internet, also an initial version of the index
|
||||||
|
// is contained in the Slic3r installation.
|
||||||
|
//
|
||||||
|
// The index has a simple format:
|
||||||
|
//
|
||||||
|
// min_sic3r_version =
|
||||||
|
// max_slic3r_version =
|
||||||
|
// config_version "comment"
|
||||||
|
// config_version "comment"
|
||||||
|
// ...
|
||||||
|
// min_slic3r_version =
|
||||||
|
// max_slic3r_version =
|
||||||
|
// config_version comment
|
||||||
|
// config_version comment
|
||||||
|
// ...
|
||||||
|
//
|
||||||
|
// The min_slic3r_version, max_slic3r_version keys are applied to the config versions below,
|
||||||
|
// empty slic3r version means an open interval.
|
||||||
|
class Index
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef std::vector<Version>::const_iterator const_iterator;
|
||||||
|
// Read a config index file in the simple format described in the Index class comment.
|
||||||
|
// Throws Slic3r::file_parser_error and the standard std file access exceptions.
|
||||||
|
size_t load(const std::string &path);
|
||||||
|
|
||||||
|
const_iterator begin() const { return m_configs.begin(); }
|
||||||
|
const_iterator end() const { return m_configs.end(); }
|
||||||
|
const std::vector<Version>& configs() const { return m_configs; }
|
||||||
|
// Finds a recommended config to be installed for the current Slic3r version.
|
||||||
|
// Returns configs().end() if such version does not exist in the index. This shall never happen
|
||||||
|
// if the index is valid.
|
||||||
|
const_iterator recommended() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Version> m_configs;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Config
|
||||||
|
} // namespace GUI
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif /* slic3r_GUI_ConfigIndex_ */
|
@ -28,6 +28,24 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const Semver zero()
|
||||||
|
{
|
||||||
|
static semver_t ver = { 0, 0, 0, nullptr, nullptr };
|
||||||
|
return Semver(ver);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Semver inf()
|
||||||
|
{
|
||||||
|
static semver_t ver = { std::numeric_limits<int>::max(), std::numeric_limits<int>::max(), std::numeric_limits<int>::max(), nullptr, nullptr };
|
||||||
|
return Semver(ver);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Semver invalid()
|
||||||
|
{
|
||||||
|
static semver_t ver = { -1, 0, 0, nullptr, nullptr };
|
||||||
|
return Semver(ver);
|
||||||
|
}
|
||||||
|
|
||||||
Semver(Semver &&other) { *this = std::move(other); }
|
Semver(Semver &&other) { *this = std::move(other); }
|
||||||
Semver(const Semver &other) { *this = other; }
|
Semver(const Semver &other) { *this = other; }
|
||||||
|
|
||||||
@ -36,13 +54,16 @@ public:
|
|||||||
ver = other.ver;
|
ver = other.ver;
|
||||||
other.ver.major = other.ver.minor = other.ver.patch = 0;
|
other.ver.major = other.ver.minor = other.ver.patch = 0;
|
||||||
other.ver.metadata = other.ver.prerelease = nullptr;
|
other.ver.metadata = other.ver.prerelease = nullptr;
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Semver &operator=(const Semver &other)
|
Semver &operator=(const Semver &other)
|
||||||
{
|
{
|
||||||
|
::semver_free(&ver);
|
||||||
ver = other.ver;
|
ver = other.ver;
|
||||||
if (other.ver.metadata != nullptr) { std::strcpy(ver.metadata, other.ver.metadata); }
|
if (other.ver.metadata != nullptr) { std::strcpy(ver.metadata, other.ver.metadata); }
|
||||||
if (other.ver.prerelease != nullptr) { std::strcpy(ver.prerelease, other.ver.prerelease); }
|
if (other.ver.prerelease != nullptr) { std::strcpy(ver.prerelease, other.ver.prerelease); }
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
~Semver() { ::semver_free(&ver); }
|
~Semver() { ::semver_free(&ver); }
|
||||||
@ -55,8 +76,10 @@ public:
|
|||||||
bool operator>=(const Semver &b) const { return ::semver_compare(ver, b.ver) >= 0; }
|
bool operator>=(const Semver &b) const { return ::semver_compare(ver, b.ver) >= 0; }
|
||||||
bool operator>(const Semver &b) const { return ::semver_compare(ver, b.ver) == 1; }
|
bool operator>(const Semver &b) const { return ::semver_compare(ver, b.ver) == 1; }
|
||||||
// We're using '&' instead of the '~' operator here as '~' is unary-only:
|
// We're using '&' instead of the '~' operator here as '~' is unary-only:
|
||||||
|
// Satisfies patch if Major and minor are equal.
|
||||||
bool operator&(const Semver &b) const { return ::semver_satisfies_patch(ver, b.ver); }
|
bool operator&(const Semver &b) const { return ::semver_satisfies_patch(ver, b.ver); }
|
||||||
bool operator^(const Semver &b) const { return ::semver_satisfies_caret(ver, b.ver); }
|
bool operator^(const Semver &b) const { return ::semver_satisfies_caret(ver, b.ver); }
|
||||||
|
bool in_range(const Semver &low, const Semver &high) const { return low <= *this && *this <= high; }
|
||||||
|
|
||||||
// Conversion
|
// Conversion
|
||||||
std::string to_string() const {
|
std::string to_string() const {
|
||||||
@ -79,6 +102,7 @@ public:
|
|||||||
Semver operator-(const Major &b) const { Semver res(*this); return res -= b; }
|
Semver operator-(const Major &b) const { Semver res(*this); return res -= b; }
|
||||||
Semver operator-(const Minor &b) const { Semver res(*this); return res -= b; }
|
Semver operator-(const Minor &b) const { Semver res(*this); return res -= b; }
|
||||||
Semver operator-(const Patch &b) const { Semver res(*this); return res -= b; }
|
Semver operator-(const Patch &b) const { Semver res(*this); return res -= b; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
semver_t ver;
|
semver_t ver;
|
||||||
|
|
||||||
|
63
xs/src/slic3r/Utils/Time.cpp
Normal file
63
xs/src/slic3r/Utils/Time.cpp
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#include "Time.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace Utils {
|
||||||
|
|
||||||
|
time_t parse_time_ISO8601Z(const std::string &sdate)
|
||||||
|
{
|
||||||
|
int y, M, d, h, m;
|
||||||
|
float s;
|
||||||
|
if (sscanf(sdate.c_str(), "%d-%d-%dT%d:%d:%fZ", &y, &M, &d, &h, &m, &s) != 6)
|
||||||
|
return (time_t)-1;
|
||||||
|
struct tm tms;
|
||||||
|
tms.tm_year = y - 1900; // Year since 1900
|
||||||
|
tms.tm_mon = M - 1; // 0-11
|
||||||
|
tms.tm_mday = d; // 1-31
|
||||||
|
tms.tm_hour = h; // 0-23
|
||||||
|
tms.tm_min = m; // 0-59
|
||||||
|
tms.tm_sec = (int)s; // 0-61 (0-60 in C++11)
|
||||||
|
return mktime(&tms);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string format_time_ISO8601Z(time_t time)
|
||||||
|
{
|
||||||
|
struct tm tms;
|
||||||
|
#ifdef WIN32
|
||||||
|
gmtime_s(time, &tms);
|
||||||
|
#else
|
||||||
|
gmtime_r(&tms, time);
|
||||||
|
#endif
|
||||||
|
char buf[128];
|
||||||
|
sprintf(buf, "%d-%d-%dT%d:%d:%fZ",
|
||||||
|
tms.tm_year + 1900
|
||||||
|
tms.tm_mon + 1
|
||||||
|
tms.tm_mday
|
||||||
|
tms.tm_hour
|
||||||
|
tms.tm_min
|
||||||
|
tms.tm_sec);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t get_current_time_utc()
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
SYSTEMTIME st;
|
||||||
|
::GetSystemTime(&st);
|
||||||
|
std::tm tm;
|
||||||
|
tm.tm_sec = st.wSecond;
|
||||||
|
tm.tm_min = st.wMinute;
|
||||||
|
tm.tm_hour = st.wHour;
|
||||||
|
tm.tm_mday = st.wDay;
|
||||||
|
tm.tm_mon = st.wMonth - 1;
|
||||||
|
tm.tm_year = st.wYear - 1900;
|
||||||
|
tm.tm_isdst = -1;
|
||||||
|
return mktime(&tm);
|
||||||
|
#else
|
||||||
|
return gmtime();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
}; // namespace Utils
|
||||||
|
}; // namespace Slic3r
|
||||||
|
|
||||||
|
#endif /* slic3r_Utils_Time_hpp_ */
|
22
xs/src/slic3r/Utils/Time.hpp
Normal file
22
xs/src/slic3r/Utils/Time.hpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#ifndef slic3r_Utils_Time_hpp_
|
||||||
|
#define slic3r_Utils_Time_hpp_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace Utils {
|
||||||
|
|
||||||
|
// Utilities to convert an UTC time_t to/from an ISO8601 time format,
|
||||||
|
// useful for putting timestamps into file and directory names.
|
||||||
|
// Returns (time_t)-1 on error.
|
||||||
|
extern time_t parse_time_ISO8601Z(const std::string &s);
|
||||||
|
extern std::string format_time_ISO8601Z(time_t time);
|
||||||
|
|
||||||
|
// There is no gmtime() on windows.
|
||||||
|
time_t get_current_time_utc();
|
||||||
|
|
||||||
|
}; // namespace Utils
|
||||||
|
}; // namespace Slic3r
|
||||||
|
|
||||||
|
#endif /* slic3r_Utils_Time_hpp_ */
|
Loading…
Reference in New Issue
Block a user