|
|
/*
* This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2020 Jon Evans <jon@craftyjon.com> * Copyright (C) 2020 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 <regex>
#include <wx/debug.h>
#include <wx/filename.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 <macros.h>
#include <settings/app_settings.h>
#include <settings/common_settings.h>
#include <settings/settings_manager.h>
#include <settings/color_settings.h>
/**
* Flag to enable settings tracing * @ingroup trace_env_vars */const char* traceSettings = "SETTINGS";
SETTINGS_MANAGER::SETTINGS_MANAGER() : m_common_settings( nullptr ), m_migration_source(){ // 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 = static_cast<COMMON_SETTINGS*>( RegisterSettings( new COMMON_SETTINGS, false ) );
// create the default color settings
m_color_settings["default"] = static_cast<COLOR_SETTINGS*>( RegisterSettings( new COLOR_SETTINGS ) );}
SETTINGS_MANAGER::~SETTINGS_MANAGER(){ m_settings.clear(); m_color_settings.clear();}
JSON_SETTINGS* SETTINGS_MANAGER::RegisterSettings( JSON_SETTINGS* aSettings, bool aLoadNow ){ std::unique_ptr<JSON_SETTINGS> ptr( aSettings );
wxLogTrace( traceSettings, "Registered new settings object %s", ptr->GetFilename() );
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
for( auto&& settings : m_settings ) settings->LoadFromFile( GetPathForSettingsFile( settings.get() ) );}
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, "Saving %s", ( *it )->GetFilename() ); ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) ); }}
void SETTINGS_MANAGER::FlushAndRelease( 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, "Flush and release %s", ( *it )->GetFilename() ); ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) ); m_settings.erase( it ); }}
COLOR_SETTINGS* SETTINGS_MANAGER::GetColorSettings( std::string aName ){ COLOR_SETTINGS* ret = nullptr;
try { ret = m_color_settings.at( aName ); } catch( std::out_of_range& ) { // This had better work
ret = m_color_settings.at( "default" ); }
return ret;}
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 auto& el ) { return el.second == aSettings; } ) != m_color_settings.end() );
nlohmann::json::json_pointer ptr = JSON_SETTINGS::PointerFromString( aNamespace );
aSettings->Store();
wxASSERT( aSettings->contains( ptr ) );
wxLogTrace( traceSettings, "Saving color scheme %s, preserving &s", aSettings->GetFilename(), aNamespace );
nlohmann::json backup = aSettings->at( ptr ); std::string path = GetColorSettingsPath();
aSettings->LoadFromFile( path );
( *aSettings )[ptr].update( backup ); aSettings->Load();
aSettings->SaveToFile( path );}
std::string SETTINGS_MANAGER::GetPathForSettingsFile( JSON_SETTINGS* aSettings ){ wxASSERT( aSettings );
switch( aSettings->GetLocation() ) { case SETTINGS_LOC::USER: return GetUserSettingsPath();
case SETTINGS_LOC::PROJECT: // TODO(JE)
return "";
case SETTINGS_LOC::COLORS: return GetColorSettingsPath();
default: wxASSERT_MSG( false, "Unknown settings location!" ); }
return "";}
class MIGRATION_TRAVERSER : public wxDirTraverser{private: wxString m_src; wxString m_dest; wxString m_errors;
public: MIGRATION_TRAVERSER( const wxString& aSrcDir, const wxString& aDestDir ) : m_src( aSrcDir ), m_dest( aDestDir ) { }
wxString GetErrors() { return m_errors; }
virtual wxDirTraverseResult OnFile( const wxString& aSrcFilePath ) override { wxFileName file( aSrcFilePath ); wxString path = file.GetPath();
path.Replace( m_src, m_dest, false ); file.SetPath( path );
wxLogTrace( traceSettings, "Copying %s to %s", aSrcFilePath, file.GetFullPath() );
// For now, just copy everything
CopyFile( aSrcFilePath, file.GetFullPath(), m_errors );
return wxDIR_CONTINUE; }
virtual wxDirTraverseResult OnDir( const wxString& dirPath ) override { wxFileName dir( dirPath );
// Whitelist of directories to migrate
if( dir.GetName() == "colors" || dir.GetName() == "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(), "" ); wxLogTrace( traceSettings, "Using settings path %s", path.GetFullPath() );
if( path.DirExists() ) { wxFileName common = path; common.SetName( "kicad_common" ); common.SetExt( "json" );
if( common.Exists() ) { wxLogTrace( traceSettings, "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, "Migration dialog canceled; exiting" ); return false; }
if( !path.DirExists() ) { wxLogTrace( traceSettings, "Path didn't exist; creating it" ); path.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ); }
if( m_migration_source.IsEmpty() ) { wxLogTrace( traceSettings, "No migration source given; starting with defaults" ); return true; }
MIGRATION_TRAVERSER traverser( m_migration_source, path.GetFullPath() ); wxDir source_dir( m_migration_source );
source_dir.Traverse( traverser );
if( !traverser.GetErrors().empty() ) DisplayErrorMessage( nullptr, traverser.GetErrors() );
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 ), "" ) );
// 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 ), "" ) );
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, "GetPreviousVersionName: %s is valid", sub_path ); } } };
for( auto base_path : base_paths ) { if( !dir.Open( base_path.GetFullPath() ) ) { wxLogTrace( traceSettings, "GetPreviousVersionName: could not open base path %s", base_path.GetFullPath() ); continue; }
wxLogTrace( traceSettings, "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, "GetPreviousVersionName: root path %s is valid", dir.GetName() ); aPaths->push_back( dir.GetName() ); } }
return aPaths->size() > 0;}
bool SETTINGS_MANAGER::IsSettingsPathValid( const wxString& aPath ){ wxFileName test( aPath, "kicad_common" ); return test.Exists();}
std::string SETTINGS_MANAGER::GetColorSettingsPath(){ wxFileName path;
path.AssignDir( GetUserSettingsPath() ); path.AppendDir( "colors" );
return path.GetPath().ToStdString();}
std::string SETTINGS_MANAGER::GetUserSettingsPath(){ static std::string user_settings_path;
if( user_settings_path.empty() ) user_settings_path = calculateUserSettingsPath();
return user_settings_path;}
std::string SETTINGS_MANAGER::calculateUserSettingsPath( bool aIncludeVer, bool aUseEnv ){ wxFileName cfgpath;
// http://docs.wxwidgets.org/3.0/classwx_standard_paths.html#a7c7cf595d94d29147360d031647476b0
cfgpath.AssignDir( wxStandardPaths::Get().GetUserConfigDir() );
// GetUserConfigDir() does not default to ~/.config which is the current standard
// configuration file location on Linux. This has been fixed in later versions of wxWidgets.
#if !defined( __WXMSW__ ) && !defined( __WXMAC__ )
wxArrayString dirs = cfgpath.GetDirs();
if( dirs.Last() != ".config" ) cfgpath.AppendDir( ".config" );#endif
wxString envstr;
// This shouldn't cause any issues on Windows or MacOS.
if( wxGetEnv( wxT( "XDG_CONFIG_HOME" ), &envstr ) && !envstr.IsEmpty() ) { // Override the assignment above with XDG_CONFIG_HOME
cfgpath.AssignDir( envstr ); }
cfgpath.AppendDir( TO_STR( KICAD_CONFIG_DIR ) );
// Use KICAD_CONFIG_HOME to allow the user to force a specific configuration path.
if( aUseEnv && wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), &envstr ) && !envstr.IsEmpty() ) { // Override the assignment above with KICAD_CONFIG_HOME
cfgpath.AssignDir( envstr ); }
if( aIncludeVer ) cfgpath.AppendDir( GetSettingsVersion() );
return cfgpath.GetPath().ToStdString();}
std::string SETTINGS_MANAGER::GetSettingsVersion(){ wxString version = GetBuildVersion().BeforeLast( '.' );
// A full build version looks like (x.y.z-nnn-g1234567) or x.y.z-xxx
// We want to extract the x.y portion here
if( version.StartsWith( '(' ) ) version = version.Mid( 1 );
return version.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, "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 { *aMajor = std::stoi( match[1].str() ); *aMinor = std::stoi( match[2].str() ); } catch( ... ) { return false; }
return true; }
return false;}
|