Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save backup versions of projects #740

Merged
merged 2 commits into from Jan 7, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 1 addition & 7 deletions CHANGELOG.md
Expand Up @@ -17,17 +17,11 @@
### Changes

- Allow creating a node with the same name that was just deleted. #732
- Natron can now keep up to 32 project backups (see Preferences/General/Save versions). #562


## Version 2.4.2

### Known issues

- Crash when closing a project window on macOS 12+ (Qt4 only). #712
- Rendering sometimes silently stalls after X frames. #248
- Some image formats may have issues (PCX, PSB). #602
- MTS video files are sometimes not read correctly. #186

### Changes

- Fix OpenFX overlay actions being executed in the wrong order. #711
Expand Down
86 changes: 76 additions & 10 deletions Engine/Project.cpp
Expand Up @@ -53,6 +53,7 @@ GCC_DIAG_UNUSED_LOCAL_TYPEDEFS_ON
#include <QtCore/QTimer>
#include <QtCore/QThread>
#include <QtCore/QDir>
#include <QtCore/QDirIterator>
#include <QtCore/QTemporaryFile>
#include <QtCore/QFileInfo>
#include <QtCore/QDebug>
Expand Down Expand Up @@ -500,6 +501,53 @@ fileCopy(const QString & source,
return success;
}

static QStringList
findBackups(const QString & filePath)
{
QStringList ret;
if ( QFile::exists(filePath) ) {
ret.append(filePath);
}
// find files matching filePath.~[0-9]+~
QRegExp rx(QString::fromUtf8("\\.~(\\d+)~$"));
QFileInfo fileInfo(filePath);
QString fileName = fileInfo.fileName();
QDirIterator it(fileInfo.dir());
while (it.hasNext()) {
QString filename = it.next();
QFileInfo file(filename);

if (file.isDir()) { // Check if it's a dir
continue;
}

// If the filename contains target string - put it in the hitlist
QString fn = file.fileName();
if (fn.startsWith(fileName) && rx.lastIndexIn(fn) >= 0) {
ret.append(file.filePath());
}
}
ret.sort();
qDebug() << "found backups:" << ret;

return ret;
}

// if filePath matches .*\.~[0-9]+~, increment the backup number
// else append .~1~
static QString
nextBackup(const QString & filePath)
{
QRegExp rx(QString::fromUtf8("\\.~(\\d+)~$"));
int pos = rx.lastIndexIn(filePath);
if (pos >= 0) {
int i = rx.cap(1).toInt();
return filePath.left(i) + QString::fromUtf8(".~%1~").arg(i+1);
} else {
return filePath + QString::fromUtf8(".~1~");
}
}

QString
Project::saveProjectInternal(const QString & path,
const QString & name,
Expand Down Expand Up @@ -595,20 +643,38 @@ Project::saveProjectInternal(const QString & path,
}
} // ofile

if ( QFile::exists(filePath) ) {
QFile::remove(filePath);
// rotate backups
int saveVersions = autoSave ? 0 : appPTR->getCurrentSettings()->saveVersions();
// find the list of ordered backups (including the file itself if it exists)
QStringList backups = findBackups(filePath);
// remove extra backups
for (int i = backups.size() - 1; i >= saveVersions; --i) {
if ( QFile::exists(backups.last()) ) {
QFile::remove(backups.last());
}
backups.removeLast();
}
int nAttemps = 0;

while ( nAttemps < 10 && !fileCopy(tmpFilename, filePath) ) {
++nAttemps;
// rename existing backups
for (int i = backups.size() - 1; i >= 0; --i) {
QFile::rename(backups.at(i), nextBackup(backups.at(i)));
}

if (nAttemps >= 10) {
throw std::runtime_error( "Failed to save to " + filePath.toStdString() );
}
if (!QFile::rename(tmpFilename, filePath)) {
// QFile::rename() may fail, e.g. if tmpFilename and filePath are not on the same partition
if (!QFile::copy(tmpFilename, filePath)) {
int nAttemps = 0;

QFile::remove(tmpFilename);
while ( nAttemps < 10 && !fileCopy(tmpFilename, filePath) ) {
++nAttemps;
}

if (nAttemps >= 10) {
throw std::runtime_error( "Failed to save to " + filePath.toStdString() );
}
}

QFile::remove(tmpFilename);
}

if (!autoSave && updateProjectProperties) {
QString lockFilePath = getLockAbsoluteFilePath();
Expand Down
20 changes: 20 additions & 0 deletions Engine/Settings.cpp
Expand Up @@ -193,6 +193,18 @@ Settings::initializeKnobsGeneral()
"Disabling this will no longer save un-saved project.").arg( QString::fromUtf8(NATRON_APPLICATION_NAME) ) );
_generalTab->addKnob(_autoSaveUnSavedProjects);

_saveVersions = AppManager::createKnob<KnobInt>( this, tr("Save versions") );
_saveVersions->setName("saveVersions");
_saveVersions->disableSlider();
_saveVersions->setMinimum(0);
_saveVersions->setMaximum(32);
_saveVersions->setHintToolTip( tr("Number of versions created (for backup) when saving newer versions of a file.\n"
"This option keeps saved versions of your file in the same directory, adding "
".~1~, .~2~, etc., with the number increasing to the number of versions you specify.\n"
"Older files will be named with a higher number. E.g. with the default setting of 2, "
"you will have three versions of your file: *.ntp (last saved), *.ntp.~1~ (second "
"last saved), *.~2~ (third last saved).") );
_generalTab->addKnob(_saveVersions);

_hostName = AppManager::createKnob<KnobChoice>( this, tr("Appear to plug-ins as") );
_hostName->setName("pluginHostName");
Expand Down Expand Up @@ -1447,6 +1459,7 @@ Settings::setDefaultValues()
#endif
_autoSaveUnSavedProjects->setDefaultValue(true);
_autoSaveDelay->setDefaultValue(5, 0);
_saveVersions->setDefaultValue(1);
_hostName->setDefaultValue(0);
_customHostName->setDefaultValue(NATRON_ORGANIZATION_DOMAIN_TOPLEVEL "." NATRON_ORGANIZATION_DOMAIN_SUB "." NATRON_APPLICATION_NAME);

Expand Down Expand Up @@ -2877,6 +2890,13 @@ Settings::isAutoSaveEnabledForUnsavedProjects() const
return _autoSaveUnSavedProjects->getValue();
}

int
Settings::saveVersions() const
{
return _saveVersions->getValue();
}


bool
Settings::isSnapToNodeEnabled() const
{
Expand Down
3 changes: 3 additions & 0 deletions Engine/Settings.h
Expand Up @@ -179,6 +179,8 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON

bool isAutoSaveEnabledForUnsavedProjects() const;

int saveVersions() const;

bool isSnapToNodeEnabled() const;

bool isCheckForUpdatesEnabled() const;
Expand Down Expand Up @@ -430,6 +432,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON
#endif
KnobBoolPtr _autoSaveUnSavedProjects;
KnobIntPtr _autoSaveDelay;
KnobIntPtr _saveVersions;
KnobChoicePtr _hostName;
KnobStringPtr _customHostName;

Expand Down