Skip to content

Commit

Permalink
Change backup policy
Browse files Browse the repository at this point in the history
1 - Reports the error to the user in a popup.
2 - Names of the backups include the timstamp instead of a number. Names have more sense and the files can be sorted by names what is also the age order.
3 - Behaviors when changing configuration improved, the number of files present on the system was not reduced when reducing the number of backups or backups deactivated.
  • Loading branch information
plgarcia authored and wwmayer committed Feb 24, 2020
1 parent 9466fdf commit d222f7b
Show file tree
Hide file tree
Showing 4 changed files with 344 additions and 17 deletions.
202 changes: 198 additions & 4 deletions src/App/Document.cpp
Expand Up @@ -2263,6 +2263,8 @@ class BackupPolicy {
BackupPolicy() {
policy = Standard;
numberOfFiles = 1;
useFCBakExtension = false;
saveBackupDateFormat = "%Y%m%d-%H%M%S";
}
~BackupPolicy() {
}
Expand All @@ -2272,6 +2274,12 @@ class BackupPolicy {
void setNumberOfFiles(int count) {
numberOfFiles = count;
}
void useBackupExtension(bool on) {
useFCBakExtension = on;
}
void setDateFormat(const std::string& fmt) {
saveBackupDateFormat = fmt;
}
void apply(const std::string& sourcename, const std::string& targetname) {
switch (policy) {
case Standard:
Expand Down Expand Up @@ -2341,16 +2349,191 @@ class BackupPolicy {
Base::Console().Warning("Cannot rename file from '%s' to '%s'\n",
sourcename.c_str(), targetname.c_str());
}

}
void applyTimeStamp(const std::string& sourcename, const std::string& targetname) {
(void)sourcename;
(void)targetname;
Base::FileInfo fi(targetname);

std::string fn = sourcename;
std::string ext = fi.extension();
std::string bn; // full path with no extension but with "."
std::string pbn; // base name of the project + "."
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; // Note error and report at the end
if (fi.exists()) {
// replace . by - in format to avoid . between base name and extension
boost::replace_all(saveBackupDateFormat, ".", "-");
{
// Remove all extra backups
std::string fn = fi.fileName();
Base::FileInfo di(fi.dirPath());
std::vector<Base::FileInfo> backup;
std::vector<Base::FileInfo> files = di.getDirectoryContent();
for (std::vector<Base::FileInfo>::iterator it = files.begin(); it != files.end(); ++it) {
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(),(int (*)(int))toupper);
// re-enforcing identification of the backup file


// old case : the name starts with the full name of the project and follows with numbers
if ((startsWith(file, fn) &&
(file.length() > fn.length()) &&
checkDigits(file.substr(fn.length()))) ||
// .FCBak case : The bame starts with the base name of the project + "."
// + complement with no "." + ".FCBak"
((fextUp == "FCBAK") && startsWith(file, pbn) &&
(checkValidComplement(file, pbn, fext)))) {
backup.push_back(*it);
}
}
}

if (!backup.empty() && (int)backup.size() >= numberOfFiles) {
std::sort (backup.begin(), backup.end(), fileComparisonByDate);
// delete the oldest backup file we found
// Base::FileInfo del = backup.front();
int nb = 0;
for (std::vector<Base::FileInfo>::iterator it = backup.begin(); it != backup.end(); ++it) {
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());
}
}
}

}
} //end remove backup

// create a new backup file
{
int ext = 1;
if (useFCBakExtension) {
std::stringstream str;
Base::TimeInfo ti = fi.lastModified();
time_t s =ti.getSeconds();
struct tm * timeinfo = localtime(& s);
char buffer[100];

strftime(buffer,sizeof(buffer),saveBackupDateFormat.c_str(),timeinfo);
str << bn << buffer ;

This comment has been minimized.

Copy link
@donovaly

donovaly Mar 15, 2020

Member

This is an issue on Windows because therefore the file name gets 2 dots, e.g. "Filename.20200315-215654.FCBak". Some security and anti-malware programs will therefore trigger such files as potentially harmful, especially since also the FCBak extension is not known to those programs.
To overcome this, could you please just replace the dot by a hyphen: "Filename-20200315-215654.FCBak"

I opened a forum thread to discuss this: https://forum.freecadweb.org/viewtopic.php?f=10&t=44211


fn = str.str();
bool done = false;

if ((fn == "") || (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") == false) {
fn = fn + "-";
}
else {
done = true;
}
}

if (!done) {
while (ext < numberOfFiles + 10) {
if (renameFileNoErase(fi, fn+std::to_string(ext)+".FCBak"))
break;
ext++;
}
}
}
else {
// changed but simpler and solves also the delay sometimes introduced by google drive
while (ext < numberOfFiles + 10) {
// linux just replace the file if exists, and then the existence is to be tested before rename
if (renameFileNoErase(fi, fi.filePath()+std::to_string(ext)))
break;
ext++;
}
}

if (ext >= numberOfFiles + 10) {
Base::Console().Error("File not saved: Cannot rename project file to backup file\n");
//throw Base::FileException("File not saved: Cannot rename project file to backup file", fi);
}
}
}

Base::FileInfo tmp(sourcename);
if (tmp.renameFile(targetname.c_str()) == false) {
Base::Console().Error("Save interrupted: Cannot rename file from '%s' to '%s'\n",
sourcename.c_str(), targetname.c_str());
//throw Base::FileException("Save interrupted: Cannot rename temporary file to project file", tmp);
}

if (numberOfFiles <= 0) {
try {
fi.deleteFile();
}
catch (...) {
Base::Console().Warning("Cannot remove backup file: %s\n", fi.fileName().c_str());
backupManagementError = true;
}
}

if (backupManagementError) {
throw Base::FileException("Warning: Save complete, but error while managing backup history.", fi);
}
}
static bool fileComparisonByDate(const Base::FileInfo& i,
const Base::FileInfo& j) {
return (i.lastModified()>j.lastModified());
}
bool startsWith(const std::string& st1,
const std::string& st2) const {
return st1.substr(0,st2.length()) == st2;
}
bool checkValidString (const std::string& cmpl, const boost::regex& e) const {
boost::smatch what;
bool res = boost::regex_search (cmpl,what,e);
return res;
}
bool checkValidComplement(const std::string& file, const std::string& pbn, const std::string& ext) const {
std::string cmpl = file.substr(pbn.length(),file.length()- pbn.length() - ext.length()-1);
boost::regex e (R"(^[^.]*$)");
return checkValidString(cmpl,e);
}
bool checkDigits (const std::string& cmpl) const {
boost::regex e (R"(^[0-9]*$)");
return checkValidString(cmpl,e);
}
bool renameFileNoErase(Base::FileInfo fi, const std::string& newName) {
// linux just replaces the file if it exists, so the existence is to be tested before rename
Base::FileInfo nf(newName);
if (!nf.exists()) {
return fi.renameFile(newName.c_str());
}
return false;
}

private:
Policy policy;
int numberOfFiles;
bool useFCBakExtension;
std::string saveBackupDateFormat;
};
}

Expand Down Expand Up @@ -2419,9 +2602,20 @@ bool Document::saveToFile(const char* filename) const
if (!backup) {
count_bak = -1;
}
bool useFCBakExtension = App::GetApplication().GetParameterGroupByPath
("User parameter:BaseApp/Preferences/Document")->GetBool("UseFCBakExtension",false);
std::string saveBackupDateFormat = App::GetApplication().GetParameterGroupByPath
("User parameter:BaseApp/Preferences/Document")->GetASCII("SaveBackupDateFormat","%Y%m%d-%H%M%S");

BackupPolicy policy;
policy.setPolicy(BackupPolicy::Standard);
if (useFCBakExtension) {
policy.setPolicy(BackupPolicy::TimeStamp);
policy.useBackupExtension(useFCBakExtension);
policy.setDateFormat(saveBackupDateFormat);
}
else {
policy.setPolicy(BackupPolicy::Standard);
}
policy.setNumberOfFiles(count_bak);
policy.apply(fn, filename);
}
Expand Down
3 changes: 3 additions & 0 deletions src/Base/FileInfo.cpp
Expand Up @@ -482,6 +482,9 @@ bool FileInfo::renameFile(const char* NewName)
int code = errno;
std::clog << "Error in renameFile: " << strerror(code) << " (" << code << ")" << std::endl;
}
else {
FileName = NewName;
}

return res;
}
Expand Down

0 comments on commit d222f7b

Please sign in to comment.