| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include <boost/algorithm/string/replace.hpp> |
| | #include <boost/regex.hpp> |
| | #include <string> |
| |
|
| | #include <Base/TimeInfo.h> |
| | #include <Base/Console.h> |
| | #include <Base/Exception.h> |
| | #include <Base/FileInfo.h> |
| | #include <Base/Interpreter.h> |
| | #include <Base/Tools.h> |
| | #include <Base/Writer.h> |
| |
|
| | #include "BackupPolicy.h" |
| |
|
| | using namespace App; |
| |
|
| | void BackupPolicy::setPolicy(const Policy p) |
| | { |
| | policy = p; |
| | } |
| | void BackupPolicy::setNumberOfFiles(const int count) |
| | { |
| | numberOfFiles = count; |
| | } |
| | void BackupPolicy::useBackupExtension(const bool on) |
| | { |
| | useFCBakExtension = on; |
| | } |
| | void BackupPolicy::setDateFormat(const std::string& fmt) |
| | { |
| | saveBackupDateFormat = fmt; |
| | } |
| | void BackupPolicy::apply(const std::string& sourcename, const std::string& targetname) |
| | { |
| | switch (policy) { |
| | case Standard: |
| | applyStandard(sourcename, targetname); |
| | break; |
| | case TimeStamp: |
| | applyTimeStamp(sourcename, targetname); |
| | break; |
| | } |
| | } |
| |
|
| | void BackupPolicy::applyStandard(const std::string& sourcename, const std::string& targetname) const |
| | { |
| | |
| | if (Base::FileInfo fi(targetname); fi.exists()) { |
| | if (numberOfFiles > 0) { |
| | int nSuff = 0; |
| | std::string fn = fi.fileName(); |
| | Base::FileInfo di(fi.dirPath()); |
| | std::vector<Base::FileInfo> backup; |
| | std::vector<Base::FileInfo> files = di.getDirectoryContent(); |
| | for (const Base::FileInfo& it : files) { |
| | if (std::string file = it.fileName(); file.substr(0, fn.length()) == fn) { |
| | |
| | std::string suf(file.substr(fn.length())); |
| | if (!suf.empty()) { |
| | std::string::size_type nPos = suf.find_first_not_of("0123456789"); |
| | if (nPos == std::string::npos) { |
| | |
| | backup.push_back(it); |
| | nSuff = |
| | std::max<int>(nSuff, static_cast<int>(std::atol(suf.c_str()))); |
| | } |
| | } |
| | } |
| | } |
| |
|
| | if (!backup.empty() && static_cast<int>(backup.size()) >= numberOfFiles) { |
| | |
| | Base::FileInfo del = backup.front(); |
| | for (const Base::FileInfo& it : backup) { |
| | if (it.lastModified() < del.lastModified()) { |
| | del = it; |
| | } |
| | } |
| |
|
| | del.deleteFile(); |
| | fn = del.filePath(); |
| | } |
| | else { |
| | |
| | std::stringstream str; |
| | str << fi.filePath() << (nSuff + 1); |
| | fn = str.str(); |
| | } |
| |
|
| | if (!fi.renameFile(fn.c_str())) { |
| | Base::Console().warning("Cannot rename project file to backup file\n"); |
| | } |
| | } |
| | else { |
| | fi.deleteFile(); |
| | } |
| | } |
| |
|
| | if (Base::FileInfo tmp(sourcename); !tmp.renameFile(targetname.c_str())) { |
| | throw Base::FileException("Cannot rename tmp save file to project file", |
| | Base::FileInfo(targetname)); |
| | } |
| | } |
| |
|
| | void BackupPolicy::applyTimeStamp(const std::string& sourcename, const std::string& targetname) |
| | { |
| | Base::FileInfo fi(targetname); |
| |
|
| | std::string fn = sourcename; |
| | std::string ext = fi.extension(); |
| | std::string bn; |
| | std::string pbn; |
| | if (!ext.empty()) { |
| | bn = fi.filePath().substr(0, fi.filePath().length() - ext.length()); |
| | pbn = fi.fileName().substr(0, fi.fileName().length() - ext.length()); |
| | } |
| | else { |
| | bn = fi.filePath() + "."; |
| | pbn = fi.fileName() + "."; |
| | } |
| |
|
| | bool backupManagementError = false; |
| | if (fi.exists()) { |
| | if (numberOfFiles > 0) { |
| | |
| | boost::replace_all(saveBackupDateFormat, ".", "-"); |
| | { |
| | |
| | std::string filename = fi.fileName(); |
| | Base::FileInfo di(fi.dirPath()); |
| | std::vector<Base::FileInfo> backup; |
| | std::vector<Base::FileInfo> files = di.getDirectoryContent(); |
| | for (const Base::FileInfo& it : files) { |
| | if (it.isFile()) { |
| | std::string file = it.fileName(); |
| | std::string fext = it.extension(); |
| | std::string fextUp = fext; |
| | std::transform(fextUp.begin(), |
| | fextUp.end(), |
| | fextUp.begin(), |
| | static_cast<int (*)(int)>(toupper)); |
| | |
| |
|
| |
|
| | |
| | |
| | if ((startsWith(file, filename) && (file.length() > filename.length()) |
| | && checkDigits(file.substr(filename.length()))) |
| | || |
| | |
| | |
| | |
| | ((fextUp == "FCBAK") && startsWith(file, pbn) |
| | && (checkValidComplement(file, pbn, fext)))) { |
| | backup.push_back(it); |
| | } |
| | } |
| | } |
| |
|
| | if (!backup.empty() && static_cast<int>(backup.size()) >= numberOfFiles) { |
| | std::sort(backup.begin(), backup.end(), fileComparisonByDate); |
| | |
| | |
| | int nb = 0; |
| | for (Base::FileInfo& it : backup) { |
| | nb++; |
| | if (nb >= numberOfFiles) { |
| | try { |
| | if (!it.deleteFile()) { |
| | backupManagementError = true; |
| | Base::Console().warning("Cannot remove backup file : %s\n", |
| | it.fileName().c_str()); |
| | } |
| | } |
| | catch (...) { |
| | backupManagementError = true; |
| | Base::Console().warning("Cannot remove backup file : %s\n", |
| | it.fileName().c_str()); |
| | } |
| | } |
| | } |
| | } |
| | } |
| |
|
| | |
| | { |
| | int ext2 = 1; |
| | if (useFCBakExtension) { |
| | std::stringstream str; |
| | Base::TimeInfo ti = fi.lastModified(); |
| | time_t s = ti.getTime_t(); |
| | std::tm local_tm {}; |
| | #if defined(_WIN32) |
| | localtime_s(&local_tm, &s); |
| | #else |
| | localtime_r(&s, &local_tm); |
| | #endif |
| | constexpr size_t bufferLength = 128; |
| | std::array<char, bufferLength> buffer {}; |
| | if (size_t bytes = std::strftime(buffer.data(), |
| | bufferLength, |
| | saveBackupDateFormat.c_str(), |
| | &local_tm); |
| | bytes == 0) { |
| | |
| | |
| | Base::Console().error("Failed to create valid backup file name from format string:\n"); |
| | Base::Console().error(saveBackupDateFormat.c_str()); |
| | const auto knownGoodFormat {"%Y-%m-%d_%H-%M-%S"}; |
| | std::strftime(buffer.data(), bufferLength, knownGoodFormat, &local_tm); |
| | } |
| | str << bn << buffer.data(); |
| |
|
| | fn = str.str(); |
| | bool done = false; |
| |
|
| | if ((fn.empty()) || (fn[fn.length() - 1] == ' ') |
| | || (fn[fn.length() - 1] == '-')) { |
| | if (fn[fn.length() - 1] == ' ') { |
| | fn = fn.substr(0, fn.length() - 1); |
| | } |
| | } |
| | else { |
| | if (!renameFileNoErase(fi, fn + ".FCBak")) { |
| | fn = fn + "-"; |
| | } |
| | else { |
| | done = true; |
| | } |
| | } |
| |
|
| | if (!done) { |
| | while (ext2 < numberOfFiles + 10) { |
| | if (renameFileNoErase(fi, fn + std::to_string(ext2) + ".FCBak")) { |
| | break; |
| | } |
| | ext2++; |
| | } |
| | } |
| | } |
| | else { |
| | |
| | |
| | while (ext2 < numberOfFiles + 10) { |
| | |
| | |
| | if (renameFileNoErase(fi, fi.filePath() + std::to_string(ext2))) { |
| | break; |
| | } |
| | ext2++; |
| | } |
| | } |
| |
|
| | if (ext2 >= numberOfFiles + 10) { |
| | Base::Console().error( |
| | "File not saved: Cannot rename project file to backup file\n"); |
| | |
| | |
| | } |
| | } |
| | } |
| | else { |
| | try { |
| | fi.deleteFile(); |
| | } |
| | catch (...) { |
| | Base::Console().warning("Cannot remove backup file: %s\n", |
| | fi.fileName().c_str()); |
| | backupManagementError = true; |
| | } |
| | } |
| | } |
| |
|
| | Base::FileInfo tmp(sourcename); |
| | if (!tmp.renameFile(targetname.c_str())) { |
| | throw Base::FileException( |
| | "Save interrupted: Cannot rename temporary file to project file", |
| | tmp); |
| | } |
| |
|
| | if (backupManagementError) { |
| | throw Base::FileException( |
| | "Warning: Save complete, but error while managing backup history.", |
| | fi); |
| | } |
| | } |
| |
|
| | bool BackupPolicy::fileComparisonByDate(const Base::FileInfo& i, const Base::FileInfo& j) |
| | { |
| | return (i.lastModified() > j.lastModified()); |
| | } |
| |
|
| | bool BackupPolicy::startsWith(const std::string& st1, const std::string& st2) const |
| | { |
| | return st1.substr(0, st2.length()) == st2; |
| | } |
| |
|
| | bool BackupPolicy::checkValidString(const std::string& cmpl, const boost::regex& e) const |
| | { |
| | boost::smatch what; |
| | const bool res = boost::regex_search(cmpl, what, e); |
| | return res; |
| | } |
| |
|
| | bool BackupPolicy::checkValidComplement(const std::string& file, |
| | const std::string& pbn, |
| | const std::string& ext) const |
| | { |
| | const std::string cmpl = |
| | file.substr(pbn.length(), file.length() - pbn.length() - ext.length() - 1); |
| | const boost::regex e(R"(^[^.]*$)"); |
| | return checkValidString(cmpl, e); |
| | } |
| |
|
| | bool BackupPolicy::checkDigits(const std::string& cmpl) const |
| | { |
| | const boost::regex e(R"(^[0-9]*$)"); |
| | return checkValidString(cmpl, e); |
| | } |
| |
|
| | bool BackupPolicy::renameFileNoErase(Base::FileInfo fi, const std::string& newName) |
| | { |
| | |
| | const Base::FileInfo nf(newName); |
| | if (!nf.exists()) { |
| | return fi.renameFile(newName.c_str()); |
| | } |
| | return false; |
| | } |
| |
|