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 <kiway.h>
32 #include <macros.h>
33 #include <project.h>
35 #include <project/project_file.h>
37 #include <settings/app_settings.h>
42 
43 
44 
46 #define PROJECT_BACKUPS_DIR_SUFFIX wxT( "-backups" )
47 
48 
50  m_headless( aHeadless ),
51  m_kiway( nullptr ),
52  m_common_settings( nullptr ),
53  m_migration_source(),
54  m_migrateLibraryTables( true )
55 {
56  // Check if the settings directory already exists, and if not, perform a migration if possible
57  if( !MigrateIfNeeded() )
58  {
59  m_ok = false;
60  return;
61  }
62 
63  m_ok = true;
64 
65  // create the common settings shared by all applications. Not loaded immediately
67  static_cast<COMMON_SETTINGS*>( RegisterSettings( new COMMON_SETTINGS, false ) );
68 
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  m_settings.erase( it );
164  }
165 }
166 
167 
169 {
170  if( m_color_settings.count( aName ) )
171  return m_color_settings.at( aName );
172 
173  COLOR_SETTINGS* ret = nullptr;
174 
175  if( !aName.empty() )
176  ret = loadColorSettingsByName( aName );
177 
178  // This had better work
179  if( !ret )
180  ret = m_color_settings.at( "_builtin_default" );
181 
182  return ret;
183 }
184 
185 
187 {
188  wxLogTrace( traceSettings, "Attempting to load color theme %s", aName );
189 
190  wxFileName fn( GetColorSettingsPath(), aName, "json" );
191 
192  if( !fn.IsOk() || !fn.Exists() )
193  {
194  wxLogTrace( traceSettings, "Theme file %s.json not found, falling back to user", aName );
195  return nullptr;
196  }
197 
198  auto cs = static_cast<COLOR_SETTINGS*>(
199  RegisterSettings( new COLOR_SETTINGS( aName.ToStdString() ) ) );
200 
201  if( cs->GetFilename() != aName.ToStdString() )
202  {
203  wxLogTrace( traceSettings, "Warning: stored filename is actually %s, ", cs->GetFilename() );
204  }
205 
206  m_color_settings[aName] = cs;
207 
208  return cs;
209 }
210 
211 
212 class COLOR_SETTINGS_LOADER : public wxDirTraverser
213 {
214 private:
215  std::function<void( const wxString& )> m_action;
216 
217 public:
218  explicit COLOR_SETTINGS_LOADER( std::function<void( const wxString& )> aAction )
219  : m_action( std::move( aAction ) )
220  {
221  }
222 
223  wxDirTraverseResult OnFile( const wxString& aFilePath ) override
224  {
225  wxFileName file( aFilePath );
226 
227  if( file.GetExt() != "json" )
228  return wxDIR_CONTINUE;
229 
230  m_action( file.GetName() );
231 
232  return wxDIR_CONTINUE;
233  }
234 
235  wxDirTraverseResult OnDir( const wxString& dirPath ) override
236  {
237  return wxDIR_IGNORE;
238  }
239 };
240 
241 
242 void SETTINGS_MANAGER::registerColorSettings( const wxString& aFilename )
243 {
244  if( m_color_settings.count( aFilename ) )
245  return;
246 
247  m_color_settings[aFilename] = static_cast<COLOR_SETTINGS*>(
248  RegisterSettings( new COLOR_SETTINGS( aFilename ) ) );
249 }
250 
251 
253 {
254  wxString filename = aFilename;
255 
256  if( filename.EndsWith( wxT( ".json" ) ) )
257  filename = filename.BeforeLast( '.' );
258 
259  registerColorSettings( filename );
260  return m_color_settings[filename];
261 }
262 
263 
265 {
266  if( !m_color_settings.count( "user" ) )
267  {
268  registerColorSettings( wxT( "user" ) );
269  m_color_settings.at( "user" )->SetName( wxT( "User" ) );
270  Save( m_color_settings.at( "user" ) );
271  }
272 
273  return m_color_settings.at( "user" );
274 }
275 
276 
278 {
279  // Create the built-in color settings
281  {
282  m_color_settings[settings->GetFilename()] =
283  static_cast<COLOR_SETTINGS*>( RegisterSettings( settings, false ) );
284  }
285 
286  // Search for and load any other settings
287  COLOR_SETTINGS_LOADER loader( [&]( const wxString& aFilename )
288  {
289  registerColorSettings( aFilename );
290  } );
291 
292  wxDir colors_dir( GetColorSettingsPath() );
293 
294  if( colors_dir.IsOpened() )
295  colors_dir.Traverse( loader );
296 }
297 
298 
300 {
301  m_color_settings.clear();
303 }
304 
305 
306 void SETTINGS_MANAGER::SaveColorSettings( COLOR_SETTINGS* aSettings, const std::string& aNamespace )
307 {
308  // The passed settings should already be managed
309  wxASSERT( std::find_if( m_color_settings.begin(), m_color_settings.end(),
310  [aSettings] ( const std::pair<wxString, COLOR_SETTINGS*>& el )
311  {
312  return el.second->GetFilename() == aSettings->GetFilename();
313  }
314  ) != m_color_settings.end() );
315 
316  nlohmann::json::json_pointer ptr = JSON_SETTINGS::PointerFromString( aNamespace );
317 
318  if( !aSettings->Store() )
319  {
320  wxLogTrace( traceSettings, "Color scheme %s not modified; skipping save",
321  aSettings->GetFilename(), aNamespace );
322  return;
323  }
324 
325  wxASSERT( aSettings->contains( ptr ) );
326 
327  wxLogTrace( traceSettings, "Saving color scheme %s, preserving %s", aSettings->GetFilename(),
328  aNamespace );
329 
330  nlohmann::json backup = aSettings->at( ptr );
331  wxString path = GetColorSettingsPath();
332 
333  aSettings->LoadFromFile( path );
334 
335  ( *aSettings )[ptr].update( backup );
336  aSettings->Load();
337 
338  aSettings->SaveToFile( path, true );
339 }
340 
341 
343 {
344  wxASSERT( aSettings );
345 
346  switch( aSettings->GetLocation() )
347  {
348  case SETTINGS_LOC::USER:
349  return GetUserSettingsPath();
350 
352  return Prj().GetProjectPath();
353 
355  return GetColorSettingsPath();
356 
357  case SETTINGS_LOC::NONE:
358  return "";
359 
360  default:
361  wxASSERT_MSG( false, "Unknown settings location!" );
362  }
363 
364  return "";
365 }
366 
367 
368 class MIGRATION_TRAVERSER : public wxDirTraverser
369 {
370 private:
371  wxString m_src;
372  wxString m_dest;
373  wxString m_errors;
375 
376 public:
377  MIGRATION_TRAVERSER( const wxString& aSrcDir, const wxString& aDestDir, bool aMigrateTables ) :
378  m_src( aSrcDir ),
379  m_dest( aDestDir ),
380  m_migrateTables( aMigrateTables )
381  {
382  }
383 
384  wxString GetErrors() { return m_errors; }
385 
386  wxDirTraverseResult OnFile( const wxString& aSrcFilePath ) override
387  {
388  wxFileName file( aSrcFilePath );
389 
390  if( !m_migrateTables && ( file.GetName() == wxT( "sym-lib-table" ) ||
391  file.GetName() == wxT( "fp-lib-table" ) ) )
392  {
393  return wxDIR_CONTINUE;
394  }
395 
396  wxString path = file.GetPath();
397 
398  path.Replace( m_src, m_dest, false );
399  file.SetPath( path );
400 
401  wxLogTrace( traceSettings, "Copying %s to %s", aSrcFilePath, file.GetFullPath() );
402 
403  // For now, just copy everything
404  KiCopyFile( aSrcFilePath, file.GetFullPath(), m_errors );
405 
406  return wxDIR_CONTINUE;
407  }
408 
409  wxDirTraverseResult OnDir( const wxString& dirPath ) override
410  {
411  wxFileName dir( dirPath );
412 
413  // Whitelist of directories to migrate
414  if( dir.GetName() == "colors" ||
415  dir.GetName() == "3d" )
416  {
417 
418  wxString path = dir.GetPath();
419 
420  path.Replace( m_src, m_dest, false );
421  dir.SetPath( path );
422 
423  wxMkdir( dir.GetFullPath() );
424 
425  return wxDIR_CONTINUE;
426  }
427  else
428  {
429  return wxDIR_IGNORE;
430  }
431  }
432 };
433 
434 
436 {
437  if( m_headless )
438  {
439  wxLogTrace( traceSettings, "Settings migration not checked; running headless" );
440  return false;
441  }
442 
443  wxFileName path( GetUserSettingsPath(), "" );
444  wxLogTrace( traceSettings, "Using settings path %s", path.GetFullPath() );
445 
446  if( path.DirExists() )
447  {
448  wxFileName common = path;
449  common.SetName( "kicad_common" );
450  common.SetExt( "json" );
451 
452  if( common.Exists() )
453  {
454  wxLogTrace( traceSettings, "Path exists and has a kicad_common, continuing!" );
455  return true;
456  }
457  }
458 
459  // Now we have an empty path, let's figure out what to put in it
460  DIALOG_MIGRATE_SETTINGS dlg( this );
461 
462  if( dlg.ShowModal() != wxID_OK )
463  {
464  wxLogTrace( traceSettings, "Migration dialog canceled; exiting" );
465  return false;
466  }
467 
468  if( !path.DirExists() )
469  {
470  wxLogTrace( traceSettings, "Path didn't exist; creating it" );
471  path.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
472  }
473 
474  if( m_migration_source.IsEmpty() )
475  {
476  wxLogTrace( traceSettings, "No migration source given; starting with defaults" );
477  return true;
478  }
479 
480  MIGRATION_TRAVERSER traverser( m_migration_source, path.GetFullPath(), m_migrateLibraryTables );
481  wxDir source_dir( m_migration_source );
482 
483  source_dir.Traverse( traverser );
484 
485  if( !traverser.GetErrors().empty() )
486  DisplayErrorMessage( nullptr, traverser.GetErrors() );
487 
488  return true;
489 }
490 
491 
492 bool SETTINGS_MANAGER::GetPreviousVersionPaths( std::vector<wxString>* aPaths )
493 {
494  wxASSERT( aPaths );
495 
496  aPaths->clear();
497 
498  wxDir dir;
499  std::vector<wxFileName> base_paths;
500 
501  base_paths.emplace_back( wxFileName( calculateUserSettingsPath( false ), "" ) );
502 
503  // If the env override is set, also check the default paths
504  if( wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), nullptr ) )
505  base_paths.emplace_back( wxFileName( calculateUserSettingsPath( false, false ), "" ) );
506 
507  wxString subdir;
508  std::string mine = GetSettingsVersion();
509 
510  auto check_dir = [&] ( const wxString& aSubDir )
511  {
512  // Only older versions are valid for migration
513  if( compareVersions( aSubDir.ToStdString(), mine ) <= 0 )
514  {
515  wxString sub_path = dir.GetNameWithSep() + aSubDir;
516 
517  if( IsSettingsPathValid( sub_path ) )
518  {
519  aPaths->push_back( sub_path );
520  wxLogTrace( traceSettings, "GetPreviousVersionName: %s is valid", sub_path );
521  }
522  }
523  };
524 
525  for( auto base_path : base_paths )
526  {
527  if( !dir.Open( base_path.GetFullPath() ) )
528  {
529  wxLogTrace( traceSettings, "GetPreviousVersionName: could not open base path %s",
530  base_path.GetFullPath() );
531  continue;
532  }
533 
534  wxLogTrace( traceSettings, "GetPreviousVersionName: checking base path %s",
535  base_path.GetFullPath() );
536 
537  if( dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS ) )
538  {
539  if( subdir != mine )
540  check_dir( subdir );
541 
542  while( dir.GetNext( &subdir ) )
543  {
544  if( subdir != mine )
545  check_dir( subdir );
546  }
547  }
548 
549  // If we didn't find one yet, check for legacy settings without a version directory
550  if( IsSettingsPathValid( dir.GetNameWithSep() ) )
551  {
552  wxLogTrace( traceSettings,
553  "GetPreviousVersionName: root path %s is valid", dir.GetName() );
554  aPaths->push_back( dir.GetName() );
555  }
556  }
557 
558  return aPaths->size() > 0;
559 }
560 
561 
562 bool SETTINGS_MANAGER::IsSettingsPathValid( const wxString& aPath )
563 {
564  wxFileName test( aPath, "kicad_common" );
565  return test.Exists();
566 }
567 
568 
570 {
571  wxFileName path;
572 
573  path.AssignDir( GetUserSettingsPath() );
574  path.AppendDir( "colors" );
575 
576  if( !path.DirExists() )
577  {
578  if( !wxMkdir( path.GetPath() ) )
579  {
580  wxLogTrace( traceSettings,
581  "GetColorSettingsPath(): Path %s missing and could not be created!",
582  path.GetPath() );
583  }
584  }
585 
586  return path.GetPath();
587 }
588 
589 
591 {
592  static wxString user_settings_path;
593 
594  if( user_settings_path.empty() )
595  user_settings_path = calculateUserSettingsPath();
596 
597  return user_settings_path;
598 }
599 
600 
601 wxString SETTINGS_MANAGER::calculateUserSettingsPath( bool aIncludeVer, bool aUseEnv )
602 {
603  wxFileName cfgpath;
604 
605  // http://docs.wxwidgets.org/3.0/classwx_standard_paths.html#a7c7cf595d94d29147360d031647476b0
606  cfgpath.AssignDir( wxStandardPaths::Get().GetUserConfigDir() );
607 
608  // GetUserConfigDir() does not default to ~/.config which is the current standard
609  // configuration file location on Linux. This has been fixed in later versions of wxWidgets.
610 #if !defined( __WXMSW__ ) && !defined( __WXMAC__ )
611  wxArrayString dirs = cfgpath.GetDirs();
612 
613  if( dirs.Last() != ".config" )
614  cfgpath.AppendDir( ".config" );
615 #endif
616 
617  wxString envstr;
618 
619  // This shouldn't cause any issues on Windows or MacOS.
620  if( wxGetEnv( wxT( "XDG_CONFIG_HOME" ), &envstr ) && !envstr.IsEmpty() )
621  {
622  // Override the assignment above with XDG_CONFIG_HOME
623  cfgpath.AssignDir( envstr );
624  }
625 
626  cfgpath.AppendDir( TO_STR( KICAD_CONFIG_DIR ) );
627 
628  // Use KICAD_CONFIG_HOME to allow the user to force a specific configuration path.
629  if( aUseEnv && wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), &envstr ) && !envstr.IsEmpty() )
630  {
631  // Override the assignment above with KICAD_CONFIG_HOME
632  cfgpath.AssignDir( envstr );
633  }
634 
635  if( aIncludeVer )
636  cfgpath.AppendDir( GetSettingsVersion() );
637 
638  return cfgpath.GetPath();
639 }
640 
641 
643 {
644  // CMake computes the major.minor string for us.
645  return GetMajorMinorVersion().ToStdString();
646 }
647 
648 
649 int SETTINGS_MANAGER::compareVersions( const std::string& aFirst, const std::string& aSecond )
650 {
651  int a_maj = 0;
652  int a_min = 0;
653  int b_maj = 0;
654  int b_min = 0;
655 
656  if( !extractVersion( aFirst, &a_maj, &a_min ) || !extractVersion( aSecond, &b_maj, &b_min ) )
657  {
658  wxLogTrace( traceSettings, "compareSettingsVersions: bad input (%s, %s)", aFirst, aSecond );
659  return -1;
660  }
661 
662  if( a_maj < b_maj )
663  {
664  return -1;
665  }
666  else if( a_maj > b_maj )
667  {
668  return 1;
669  }
670  else
671  {
672  if( a_min < b_min )
673  {
674  return -1;
675  }
676  else if( a_min > b_min )
677  {
678  return 1;
679  }
680  else
681  {
682  return 0;
683  }
684  }
685 }
686 
687 
688 bool SETTINGS_MANAGER::extractVersion( const std::string& aVersionString, int* aMajor, int* aMinor )
689 {
690  std::regex re_version( "(\\d+)\\.(\\d+)" );
691  std::smatch match;
692 
693  if( std::regex_match( aVersionString, match, re_version ) )
694  {
695  try
696  {
697  *aMajor = std::stoi( match[1].str() );
698  *aMinor = std::stoi( match[2].str() );
699  }
700  catch( ... )
701  {
702  return false;
703  }
704 
705  return true;
706  }
707 
708  return false;
709 }
710 
711 
712 bool SETTINGS_MANAGER::LoadProject( const wxString& aFullPath, bool aSetActive )
713 {
714  // Normalize path to new format even if migrating from a legacy file
715  wxFileName path( aFullPath );
716 
717  if( path.GetExt() == LegacyProjectFileExtension )
718  path.SetExt( ProjectFileExtension );
719 
720  wxString fullPath = path.GetFullPath();
721 
722  // If already loaded, we are all set. This might be called more than once over a project's
723  // lifetime in case the project is first loaded by the KiCad manager and then eeschema or
724  // pcbnew try to load it again when they are launched.
725  if( m_projects.count( fullPath ) )
726  return true;
727 
728  // No MDI yet
729  if( aSetActive && !m_projects.empty() )
730  {
731  PROJECT* oldProject = m_projects.begin()->second;
732  unloadProjectFile( oldProject, true );
733  m_projects.erase( m_projects.begin() );
734  }
735 
736  wxLogTrace( traceSettings, "Load project %s", fullPath );
737 
738  std::unique_ptr<PROJECT> project = std::make_unique<PROJECT>();
739  project->setProjectFullName( fullPath );
740 
741  bool success = loadProjectFile( *project );
742 
743  if( success )
744  project->SetReadOnly( project->GetProjectFile().IsReadOnly() );
745 
746  m_projects_list.push_back( std::move( project ) );
747  m_projects[fullPath] = m_projects_list.back().get();
748 
749  wxString fn( path.GetName() );
750 
751  PROJECT_LOCAL_SETTINGS* settings = new PROJECT_LOCAL_SETTINGS( m_projects[fullPath], fn );
752 
753  if( aSetActive )
754  settings = static_cast<PROJECT_LOCAL_SETTINGS*>( RegisterSettings( settings ) );
755 
756  m_projects[fullPath]->setLocalSettings( settings );
757 
758  if( aSetActive && m_kiway )
760 
761  return success;
762 }
763 
764 
765 bool SETTINGS_MANAGER::UnloadProject( PROJECT* aProject, bool aSave )
766 {
767  if( !aProject || !m_projects.count( aProject->GetProjectFullName() ) )
768  return false;
769 
770  if( !unloadProjectFile( aProject, aSave ) )
771  return false;
772 
773  wxString projectPath = aProject->GetProjectFullName();
774  wxLogTrace( traceSettings, "Unload project %s", projectPath );
775 
776  PROJECT* toRemove = m_projects.at( projectPath );
777  auto it = std::find_if( m_projects_list.begin(), m_projects_list.end(),
778  [&]( const std::unique_ptr<PROJECT>& ptr )
779  {
780  return ptr.get() == toRemove;
781  } );
782 
783  wxASSERT( it != m_projects_list.end() );
784  m_projects_list.erase( it );
785 
786  m_projects.erase( projectPath );
787 
788  // Immediately reload a null project; this is required until the rest of the application
789  // is refactored to not assume that Prj() always works
790  if( m_projects.empty() )
791  LoadProject( "" );
792 
793  // Remove the reference in the environment to the previous project
794  wxSetEnv( PROJECT_VAR_NAME, "" );
795 
796  if( m_kiway )
798 
799  return true;
800 }
801 
802 
804 {
805  // No MDI yet: First project in the list is the active project
806  return *m_projects.begin()->second;
807 }
808 
809 
811 {
812  return !m_projects.empty();
813 }
814 
815 
816 PROJECT* SETTINGS_MANAGER::GetProject( const wxString& aFullPath ) const
817 {
818  if( m_projects.count( aFullPath ) )
819  return m_projects.at( aFullPath );
820 
821  return nullptr;
822 }
823 
824 
825 std::vector<wxString> SETTINGS_MANAGER::GetOpenProjects() const
826 {
827  std::vector<wxString> ret;
828 
829  for( const std::pair<const wxString, PROJECT*>& pair : m_projects )
830  ret.emplace_back( pair.first );
831 
832  return ret;
833 }
834 
835 
836 bool SETTINGS_MANAGER::SaveProject( const wxString& aFullPath )
837 {
838  wxString path = aFullPath;
839 
840  if( path.empty() )
841  path = Prj().GetProjectFullName();
842 
843  // TODO: refactor for MDI
844  if( Prj().IsReadOnly() )
845  return false;
846 
847  if( !m_project_files.count( path ) )
848  return false;
849 
850  PROJECT_FILE* project = m_project_files.at( path );
851  wxString projectPath = GetPathForSettingsFile( project );
852 
853  project->SaveToFile( projectPath );
854  Prj().GetLocalSettings().SaveToFile( projectPath );
855 
856  return true;
857 }
858 
859 
860 void SETTINGS_MANAGER::SaveProjectAs( const wxString& aFullPath )
861 {
862  wxString oldName = Prj().GetProjectFullName();
863 
864  // Changing this will cause UnloadProject to not save over the "old" project when loading below
865  Prj().setProjectFullName( aFullPath );
866 
867  wxFileName fn( aFullPath );
868 
869  PROJECT_FILE* project = m_project_files.at( oldName );
870  project->SetFilename( fn.GetName() );
871  project->SaveToFile( fn.GetPath() );
872 
873  Prj().GetLocalSettings().SetFilename( fn.GetName() );
874  Prj().GetLocalSettings().SaveToFile( fn.GetPath() );
875 
876  m_project_files[fn.GetFullPath()] = project;
877  m_project_files.erase( oldName );
878 
879  m_projects[fn.GetFullPath()] = m_projects[oldName];
880  m_projects.erase( oldName );
881 }
882 
883 
885 {
886  wxFileName fullFn( aProject.GetProjectFullName() );
887  wxString fn( fullFn.GetName() );
888 
889  PROJECT_FILE* file = static_cast<PROJECT_FILE*>( RegisterSettings( new PROJECT_FILE( fn ),
890  false ) );
891 
892  m_project_files[aProject.GetProjectFullName()] = file;
893 
894  aProject.setProjectFile( file );
895  file->SetProject( &aProject );
896 
897  wxString path( fullFn.GetPath() );
898 
899  return file->LoadFromFile( path );
900 }
901 
902 
903 bool SETTINGS_MANAGER::unloadProjectFile( PROJECT* aProject, bool aSave )
904 {
905  if( !aProject )
906  return false;
907 
908  wxString name = aProject->GetProjectFullName();
909 
910  if( !m_project_files.count( name ) )
911  return false;
912 
914 
915  auto it = std::find_if( m_settings.begin(), m_settings.end(),
916  [&file]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
917  {
918  return aPtr.get() == file;
919  } );
920 
921  if( it != m_settings.end() )
922  {
923  wxString projectPath = GetPathForSettingsFile( it->get() );
924 
925  FlushAndRelease( &aProject->GetLocalSettings(), aSave );
926 
927  if( aSave )
928  ( *it )->SaveToFile( projectPath );
929 
930  m_settings.erase( it );
931  }
932 
933  m_project_files.erase( name );
934 
935  return true;
936 }
937 
938 
940 {
942 }
943 
944 
945 wxString SETTINGS_MANAGER::backupDateTimeFormat = wxT( "%Y-%m-%d_%H%M%S" );
946 
947 
949 {
950  wxDateTime timestamp = wxDateTime::Now();
951 
952  wxString fileName = wxString::Format( wxT( "%s-%s" ), Prj().GetProjectName(),
953  timestamp.Format( backupDateTimeFormat ) );
954 
955  wxFileName target;
956  target.SetPath( GetProjectBackupsPath() );
957  target.SetName( fileName );
958  target.SetExt( ArchiveFileExtension );
959 
960  wxDir dir( target.GetPath() );
961 
962  if( !target.DirExists() && !wxMkdir( target.GetPath() ) )
963  {
964  wxLogTrace( traceSettings, "Could not create project backup path %s", target.GetPath() );
965  return false;
966  }
967 
968  if( !target.IsDirWritable() )
969  {
970  wxLogTrace( traceSettings, "Backup directory %s is not writeable", target.GetPath() );
971  return false;
972  }
973 
974  wxLogTrace( traceSettings, "Backing up project to %s", target.GetPath() );
975 
976  PROJECT_ARCHIVER archiver;
977 
978  return archiver.Archive( Prj().GetProjectPath(), target.GetFullPath(), aReporter );
979 }
980 
981 
982 class VECTOR_INSERT_TRAVERSER : public wxDirTraverser
983 {
984 public:
985  VECTOR_INSERT_TRAVERSER( std::vector<wxString>& aVec,
986  std::function<bool( const wxString& )> aCond ) :
987  m_files( aVec ),
988  m_condition( aCond )
989  {
990  }
991 
992  wxDirTraverseResult OnFile( const wxString& aFile ) override
993  {
994  if( m_condition( aFile ) )
995  m_files.emplace_back( aFile );
996 
997  return wxDIR_CONTINUE;
998  }
999 
1000  wxDirTraverseResult OnDir( const wxString& aDirName ) override
1001  {
1002  return wxDIR_CONTINUE;
1003  }
1004 
1005 private:
1006  std::vector<wxString>& m_files;
1007 
1008  std::function<bool( const wxString& )> m_condition;
1009 };
1010 
1011 
1013 {
1015 
1016  if( !settings.enabled )
1017  return true;
1018 
1019  wxString prefix = Prj().GetProjectName() + '-';
1020 
1021  auto modTime =
1022  [&prefix]( const wxString& aFile )
1023  {
1024  wxDateTime dt;
1025  wxString fn( wxFileName( aFile ).GetName() );
1026  fn.Replace( prefix, "" );
1027  dt.ParseFormat( fn, backupDateTimeFormat );
1028  return dt;
1029  };
1030 
1031  // Project not saved yet
1032  if( Prj().GetProjectPath().empty() )
1033  return true;
1034 
1035  wxString backupPath = GetProjectBackupsPath();
1036 
1037  if( !wxDirExists( backupPath ) )
1038  {
1039  wxLogTrace( traceSettings, "Backup path %s doesn't exist, creating it", backupPath );
1040 
1041  if( !wxMkdir( backupPath ) )
1042  {
1043  wxLogTrace( traceSettings, "Could not create backups path! Skipping backup" );
1044  return false;
1045  }
1046  }
1047 
1048  wxDir dir( backupPath );
1049 
1050  if( !dir.IsOpened() )
1051  {
1052  wxLogTrace( traceSettings, "Could not open project backups path %s", dir.GetName() );
1053  return false;
1054  }
1055 
1056  std::vector<wxString> files;
1057 
1058  VECTOR_INSERT_TRAVERSER traverser( files,
1059  [&modTime]( const wxString& aFile )
1060  {
1061  return modTime( aFile ).IsValid();
1062  } );
1063 
1064  dir.Traverse( traverser, wxT( "*.zip" ) );
1065 
1066  // Sort newest-first
1067  std::sort( files.begin(), files.end(),
1068  [&]( const wxString& aFirst, const wxString& aSecond ) -> bool
1069  {
1070  wxDateTime first = modTime( aFirst );
1071  wxDateTime second = modTime( aSecond );
1072 
1073  return first.GetTicks() > second.GetTicks();
1074  } );
1075 
1076  // Do we even need to back up?
1077  if( !files.empty() )
1078  {
1079  wxDateTime lastTime = modTime( files[0] );
1080 
1081  if( lastTime.IsValid() )
1082  {
1083  wxTimeSpan delta = wxDateTime::Now() - modTime( files[0] );
1084 
1085  if( delta.IsShorterThan( wxTimeSpan::Seconds( settings.min_interval ) ) )
1086  return true;
1087  }
1088  }
1089 
1090  // Now that we know a backup is needed, apply the retention policy
1091 
1092  // Step 1: if we're over the total file limit, remove the oldest
1093  if( !files.empty() && settings.limit_total_files > 0 )
1094  {
1095  while( files.size() > static_cast<size_t>( settings.limit_total_files ) )
1096  {
1097  wxRemoveFile( files.back() );
1098  files.pop_back();
1099  }
1100  }
1101 
1102  // Step 2: Stay under the total size limit
1103  if( settings.limit_total_size > 0 )
1104  {
1105  wxULongLong totalSize = 0;
1106 
1107  for( const wxString& file : files )
1108  totalSize += wxFileName::GetSize( file );
1109 
1110  while( !files.empty() && totalSize > static_cast<wxULongLong>( settings.limit_total_size ) )
1111  {
1112  totalSize -= wxFileName::GetSize( files.back() );
1113  wxRemoveFile( files.back() );
1114  files.pop_back();
1115  }
1116  }
1117 
1118  // Step 3: Stay under the daily limit
1119  if( settings.limit_daily_files > 0 && files.size() > 1 )
1120  {
1121  wxDateTime day = modTime( files[0] );
1122  int num = 1;
1123 
1124  wxASSERT( day.IsValid() );
1125 
1126  std::vector<wxString> filesToDelete;
1127 
1128  for( size_t i = 1; i < files.size(); i++ )
1129  {
1130  wxDateTime dt = modTime( files[i] );
1131 
1132  if( dt.IsSameDate( day ) )
1133  {
1134  num++;
1135 
1136  if( num > settings.limit_daily_files )
1137  filesToDelete.emplace_back( files[i] );
1138  }
1139  else
1140  {
1141  day = dt;
1142  num = 1;
1143  }
1144  }
1145 
1146  for( const wxString& file : filesToDelete )
1147  wxRemoveFile( file );
1148  }
1149 
1150  return BackupProject( aReporter );
1151 }
static wxString backupDateTimeFormat
virtual bool Store()
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
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:266
This file is part of the common library TODO brief description.
#define TO_STR(x)
Definition: macros.h:105
This file is part of the common library.
void SaveProjectAs(const wxString &aFullPath)
Sets the currently loaded project path and saves it (pointers remain valid)
static bool IsSettingsPathValid(const wxString &aPath)
Checks if a given path is probably a valid KiCad configuration directory.
wxDirTraverseResult OnFile(const wxString &aFilePath) override
const std::string ProjectFileExtension
KIWAY * m_kiway
The kiway this settings manager interacts with.
wxString GetFilename() const
Definition: json_settings.h:64
bool m_migrateLibraryTables
If true, the symbol and footprint library tables will be migrated from the previous version.
bool Archive(const wxString &aSrcDir, const wxString &aDestFile, REPORTER &aReporter, bool aVerbose=true)
Creates an archive of the project.
#define PROJECT_VAR_NAME
A variable name whose value holds the current project directory.
Definition: project.h:38
bool SaveToFile(const wxString &aDirectory="", bool aForce=false) override
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:363
#define PROJECT_BACKUPS_DIR_SUFFIX
Project settings path will be <projectname> + this.
Template specialization to enable wxStrings for certain containers (e.g. unordered_map)
Definition: bitmap.cpp:58
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:64
COLOR_SETTINGS * AddNewColorSettings(const wxString &aFilename)
Registers a new color settings object with the given filename.
nlohmann::json json
Definition: gerbview.cpp:40
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.
PROJECT_FILE is the backing store for a PROJECT, in JSON format.
Definition: project_file.h:62
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)
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.
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:70
std::function< bool(const wxString &)> m_condition
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...
COMMON_SETTINGS * GetCommonSettings() const
Retrieves the common settings shared by all applications.
virtual void ProjectChanged()
Calls ProjectChanged() on all KIWAY_PLAYERs.
Definition: kiway.cpp:538
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
void registerColorSettings(const wxString &aFilename)
virtual const wxString GetProjectFullName() const
Return the full path and name of the project.
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.
SETTINGS_MANAGER(bool aHeadless=false)
virtual void setProjectFullName(const wxString &aFullPathAndName)
Set the full directory, basename, and extension of the project.
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.
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
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.
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
const char * name
Definition: DXF_plotter.cpp:59
std::vector< wxString > GetOpenProjects() const
static wxString calculateUserSettingsPath(bool aIncludeVer=true, bool aUseEnv=true)
Determines the base path for user settings files.
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.
static bool empty(const wxTextEntryBase *aCtrl)
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...
virtual const wxString GetProjectName() const
Return the short name of the project.
int limit_daily_files
Maximum files to keep per day, 0 for unlimited.
wxDirTraverseResult OnDir(const wxString &dirPath) override
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:68
virtual void setProjectFile(PROJECT_FILE *aFile)
Set the backing store file for this project.
Definition: project.h:320
COLOR_SETTINGS * GetMigratedColorSettings()
Returns a color theme for storing colors migrated from legacy (5.x and earlier) settings,...
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
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