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) 2021 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/dir.h>
24 #include <wx/filename.h>
25 #include <wx/snglinst.h>
26 #include <wx/stdpaths.h>
27 #include <wx/utils.h>
28 
29 #include <build_version.h>
30 #include <confirm.h>
32 #include <gestfich.h>
33 #include <kiplatform/environment.h>
34 #include <kiway.h>
35 #include <lockfile.h>
36 #include <macros.h>
37 #include <pgm_base.h>
38 #include <paths.h>
39 #include <project.h>
41 #include <project/project_file.h>
48 
49 
51  m_headless( aHeadless ),
52  m_kiway( nullptr ),
53  m_common_settings( nullptr ),
54  m_migration_source(),
55  m_migrateLibraryTables( true )
56 {
58 
59  // Check if the settings directory already exists, and if not, perform a migration if possible
60  if( !MigrateIfNeeded() )
61  {
62  m_ok = false;
63  return;
64  }
65 
66  m_ok = true;
67 
68  // create the common settings shared by all applications. Not loaded immediately
70 }
71 
73 {
74  m_settings.clear();
75  m_color_settings.clear();
76  m_projects.clear();
77 }
78 
79 
81 {
82  std::unique_ptr<JSON_SETTINGS> ptr( aSettings );
83 
84  ptr->SetManager( this );
85 
86  wxLogTrace( traceSettings, "Registered new settings object <%s>", ptr->GetFullFilename() );
87 
88  if( aLoadNow )
89  ptr->LoadFromFile( GetPathForSettingsFile( ptr.get() ) );
90 
91  m_settings.push_back( std::move( ptr ) );
92  return m_settings.back().get();
93 }
94 
95 
97 {
98  // TODO(JE) We should check for dirty settings here and write them if so, because
99  // Load() could be called late in the application lifecycle
100 
101  for( auto&& settings : m_settings )
102  settings->LoadFromFile( GetPathForSettingsFile( settings.get() ) );
103 }
104 
105 
107 {
108  auto it = std::find_if( m_settings.begin(), m_settings.end(),
109  [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
110  {
111  return aPtr.get() == aSettings;
112  } );
113 
114  if( it != m_settings.end() )
115  ( *it )->LoadFromFile( GetPathForSettingsFile( it->get() ) );
116 }
117 
118 
120 {
121  for( auto&& settings : m_settings )
122  {
123  // Never automatically save color settings, caller should use SaveColorSettings
124  if( dynamic_cast<COLOR_SETTINGS*>( settings.get() ) )
125  continue;
126 
127  settings->SaveToFile( GetPathForSettingsFile( settings.get() ) );
128  }
129 }
130 
131 
133 {
134  auto it = std::find_if( m_settings.begin(), m_settings.end(),
135  [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
136  {
137  return aPtr.get() == aSettings;
138  } );
139 
140  if( it != m_settings.end() )
141  {
142  wxLogTrace( traceSettings, "Saving %s", ( *it )->GetFullFilename() );
143  ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
144  }
145 }
146 
147 
148 void SETTINGS_MANAGER::FlushAndRelease( JSON_SETTINGS* aSettings, bool aSave )
149 {
150  auto it = std::find_if( m_settings.begin(), m_settings.end(),
151  [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
152  {
153  return aPtr.get() == aSettings;
154  } );
155 
156  if( it != m_settings.end() )
157  {
158  wxLogTrace( traceSettings, "Flush and release %s", ( *it )->GetFullFilename() );
159 
160  if( aSave )
161  ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
162 
163  size_t typeHash = typeid( *it->get() ).hash_code();
164 
165  if( m_app_settings_cache.count( typeHash ) )
166  m_app_settings_cache.erase( typeHash );
167 
168  m_settings.erase( it );
169  }
170 }
171 
172 
174 {
175  if( m_color_settings.count( aName ) )
176  return m_color_settings.at( aName );
177 
178  if( !aName.empty() )
179  {
180  COLOR_SETTINGS* ret = loadColorSettingsByName( aName );
181 
182  if( !ret )
183  {
184  ret = registerColorSettings( aName );
185  *ret = *m_color_settings.at( "_builtin_default" );
186  ret->SetFilename( wxT( "user" ) );
187  ret->SetReadOnly( false );
188  }
189 
190  return ret;
191  }
192 
193  // This had better work
194  return m_color_settings.at( "_builtin_default" );
195 }
196 
197 
199 {
200  wxLogTrace( traceSettings, "Attempting to load color theme %s", aName );
201 
202  wxFileName fn( GetColorSettingsPath(), aName, "json" );
203 
204  if( !fn.IsOk() || !fn.Exists() )
205  {
206  wxLogTrace( traceSettings, "Theme file %s.json not found, falling back to user", aName );
207  return nullptr;
208  }
209 
210  COLOR_SETTINGS* settings = RegisterSettings( new COLOR_SETTINGS( aName ) );
211 
212  if( settings->GetFilename() != aName.ToStdString() )
213  {
214  wxLogTrace( traceSettings, "Warning: stored filename is actually %s, ",
215  settings->GetFilename() );
216  }
217 
218  m_color_settings[aName] = settings;
219 
220  return settings;
221 }
222 
223 
224 class JSON_DIR_TRAVERSER : public wxDirTraverser
225 {
226 private:
227  std::function<void( const wxFileName& )> m_action;
228 
229 public:
230  explicit JSON_DIR_TRAVERSER( std::function<void( const wxFileName& )> aAction )
231  : m_action( std::move( aAction ) )
232  {
233  }
234 
235  wxDirTraverseResult OnFile( const wxString& aFilePath ) override
236  {
237  wxFileName file( aFilePath );
238 
239  if( file.GetExt() == "json" )
240  m_action( file );
241 
242  return wxDIR_CONTINUE;
243  }
244 
245  wxDirTraverseResult OnDir( const wxString& dirPath ) override
246  {
247  return wxDIR_CONTINUE;
248  }
249 };
250 
251 
253 {
254  if( !m_color_settings.count( aName ) )
255  {
256  COLOR_SETTINGS* colorSettings = RegisterSettings( new COLOR_SETTINGS( aName ) );
257  m_color_settings[aName] = colorSettings;
258  }
259 
260  return m_color_settings.at( aName );
261 }
262 
263 
265 {
266  if( aName.EndsWith( wxT( ".json" ) ) )
267  return registerColorSettings( aName.BeforeLast( '.' ) );
268  else
269  return registerColorSettings( aName );
270 }
271 
272 
274 {
275  if( !m_color_settings.count( "user" ) )
276  {
277  COLOR_SETTINGS* settings = registerColorSettings( wxT( "user" ) );
278  settings->SetName( wxT( "User" ) );
279  Save( settings );
280  }
281 
282  return m_color_settings.at( "user" );
283 }
284 
285 
287 {
288  // Create the built-in color settings
290  m_color_settings[settings->GetFilename()] = RegisterSettings( settings, false );
291 
292  wxFileName third_party_path;
293  const ENV_VAR_MAP& env = Pgm().GetLocalEnvVariables();
294  auto it = env.find( "KICAD6_3RD_PARTY" );
295 
296  if( it != env.end() && !it->second.GetValue().IsEmpty() )
297  third_party_path.SetPath( it->second.GetValue() );
298  else
299  third_party_path.SetPath( PATHS::GetDefault3rdPartyPath() );
300 
301  third_party_path.AppendDir( "colors" );
302 
303  wxDir third_party_colors_dir( third_party_path.GetFullPath() );
304  wxString color_settings_path = GetColorSettingsPath();
305 
306  JSON_DIR_TRAVERSER copier(
307  [&]( const wxFileName& aFilename )
308  {
309  wxFileName new_file( color_settings_path, aFilename.GetFullName() );
310 
311  if( !new_file.Exists() )
312  wxCopyFile( aFilename.GetFullPath(), new_file.GetFullPath());
313  } );
314 
315  // Search for and load any other settings
316  JSON_DIR_TRAVERSER loader( [&]( const wxFileName& aFilename )
317  {
318  registerColorSettings( aFilename.GetName() );
319  } );
320 
321  wxDir colors_dir( color_settings_path );
322 
323  if( colors_dir.IsOpened() )
324  {
325  if( third_party_colors_dir.IsOpened() )
326  third_party_colors_dir.Traverse( copier );
327 
328  colors_dir.Traverse( loader );
329  }
330 }
331 
332 
334 {
335  m_color_settings.clear();
337 }
338 
339 
340 void SETTINGS_MANAGER::SaveColorSettings( COLOR_SETTINGS* aSettings, const std::string& aNamespace )
341 {
342  // The passed settings should already be managed
343  wxASSERT( std::find_if( m_color_settings.begin(), m_color_settings.end(),
344  [aSettings] ( const std::pair<wxString, COLOR_SETTINGS*>& el )
345  {
346  return el.second->GetFilename() == aSettings->GetFilename();
347  }
348  ) != m_color_settings.end() );
349 
350  if( aSettings->IsReadOnly() )
351  return;
352 
353  if( !aSettings->Store() )
354  {
355  wxLogTrace( traceSettings, "Color scheme %s not modified; skipping save",
356  aNamespace );
357  return;
358  }
359 
360  wxASSERT( aSettings->Contains( aNamespace ) );
361 
362  wxLogTrace( traceSettings, "Saving color scheme %s, preserving %s",
363  aSettings->GetFilename(),
364  aNamespace );
365 
366  OPT<nlohmann::json> backup = aSettings->GetJson( aNamespace );
367  wxString path = GetColorSettingsPath();
368 
369  aSettings->LoadFromFile( path );
370 
371  if( backup )
372  ( *aSettings->Internals() )[aNamespace].update( *backup );
373 
374  aSettings->Load();
375 
376  aSettings->SaveToFile( path, true );
377 }
378 
379 
381 {
382  wxASSERT( aSettings );
383 
384  switch( aSettings->GetLocation() )
385  {
386  case SETTINGS_LOC::USER:
387  return GetUserSettingsPath();
388 
390  return Prj().GetProjectPath();
391 
393  return GetColorSettingsPath();
394 
395  case SETTINGS_LOC::NONE:
396  return "";
397 
398  default:
399  wxASSERT_MSG( false, "Unknown settings location!" );
400  }
401 
402  return "";
403 }
404 
405 
406 class MIGRATION_TRAVERSER : public wxDirTraverser
407 {
408 private:
409  wxString m_src;
410  wxString m_dest;
411  wxString m_errors;
413 
414 public:
415  MIGRATION_TRAVERSER( const wxString& aSrcDir, const wxString& aDestDir, bool aMigrateTables ) :
416  m_src( aSrcDir ),
417  m_dest( aDestDir ),
418  m_migrateTables( aMigrateTables )
419  {
420  }
421 
422  wxString GetErrors() { return m_errors; }
423 
424  wxDirTraverseResult OnFile( const wxString& aSrcFilePath ) override
425  {
426  wxFileName file( aSrcFilePath );
427 
428  if( !m_migrateTables && ( file.GetName() == wxT( "sym-lib-table" ) ||
429  file.GetName() == wxT( "fp-lib-table" ) ) )
430  {
431  return wxDIR_CONTINUE;
432  }
433 
434  // Don't migrate hotkeys config files; we don't have a reasonable migration handler for them
435  // and so there is no way to resolve conflicts at the moment
436  if( file.GetExt() == wxT( "hotkeys" ) )
437  return wxDIR_CONTINUE;
438 
439  wxString path = file.GetPath();
440 
441  path.Replace( m_src, m_dest, false );
442  file.SetPath( path );
443 
444  wxLogTrace( traceSettings, "Copying %s to %s", aSrcFilePath, file.GetFullPath() );
445 
446  // For now, just copy everything
447  KiCopyFile( aSrcFilePath, file.GetFullPath(), m_errors );
448 
449  return wxDIR_CONTINUE;
450  }
451 
452  wxDirTraverseResult OnDir( const wxString& dirPath ) override
453  {
454  wxFileName dir( dirPath );
455 
456  // Whitelist of directories to migrate
457  if( dir.GetName() == "colors" ||
458  dir.GetName() == "3d" )
459  {
460 
461  wxString path = dir.GetPath();
462 
463  path.Replace( m_src, m_dest, false );
464  dir.SetPath( path );
465 
466  wxMkdir( dir.GetFullPath() );
467 
468  return wxDIR_CONTINUE;
469  }
470  else
471  {
472  return wxDIR_IGNORE;
473  }
474  }
475 };
476 
477 
479 {
480  if( m_headless )
481  {
482  wxLogTrace( traceSettings, "Settings migration not checked; running headless" );
483  return false;
484  }
485 
486  wxFileName path( GetUserSettingsPath(), "" );
487  wxLogTrace( traceSettings, "Using settings path %s", path.GetFullPath() );
488 
489  if( path.DirExists() )
490  {
491  wxFileName common = path;
492  common.SetName( "kicad_common" );
493  common.SetExt( "json" );
494 
495  if( common.Exists() )
496  {
497  wxLogTrace( traceSettings, "Path exists and has a kicad_common, continuing!" );
498  return true;
499  }
500  }
501 
502  // Now we have an empty path, let's figure out what to put in it
503  DIALOG_MIGRATE_SETTINGS dlg( this );
504 
505  if( dlg.ShowModal() != wxID_OK )
506  {
507  wxLogTrace( traceSettings, "Migration dialog canceled; exiting" );
508  return false;
509  }
510 
511  if( !path.DirExists() )
512  {
513  wxLogTrace( traceSettings, "Path didn't exist; creating it" );
514  path.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
515  }
516 
517  if( m_migration_source.IsEmpty() )
518  {
519  wxLogTrace( traceSettings, "No migration source given; starting with defaults" );
520  return true;
521  }
522 
524  wxDir source_dir( m_migration_source );
525 
526  source_dir.Traverse( traverser );
527 
528  if( !traverser.GetErrors().empty() )
529  DisplayErrorMessage( nullptr, traverser.GetErrors() );
530 
531  return true;
532 }
533 
534 
535 bool SETTINGS_MANAGER::GetPreviousVersionPaths( std::vector<wxString>* aPaths )
536 {
537  wxASSERT( aPaths );
538 
539  aPaths->clear();
540 
541  wxDir dir;
542  std::vector<wxFileName> base_paths;
543 
544  base_paths.emplace_back( wxFileName( calculateUserSettingsPath( false ), "" ) );
545 
546  // If the env override is set, also check the default paths
547  if( wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), nullptr ) )
548  base_paths.emplace_back( wxFileName( calculateUserSettingsPath( false, false ), "" ) );
549 
550 #ifdef __WXGTK__
551  // When running inside FlatPak, KIPLATFORM::ENV::GetUserConfigPath() will return a sandboxed
552  // path. In case the user wants to move from non-FlatPak KiCad to FlatPak KiCad, let's add our
553  // best guess as to the non-FlatPak config path. Unfortunately FlatPak also hides the host
554  // XDG_CONFIG_HOME, so if the user customizes their config path, they will have to browse
555  // for it.
556  {
557  wxFileName wxGtkPath;
558  wxGtkPath.AssignDir( "~/.config/kicad" );
559  wxGtkPath.MakeAbsolute();
560  base_paths.emplace_back( wxGtkPath.GetPath() );
561 
562  // We also want to pick up regular flatpak if we are nightly
563  wxGtkPath.AssignDir( "~/.var/app/org.kicad.KiCad/config/kicad" );
564  wxGtkPath.MakeAbsolute();
565  base_paths.emplace_back( wxGtkPath.GetPath() );
566  }
567 #endif
568 
569  wxString subdir;
570  std::string mine = GetSettingsVersion();
571 
572  auto check_dir = [&] ( const wxString& aSubDir )
573  {
574  // Only older versions are valid for migration
575  if( compareVersions( aSubDir.ToStdString(), mine ) <= 0 )
576  {
577  wxString sub_path = dir.GetNameWithSep() + aSubDir;
578 
579  if( IsSettingsPathValid( sub_path ) )
580  {
581  aPaths->push_back( sub_path );
582  wxLogTrace( traceSettings, "GetPreviousVersionName: %s is valid", sub_path );
583  }
584  }
585  };
586 
587  std::set<wxString> checkedPaths;
588 
589  for( auto base_path : base_paths )
590  {
591  if( checkedPaths.count( base_path.GetFullPath() ) )
592  continue;
593 
594  checkedPaths.insert( base_path.GetFullPath() );
595 
596  if( !dir.Open( base_path.GetFullPath() ) )
597  {
598  wxLogTrace( traceSettings, "GetPreviousVersionName: could not open base path %s",
599  base_path.GetFullPath() );
600  continue;
601  }
602 
603  wxLogTrace( traceSettings, "GetPreviousVersionName: checking base path %s",
604  base_path.GetFullPath() );
605 
606  if( dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS ) )
607  {
608  if( subdir != mine )
609  check_dir( subdir );
610 
611  while( dir.GetNext( &subdir ) )
612  {
613  if( subdir != mine )
614  check_dir( subdir );
615  }
616  }
617 
618  // If we didn't find one yet, check for legacy settings without a version directory
619  if( IsSettingsPathValid( dir.GetNameWithSep() ) )
620  {
621  wxLogTrace( traceSettings,
622  "GetPreviousVersionName: root path %s is valid", dir.GetName() );
623  aPaths->push_back( dir.GetName() );
624  }
625  }
626 
627  return aPaths->size() > 0;
628 }
629 
630 
631 bool SETTINGS_MANAGER::IsSettingsPathValid( const wxString& aPath )
632 {
633  wxFileName test( aPath, "kicad_common" );
634 
635  if( test.Exists() )
636  return true;
637 
638  test.SetExt( "json" );
639 
640  return test.Exists();
641 }
642 
643 
645 {
646  wxFileName path;
647 
648  path.AssignDir( GetUserSettingsPath() );
649  path.AppendDir( "colors" );
650 
651  if( !path.DirExists() )
652  {
653  if( !wxMkdir( path.GetPath() ) )
654  {
655  wxLogTrace( traceSettings,
656  "GetColorSettingsPath(): Path %s missing and could not be created!",
657  path.GetPath() );
658  }
659  }
660 
661  return path.GetPath();
662 }
663 
664 
666 {
667  static wxString user_settings_path;
668 
669  if( user_settings_path.empty() )
670  user_settings_path = calculateUserSettingsPath();
671 
672  return user_settings_path;
673 }
674 
675 
676 wxString SETTINGS_MANAGER::calculateUserSettingsPath( bool aIncludeVer, bool aUseEnv )
677 {
678  wxFileName cfgpath;
679 
680  // http://docs.wxwidgets.org/3.0/classwx_standard_paths.html#a7c7cf595d94d29147360d031647476b0
681 
682  wxString envstr;
683  if( aUseEnv && wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), &envstr ) && !envstr.IsEmpty() )
684  {
685  // Override the assignment above with KICAD_CONFIG_HOME
686  cfgpath.AssignDir( envstr );
687  }
688  else
689  {
690  cfgpath.AssignDir( KIPLATFORM::ENV::GetUserConfigPath() );
691 
692  cfgpath.AppendDir( TO_STR( KICAD_CONFIG_DIR ) );
693  }
694 
695  if( aIncludeVer )
696  cfgpath.AppendDir( GetSettingsVersion() );
697 
698  return cfgpath.GetPath();
699 }
700 
701 
703 {
704  // CMake computes the major.minor string for us.
705  return GetMajorMinorVersion().ToStdString();
706 }
707 
708 
709 int SETTINGS_MANAGER::compareVersions( const std::string& aFirst, const std::string& aSecond )
710 {
711  int a_maj = 0;
712  int a_min = 0;
713  int b_maj = 0;
714  int b_min = 0;
715 
716  if( !extractVersion( aFirst, &a_maj, &a_min ) || !extractVersion( aSecond, &b_maj, &b_min ) )
717  {
718  wxLogTrace( traceSettings, "compareSettingsVersions: bad input (%s, %s)", aFirst, aSecond );
719  return -1;
720  }
721 
722  if( a_maj < b_maj )
723  {
724  return -1;
725  }
726  else if( a_maj > b_maj )
727  {
728  return 1;
729  }
730  else
731  {
732  if( a_min < b_min )
733  {
734  return -1;
735  }
736  else if( a_min > b_min )
737  {
738  return 1;
739  }
740  else
741  {
742  return 0;
743  }
744  }
745 }
746 
747 
748 bool SETTINGS_MANAGER::extractVersion( const std::string& aVersionString, int* aMajor, int* aMinor )
749 {
750  std::regex re_version( "(\\d+)\\.(\\d+)" );
751  std::smatch match;
752 
753  if( std::regex_match( aVersionString, match, re_version ) )
754  {
755  try
756  {
757  *aMajor = std::stoi( match[1].str() );
758  *aMinor = std::stoi( match[2].str() );
759  }
760  catch( ... )
761  {
762  return false;
763  }
764 
765  return true;
766  }
767 
768  return false;
769 }
770 
771 
772 bool SETTINGS_MANAGER::LoadProject( const wxString& aFullPath, bool aSetActive )
773 {
774  // Normalize path to new format even if migrating from a legacy file
775  wxFileName path( aFullPath );
776 
777  if( path.GetExt() == LegacyProjectFileExtension )
778  path.SetExt( ProjectFileExtension );
779 
780  wxString fullPath = path.GetFullPath();
781 
782  // If already loaded, we are all set. This might be called more than once over a project's
783  // lifetime in case the project is first loaded by the KiCad manager and then eeschema or
784  // pcbnew try to load it again when they are launched.
785  if( m_projects.count( fullPath ) )
786  return true;
787 
788  bool readOnly = false;
789  std::unique_ptr<wxSingleInstanceChecker> lockFile = ::LockFile( fullPath );
790 
791  if( !lockFile )
792  {
793  wxLogTrace( traceSettings, "Project %s is locked; opening read-only", fullPath );
794  readOnly = true;
795  }
796 
797  // No MDI yet
798  if( aSetActive && !m_projects.empty() )
799  {
800  PROJECT* oldProject = m_projects.begin()->second;
801  unloadProjectFile( oldProject, false );
802  m_projects.erase( m_projects.begin() );
803 
804  auto it = std::find_if( m_projects_list.begin(), m_projects_list.end(),
805  [&]( const std::unique_ptr<PROJECT>& ptr )
806  {
807  return ptr.get() == oldProject;
808  } );
809 
810  wxASSERT( it != m_projects_list.end() );
811  m_projects_list.erase( it );
812  }
813 
814  wxLogTrace( traceSettings, "Load project %s", fullPath );
815 
816  std::unique_ptr<PROJECT> project = std::make_unique<PROJECT>();
817  project->setProjectFullName( fullPath );
818 
819  bool success = loadProjectFile( *project );
820 
821  if( success )
822  {
823  project->SetReadOnly( readOnly || project->GetProjectFile().IsReadOnly() );
824 
825  if( lockFile )
826  m_project_lock.reset( lockFile.release() );
827  }
828 
829  m_projects_list.push_back( std::move( project ) );
830  m_projects[fullPath] = m_projects_list.back().get();
831 
832  wxString fn( path.GetName() );
833 
834  PROJECT_LOCAL_SETTINGS* settings = new PROJECT_LOCAL_SETTINGS( m_projects[fullPath], fn );
835 
836  if( aSetActive )
837  settings = RegisterSettings( settings );
838 
839  m_projects[fullPath]->setLocalSettings( settings );
840 
841  if( aSetActive && m_kiway )
843 
844  return success;
845 }
846 
847 
848 bool SETTINGS_MANAGER::UnloadProject( PROJECT* aProject, bool aSave )
849 {
850  if( !aProject || !m_projects.count( aProject->GetProjectFullName() ) )
851  return false;
852 
853  if( !unloadProjectFile( aProject, aSave ) )
854  return false;
855 
856  wxString projectPath = aProject->GetProjectFullName();
857  wxLogTrace( traceSettings, "Unload project %s", projectPath );
858 
859  PROJECT* toRemove = m_projects.at( projectPath );
860  auto it = std::find_if( m_projects_list.begin(), m_projects_list.end(),
861  [&]( const std::unique_ptr<PROJECT>& ptr )
862  {
863  return ptr.get() == toRemove;
864  } );
865 
866  wxASSERT( it != m_projects_list.end() );
867  m_projects_list.erase( it );
868 
869  m_projects.erase( projectPath );
870 
871  // Immediately reload a null project; this is required until the rest of the application
872  // is refactored to not assume that Prj() always works
873  if( m_projects.empty() )
874  LoadProject( "" );
875 
876  // Remove the reference in the environment to the previous project
877  wxSetEnv( PROJECT_VAR_NAME, "" );
878 
879  // Release lock on the file, in case we had one
880  m_project_lock = nullptr;
881 
882  if( m_kiway )
884 
885  return true;
886 }
887 
888 
890 {
891  // No MDI yet: First project in the list is the active project
892  wxASSERT_MSG( m_projects_list.size(), "no project in list" );
893  return *m_projects_list.begin()->get();
894 }
895 
896 
898 {
899  return !m_projects.empty();
900 }
901 
902 
903 PROJECT* SETTINGS_MANAGER::GetProject( const wxString& aFullPath ) const
904 {
905  if( m_projects.count( aFullPath ) )
906  return m_projects.at( aFullPath );
907 
908  return nullptr;
909 }
910 
911 
912 std::vector<wxString> SETTINGS_MANAGER::GetOpenProjects() const
913 {
914  std::vector<wxString> ret;
915 
916  for( const std::pair<const wxString, PROJECT*>& pair : m_projects )
917  ret.emplace_back( pair.first );
918 
919  return ret;
920 }
921 
922 
923 bool SETTINGS_MANAGER::SaveProject( const wxString& aFullPath )
924 {
925  wxString path = aFullPath;
926 
927  if( path.empty() )
929 
930  // TODO: refactor for MDI
931  if( Prj().IsReadOnly() )
932  return false;
933 
934  if( !m_project_files.count( path ) )
935  return false;
936 
938  wxString projectPath = GetPathForSettingsFile( project );
939 
940  project->SaveToFile( projectPath );
941  Prj().GetLocalSettings().SaveToFile( projectPath );
942 
943  return true;
944 }
945 
946 
947 void SETTINGS_MANAGER::SaveProjectAs( const wxString& aFullPath )
948 {
949  wxString oldName = Prj().GetProjectFullName();
950 
951  if( aFullPath.IsSameAs( oldName ) )
952  {
953  SaveProject( aFullPath );
954  return;
955  }
956 
957  // Changing this will cause UnloadProject to not save over the "old" project when loading below
958  Prj().setProjectFullName( aFullPath );
959 
960  wxFileName fn( aFullPath );
961 
962  PROJECT_FILE* project = m_project_files.at( oldName );
963 
964  // Ensure read-only flags are copied; this allows doing a "Save As" on a standalong board/sch
965  // without creating project files if the checkbox is turned off
966  project->SetReadOnly( Prj().IsReadOnly() );
967  Prj().GetLocalSettings().SetReadOnly( Prj().IsReadOnly() );
968 
969  project->SetFilename( fn.GetName() );
970  project->SaveToFile( fn.GetPath() );
971 
972  Prj().GetLocalSettings().SetFilename( fn.GetName() );
973  Prj().GetLocalSettings().SaveToFile( fn.GetPath() );
974 
975  m_project_files[fn.GetFullPath()] = project;
976  m_project_files.erase( oldName );
977 
978  m_projects[fn.GetFullPath()] = m_projects[oldName];
979  m_projects.erase( oldName );
980 }
981 
982 
983 void SETTINGS_MANAGER::SaveProjectCopy( const wxString& aFullPath )
984 {
985  PROJECT_FILE* project = m_project_files.at( Prj().GetProjectFullName() );
986  wxString oldName = project->GetFilename();
987  wxFileName fn( aFullPath );
988 
989  bool readOnly = project->IsReadOnly();
990  project->SetReadOnly( false );
991 
992  project->SetFilename( fn.GetName() );
993  project->SaveToFile( fn.GetPath() );
994  project->SetFilename( oldName );
995 
996  Prj().GetLocalSettings().SetFilename( fn.GetName() );
997  Prj().GetLocalSettings().SaveToFile( fn.GetPath() );
998  Prj().GetLocalSettings().SetFilename( oldName );
999 
1000  project->SetReadOnly( readOnly );
1001 }
1002 
1003 
1005 {
1006  wxFileName fullFn( aProject.GetProjectFullName() );
1007  wxString fn( fullFn.GetName() );
1008 
1009  PROJECT_FILE* file = RegisterSettings( new PROJECT_FILE( fn ), false );
1010 
1011  m_project_files[aProject.GetProjectFullName()] = file;
1012 
1013  aProject.setProjectFile( file );
1014  file->SetProject( &aProject );
1015 
1016  wxString path( fullFn.GetPath() );
1017 
1018  return file->LoadFromFile( path );
1019 }
1020 
1021 
1022 bool SETTINGS_MANAGER::unloadProjectFile( PROJECT* aProject, bool aSave )
1023 {
1024  if( !aProject )
1025  return false;
1026 
1027  wxString name = aProject->GetProjectFullName();
1028 
1029  if( !m_project_files.count( name ) )
1030  return false;
1031 
1033 
1034  auto it = std::find_if( m_settings.begin(), m_settings.end(),
1035  [&file]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
1036  {
1037  return aPtr.get() == file;
1038  } );
1039 
1040  if( it != m_settings.end() )
1041  {
1042  wxString projectPath = GetPathForSettingsFile( it->get() );
1043 
1044  FlushAndRelease( &aProject->GetLocalSettings(), aSave );
1045 
1046  if( aSave )
1047  ( *it )->SaveToFile( projectPath );
1048 
1049  m_settings.erase( it );
1050  }
1051 
1052  m_project_files.erase( name );
1053 
1054  return true;
1055 }
1056 
1057 
1059 {
1061 }
1062 
1063 
1064 wxString SETTINGS_MANAGER::backupDateTimeFormat = wxT( "%Y-%m-%d_%H%M%S" );
1065 
1066 
1068 {
1069  wxDateTime timestamp = wxDateTime::Now();
1070 
1071  wxString fileName = wxString::Format( wxT( "%s-%s" ), Prj().GetProjectName(),
1072  timestamp.Format( backupDateTimeFormat ) );
1073 
1074  wxFileName target;
1075  target.SetPath( GetProjectBackupsPath() );
1076  target.SetName( fileName );
1077  target.SetExt( ArchiveFileExtension );
1078 
1079  wxDir dir( target.GetPath() );
1080 
1081  if( !target.DirExists() && !wxMkdir( target.GetPath() ) )
1082  {
1083  wxLogTrace( traceSettings, "Could not create project backup path %s", target.GetPath() );
1084  return false;
1085  }
1086 
1087  if( !target.IsDirWritable() )
1088  {
1089  wxLogTrace( traceSettings, "Backup directory %s is not writeable", target.GetPath() );
1090  return false;
1091  }
1092 
1093  wxLogTrace( traceSettings, "Backing up project to %s", target.GetPath() );
1094 
1095  PROJECT_ARCHIVER archiver;
1096 
1097  return archiver.Archive( Prj().GetProjectPath(), target.GetFullPath(), aReporter );
1098 }
1099 
1100 
1101 class VECTOR_INSERT_TRAVERSER : public wxDirTraverser
1102 {
1103 public:
1104  VECTOR_INSERT_TRAVERSER( std::vector<wxString>& aVec,
1105  std::function<bool( const wxString& )> aCond ) :
1106  m_files( aVec ),
1107  m_condition( aCond )
1108  {
1109  }
1110 
1111  wxDirTraverseResult OnFile( const wxString& aFile ) override
1112  {
1113  if( m_condition( aFile ) )
1114  m_files.emplace_back( aFile );
1115 
1116  return wxDIR_CONTINUE;
1117  }
1118 
1119  wxDirTraverseResult OnDir( const wxString& aDirName ) override
1120  {
1121  return wxDIR_CONTINUE;
1122  }
1123 
1124 private:
1125  std::vector<wxString>& m_files;
1126 
1127  std::function<bool( const wxString& )> m_condition;
1128 };
1129 
1130 
1132 {
1134 
1135  if( !settings.enabled )
1136  return true;
1137 
1138  wxString prefix = Prj().GetProjectName() + '-';
1139 
1140  auto modTime =
1141  [&prefix]( const wxString& aFile )
1142  {
1143  wxDateTime dt;
1144  wxString fn( wxFileName( aFile ).GetName() );
1145  fn.Replace( prefix, "" );
1146  dt.ParseFormat( fn, backupDateTimeFormat );
1147  return dt;
1148  };
1149 
1150  wxFileName projectPath( Prj().GetProjectPath() );
1151 
1152  // Skip backup if project path isn't valid or writeable
1153  if( !projectPath.IsOk() || !projectPath.Exists() || !projectPath.IsDirWritable() )
1154  return true;
1155 
1156  wxString backupPath = GetProjectBackupsPath();
1157 
1158  if( !wxDirExists( backupPath ) )
1159  {
1160  wxLogTrace( traceSettings, "Backup path %s doesn't exist, creating it", backupPath );
1161 
1162  if( !wxMkdir( backupPath ) )
1163  {
1164  wxLogTrace( traceSettings, "Could not create backups path! Skipping backup" );
1165  return false;
1166  }
1167  }
1168 
1169  wxDir dir( backupPath );
1170 
1171  if( !dir.IsOpened() )
1172  {
1173  wxLogTrace( traceSettings, "Could not open project backups path %s", dir.GetName() );
1174  return false;
1175  }
1176 
1177  std::vector<wxString> files;
1178 
1179  VECTOR_INSERT_TRAVERSER traverser( files,
1180  [&modTime]( const wxString& aFile )
1181  {
1182  return modTime( aFile ).IsValid();
1183  } );
1184 
1185  dir.Traverse( traverser, wxT( "*.zip" ) );
1186 
1187  // Sort newest-first
1188  std::sort( files.begin(), files.end(),
1189  [&]( const wxString& aFirst, const wxString& aSecond ) -> bool
1190  {
1191  wxDateTime first = modTime( aFirst );
1192  wxDateTime second = modTime( aSecond );
1193 
1194  return first.GetTicks() > second.GetTicks();
1195  } );
1196 
1197  // Do we even need to back up?
1198  if( !files.empty() )
1199  {
1200  wxDateTime lastTime = modTime( files[0] );
1201 
1202  if( lastTime.IsValid() )
1203  {
1204  wxTimeSpan delta = wxDateTime::Now() - modTime( files[0] );
1205 
1206  if( delta.IsShorterThan( wxTimeSpan::Seconds( settings.min_interval ) ) )
1207  return true;
1208  }
1209  }
1210 
1211  // Now that we know a backup is needed, apply the retention policy
1212 
1213  // Step 1: if we're over the total file limit, remove the oldest
1214  if( !files.empty() && settings.limit_total_files > 0 )
1215  {
1216  while( files.size() > static_cast<size_t>( settings.limit_total_files ) )
1217  {
1218  wxRemoveFile( files.back() );
1219  files.pop_back();
1220  }
1221  }
1222 
1223  // Step 2: Stay under the total size limit
1224  if( settings.limit_total_size > 0 )
1225  {
1226  wxULongLong totalSize = 0;
1227 
1228  for( const wxString& file : files )
1229  totalSize += wxFileName::GetSize( file );
1230 
1231  while( !files.empty() && totalSize > static_cast<wxULongLong>( settings.limit_total_size ) )
1232  {
1233  totalSize -= wxFileName::GetSize( files.back() );
1234  wxRemoveFile( files.back() );
1235  files.pop_back();
1236  }
1237  }
1238 
1239  // Step 3: Stay under the daily limit
1240  if( settings.limit_daily_files > 0 && files.size() > 1 )
1241  {
1242  wxDateTime day = modTime( files[0] );
1243  int num = 1;
1244 
1245  wxASSERT( day.IsValid() );
1246 
1247  std::vector<wxString> filesToDelete;
1248 
1249  for( size_t i = 1; i < files.size(); i++ )
1250  {
1251  wxDateTime dt = modTime( files[i] );
1252 
1253  if( dt.IsSameDate( day ) )
1254  {
1255  num++;
1256 
1257  if( num > settings.limit_daily_files )
1258  filesToDelete.emplace_back( files[i] );
1259  }
1260  else
1261  {
1262  day = dt;
1263  num = 1;
1264  }
1265  }
1266 
1267  for( const wxString& file : filesToDelete )
1268  wxRemoveFile( file );
1269  }
1270 
1271  return BackupProject( aReporter );
1272 }
static wxString backupDateTimeFormat
bool Archive(const wxString &aSrcDir, const wxString &aDestFile, REPORTER &aReporter, bool aVerbose=true, bool aIncludeExtraFiles=false)
Creates an archive of the project.
virtual bool Store()
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
JSON_SETTINGS_INTERNALS * Internals()
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
Container for project specific data.
Definition: project.h:62
unsigned long long limit_total_size
Maximum total size of backups (bytes), 0 for unlimited.
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition: confirm.cpp:292
#define TO_STR(x)
Definition: macros.h:105
std::unique_ptr< wxSingleInstanceChecker > m_project_lock
Lock for loaded project (expand to multiple once we support MDI)
T * RegisterSettings(T *aSettings, bool aLoadNow=true)
Takes ownership of the pointer passed in.
This file is part of the common library.
std::function< void(const wxFileName &)> m_action
void SaveProjectAs(const wxString &aFullPath)
Sets the currently loaded project path and saves it (pointers remain valid) Note that this will not m...
static bool IsSettingsPathValid(const wxString &aPath)
Checks if a given path is probably a valid KiCad configuration directory.
const std::string ProjectFileExtension
KIWAY * m_kiway
The kiway this settings manager interacts with.
void SetReadOnly(bool aReadOnly)
Definition: json_settings.h:83
wxString GetFilename() const
Definition: json_settings.h:72
bool m_migrateLibraryTables
If true, the symbol and footprint library tables will be migrated from the previous version.
#define PROJECT_VAR_NAME
A variable name whose value holds the current project directory.
Definition: project.h:38
bool enabled
Automatically back up the project when files are saved.
AUTO_BACKUP m_Backup
virtual bool LoadFromFile(const wxString &aDirectory="")
Loads the backing file from disk and then calls Load()
The project local settings are things that are attached to a particular project, but also might be pa...
bool SaveToFile(const wxString &aDirectory="", bool aForce=false) override
wxDirTraverseResult OnFile(const wxString &aSrcFilePath) override
void KiCopyFile(const wxString &aSrcPath, const wxString &aDestPath, wxString &aErrors)
Definition: gestfich.cpp:326
std::unordered_map< size_t, JSON_SETTINGS * > m_app_settings_cache
Cache for app settings.
Definition: bitmap.cpp:64
OPT< nlohmann::json > GetJson(const std::string &aPath) const
Fetches a JSON object that is a subset of this JSON_SETTINGS object, using a path of the form "key1....
VECTOR_INSERT_TRAVERSER(std::vector< wxString > &aVec, std::function< bool(const wxString &)> aCond)
std::map< wxString, PROJECT_FILE * > m_project_files
Loaded project files, mapped according to project full name.
bool MigrateIfNeeded()
Handles the initialization of the user settings directory and migration from previous KiCad versions ...
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:70
COLOR_SETTINGS * AddNewColorSettings(const wxString &aFilename)
Registers a new color settings object with the given filename.
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:106
static int compareVersions(const std::string &aFirst, const std::string &aSecond)
Compares two settings versions, like "5.99" and "6.0".
bool BackupProject(REPORTER &aReporter) const
Creates a backup archive of the current project.
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition: project.cpp:122
The backing store for a PROJECT, in JSON format.
Definition: project_file.h:64
wxString GetMajorMinorVersion()
Get only the major and minor version in a string major.minor.
This file contains miscellaneous commonly used macros and functions.
virtual PROJECT_LOCAL_SETTINGS & GetLocalSettings() const
Definition: project.h:151
std::vector< wxString > & m_files
The color scheme directory (e.g. ~/.config/kicad/colors/)
wxString m_migration_source
bool m_ok
True if settings loaded successfully at construction.
bool unloadProjectFile(PROJECT *aProject, bool aSave)
Optionally saves, and then unloads and unregisters the given PROJECT_FILE.
virtual bool SaveToFile(const wxString &aDirectory="", bool aForce=false)
void SaveProjectCopy(const wxString &aFullPath)
Saves a copy of the current project under the given path.
bool IsProjectOpen() const
Helper for checking if we have a project open TODO: This should be deprecated along with Prj() once w...
wxDirTraverseResult OnDir(const wxString &aDirName) override
The settings directory inside a project folder.
wxDirTraverseResult OnDir(const wxString &dirPath) override
bool TriggerBackupIfNeeded(REPORTER &aReporter) const
Calls BackupProject if a new backup is needed according to the current backup policy.
SETTINGS_LOC GetLocation() const
Definition: json_settings.h:78
std::function< bool(const wxString &)> m_condition
bool GetPreviousVersionPaths(std::vector< wxString > *aName=nullptr)
Retrieves the name of the most recent previous KiCad version that can be found in the user settings d...
COMMON_SETTINGS * GetCommonSettings() const
Retrieves the common settings shared by all applications.
virtual void ProjectChanged()
Calls ProjectChanged() on all KIWAY_PLAYERs.
Definition: kiway.cpp:577
std::unique_ptr< wxSingleInstanceChecker > LockFile(const wxString &aFileName)
Test to see if aFileName can be locked (is not already locked) and only then returns a wxSingleInstan...
Definition: lockfile.cpp:34
Definition of file extensions used in Kicad.
static std::vector< COLOR_SETTINGS * > CreateBuiltinColorSettings()
Constructs and returns a list of color settings objects based on the built-in color themes.
bool m_headless
True if running outside a UI context.
wxDirTraverseResult OnFile(const wxString &aFile) override
static void EnsureUserPathsExist()
Ensures/creates user default paths.
Definition: paths.cpp:311
void SetName(const wxString &aName)
virtual const wxString GetProjectFullName() const
Return the full path and name of the project.
Definition: project.cpp:116
const std::string LegacyProjectFileExtension
No directory prepended, full path in filename (used for PROJECT_FILE)
std::map< wxString, PROJECT * > m_projects
Loaded projects, mapped according to project full name.
COLOR_SETTINGS * registerColorSettings(const wxString &aFilename)
JSON_DIR_TRAVERSER(std::function< void(const wxFileName &)> aAction)
wxDirTraverseResult OnFile(const wxString &aFilePath) override
SETTINGS_MANAGER(bool aHeadless=false)
virtual void setProjectFullName(const wxString &aFullPathAndName)
Set the full directory, basename, and extension of the project.
Definition: project.cpp:84
static wxString GetColorSettingsPath()
Returns the path where color scheme files are stored; creating it if missing (normally .
wxString GetProjectBackupsPath() const
bool loadProjectFile(PROJECT &aProject)
Registers a PROJECT_FILE and attempts to load it from disk.
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
std::unordered_map< wxString, COLOR_SETTINGS * > m_color_settings
int min_interval
Minimum time, in seconds, between subsequent backups.
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Loads a project or sets up a new project with a specified path.
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.
void ReloadColorSettings()
Re-scans the color themes directory, reloading any changes it finds.
std::vector< std::unique_ptr< JSON_SETTINGS > > m_settings
see class PGM_BASE
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
const char * name
Definition: DXF_plotter.cpp:56
std::vector< wxString > GetOpenProjects() const
static wxString calculateUserSettingsPath(bool aIncludeVer=true, bool aUseEnv=true)
Determines the base path for user settings files.
#define PROJECT_BACKUPS_DIR_SUFFIX
Project settings path will be <projectname> + this.
bool UnloadProject(PROJECT *aProject, bool aSave=true)
Saves, unloads and unregisters the given PROJECT.
The main config directory (e.g. ~/.config/kicad/)
bool SaveProject(const wxString &aFullPath=wxEmptyString)
Saves a loaded project.
wxDirTraverseResult OnDir(const wxString &dirPath) override
void SaveColorSettings(COLOR_SETTINGS *aSettings, const std::string &aNamespace="")
Safely saves a COLOR_SETTINGS to disk, preserving any changes outside the given namespace.
bool IsReadOnly() const
Definition: json_settings.h:82
wxString GetUserConfigPath()
Retrieves the operating system specific path for a user's configuration store.
boost::optional< T > OPT
Definition: optional.h:7
static std::string GetSettingsVersion()
Parses the current KiCad build version and extracts the major and minor revision to use as the name o...
JSON_SETTINGS * registerSettings(JSON_SETTINGS *aSettings, bool aLoadNow=true)
Color settings are a bit different than most of the settings objects in that there can be more than o...
constexpr int delta
virtual const wxString GetProjectName() const
Return the short name of the project.
Definition: project.cpp:128
bool Contains(const std::string &aPath) const
int limit_daily_files
Maximum files to keep per day, 0 for unlimited.
const wxChar *const traceSettings
Flag to enable debug output of settings operations and management.
COMMON_SETTINGS * m_common_settings
void SetFilename(const wxString &aFilename)
Definition: json_settings.h:76
virtual void setProjectFile(PROJECT_FILE *aFile)
Set the backing store file for this project.
Definition: project.h:320
static wxString GetDefault3rdPartyPath()
Gets the default path for PCM packages.
Definition: paths.cpp:129
COLOR_SETTINGS * GetMigratedColorSettings()
Returns a color theme for storing colors migrated from legacy (5.x and earlier) settings,...
MIGRATION_TRAVERSER(const wxString &aSrcDir, const wxString &aDestDir, bool aMigrateTables)
void FlushAndRelease(JSON_SETTINGS *aSettings, bool aSave=true)
If the given settings object is registered, save it to disk and unregister it.
std::vector< std::unique_ptr< PROJECT > > m_projects_list
Loaded projects (ownership here)
virtual void Load()
Updates the parameters of this object based on the current JSON document contents.
PROJECT * GetProject(const wxString &aFullPath) const
Retrieves a loaded project by name.
int limit_total_files
Maximum number of backup archives to retain.
static bool extractVersion(const std::string &aVersionString, int *aMajor, int *aMinor)
Extracts the numeric version from a given settings string.
wxString GetPathForSettingsFile(JSON_SETTINGS *aSettings)
Returns the path a given settings file should be loaded from / stored to.
const std::string ArchiveFileExtension
File locking utilities.