KiCad PCB EDA Suite
settings_manager.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2020 Jon Evans <jon@craftyjon.com>
5  * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software: you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation, either version 3 of the License, or (at your
10  * option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <regex>
22 #include <wx/debug.h>
23 #include <wx/filename.h>
24 #include <wx/stdpaths.h>
25 #include <wx/utils.h>
26 
27 #include <build_version.h>
28 #include <confirm.h>
30 #include <gestfich.h>
31 #include <macros.h>
32 #include <settings/app_settings.h>
36 
37 
42 const char* traceSettings = "SETTINGS";
43 
44 
46  m_common_settings( nullptr ),
47  m_migration_source()
48 {
49  // Check if the settings directory already exists, and if not, perform a migration if possible
50  if( !MigrateIfNeeded() )
51  {
52  m_ok = false;
53  return;
54  }
55 
56  m_ok = true;
57 
58  // create the common settings shared by all applications. Not loaded immediately
60  static_cast<COMMON_SETTINGS*>( RegisterSettings( new COMMON_SETTINGS, false ) );
61 
63 }
64 
66 {
67  m_settings.clear();
68  m_color_settings.clear();
69 }
70 
71 
73 {
74  std::unique_ptr<JSON_SETTINGS> ptr( aSettings );
75 
76  ptr->SetManager( this );
77 
78  wxLogTrace( traceSettings, "Registered new settings object %s", ptr->GetFilename() );
79 
80  if( aLoadNow )
81  ptr->LoadFromFile( GetPathForSettingsFile( ptr.get() ) );
82 
83  m_settings.push_back( std::move( ptr ) );
84  return m_settings.back().get();
85 }
86 
87 
89 {
90  // TODO(JE) We should check for dirty settings here and write them if so, because
91  // Load() could be called late in the application lifecycle
92 
93  for( auto&& settings : m_settings )
94  settings->LoadFromFile( GetPathForSettingsFile( settings.get() ) );
95 }
96 
97 
99 {
100  auto it = std::find_if( m_settings.begin(), m_settings.end(),
101  [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
102  {
103  return aPtr.get() == aSettings;
104  } );
105 
106  if( it != m_settings.end() )
107  ( *it )->LoadFromFile( GetPathForSettingsFile( it->get() ) );
108 }
109 
110 
112 {
113  for( auto&& settings : m_settings )
114  {
115  // Never automatically save color settings, caller should use SaveColorSettings
116  if( dynamic_cast<COLOR_SETTINGS*>( settings.get() ) )
117  continue;
118 
119  settings->SaveToFile( GetPathForSettingsFile( settings.get() ) );
120  }
121 }
122 
123 
125 {
126  auto it = std::find_if( m_settings.begin(), m_settings.end(),
127  [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
128  {
129  return aPtr.get() == aSettings;
130  } );
131 
132  if( it != m_settings.end() )
133  {
134  wxLogTrace( traceSettings, "Saving %s", ( *it )->GetFilename() );
135  ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
136  }
137 }
138 
139 
141 {
142  auto it = std::find_if( m_settings.begin(), m_settings.end(),
143  [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
144  {
145  return aPtr.get() == aSettings;
146  } );
147 
148  if( it != m_settings.end() )
149  {
150  wxLogTrace( traceSettings, "Flush and release %s", ( *it )->GetFilename() );
151  ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
152  m_settings.erase( it );
153  }
154 }
155 
156 
158 {
159  if( m_color_settings.count( aName ) )
160  return m_color_settings.at( aName );
161 
162  COLOR_SETTINGS* ret = nullptr;
163 
164  if( !aName.empty() )
165  ret = loadColorSettingsByName( aName );
166 
167  // This had better work
168  if( !ret )
169  ret = m_color_settings.at( "user" );
170 
171  return ret;
172 }
173 
174 
176 {
177  wxLogTrace( traceSettings, "Attempting to load color theme %s", aName );
178 
179  wxFileName fn( GetColorSettingsPath(), aName, "json" );
180 
181  if( !fn.IsOk() || !fn.Exists() )
182  {
183  wxLogTrace( traceSettings, "Theme file %s.json not found, falling back to user", aName );
184  return nullptr;
185  }
186 
187  auto cs = static_cast<COLOR_SETTINGS*>(
188  RegisterSettings( new COLOR_SETTINGS( aName.ToStdString() ) ) );
189 
190  if( cs->GetFilename() != aName.ToStdString() )
191  {
192  wxLogTrace( traceSettings, "Warning: stored filename is actually %s, ", cs->GetFilename() );
193  }
194 
195  m_color_settings[aName] = cs;
196 
197  return cs;
198 }
199 
200 
201 class COLOR_SETTINGS_LOADER : public wxDirTraverser
202 {
203 private:
204  std::function<void( const wxString& )> m_action;
205 
206 public:
207  explicit COLOR_SETTINGS_LOADER( std::function<void( const wxString& )> aAction )
208  : m_action( std::move( aAction ) )
209  {
210  }
211 
212  wxDirTraverseResult OnFile( const wxString& aFilePath ) override
213  {
214  wxFileName file( aFilePath );
215 
216  if( file.GetExt() != "json" )
217  return wxDIR_CONTINUE;
218 
219  if( file.GetName() == "user" )
220  return wxDIR_CONTINUE;
221 
222  m_action( file.GetName() );
223 
224  return wxDIR_CONTINUE;
225  }
226 
227  wxDirTraverseResult OnDir( const wxString& dirPath ) override
228  {
229  return wxDIR_IGNORE;
230  }
231 };
232 
233 
234 void SETTINGS_MANAGER::registerColorSettings( const wxString& aFilename )
235 {
236  if( m_color_settings.count( aFilename ) )
237  return;
238 
239  m_color_settings[aFilename] = static_cast<COLOR_SETTINGS*>(
240  RegisterSettings( new COLOR_SETTINGS( aFilename.ToStdString() ) ) );
241 }
242 
243 
245 {
246  wxString filename = aFilename;
247 
248  if( filename.EndsWith( wxT( ".json" ) ) )
249  filename = filename.BeforeLast( '.' );
250 
251  registerColorSettings( filename );
252  return m_color_settings[filename];
253 }
254 
255 
257 {
258  // Create the default color settings
259  registerColorSettings( "user" );
260 
261  // Search for and load any other settings
262  COLOR_SETTINGS_LOADER loader( [&]( const wxString& aFilename )
263  {
264  registerColorSettings( aFilename );
265  } );
266 
267  wxDir colors_dir( GetColorSettingsPath() );
268 
269  if( colors_dir.IsOpened() )
270  colors_dir.Traverse( loader );
271 }
272 
273 
275 {
276  m_color_settings.clear();
278 }
279 
280 
281 void SETTINGS_MANAGER::SaveColorSettings( COLOR_SETTINGS* aSettings, const std::string& aNamespace )
282 {
283  // The passed settings should already be managed
284  wxASSERT( std::find_if( m_color_settings.begin(), m_color_settings.end(),
285  [aSettings] ( const std::pair<wxString, COLOR_SETTINGS*>& el )
286  {
287  return el.second->GetFilename() == aSettings->GetFilename();
288  }
289  ) != m_color_settings.end() );
290 
291  nlohmann::json::json_pointer ptr = JSON_SETTINGS::PointerFromString( aNamespace );
292 
293  aSettings->Store();
294 
295  wxASSERT( aSettings->contains( ptr ) );
296 
297  wxLogTrace( traceSettings, "Saving color scheme %s, preserving %s", aSettings->GetFilename(),
298  aNamespace );
299 
300  nlohmann::json backup = aSettings->at( ptr );
301  std::string path = GetColorSettingsPath();
302 
303  aSettings->LoadFromFile( path );
304 
305  ( *aSettings )[ptr].update( backup );
306  aSettings->Load();
307 
308  aSettings->SaveToFile( path );
309 }
310 
311 
313 {
314  wxASSERT( aSettings );
315 
316  switch( aSettings->GetLocation() )
317  {
318  case SETTINGS_LOC::USER:
319  return GetUserSettingsPath();
320 
322  // TODO(JE)
323  return "";
324 
326  return GetColorSettingsPath();
327 
328  default:
329  wxASSERT_MSG( false, "Unknown settings location!" );
330  }
331 
332  return "";
333 }
334 
335 
336 class MIGRATION_TRAVERSER : public wxDirTraverser
337 {
338 private:
339  wxString m_src;
340  wxString m_dest;
341  wxString m_errors;
342 
343 public:
344  MIGRATION_TRAVERSER( const wxString& aSrcDir, const wxString& aDestDir ) :
345  m_src( aSrcDir ), m_dest( aDestDir )
346  {
347  }
348 
349  wxString GetErrors() { return m_errors; }
350 
351  virtual wxDirTraverseResult OnFile( const wxString& aSrcFilePath ) override
352  {
353  wxFileName file( aSrcFilePath );
354  wxString path = file.GetPath();
355 
356  path.Replace( m_src, m_dest, false );
357  file.SetPath( path );
358 
359  wxLogTrace( traceSettings, "Copying %s to %s", aSrcFilePath, file.GetFullPath() );
360 
361  // For now, just copy everything
362  CopyFile( aSrcFilePath, file.GetFullPath(), m_errors );
363 
364  return wxDIR_CONTINUE;
365  }
366 
367  virtual wxDirTraverseResult OnDir( const wxString& dirPath ) override
368  {
369  wxFileName dir( dirPath );
370 
371  // Whitelist of directories to migrate
372  if( dir.GetName() == "colors" ||
373  dir.GetName() == "3d" )
374  {
375 
376  wxString path = dir.GetPath();
377 
378  path.Replace( m_src, m_dest, false );
379  dir.SetPath( path );
380 
381  wxMkdir( dir.GetFullPath() );
382 
383  return wxDIR_CONTINUE;
384  }
385  else
386  {
387  return wxDIR_IGNORE;
388  }
389  }
390 };
391 
392 
394 {
395  wxFileName path( GetUserSettingsPath(), "" );
396  wxLogTrace( traceSettings, "Using settings path %s", path.GetFullPath() );
397 
398  if( path.DirExists() )
399  {
400  wxFileName common = path;
401  common.SetName( "kicad_common" );
402  common.SetExt( "json" );
403 
404  if( common.Exists() )
405  {
406  wxLogTrace( traceSettings, "Path exists and has a kicad_common, continuing!" );
407  return true;
408  }
409  }
410 
411  // Now we have an empty path, let's figure out what to put in it
412  DIALOG_MIGRATE_SETTINGS dlg( this );
413 
414  if( dlg.ShowModal() != wxID_OK )
415  {
416  wxLogTrace( traceSettings, "Migration dialog canceled; exiting" );
417  return false;
418  }
419 
420  if( !path.DirExists() )
421  {
422  wxLogTrace( traceSettings, "Path didn't exist; creating it" );
423  path.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
424  }
425 
426  if( m_migration_source.IsEmpty() )
427  {
428  wxLogTrace( traceSettings, "No migration source given; starting with defaults" );
429  return true;
430  }
431 
432  MIGRATION_TRAVERSER traverser( m_migration_source, path.GetFullPath() );
433  wxDir source_dir( m_migration_source );
434 
435  source_dir.Traverse( traverser );
436 
437  if( !traverser.GetErrors().empty() )
438  DisplayErrorMessage( nullptr, traverser.GetErrors() );
439 
440  return true;
441 }
442 
443 
444 bool SETTINGS_MANAGER::GetPreviousVersionPaths( std::vector<wxString>* aPaths )
445 {
446  wxASSERT( aPaths );
447 
448  aPaths->clear();
449 
450  wxDir dir;
451  std::vector<wxFileName> base_paths;
452 
453  base_paths.emplace_back( wxFileName( calculateUserSettingsPath( false ), "" ) );
454 
455  // If the env override is set, also check the default paths
456  if( wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), nullptr ) )
457  base_paths.emplace_back( wxFileName( calculateUserSettingsPath( false, false ), "" ) );
458 
459  wxString subdir;
460  std::string mine = GetSettingsVersion();
461 
462  auto check_dir = [&] ( const wxString& aSubDir )
463  {
464  // Only older versions are valid for migration
465  if( compareVersions( aSubDir.ToStdString(), mine ) <= 0 )
466  {
467  wxString sub_path = dir.GetNameWithSep() + aSubDir;
468 
469  if( IsSettingsPathValid( sub_path ) )
470  {
471  aPaths->push_back( sub_path );
472  wxLogTrace( traceSettings, "GetPreviousVersionName: %s is valid", sub_path );
473  }
474  }
475  };
476 
477  for( auto base_path : base_paths )
478  {
479  if( !dir.Open( base_path.GetFullPath() ) )
480  {
481  wxLogTrace( traceSettings, "GetPreviousVersionName: could not open base path %s",
482  base_path.GetFullPath() );
483  continue;
484  }
485 
486  wxLogTrace( traceSettings, "GetPreviousVersionName: checking base path %s",
487  base_path.GetFullPath() );
488 
489  if( dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS ) )
490  {
491  if( subdir != mine )
492  check_dir( subdir );
493 
494  while( dir.GetNext( &subdir ) )
495  {
496  if( subdir != mine )
497  check_dir( subdir );
498  }
499  }
500 
501  // If we didn't find one yet, check for legacy settings without a version directory
502  if( IsSettingsPathValid( dir.GetNameWithSep() ) )
503  {
504  wxLogTrace( traceSettings,
505  "GetPreviousVersionName: root path %s is valid", dir.GetName() );
506  aPaths->push_back( dir.GetName() );
507  }
508  }
509 
510  return aPaths->size() > 0;
511 }
512 
513 
514 bool SETTINGS_MANAGER::IsSettingsPathValid( const wxString& aPath )
515 {
516  wxFileName test( aPath, "kicad_common" );
517  return test.Exists();
518 }
519 
520 
522 {
523  wxFileName path;
524 
525  path.AssignDir( GetUserSettingsPath() );
526  path.AppendDir( "colors" );
527 
528  return path.GetPath().ToStdString();
529 }
530 
531 
533 {
534  static std::string user_settings_path;
535 
536  if( user_settings_path.empty() )
537  user_settings_path = calculateUserSettingsPath();
538 
539  return user_settings_path;
540 }
541 
542 
543 std::string SETTINGS_MANAGER::calculateUserSettingsPath( bool aIncludeVer, bool aUseEnv )
544 {
545  wxFileName cfgpath;
546 
547  // http://docs.wxwidgets.org/3.0/classwx_standard_paths.html#a7c7cf595d94d29147360d031647476b0
548  cfgpath.AssignDir( wxStandardPaths::Get().GetUserConfigDir() );
549 
550  // GetUserConfigDir() does not default to ~/.config which is the current standard
551  // configuration file location on Linux. This has been fixed in later versions of wxWidgets.
552 #if !defined( __WXMSW__ ) && !defined( __WXMAC__ )
553  wxArrayString dirs = cfgpath.GetDirs();
554 
555  if( dirs.Last() != ".config" )
556  cfgpath.AppendDir( ".config" );
557 #endif
558 
559  wxString envstr;
560 
561  // This shouldn't cause any issues on Windows or MacOS.
562  if( wxGetEnv( wxT( "XDG_CONFIG_HOME" ), &envstr ) && !envstr.IsEmpty() )
563  {
564  // Override the assignment above with XDG_CONFIG_HOME
565  cfgpath.AssignDir( envstr );
566  }
567 
568  cfgpath.AppendDir( TO_STR( KICAD_CONFIG_DIR ) );
569 
570  // Use KICAD_CONFIG_HOME to allow the user to force a specific configuration path.
571  if( aUseEnv && wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), &envstr ) && !envstr.IsEmpty() )
572  {
573  // Override the assignment above with KICAD_CONFIG_HOME
574  cfgpath.AssignDir( envstr );
575  }
576 
577  if( aIncludeVer )
578  cfgpath.AppendDir( GetSettingsVersion() );
579 
580  return cfgpath.GetPath().ToStdString();
581 }
582 
583 
585 {
586  // CMake computes the major.minor string for us.
587  return GetMajorMinorVersion().ToStdString();
588 }
589 
590 
591 int SETTINGS_MANAGER::compareVersions( const std::string& aFirst, const std::string& aSecond )
592 {
593  int a_maj = 0;
594  int a_min = 0;
595  int b_maj = 0;
596  int b_min = 0;
597 
598  if( !extractVersion( aFirst, &a_maj, &a_min ) || !extractVersion( aSecond, &b_maj, &b_min ) )
599  {
600  wxLogTrace( traceSettings, "compareSettingsVersions: bad input (%s, %s)", aFirst, aSecond );
601  return -1;
602  }
603 
604  if( a_maj < b_maj )
605  {
606  return -1;
607  }
608  else if( a_maj > b_maj )
609  {
610  return 1;
611  }
612  else
613  {
614  if( a_min < b_min )
615  {
616  return -1;
617  }
618  else if( a_min > b_min )
619  {
620  return 1;
621  }
622  else
623  {
624  return 0;
625  }
626  }
627 }
628 
629 
630 bool SETTINGS_MANAGER::extractVersion( const std::string& aVersionString, int* aMajor, int* aMinor )
631 {
632  std::regex re_version( "(\\d+)\\.(\\d+)" );
633  std::smatch match;
634 
635  if( std::regex_match( aVersionString, match, re_version ) )
636  {
637  try
638  {
639  *aMajor = std::stoi( match[1].str() );
640  *aMinor = std::stoi( match[2].str() );
641  }
642  catch( ... )
643  {
644  return false;
645  }
646 
647  return true;
648  }
649 
650  return false;
651 }
#define TO_STR(x)
void FlushAndRelease(JSON_SETTINGS *aSettings)
If the given settings object is registered, save it to disk and unregister it.
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition: confirm.cpp:252
This file is part of the common library TODO brief description.
This file is part of the common library.
static bool IsSettingsPathValid(const wxString &aPath)
Checks if a given path is probably a valid KiCad configuration directory.
wxDirTraverseResult OnFile(const wxString &aFilePath) override
void CopyFile(const wxString &aSrcPath, const wxString &aDestPath, wxString &aErrors)
Function CopyFile.
Definition: gestfich.cpp:363
Template specialization to enable wxStrings for certain containers (e.g. unordered_map)
Definition: bitmap.cpp:56
bool MigrateIfNeeded()
Handles the initialization of the user settings directory and migration from previous KiCad versions ...
COLOR_SETTINGS * AddNewColorSettings(const wxString &aFilename)
Registers a new color settings object with the given filename.
nlohmann::json json
Definition: gerbview.cpp:40
std::string GetPathForSettingsFile(JSON_SETTINGS *aSettings)
Returns the path a given settings file should be loaded from / stored to.
static int compareVersions(const std::string &aFirst, const std::string &aSecond)
Compares two settings versions, like "5.99" and "6.0".
static std::string calculateUserSettingsPath(bool aIncludeVer=true, bool aUseEnv=true)
Determines the base path for user settings files.
wxString GetMajorMinorVersion()
Get only the major and minor version in a string major.minor.
static std::string GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
This file contains miscellaneous commonly used macros and functions.
The color scheme directory (e.g. ~/.config/kicad/colors/)
wxString m_migration_source
bool m_ok
True if settings loaded successfully at construction.
MIGRATION_TRAVERSER(const wxString &aSrcDir, const wxString &aDestDir)
The settings directory inside a project folder.
SETTINGS_LOC GetLocation() const
Definition: json_settings.h:59
bool GetPreviousVersionPaths(std::vector< wxString > *aName=nullptr)
Retreives the name of the most recent previous KiCad version that can be found in the user settings d...
virtual void LoadFromFile(const std::string &aDirectory)
Loads the backing file from disk and then calls Load()
void registerColorSettings(const wxString &aFilename)
static std::string GetColorSettingsPath()
Returns the path where color scheme files are stored (normally .
virtual void Store()
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
virtual wxDirTraverseResult OnFile(const wxString &aSrcFilePath) override
JSON_SETTINGS * RegisterSettings(JSON_SETTINGS *aSettings, bool aLoadNow=true)
Takes ownership of the pointer passed in.
std::unordered_map< wxString, COLOR_SETTINGS * > m_color_settings
COLOR_SETTINGS * loadColorSettingsByName(const wxString &aName)
Attempts to load a color theme by name (the color theme directory and .json ext are assumed)
COLOR_SETTINGS * GetColorSettings(const wxString &aName="user")
Retrieves a color settings object that applications can read colors from.
COLOR_SETTINGS_LOADER(std::function< void(const wxString &)> aAction)
void ReloadColorSettings()
Re-scans the color themes directory, reloading any changes it finds.
std::vector< std::unique_ptr< JSON_SETTINGS > > m_settings
virtual wxDirTraverseResult OnDir(const wxString &dirPath) override
The main config directory (e.g. ~/.config/kicad/)
void SaveColorSettings(COLOR_SETTINGS *aSettings, const std::string &aNamespace="")
Safely saves a COLOR_SETTINGS to disk, preserving any changes outside the given namespace.
static std::string GetSettingsVersion()
Parses the current KiCad build version and extracts the major and minor revision to use as the name o...
Color settings are a bit different than most of the settings objects in that there can be more than o...
std::string GetFilename() const
Definition: json_settings.h:57
wxDirTraverseResult OnDir(const wxString &dirPath) override
COMMON_SETTINGS * m_common_settings
virtual void SaveToFile(const std::string &aDirectory)
Calls Store() and then writes the contents of the JSON document to a file.
const char * traceSettings
Flag to enable settings tracing.
static nlohmann::json::json_pointer PointerFromString(std::string aPath)
Builds a JSON pointer based on a given string.
std::function< void(const wxString &)> m_action
virtual void Load()
Updates the parameters of this object based on the current JSON document contents.
static bool extractVersion(const std::string &aVersionString, int *aMajor, int *aMinor)
Extracts the numeric version from a given settings string.