wxSingleInstanceChecker is meant for running programs, not file locking.
This implements an RAII class for file locking that stores the lock
files next to the file being locked, allowing it to be easily found and
removed. Also includes the ability to override the lock, with
information about the original owner
Fixes https://gitlab.com/kicad/code/kicad/-/issues/14734
(cherry picked from commit 122be418bb)
1390 lines
39 KiB
C++
1390 lines
39 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2020 Jon Evans <[email protected]>
|
|
* Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation, either version 3 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "settings/json_settings.h"
|
|
#include <regex>
|
|
#include <wx/debug.h>
|
|
#include <wx/dir.h>
|
|
#include <wx/filename.h>
|
|
#include <wx/snglinst.h>
|
|
#include <wx/stdpaths.h>
|
|
#include <wx/utils.h>
|
|
|
|
#include <build_version.h>
|
|
#include <confirm.h>
|
|
#include <dialogs/dialog_migrate_settings.h>
|
|
#include <gestfich.h>
|
|
#include <kiplatform/environment.h>
|
|
#include <kiway.h>
|
|
#include <lockfile.h>
|
|
#include <macros.h>
|
|
#include <pgm_base.h>
|
|
#include <paths.h>
|
|
#include <project.h>
|
|
#include <project/project_archiver.h>
|
|
#include <project/project_file.h>
|
|
#include <project/project_local_settings.h>
|
|
#include <settings/color_settings.h>
|
|
#include <settings/common_settings.h>
|
|
#include <settings/json_settings_internals.h>
|
|
#include <settings/settings_manager.h>
|
|
#include <wildcards_and_files_ext.h>
|
|
|
|
|
|
SETTINGS_MANAGER::SETTINGS_MANAGER( bool aHeadless ) :
|
|
m_headless( aHeadless ),
|
|
m_kiway( nullptr ),
|
|
m_common_settings( nullptr ),
|
|
m_migration_source(),
|
|
m_migrateLibraryTables( true )
|
|
{
|
|
// Check if the settings directory already exists, and if not, perform a migration if possible
|
|
if( !MigrateIfNeeded() )
|
|
{
|
|
m_ok = false;
|
|
return;
|
|
}
|
|
|
|
m_ok = true;
|
|
|
|
// create the common settings shared by all applications. Not loaded immediately
|
|
m_common_settings = RegisterSettings( new COMMON_SETTINGS, false );
|
|
|
|
// Create the built-in color settings
|
|
// Here to allow the Python API to access the built-in colors
|
|
registerBuiltinColorSettings();
|
|
}
|
|
|
|
SETTINGS_MANAGER::~SETTINGS_MANAGER()
|
|
{
|
|
for( std::unique_ptr<PROJECT>& project : m_projects_list )
|
|
project.reset();
|
|
|
|
m_projects.clear();
|
|
|
|
for( std::unique_ptr<JSON_SETTINGS>& settings : m_settings )
|
|
settings.reset();
|
|
|
|
m_settings.clear();
|
|
|
|
m_color_settings.clear();
|
|
}
|
|
|
|
|
|
JSON_SETTINGS* SETTINGS_MANAGER::registerSettings( JSON_SETTINGS* aSettings, bool aLoadNow )
|
|
{
|
|
std::unique_ptr<JSON_SETTINGS> ptr( aSettings );
|
|
|
|
ptr->SetManager( this );
|
|
|
|
wxLogTrace( traceSettings, wxT( "Registered new settings object <%s>" ), ptr->GetFullFilename() );
|
|
|
|
if( aLoadNow )
|
|
ptr->LoadFromFile( GetPathForSettingsFile( ptr.get() ) );
|
|
|
|
m_settings.push_back( std::move( ptr ) );
|
|
return m_settings.back().get();
|
|
}
|
|
|
|
|
|
void SETTINGS_MANAGER::Load()
|
|
{
|
|
// TODO(JE) We should check for dirty settings here and write them if so, because
|
|
// Load() could be called late in the application lifecycle
|
|
|
|
std::vector<JSON_SETTINGS*> toLoad;
|
|
|
|
// Cache a copy of raw pointers; m_settings may be modified during the load loop
|
|
std::transform( m_settings.begin(), m_settings.end(), std::back_inserter( toLoad ),
|
|
[]( std::unique_ptr<JSON_SETTINGS>& aSettings )
|
|
{
|
|
return aSettings.get();
|
|
} );
|
|
|
|
for( JSON_SETTINGS* settings : toLoad )
|
|
settings->LoadFromFile( GetPathForSettingsFile( settings ) );
|
|
}
|
|
|
|
|
|
void SETTINGS_MANAGER::Load( JSON_SETTINGS* aSettings )
|
|
{
|
|
auto it = std::find_if( m_settings.begin(), m_settings.end(),
|
|
[&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
|
|
{
|
|
return aPtr.get() == aSettings;
|
|
} );
|
|
|
|
if( it != m_settings.end() )
|
|
( *it )->LoadFromFile( GetPathForSettingsFile( it->get() ) );
|
|
}
|
|
|
|
|
|
void SETTINGS_MANAGER::Save()
|
|
{
|
|
for( auto&& settings : m_settings )
|
|
{
|
|
// Never automatically save color settings, caller should use SaveColorSettings
|
|
if( dynamic_cast<COLOR_SETTINGS*>( settings.get() ) )
|
|
continue;
|
|
|
|
settings->SaveToFile( GetPathForSettingsFile( settings.get() ) );
|
|
}
|
|
}
|
|
|
|
|
|
void SETTINGS_MANAGER::Save( JSON_SETTINGS* aSettings )
|
|
{
|
|
auto it = std::find_if( m_settings.begin(), m_settings.end(),
|
|
[&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
|
|
{
|
|
return aPtr.get() == aSettings;
|
|
} );
|
|
|
|
if( it != m_settings.end() )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "Saving %s" ), ( *it )->GetFullFilename() );
|
|
( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
|
|
}
|
|
}
|
|
|
|
|
|
void SETTINGS_MANAGER::FlushAndRelease( JSON_SETTINGS* aSettings, bool aSave )
|
|
{
|
|
auto it = std::find_if( m_settings.begin(), m_settings.end(),
|
|
[&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
|
|
{
|
|
return aPtr.get() == aSettings;
|
|
} );
|
|
|
|
if( it != m_settings.end() )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "Flush and release %s" ), ( *it )->GetFullFilename() );
|
|
|
|
if( aSave )
|
|
( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
|
|
|
|
size_t typeHash = typeid( *it->get() ).hash_code();
|
|
|
|
if( m_app_settings_cache.count( typeHash ) )
|
|
m_app_settings_cache.erase( typeHash );
|
|
|
|
m_settings.erase( it );
|
|
}
|
|
}
|
|
|
|
|
|
COLOR_SETTINGS* SETTINGS_MANAGER::GetColorSettings( const wxString& aName )
|
|
{
|
|
// Find settings the fast way
|
|
if( m_color_settings.count( aName ) )
|
|
return m_color_settings.at( aName );
|
|
|
|
// Maybe it's the display name (cli is one method of invoke)
|
|
auto it = std::find_if( m_color_settings.begin(), m_color_settings.end(),
|
|
[&aName]( const std::pair<wxString, COLOR_SETTINGS*>& p )
|
|
{
|
|
return p.second->GetName().Lower() == aName.Lower();
|
|
} );
|
|
|
|
if( it != m_color_settings.end() )
|
|
{
|
|
return it->second;
|
|
}
|
|
|
|
// No match? See if we can load it
|
|
if( !aName.empty() )
|
|
{
|
|
COLOR_SETTINGS* ret = loadColorSettingsByName( aName );
|
|
|
|
if( !ret )
|
|
{
|
|
ret = registerColorSettings( aName );
|
|
*ret = *m_color_settings.at( COLOR_SETTINGS::COLOR_BUILTIN_DEFAULT );
|
|
ret->SetFilename( wxT( "user" ) );
|
|
ret->SetReadOnly( false );
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// This had better work
|
|
return m_color_settings.at( COLOR_SETTINGS::COLOR_BUILTIN_DEFAULT );
|
|
}
|
|
|
|
|
|
COLOR_SETTINGS* SETTINGS_MANAGER::loadColorSettingsByName( const wxString& aName )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "Attempting to load color theme %s" ), aName );
|
|
|
|
wxFileName fn( GetColorSettingsPath(), aName, wxS( "json" ) );
|
|
|
|
if( !fn.IsOk() || !fn.Exists() )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "Theme file %s.json not found, falling back to user" ), aName );
|
|
return nullptr;
|
|
}
|
|
|
|
COLOR_SETTINGS* settings = RegisterSettings( new COLOR_SETTINGS( aName ) );
|
|
|
|
if( settings->GetFilename() != aName.ToStdString() )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "Warning: stored filename is actually %s, " ),
|
|
settings->GetFilename() );
|
|
}
|
|
|
|
m_color_settings[aName] = settings;
|
|
|
|
return settings;
|
|
}
|
|
|
|
|
|
class JSON_DIR_TRAVERSER : public wxDirTraverser
|
|
{
|
|
private:
|
|
std::function<void( const wxFileName& )> m_action;
|
|
|
|
public:
|
|
explicit JSON_DIR_TRAVERSER( std::function<void( const wxFileName& )> aAction )
|
|
: m_action( std::move( aAction ) )
|
|
{
|
|
}
|
|
|
|
wxDirTraverseResult OnFile( const wxString& aFilePath ) override
|
|
{
|
|
wxFileName file( aFilePath );
|
|
|
|
if( file.GetExt() == wxS( "json" ) )
|
|
m_action( file );
|
|
|
|
return wxDIR_CONTINUE;
|
|
}
|
|
|
|
wxDirTraverseResult OnDir( const wxString& dirPath ) override
|
|
{
|
|
return wxDIR_CONTINUE;
|
|
}
|
|
};
|
|
|
|
|
|
COLOR_SETTINGS* SETTINGS_MANAGER::registerColorSettings( const wxString& aName, bool aAbsolutePath )
|
|
{
|
|
if( !m_color_settings.count( aName ) )
|
|
{
|
|
COLOR_SETTINGS* colorSettings = RegisterSettings( new COLOR_SETTINGS( aName,
|
|
aAbsolutePath ) );
|
|
m_color_settings[aName] = colorSettings;
|
|
}
|
|
|
|
return m_color_settings.at( aName );
|
|
}
|
|
|
|
|
|
COLOR_SETTINGS* SETTINGS_MANAGER::AddNewColorSettings( const wxString& aName )
|
|
{
|
|
if( aName.EndsWith( wxT( ".json" ) ) )
|
|
return registerColorSettings( aName.BeforeLast( '.' ) );
|
|
else
|
|
return registerColorSettings( aName );
|
|
}
|
|
|
|
|
|
COLOR_SETTINGS* SETTINGS_MANAGER::GetMigratedColorSettings()
|
|
{
|
|
if( !m_color_settings.count( "user" ) )
|
|
{
|
|
COLOR_SETTINGS* settings = registerColorSettings( wxT( "user" ) );
|
|
settings->SetName( wxT( "User" ) );
|
|
Save( settings );
|
|
}
|
|
|
|
return m_color_settings.at( "user" );
|
|
}
|
|
|
|
|
|
void SETTINGS_MANAGER::registerBuiltinColorSettings()
|
|
{
|
|
for( COLOR_SETTINGS* settings : COLOR_SETTINGS::CreateBuiltinColorSettings() )
|
|
m_color_settings[settings->GetFilename()] = RegisterSettings( settings, false );
|
|
}
|
|
|
|
|
|
void SETTINGS_MANAGER::loadAllColorSettings()
|
|
{
|
|
// Create the built-in color settings
|
|
registerBuiltinColorSettings();
|
|
|
|
wxFileName third_party_path;
|
|
const ENV_VAR_MAP& env = Pgm().GetLocalEnvVariables();
|
|
auto it = env.find( wxS( "KICAD7_3RD_PARTY" ) );
|
|
|
|
if( it != env.end() && !it->second.GetValue().IsEmpty() )
|
|
third_party_path.SetPath( it->second.GetValue() );
|
|
else
|
|
third_party_path.SetPath( PATHS::GetDefault3rdPartyPath() );
|
|
|
|
third_party_path.AppendDir( wxS( "colors" ) );
|
|
|
|
wxDir third_party_colors_dir( third_party_path.GetFullPath() );
|
|
wxString color_settings_path = GetColorSettingsPath();
|
|
|
|
// Search for and load any other settings
|
|
JSON_DIR_TRAVERSER loader( [&]( const wxFileName& aFilename )
|
|
{
|
|
registerColorSettings( aFilename.GetName() );
|
|
} );
|
|
|
|
JSON_DIR_TRAVERSER thirdPartyLoader(
|
|
[&]( const wxFileName& aFilename )
|
|
{
|
|
COLOR_SETTINGS* settings = registerColorSettings( aFilename.GetFullPath(), true );
|
|
settings->SetReadOnly( true );
|
|
} );
|
|
|
|
wxDir colors_dir( color_settings_path );
|
|
|
|
if( colors_dir.IsOpened() )
|
|
{
|
|
if( third_party_colors_dir.IsOpened() )
|
|
third_party_colors_dir.Traverse( thirdPartyLoader );
|
|
|
|
colors_dir.Traverse( loader );
|
|
}
|
|
}
|
|
|
|
|
|
void SETTINGS_MANAGER::ReloadColorSettings()
|
|
{
|
|
m_color_settings.clear();
|
|
loadAllColorSettings();
|
|
}
|
|
|
|
|
|
void SETTINGS_MANAGER::SaveColorSettings( COLOR_SETTINGS* aSettings, const std::string& aNamespace )
|
|
{
|
|
// The passed settings should already be managed
|
|
wxASSERT( std::find_if( m_color_settings.begin(), m_color_settings.end(),
|
|
[aSettings] ( const std::pair<wxString, COLOR_SETTINGS*>& el )
|
|
{
|
|
return el.second->GetFilename() == aSettings->GetFilename();
|
|
}
|
|
) != m_color_settings.end() );
|
|
|
|
if( aSettings->IsReadOnly() )
|
|
return;
|
|
|
|
if( !aSettings->Store() )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "Color scheme %s not modified; skipping save" ),
|
|
aNamespace );
|
|
return;
|
|
}
|
|
|
|
wxASSERT( aSettings->Contains( aNamespace ) );
|
|
|
|
wxLogTrace( traceSettings, wxT( "Saving color scheme %s, preserving %s" ),
|
|
aSettings->GetFilename(),
|
|
aNamespace );
|
|
|
|
std::optional<nlohmann::json> backup = aSettings->GetJson( aNamespace );
|
|
wxString path = GetColorSettingsPath();
|
|
|
|
aSettings->LoadFromFile( path );
|
|
|
|
if( backup )
|
|
( *aSettings->Internals() )[aNamespace].update( *backup );
|
|
|
|
aSettings->Load();
|
|
|
|
aSettings->SaveToFile( path, true );
|
|
}
|
|
|
|
|
|
wxString SETTINGS_MANAGER::GetPathForSettingsFile( JSON_SETTINGS* aSettings )
|
|
{
|
|
wxASSERT( aSettings );
|
|
|
|
switch( aSettings->GetLocation() )
|
|
{
|
|
case SETTINGS_LOC::USER:
|
|
return GetUserSettingsPath();
|
|
|
|
case SETTINGS_LOC::PROJECT:
|
|
// TODO: MDI support
|
|
return Prj().GetProjectPath();
|
|
|
|
case SETTINGS_LOC::COLORS:
|
|
return GetColorSettingsPath();
|
|
|
|
case SETTINGS_LOC::NONE:
|
|
return "";
|
|
|
|
default:
|
|
wxASSERT_MSG( false, wxT( "Unknown settings location!" ) );
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
|
|
class MIGRATION_TRAVERSER : public wxDirTraverser
|
|
{
|
|
private:
|
|
wxString m_src;
|
|
wxString m_dest;
|
|
wxString m_errors;
|
|
bool m_migrateTables;
|
|
|
|
public:
|
|
MIGRATION_TRAVERSER( const wxString& aSrcDir, const wxString& aDestDir, bool aMigrateTables ) :
|
|
m_src( aSrcDir ),
|
|
m_dest( aDestDir ),
|
|
m_migrateTables( aMigrateTables )
|
|
{
|
|
}
|
|
|
|
wxString GetErrors() { return m_errors; }
|
|
|
|
wxDirTraverseResult OnFile( const wxString& aSrcFilePath ) override
|
|
{
|
|
wxFileName file( aSrcFilePath );
|
|
|
|
if( !m_migrateTables && ( file.GetName() == wxT( "sym-lib-table" ) ||
|
|
file.GetName() == wxT( "fp-lib-table" ) ) )
|
|
{
|
|
return wxDIR_CONTINUE;
|
|
}
|
|
|
|
// Skip migrating PCM installed packages as packages themselves are not moved
|
|
if( file.GetFullName() == wxT( "installed_packages.json" ) )
|
|
return wxDIR_CONTINUE;
|
|
|
|
// Don't migrate hotkeys config files; we don't have a reasonable migration handler for them
|
|
// and so there is no way to resolve conflicts at the moment
|
|
if( file.GetExt() == wxT( "hotkeys" ) )
|
|
return wxDIR_CONTINUE;
|
|
|
|
wxString path = file.GetPath();
|
|
|
|
path.Replace( m_src, m_dest, false );
|
|
file.SetPath( path );
|
|
|
|
wxLogTrace( traceSettings, wxT( "Copying %s to %s" ), aSrcFilePath, file.GetFullPath() );
|
|
|
|
// For now, just copy everything
|
|
KiCopyFile( aSrcFilePath, file.GetFullPath(), m_errors );
|
|
|
|
return wxDIR_CONTINUE;
|
|
}
|
|
|
|
wxDirTraverseResult OnDir( const wxString& dirPath ) override
|
|
{
|
|
wxFileName dir( dirPath );
|
|
|
|
// Whitelist of directories to migrate
|
|
if( dir.GetName() == wxS( "colors" ) ||
|
|
dir.GetName() == wxS( "3d" ) )
|
|
{
|
|
|
|
wxString path = dir.GetPath();
|
|
|
|
path.Replace( m_src, m_dest, false );
|
|
dir.SetPath( path );
|
|
|
|
wxMkdir( dir.GetFullPath() );
|
|
|
|
return wxDIR_CONTINUE;
|
|
}
|
|
else
|
|
{
|
|
return wxDIR_IGNORE;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
bool SETTINGS_MANAGER::MigrateIfNeeded()
|
|
{
|
|
wxFileName path( GetUserSettingsPath(), wxS( "" ) );
|
|
wxLogTrace( traceSettings, wxT( "Using settings path %s" ), path.GetFullPath() );
|
|
|
|
if( m_headless )
|
|
{
|
|
// Special case namely for cli
|
|
// Ensure the settings directory at least exists to prevent additional loading errors from subdirectories
|
|
// TODO review headless (unit tests) vs cli needs, this should be fine for unit tests though
|
|
if( !path.DirExists() )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "Path didn't exist; creating it" ) );
|
|
path.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
|
|
}
|
|
|
|
wxLogTrace( traceSettings, wxT( "Settings migration not checked; running headless" ) );
|
|
return true;
|
|
}
|
|
|
|
if( path.DirExists() )
|
|
{
|
|
wxFileName common = path;
|
|
common.SetName( wxS( "kicad_common" ) );
|
|
common.SetExt( wxS( "json" ) );
|
|
|
|
if( common.Exists() )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "Path exists and has a kicad_common, continuing!" ) );
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Now we have an empty path, let's figure out what to put in it
|
|
DIALOG_MIGRATE_SETTINGS dlg( this );
|
|
|
|
if( dlg.ShowModal() != wxID_OK )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "Migration dialog canceled; exiting" ) );
|
|
return false;
|
|
}
|
|
|
|
if( !path.DirExists() )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "Path didn't exist; creating it" ) );
|
|
path.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
|
|
}
|
|
|
|
if( m_migration_source.IsEmpty() )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "No migration source given; starting with defaults" ) );
|
|
return true;
|
|
}
|
|
|
|
wxLogTrace( traceSettings, wxT( "Migrating from path %s" ), m_migration_source );
|
|
|
|
MIGRATION_TRAVERSER traverser( m_migration_source, path.GetFullPath(), m_migrateLibraryTables );
|
|
wxDir source_dir( m_migration_source );
|
|
|
|
source_dir.Traverse( traverser );
|
|
|
|
if( !traverser.GetErrors().empty() )
|
|
DisplayErrorMessage( nullptr, traverser.GetErrors() );
|
|
|
|
// Remove any library configuration if we didn't choose to import
|
|
if( !m_migrateLibraryTables )
|
|
{
|
|
COMMON_SETTINGS common;
|
|
wxString commonPath = GetPathForSettingsFile( &common );
|
|
common.LoadFromFile( commonPath );
|
|
|
|
const std::vector<wxString> libKeys = {
|
|
wxT( "KICAD6_SYMBOL_DIR" ),
|
|
wxT( "KICAD6_3DMODEL_DIR" ),
|
|
wxT( "KICAD6_FOOTPRINT_DIR" ),
|
|
wxT( "KICAD6_TEMPLATE_DIR" ), // Stores the default library table to be copied
|
|
wxT( "KICAD7_SYMBOL_DIR" ),
|
|
wxT( "KICAD7_3DMODEL_DIR" ),
|
|
wxT( "KICAD7_FOOTPRINT_DIR" ),
|
|
wxT( "KICAD7_TEMPLATE_DIR" ), // Stores the default library table to be copied
|
|
|
|
// Deprecated keys
|
|
wxT( "KICAD_PTEMPLATES" ),
|
|
wxT( "KISYS3DMOD" ),
|
|
wxT( "KISYSMOD" ),
|
|
wxT( "KICAD_SYMBOL_DIR" ),
|
|
};
|
|
|
|
for( const wxString& key : libKeys )
|
|
common.m_Env.vars.erase( key );
|
|
|
|
common.SaveToFile( commonPath );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool SETTINGS_MANAGER::GetPreviousVersionPaths( std::vector<wxString>* aPaths )
|
|
{
|
|
wxASSERT( aPaths );
|
|
|
|
aPaths->clear();
|
|
|
|
wxDir dir;
|
|
std::vector<wxFileName> base_paths;
|
|
|
|
base_paths.emplace_back( wxFileName( calculateUserSettingsPath( false ), wxS( "" ) ) );
|
|
|
|
// If the env override is set, also check the default paths
|
|
if( wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), nullptr ) )
|
|
base_paths.emplace_back( wxFileName( calculateUserSettingsPath( false, false ), wxS( "" ) ) );
|
|
|
|
#ifdef __WXGTK__
|
|
// When running inside FlatPak, KIPLATFORM::ENV::GetUserConfigPath() will return a sandboxed
|
|
// path. In case the user wants to move from non-FlatPak KiCad to FlatPak KiCad, let's add our
|
|
// best guess as to the non-FlatPak config path. Unfortunately FlatPak also hides the host
|
|
// XDG_CONFIG_HOME, so if the user customizes their config path, they will have to browse
|
|
// for it.
|
|
{
|
|
wxFileName wxGtkPath;
|
|
wxGtkPath.AssignDir( wxS( "~/.config/kicad" ) );
|
|
wxGtkPath.MakeAbsolute();
|
|
base_paths.emplace_back( wxGtkPath );
|
|
|
|
// We also want to pick up regular flatpak if we are nightly
|
|
wxGtkPath.AssignDir( wxS( "~/.var/app/org.kicad.KiCad/config/kicad" ) );
|
|
wxGtkPath.MakeAbsolute();
|
|
base_paths.emplace_back( wxGtkPath );
|
|
}
|
|
#endif
|
|
|
|
wxString subdir;
|
|
std::string mine = GetSettingsVersion();
|
|
|
|
auto check_dir = [&] ( const wxString& aSubDir )
|
|
{
|
|
// Only older versions are valid for migration
|
|
if( compareVersions( aSubDir.ToStdString(), mine ) <= 0 )
|
|
{
|
|
wxString sub_path = dir.GetNameWithSep() + aSubDir;
|
|
|
|
if( IsSettingsPathValid( sub_path ) )
|
|
{
|
|
aPaths->push_back( sub_path );
|
|
wxLogTrace( traceSettings, wxT( "GetPreviousVersionName: %s is valid" ), sub_path );
|
|
}
|
|
}
|
|
};
|
|
|
|
std::set<wxString> checkedPaths;
|
|
|
|
for( const wxFileName& base_path : base_paths )
|
|
{
|
|
if( checkedPaths.count( base_path.GetFullPath() ) )
|
|
continue;
|
|
|
|
checkedPaths.insert( base_path.GetFullPath() );
|
|
|
|
if( !dir.Open( base_path.GetFullPath() ) )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "GetPreviousVersionName: could not open base path %s" ),
|
|
base_path.GetFullPath() );
|
|
continue;
|
|
}
|
|
|
|
wxLogTrace( traceSettings, wxT( "GetPreviousVersionName: checking base path %s" ),
|
|
base_path.GetFullPath() );
|
|
|
|
if( dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS ) )
|
|
{
|
|
if( subdir != mine )
|
|
check_dir( subdir );
|
|
|
|
while( dir.GetNext( &subdir ) )
|
|
{
|
|
if( subdir != mine )
|
|
check_dir( subdir );
|
|
}
|
|
}
|
|
|
|
// If we didn't find one yet, check for legacy settings without a version directory
|
|
if( IsSettingsPathValid( dir.GetNameWithSep() ) )
|
|
{
|
|
wxLogTrace( traceSettings,
|
|
wxT( "GetPreviousVersionName: root path %s is valid" ), dir.GetName() );
|
|
aPaths->push_back( dir.GetName() );
|
|
}
|
|
}
|
|
|
|
std::sort( aPaths->begin(), aPaths->end(),
|
|
[&]( const wxString& a, const wxString& b ) -> bool
|
|
{
|
|
wxString verA = wxFileName::DirName( a ).GetDirs().back();
|
|
wxString verB = wxFileName::DirName( b ).GetDirs().back();
|
|
|
|
if( !extractVersion( verA.ToStdString() )
|
|
|| !extractVersion( verB.ToStdString() ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return compareVersions( verA.ToStdString(), verB.ToStdString() ) >= 0;
|
|
} );
|
|
|
|
return aPaths->size() > 0;
|
|
}
|
|
|
|
|
|
bool SETTINGS_MANAGER::IsSettingsPathValid( const wxString& aPath )
|
|
{
|
|
wxFileName test( aPath, wxS( "kicad_common" ) );
|
|
|
|
if( test.Exists() )
|
|
return true;
|
|
|
|
test.SetExt( "json" );
|
|
|
|
return test.Exists();
|
|
}
|
|
|
|
|
|
wxString SETTINGS_MANAGER::GetColorSettingsPath()
|
|
{
|
|
wxFileName path;
|
|
|
|
path.AssignDir( GetUserSettingsPath() );
|
|
path.AppendDir( wxS( "colors" ) );
|
|
|
|
if( !path.DirExists() )
|
|
{
|
|
if( !wxMkdir( path.GetPath() ) )
|
|
{
|
|
wxLogTrace( traceSettings,
|
|
wxT( "GetColorSettingsPath(): Path %s missing and could not be created!" ),
|
|
path.GetPath() );
|
|
}
|
|
}
|
|
|
|
return path.GetPath();
|
|
}
|
|
|
|
|
|
wxString SETTINGS_MANAGER::GetUserSettingsPath()
|
|
{
|
|
static wxString user_settings_path;
|
|
|
|
if( user_settings_path.empty() )
|
|
user_settings_path = calculateUserSettingsPath();
|
|
|
|
return user_settings_path;
|
|
}
|
|
|
|
|
|
wxString SETTINGS_MANAGER::calculateUserSettingsPath( bool aIncludeVer, bool aUseEnv )
|
|
{
|
|
wxFileName cfgpath;
|
|
|
|
// http://docs.wxwidgets.org/3.0/classwx_standard_paths.html#a7c7cf595d94d29147360d031647476b0
|
|
|
|
wxString envstr;
|
|
if( aUseEnv && wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), &envstr ) && !envstr.IsEmpty() )
|
|
{
|
|
// Override the assignment above with KICAD_CONFIG_HOME
|
|
cfgpath.AssignDir( envstr );
|
|
}
|
|
else
|
|
{
|
|
cfgpath.AssignDir( KIPLATFORM::ENV::GetUserConfigPath() );
|
|
|
|
cfgpath.AppendDir( TO_STR( KICAD_CONFIG_DIR ) );
|
|
}
|
|
|
|
if( aIncludeVer )
|
|
cfgpath.AppendDir( GetSettingsVersion() );
|
|
|
|
return cfgpath.GetPath();
|
|
}
|
|
|
|
|
|
std::string SETTINGS_MANAGER::GetSettingsVersion()
|
|
{
|
|
// CMake computes the major.minor string for us.
|
|
return GetMajorMinorVersion().ToStdString();
|
|
}
|
|
|
|
|
|
int SETTINGS_MANAGER::compareVersions( const std::string& aFirst, const std::string& aSecond )
|
|
{
|
|
int a_maj = 0;
|
|
int a_min = 0;
|
|
int b_maj = 0;
|
|
int b_min = 0;
|
|
|
|
if( !extractVersion( aFirst, &a_maj, &a_min ) || !extractVersion( aSecond, &b_maj, &b_min ) )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "compareSettingsVersions: bad input (%s, %s)" ), aFirst, aSecond );
|
|
return -1;
|
|
}
|
|
|
|
if( a_maj < b_maj )
|
|
{
|
|
return -1;
|
|
}
|
|
else if( a_maj > b_maj )
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
if( a_min < b_min )
|
|
{
|
|
return -1;
|
|
}
|
|
else if( a_min > b_min )
|
|
{
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool SETTINGS_MANAGER::extractVersion( const std::string& aVersionString, int* aMajor, int* aMinor )
|
|
{
|
|
std::regex re_version( "(\\d+)\\.(\\d+)" );
|
|
std::smatch match;
|
|
|
|
if( std::regex_match( aVersionString, match, re_version ) )
|
|
{
|
|
try
|
|
{
|
|
int major = std::stoi( match[1].str() );
|
|
int minor = std::stoi( match[2].str() );
|
|
|
|
if( aMajor )
|
|
*aMajor = major;
|
|
|
|
if( aMinor )
|
|
*aMinor = minor;
|
|
}
|
|
catch( ... )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool SETTINGS_MANAGER::LoadProject( const wxString& aFullPath, bool aSetActive )
|
|
{
|
|
// Normalize path to new format even if migrating from a legacy file
|
|
wxFileName path( aFullPath );
|
|
|
|
if( path.GetExt() == LegacyProjectFileExtension )
|
|
path.SetExt( ProjectFileExtension );
|
|
|
|
wxString fullPath = path.GetFullPath();
|
|
|
|
// If already loaded, we are all set. This might be called more than once over a project's
|
|
// lifetime in case the project is first loaded by the KiCad manager and then eeschema or
|
|
// pcbnew try to load it again when they are launched.
|
|
if( m_projects.count( fullPath ) )
|
|
return true;
|
|
|
|
bool readOnly = false;
|
|
LOCKFILE lockFile( fullPath );
|
|
|
|
if( !lockFile )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "Project %s is locked; opening read-only" ), fullPath );
|
|
readOnly = true;
|
|
}
|
|
|
|
// No MDI yet
|
|
if( aSetActive && !m_projects.empty() )
|
|
{
|
|
PROJECT* oldProject = m_projects.begin()->second;
|
|
unloadProjectFile( oldProject, false );
|
|
m_projects.erase( m_projects.begin() );
|
|
|
|
auto it = std::find_if( m_projects_list.begin(), m_projects_list.end(),
|
|
[&]( const std::unique_ptr<PROJECT>& ptr )
|
|
{
|
|
return ptr.get() == oldProject;
|
|
} );
|
|
|
|
wxASSERT( it != m_projects_list.end() );
|
|
m_projects_list.erase( it );
|
|
}
|
|
|
|
wxLogTrace( traceSettings, wxT( "Load project %s" ), fullPath );
|
|
|
|
std::unique_ptr<PROJECT> project = std::make_unique<PROJECT>();
|
|
project->setProjectFullName( fullPath );
|
|
|
|
bool success = loadProjectFile( *project );
|
|
|
|
if( success )
|
|
{
|
|
project->SetReadOnly( readOnly || project->GetProjectFile().IsReadOnly() );
|
|
|
|
if( lockFile )
|
|
m_project_lock.reset( new LOCKFILE( std::move( lockFile ) ) );
|
|
}
|
|
|
|
m_projects_list.push_back( std::move( project ) );
|
|
m_projects[fullPath] = m_projects_list.back().get();
|
|
|
|
wxString fn( path.GetName() );
|
|
|
|
PROJECT_LOCAL_SETTINGS* settings = new PROJECT_LOCAL_SETTINGS( m_projects[fullPath], fn );
|
|
|
|
if( aSetActive )
|
|
settings = RegisterSettings( settings );
|
|
else
|
|
settings->LoadFromFile( path.GetPath() );
|
|
|
|
m_projects[fullPath]->setLocalSettings( settings );
|
|
|
|
if( aSetActive && m_kiway )
|
|
m_kiway->ProjectChanged();
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
bool SETTINGS_MANAGER::UnloadProject( PROJECT* aProject, bool aSave )
|
|
{
|
|
if( !aProject || !m_projects.count( aProject->GetProjectFullName() ) )
|
|
return false;
|
|
|
|
if( !unloadProjectFile( aProject, aSave ) )
|
|
return false;
|
|
|
|
wxString projectPath = aProject->GetProjectFullName();
|
|
wxLogTrace( traceSettings, wxT( "Unload project %s" ), projectPath );
|
|
|
|
PROJECT* toRemove = m_projects.at( projectPath );
|
|
auto it = std::find_if( m_projects_list.begin(), m_projects_list.end(),
|
|
[&]( const std::unique_ptr<PROJECT>& ptr )
|
|
{
|
|
return ptr.get() == toRemove;
|
|
} );
|
|
|
|
wxASSERT( it != m_projects_list.end() );
|
|
m_projects_list.erase( it );
|
|
|
|
m_projects.erase( projectPath );
|
|
|
|
// Immediately reload a null project; this is required until the rest of the application
|
|
// is refactored to not assume that Prj() always works
|
|
if( m_projects.empty() )
|
|
LoadProject( "" );
|
|
|
|
// Remove the reference in the environment to the previous project
|
|
wxSetEnv( PROJECT_VAR_NAME, wxS( "" ) );
|
|
|
|
// Release lock on the file, in case we had one
|
|
m_project_lock = nullptr;
|
|
|
|
if( m_kiway )
|
|
m_kiway->ProjectChanged();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
PROJECT& SETTINGS_MANAGER::Prj() const
|
|
{
|
|
// No MDI yet: First project in the list is the active project
|
|
wxASSERT_MSG( m_projects_list.size(), wxT( "no project in list" ) );
|
|
return *m_projects_list.begin()->get();
|
|
}
|
|
|
|
|
|
bool SETTINGS_MANAGER::IsProjectOpen() const
|
|
{
|
|
return !m_projects.empty();
|
|
}
|
|
|
|
|
|
PROJECT* SETTINGS_MANAGER::GetProject( const wxString& aFullPath ) const
|
|
{
|
|
if( m_projects.count( aFullPath ) )
|
|
return m_projects.at( aFullPath );
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
std::vector<wxString> SETTINGS_MANAGER::GetOpenProjects() const
|
|
{
|
|
std::vector<wxString> ret;
|
|
|
|
for( const std::pair<const wxString, PROJECT*>& pair : m_projects )
|
|
ret.emplace_back( pair.first );
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
bool SETTINGS_MANAGER::SaveProject( const wxString& aFullPath, PROJECT* aProject )
|
|
{
|
|
if( !aProject )
|
|
aProject = &Prj();
|
|
|
|
wxString path = aFullPath;
|
|
|
|
if( path.empty() )
|
|
path = aProject->GetProjectFullName();
|
|
|
|
// TODO: refactor for MDI
|
|
if( aProject->IsReadOnly() )
|
|
return false;
|
|
|
|
if( !m_project_files.count( path ) )
|
|
return false;
|
|
|
|
PROJECT_FILE* project = m_project_files.at( path );
|
|
wxString projectPath = aProject->GetProjectPath();
|
|
|
|
project->SaveToFile( projectPath );
|
|
aProject->GetLocalSettings().SaveToFile( projectPath );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void SETTINGS_MANAGER::SaveProjectAs( const wxString& aFullPath, PROJECT* aProject )
|
|
{
|
|
if( !aProject )
|
|
aProject = &Prj();
|
|
|
|
wxString oldName = aProject->GetProjectFullName();
|
|
|
|
if( aFullPath.IsSameAs( oldName ) )
|
|
{
|
|
SaveProject( aFullPath, aProject );
|
|
return;
|
|
}
|
|
|
|
// Changing this will cause UnloadProject to not save over the "old" project when loading below
|
|
aProject->setProjectFullName( aFullPath );
|
|
|
|
wxFileName fn( aFullPath );
|
|
|
|
PROJECT_FILE* project = m_project_files.at( oldName );
|
|
|
|
// Ensure read-only flags are copied; this allows doing a "Save As" on a standalong board/sch
|
|
// without creating project files if the checkbox is turned off
|
|
project->SetReadOnly( aProject->IsReadOnly() );
|
|
aProject->GetLocalSettings().SetReadOnly( aProject->IsReadOnly() );
|
|
|
|
project->SetFilename( fn.GetName() );
|
|
project->SaveToFile( fn.GetPath() );
|
|
|
|
aProject->GetLocalSettings().SetFilename( fn.GetName() );
|
|
aProject->GetLocalSettings().SaveToFile( fn.GetPath() );
|
|
|
|
m_project_files[fn.GetFullPath()] = project;
|
|
m_project_files.erase( oldName );
|
|
|
|
m_projects[fn.GetFullPath()] = m_projects[oldName];
|
|
m_projects.erase( oldName );
|
|
}
|
|
|
|
|
|
void SETTINGS_MANAGER::SaveProjectCopy( const wxString& aFullPath, PROJECT* aProject )
|
|
{
|
|
if( !aProject )
|
|
aProject = &Prj();
|
|
|
|
PROJECT_FILE* project = m_project_files.at( aProject->GetProjectFullName() );
|
|
wxString oldName = project->GetFilename();
|
|
wxFileName fn( aFullPath );
|
|
|
|
bool readOnly = project->IsReadOnly();
|
|
project->SetReadOnly( false );
|
|
|
|
project->SetFilename( fn.GetName() );
|
|
project->SaveToFile( fn.GetPath() );
|
|
project->SetFilename( oldName );
|
|
|
|
PROJECT_LOCAL_SETTINGS& localSettings = aProject->GetLocalSettings();
|
|
|
|
localSettings.SetFilename( fn.GetName() );
|
|
localSettings.SaveToFile( fn.GetPath() );
|
|
localSettings.SetFilename( oldName );
|
|
|
|
project->SetReadOnly( readOnly );
|
|
}
|
|
|
|
|
|
bool SETTINGS_MANAGER::loadProjectFile( PROJECT& aProject )
|
|
{
|
|
wxFileName fullFn( aProject.GetProjectFullName() );
|
|
wxString fn( fullFn.GetName() );
|
|
|
|
PROJECT_FILE* file = RegisterSettings( new PROJECT_FILE( fn ), false );
|
|
|
|
m_project_files[aProject.GetProjectFullName()] = file;
|
|
|
|
aProject.setProjectFile( file );
|
|
file->SetProject( &aProject );
|
|
|
|
wxString path( fullFn.GetPath() );
|
|
|
|
return file->LoadFromFile( path );
|
|
}
|
|
|
|
|
|
bool SETTINGS_MANAGER::unloadProjectFile( PROJECT* aProject, bool aSave )
|
|
{
|
|
if( !aProject )
|
|
return false;
|
|
|
|
wxString name = aProject->GetProjectFullName();
|
|
|
|
if( !m_project_files.count( name ) )
|
|
return false;
|
|
|
|
PROJECT_FILE* file = m_project_files[name];
|
|
|
|
auto it = std::find_if( m_settings.begin(), m_settings.end(),
|
|
[&file]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
|
|
{
|
|
return aPtr.get() == file;
|
|
} );
|
|
|
|
if( it != m_settings.end() )
|
|
{
|
|
wxString projectPath = GetPathForSettingsFile( it->get() );
|
|
|
|
FlushAndRelease( &aProject->GetLocalSettings(), aSave );
|
|
|
|
if( aSave )
|
|
( *it )->SaveToFile( projectPath );
|
|
|
|
m_settings.erase( it );
|
|
}
|
|
|
|
m_project_files.erase( name );
|
|
|
|
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 );
|
|
|
|
if( !target.DirExists() && !wxMkdir( target.GetPath() ) )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "Could not create project backup path %s" ), target.GetPath() );
|
|
return false;
|
|
}
|
|
|
|
if( !target.IsDirWritable() )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "Backup directory %s is not writable" ), target.GetPath() );
|
|
return false;
|
|
}
|
|
|
|
wxLogTrace( traceSettings, wxT( "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, wxS( "" ) );
|
|
dt.ParseFormat( fn, backupDateTimeFormat );
|
|
return dt;
|
|
};
|
|
|
|
wxFileName projectPath( Prj().GetProjectPath() );
|
|
|
|
// Skip backup if project path isn't valid or writable
|
|
if( !projectPath.IsOk() || !projectPath.Exists() || !projectPath.IsDirWritable() )
|
|
return true;
|
|
|
|
wxString backupPath = GetProjectBackupsPath();
|
|
|
|
if( !wxDirExists( backupPath ) )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "Backup path %s doesn't exist, creating it" ), backupPath );
|
|
|
|
if( !wxMkdir( backupPath ) )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "Could not create backups path! Skipping backup" ) );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
wxDir dir( backupPath );
|
|
|
|
if( !dir.IsOpened() )
|
|
{
|
|
wxLogTrace( traceSettings, wxT( "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 );
|
|
}
|