Full-project automatic backup system

Fixes https://gitlab.com/kicad/code/kicad/-/issues/4763
This commit is contained in:
Jon Evans
2020-07-09 13:55:19 +00:00
parent 5226d6b4df
commit b1b9cef5fb
18 changed files with 1585 additions and 231 deletions
+216
View File
@@ -30,6 +30,7 @@
#include <gestfich.h>
#include <macros.h>
#include <project.h>
#include <project/project_archiver.h>
#include <project/project_file.h>
#include <project/project_local_settings.h>
#include <settings/app_settings.h>
@@ -823,3 +824,218 @@ bool SETTINGS_MANAGER::unloadProjectFile( PROJECT* aProject, bool aSave )
return true;
}
wxString SETTINGS_MANAGER::GetProjectBackupsPath() const
{
return Prj().GetProjectPath() + Prj().GetProjectName() + PROJECT_BACKUPS_DIR_SUFFIX;
}
wxString SETTINGS_MANAGER::backupDateTimeFormat = wxT( "%Y-%m-%d_%H%M%S" );
bool SETTINGS_MANAGER::BackupProject( REPORTER& aReporter ) const
{
wxDateTime timestamp = wxDateTime::Now();
wxString fileName = wxString::Format( wxT( "%s-%s" ), Prj().GetProjectName(),
timestamp.Format( backupDateTimeFormat ) );
wxFileName target;
target.SetPath( GetProjectBackupsPath() );
target.SetName( fileName );
target.SetExt( ArchiveFileExtension );
wxDir dir( target.GetPath() );
if( !target.DirExists() && !wxMkdir( target.GetPath() ) )
{
wxLogTrace( traceSettings, "Could not create project backup path %s", target.GetPath() );
return false;
}
if( !target.IsDirWritable() )
{
wxLogTrace( traceSettings, "Backup directory %s is not writeable", target.GetPath() );
return false;
}
wxLogTrace( traceSettings, "Backing up project to %s", target.GetPath() );
PROJECT_ARCHIVER archiver;
return archiver.Archive( Prj().GetProjectPath(), target.GetFullPath(), aReporter );
}
class VECTOR_INSERT_TRAVERSER : public wxDirTraverser
{
public:
VECTOR_INSERT_TRAVERSER( std::vector<wxString>& aVec,
std::function<bool( const wxString& )> aCond ) :
m_files( aVec ),
m_condition( aCond )
{
}
wxDirTraverseResult OnFile( const wxString& aFile ) override
{
if( m_condition( aFile ) )
m_files.emplace_back( aFile );
return wxDIR_CONTINUE;
}
wxDirTraverseResult OnDir( const wxString& aDirName ) override
{
return wxDIR_CONTINUE;
}
private:
std::vector<wxString>& m_files;
std::function<bool( const wxString& )> m_condition;
};
bool SETTINGS_MANAGER::TriggerBackupIfNeeded( REPORTER& aReporter ) const
{
COMMON_SETTINGS::AUTO_BACKUP settings = GetCommonSettings()->m_Backup;
if( !settings.enabled )
return true;
wxString prefix = Prj().GetProjectName() + '-';
auto modTime =
[&prefix]( const wxString& aFile )
{
wxDateTime dt;
wxString fn( wxFileName( aFile ).GetName() );
fn.Replace( prefix, "" );
dt.ParseFormat( fn, backupDateTimeFormat );
return dt;
};
// Project not saved yet
if( Prj().GetProjectPath().empty() )
return true;
wxString backupPath = GetProjectBackupsPath();
if( !wxDirExists( backupPath ) )
{
wxLogTrace( traceSettings, "Backup path %s doesn't exist, creating it", backupPath );
if( !wxMkdir( backupPath ) )
{
wxLogTrace( traceSettings, "Could not create backups path! Skipping backup" );
return false;
}
}
wxDir dir( backupPath );
if( !dir.IsOpened() )
{
wxLogTrace( traceSettings, "Could not open project backups path %s", dir.GetName() );
return false;
}
std::vector<wxString> files;
VECTOR_INSERT_TRAVERSER traverser( files,
[&modTime]( const wxString& aFile )
{
return modTime( aFile ).IsValid();
} );
dir.Traverse( traverser, wxT( "*.zip" ) );
// Sort newest-first
std::sort( files.begin(), files.end(),
[&]( const wxString& aFirst, const wxString& aSecond ) -> bool
{
wxDateTime first = modTime( aFirst );
wxDateTime second = modTime( aSecond );
return first.GetTicks() > second.GetTicks();
} );
// Do we even need to back up?
if( !files.empty() )
{
wxDateTime lastTime = modTime( files[0] );
if( lastTime.IsValid() )
{
wxTimeSpan delta = wxDateTime::Now() - modTime( files[0] );
if( delta.IsShorterThan( wxTimeSpan::Seconds( settings.min_interval ) ) )
return true;
}
}
// Now that we know a backup is needed, apply the retention policy
// Step 1: if we're over the total file limit, remove the oldest
if( !files.empty() && settings.limit_total_files > 0 )
{
while( files.size() > static_cast<size_t>( settings.limit_total_files ) )
{
wxRemoveFile( files.back() );
files.pop_back();
}
}
// Step 2: Stay under the total size limit
if( settings.limit_total_size > 0 )
{
wxULongLong totalSize = 0;
for( const wxString& file : files )
totalSize += wxFileName::GetSize( file );
while( !files.empty() && totalSize > static_cast<wxULongLong>( settings.limit_total_size ) )
{
totalSize -= wxFileName::GetSize( files.back() );
wxRemoveFile( files.back() );
files.pop_back();
}
}
// Step 3: Stay under the daily limit
if( settings.limit_daily_files > 0 && files.size() > 1 )
{
wxDateTime day = modTime( files[0] );
int num = 1;
wxASSERT( day.IsValid() );
std::vector<wxString> filesToDelete;
for( size_t i = 1; i < files.size(); i++ )
{
wxDateTime dt = modTime( files[i] );
if( dt.IsSameDate( day ) )
{
num++;
if( num > settings.limit_daily_files )
filesToDelete.emplace_back( files[i] );
}
else
{
day = dt;
num = 1;
}
}
for( const wxString& file : filesToDelete )
wxRemoveFile( file );
}
return BackupProject( aReporter );
}