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/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 <paths.h>
38 #include <project.h>
40 #include <project/project_file.h>
46 
47 
49 #define PROJECT_BACKUPS_DIR_SUFFIX wxT( "-backups" )
50 
51 
53  m_headless( aHeadless ),
54  m_kiway( nullptr ),
55  m_common_settings( nullptr ),
56  m_migration_source(),
57  m_migrateLibraryTables( true )
58 {
60 
61  // Check if the settings directory already exists, and if not, perform a migration if possible
62  if( !MigrateIfNeeded() )
63  {
64  m_ok = false;
65  return;
66  }
67 
68  m_ok = true;
69 
70  // create the common settings shared by all applications. Not loaded immediately
72  static_cast<COMMON_SETTINGS*>( RegisterSettings( new COMMON_SETTINGS, false ) );
73 
75 }
76 
78 {
79  m_settings.clear();
80  m_color_settings.clear();
81  m_projects.clear();
82 }
83 
84 
86 {
87  std::unique_ptr<JSON_SETTINGS> ptr( aSettings );
88 
89  ptr->SetManager( this );
90 
91  wxLogTrace( traceSettings, "Registered new settings object <%s>", ptr->GetFullFilename() );
92 
93  if( aLoadNow )
94  ptr->LoadFromFile( GetPathForSettingsFile( ptr.get() ) );
95 
96  m_settings.push_back( std::move( ptr ) );
97  return m_settings.back().get();
98 }
99 
100 
102 {
103  // TODO(JE) We should check for dirty settings here and write them if so, because
104  // Load() could be called late in the application lifecycle
105 
106  for( auto&& settings : m_settings )
107  settings->LoadFromFile( GetPathForSettingsFile( settings.get() ) );
108 }
109 
110 
112 {
113  auto it = std::find_if( m_settings.begin(), m_settings.end(),
114  [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
115  {
116  return aPtr.get() == aSettings;
117  } );
118 
119  if( it != m_settings.end() )
120  ( *it )->LoadFromFile( GetPathForSettingsFile( it->get() ) );
121 }
122 
123 
125 {
126  for( auto&& settings : m_settings )
127  {
128  // Never automatically save color settings, caller should use SaveColorSettings
129  if( dynamic_cast<COLOR_SETTINGS*>( settings.get() ) )
130  continue;
131 
132  settings->SaveToFile( GetPathForSettingsFile( settings.get() ) );
133  }
134 }
135 
136 
138 {
139  auto it = std::find_if( m_settings.begin(), m_settings.end(),
140  [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
141  {
142  return aPtr.get() == aSettings;
143  } );
144 
145  if( it != m_settings.end() )
146  {
147  wxLogTrace( traceSettings, "Saving %s", ( *it )->GetFullFilename() );
148  ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
149  }
150 }
151 
152 
153 void SETTINGS_MANAGER::FlushAndRelease( JSON_SETTINGS* aSettings, bool aSave )
154 {
155  auto it = std::find_if( m_settings.begin(), m_settings.end(),
156  [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
157  {
158  return aPtr.get() == aSettings;
159  } );
160 
161  if( it != m_settings.end() )
162  {
163  wxLogTrace( traceSettings, "Flush and release %s", ( *it )->GetFullFilename() );
164 
165  if( aSave )
166  ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
167 
168  size_t typeHash = typeid( *it->get() ).hash_code();
169 
170  if( m_app_settings_cache.count( typeHash ) )
171  m_app_settings_cache.erase( typeHash );
172 
173  m_settings.erase( it );
174 
175 
176  }
177 }
178 
179 
181 {
182  if( m_color_settings.count( aName ) )
183  return m_color_settings.at( aName );
184 
185  COLOR_SETTINGS* ret = nullptr;
186 
187  if( !aName.empty() )
188  ret = loadColorSettingsByName( aName );
189 
190  // This had better work
191  if( !ret )
192  ret = m_color_settings.at( "_builtin_default" );
193 
194  return ret;
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  auto cs = static_cast<COLOR_SETTINGS*>(
211  RegisterSettings( new COLOR_SETTINGS( aName.ToStdString() ) ) );
212 
213  if( cs->GetFilename() != aName.ToStdString() )
214  {
215  wxLogTrace( traceSettings, "Warning: stored filename is actually %s, ", cs->GetFilename() );
216  }
217 
218  m_color_settings[aName] = cs;
219 
220  return cs;
221 }
222 
223 
224 class COLOR_SETTINGS_LOADER : public wxDirTraverser
225 {
226 private:
227  std::function<void( const wxString& )> m_action;
228 
229 public:
230  explicit COLOR_SETTINGS_LOADER( std::function<void( const wxString& )> 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  return wxDIR_CONTINUE;
241 
242  m_action( file.GetName() );
243 
244  return wxDIR_CONTINUE;
245  }
246 
247  wxDirTraverseResult OnDir( const wxString& dirPath ) override
248  {
249  return wxDIR_IGNORE;
250  }
251 };
252 
253 
254 void SETTINGS_MANAGER::registerColorSettings( const wxString& aFilename )
255 {
256  if( m_color_settings.count( aFilename ) )
257  return;
258 
259  m_color_settings[aFilename] = static_cast<COLOR_SETTINGS*>(
260  RegisterSettings( new COLOR_SETTINGS( aFilename ) ) );
261 }
262 
263 
265 {
266  wxString filename = aFilename;
267 
268  if( filename.EndsWith( wxT( ".json" ) ) )
269  filename = filename.BeforeLast( '.' );
270 
271  registerColorSettings( filename );
272  return m_color_settings[filename];
273 }
274 
275 
277 {
278  if( !m_color_settings.count( "user" ) )
279  {
280  registerColorSettings( wxT( "user" ) );
281  m_color_settings.at( "user" )->SetName( wxT( "User" ) );
282  Save( m_color_settings.at( "user" ) );
283  }
284 
285  return m_color_settings.at( "user" );
286 }
287 
288 
290 {
291  // Create the built-in color settings
293  {
294  m_color_settings[settings->GetFilename()] =
295  static_cast<COLOR_SETTINGS*>( RegisterSettings( settings, false ) );
296  }
297 
298  // Search for and load any other settings
299  COLOR_SETTINGS_LOADER loader( [&]( const wxString& aFilename )
300  {
301  registerColorSettings( aFilename );
302  } );
303 
304  wxDir colors_dir( GetColorSettingsPath() );
305 
306  if( colors_dir.IsOpened() )
307  colors_dir.Traverse( loader );
308 }
309 
310 
312 {
313  m_color_settings.clear();
315 }
316 
317 
318 void SETTINGS_MANAGER::SaveColorSettings( COLOR_SETTINGS* aSettings, const std::string& aNamespace )
319 {
320  // The passed settings should already be managed
321  wxASSERT( std::find_if( m_color_settings.begin(), m_color_settings.end(),
322  [aSettings] ( const std::pair<wxString, COLOR_SETTINGS*>& el )
323  {
324  return el.second->GetFilename() == aSettings->GetFilename();
325  }
326  ) != m_color_settings.end() );
327 
328  nlohmann::json::json_pointer ptr = JSON_SETTINGS::PointerFromString( aNamespace );
329 
330  if( !aSettings->Store() )
331  {
332  wxLogTrace( traceSettings, "Color scheme %s not modified; skipping save",
333  aSettings->GetFilename(), aNamespace );
334  return;
335  }
336 
337  wxASSERT( aSettings->contains( ptr ) );
338 
339  wxLogTrace( traceSettings, "Saving color scheme %s, preserving %s", aSettings->GetFilename(),
340  aNamespace );
341 
342  nlohmann::json backup = aSettings->at( ptr );
343  wxString path = GetColorSettingsPath();
344 
345  aSettings->LoadFromFile( path );
346 
347  ( *aSettings )[ptr].update( backup );
348  aSettings->Load();
349 
350  aSettings->SaveToFile( path, true );
351 }
352 
353 
355 {
356  wxASSERT( aSettings );
357 
358  switch( aSettings->GetLocation() )
359  {
360  case SETTINGS_LOC::USER:
361  return GetUserSettingsPath();
362 
364  return Prj().GetProjectPath();
365 
367  return GetColorSettingsPath();
368 
369  case SETTINGS_LOC::NONE:
370  return "";
371 
372  default:
373  wxASSERT_MSG( false, "Unknown settings location!" );
374  }
375 
376  return "";
377 }
378 
379 
380 class MIGRATION_TRAVERSER : public wxDirTraverser
381 {
382 private:
383  wxString m_src;
384  wxString m_dest;
385  wxString m_errors;
387 
388 public:
389  MIGRATION_TRAVERSER( const wxString& aSrcDir, const wxString& aDestDir, bool aMigrateTables ) :
390  m_src( aSrcDir ),
391  m_dest( aDestDir ),
392  m_migrateTables( aMigrateTables )
393  {
394  }
395 
396  wxString GetErrors() { return m_errors; }
397 
398  wxDirTraverseResult OnFile( const wxString& aSrcFilePath ) override
399  {
400  wxFileName file( aSrcFilePath );
401 
402  if( !m_migrateTables && ( file.GetName() == wxT( "sym-lib-table" ) ||
403  file.GetName() == wxT( "fp-lib-table" ) ) )
404  {
405  return wxDIR_CONTINUE;
406  }
407 
408  // Don't migrate hotkeys config files; we don't have a reasonable migration handler for them
409  // and so there is no way to resolve conflicts at the moment
410  if( file.GetExt() == wxT( "hotkeys" ) )
411  return wxDIR_CONTINUE;
412 
413  wxString path = file.GetPath();
414 
415  path.Replace( m_src, m_dest, false );
416  file.SetPath( path );
417 
418  wxLogTrace( traceSettings, "Copying %s to %s", aSrcFilePath, file.GetFullPath() );
419 
420  // For now, just copy everything
421  KiCopyFile( aSrcFilePath, file.GetFullPath(), m_errors );
422 
423  return wxDIR_CONTINUE;
424  }
425 
426  wxDirTraverseResult OnDir( const wxString& dirPath ) override
427  {
428  wxFileName dir( dirPath );
429 
430  // Whitelist of directories to migrate
431  if( dir.GetName() == "colors" ||
432  dir.GetName() == "3d" )
433  {
434 
435  wxString path = dir.GetPath();
436 
437  path.Replace( m_src, m_dest, false );
438  dir.SetPath( path );
439 
440  wxMkdir( dir.GetFullPath() );
441 
442  return wxDIR_CONTINUE;
443  }
444  else
445  {
446  return wxDIR_IGNORE;
447  }
448  }
449 };
450 
451 
453 {
454  if( m_headless )
455  {
456  wxLogTrace( traceSettings, "Settings migration not checked; running headless" );
457  return false;
458  }
459 
460  wxFileName path( GetUserSettingsPath(), "" );
461  wxLogTrace( traceSettings, "Using settings path %s", path.GetFullPath() );
462 
463  if( path.DirExists() )
464  {
465  wxFileName common = path;
466  common.SetName( "kicad_common" );
467  common.SetExt( "json" );
468 
469  if( common.Exists() )
470  {
471  wxLogTrace( traceSettings, "Path exists and has a kicad_common, continuing!" );
472  return true;
473  }
474  }
475 
476  // Now we have an empty path, let's figure out what to put in it
477  DIALOG_MIGRATE_SETTINGS dlg( this );
478 
479  if( dlg.ShowModal() != wxID_OK )
480  {
481  wxLogTrace( traceSettings, "Migration dialog canceled; exiting" );
482  return false;
483  }
484 
485  if( !path.DirExists() )
486  {
487  wxLogTrace( traceSettings, "Path didn't exist; creating it" );
488  path.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
489  }
490 
491  if( m_migration_source.IsEmpty() )
492  {
493  wxLogTrace( traceSettings, "No migration source given; starting with defaults" );
494  return true;
495  }
496 
498  wxDir source_dir( m_migration_source );
499 
500  source_dir.Traverse( traverser );
501 
502  if( !traverser.GetErrors().empty() )
503  DisplayErrorMessage( nullptr, traverser.GetErrors() );
504 
505  return true;
506 }
507 
508 
509 bool SETTINGS_MANAGER::GetPreviousVersionPaths( std::vector<wxString>* aPaths )
510 {
511  wxASSERT( aPaths );
512 
513  aPaths->clear();
514 
515  wxDir dir;
516  std::vector<wxFileName> base_paths;
517 
518  base_paths.emplace_back( wxFileName( calculateUserSettingsPath( false ), "" ) );
519 
520  // If the env override is set, also check the default paths
521  if( wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), nullptr ) )
522  base_paths.emplace_back( wxFileName( calculateUserSettingsPath( false, false ), "" ) );
523 
524 #ifdef __WXGTK__
525  // When running inside FlatPak, KIPLATFORM::ENV::GetUserConfigPath() will return a sandboxed
526  // path. In case the user wants to move from non-FlatPak KiCad to FlatPak KiCad, let's add our
527  // best guess as to the non-FlatPak config path. Unfortunately FlatPak also hides the host
528  // XDG_CONFIG_HOME, so if the user customizes their config path, they will have to browse
529  // for it.
530  {
531  wxFileName wxGtkPath;
532  wxGtkPath.AssignDir( "~/.config/kicad" );
533  wxGtkPath.MakeAbsolute();
534  base_paths.emplace_back( wxGtkPath.GetPath() );
535 
536  // We also want to pick up regular flatpak if we are nightly
537  wxGtkPath.AssignDir( "~/.var/app/org.kicad.KiCad/config/kicad" );
538  wxGtkPath.MakeAbsolute();
539  base_paths.emplace_back( wxGtkPath.GetPath() );
540  }
541 #endif
542 
543  wxString subdir;
544  std::string mine = GetSettingsVersion();
545 
546  auto check_dir = [&] ( const wxString& aSubDir )
547  {
548  // Only older versions are valid for migration
549  if( compareVersions( aSubDir.ToStdString(), mine ) <= 0 )
550  {
551  wxString sub_path = dir.GetNameWithSep() + aSubDir;
552 
553  if( IsSettingsPathValid( sub_path ) )
554  {
555  aPaths->push_back( sub_path );
556  wxLogTrace( traceSettings, "GetPreviousVersionName: %s is valid", sub_path );
557  }
558  }
559  };
560 
561  std::set<wxString> checkedPaths;
562 
563  for( auto base_path : base_paths )
564  {
565  if( checkedPaths.count( base_path.GetFullPath() ) )
566  continue;
567 
568  checkedPaths.insert( base_path.GetFullPath() );
569 
570  if( !dir.Open( base_path.GetFullPath() ) )
571  {
572  wxLogTrace( traceSettings, "GetPreviousVersionName: could not open base path %s",
573  base_path.GetFullPath() );
574  continue;
575  }
576 
577  wxLogTrace( traceSettings, "GetPreviousVersionName: checking base path %s",
578  base_path.GetFullPath() );
579 
580  if( dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS ) )
581  {
582  if( subdir != mine )
583  check_dir( subdir );
584 
585  while( dir.GetNext( &subdir ) )
586  {
587  if( subdir != mine )
588  check_dir( subdir );
589  }
590  }
591 
592  // If we didn't find one yet, check for legacy settings without a version directory
593  if( IsSettingsPathValid( dir.GetNameWithSep() ) )
594  {
595  wxLogTrace( traceSettings,
596  "GetPreviousVersionName: root path %s is valid", dir.GetName() );
597  aPaths->push_back( dir.GetName() );
598  }
599  }
600 
601  return aPaths->size() > 0;
602 }
603 
604 
605 bool SETTINGS_MANAGER::IsSettingsPathValid( const wxString& aPath )
606 {
607  wxFileName test( aPath, "kicad_common" );
608 
609  if( test.Exists() )
610  return true;
611 
612  test.SetExt( "json" );
613 
614  return test.Exists();
615 }
616 
617 
619 {
620  wxFileName path;
621 
622  path.AssignDir( GetUserSettingsPath() );
623  path.AppendDir( "colors" );
624 
625  if( !path.DirExists() )
626  {
627  if( !wxMkdir( path.GetPath() ) )
628  {
629  wxLogTrace( traceSettings,
630  "GetColorSettingsPath(): Path %s missing and could not be created!",
631  path.GetPath() );
632  }
633  }
634 
635  return path.GetPath();
636 }
637 
638 
640 {
641  static wxString user_settings_path;
642 
643  if( user_settings_path.empty() )
644  user_settings_path = calculateUserSettingsPath();
645 
646  return user_settings_path;
647 }
648 
649 
650 wxString SETTINGS_MANAGER::calculateUserSettingsPath( bool aIncludeVer, bool aUseEnv )
651 {
652  wxFileName cfgpath;
653 
654  // http://docs.wxwidgets.org/3.0/classwx_standard_paths.html#a7c7cf595d94d29147360d031647476b0
655 
656  wxString envstr;
657  if( aUseEnv && wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), &envstr ) && !envstr.IsEmpty() )
658  {
659  // Override the assignment above with KICAD_CONFIG_HOME
660  cfgpath.AssignDir( envstr );
661  }
662  else
663  {
664  cfgpath.AssignDir( KIPLATFORM::ENV::GetUserConfigPath() );
665 
666  cfgpath.AppendDir( TO_STR( KICAD_CONFIG_DIR ) );
667  }
668 
669  if( aIncludeVer )
670  cfgpath.AppendDir( GetSettingsVersion() );
671 
672  return cfgpath.GetPath();
673 }
674 
675 
677 {
678  // CMake computes the major.minor string for us.
679  return GetMajorMinorVersion().ToStdString();
680 }
681 
682 
683 int SETTINGS_MANAGER::compareVersions( const std::string& aFirst, const std::string& aSecond )
684 {
685  int a_maj = 0;
686  int a_min = 0;
687  int b_maj = 0;
688  int b_min = 0;
689 
690  if( !extractVersion( aFirst, &a_maj, &a_min ) || !extractVersion( aSecond, &b_maj, &b_min ) )
691  {
692  wxLogTrace( traceSettings, "compareSettingsVersions: bad input (%s, %s)", aFirst, aSecond );
693  return -1;
694  }
695 
696  if( a_maj < b_maj )
697  {
698  return -1;
699  }
700  else if( a_maj > b_maj )
701  {
702  return 1;
703  }
704  else
705  {
706  if( a_min < b_min )
707  {
708  return -1;
709  }
710  else if( a_min > b_min )
711  {
712  return 1;
713  }
714  else
715  {
716  return 0;
717  }
718  }
719 }
720 
721 
722 bool SETTINGS_MANAGER::extractVersion( const std::string& aVersionString, int* aMajor, int* aMinor )
723 {
724  std::regex re_version( "(\\d+)\\.(\\d+)" );
725  std::smatch match;
726 
727  if( std::regex_match( aVersionString, match, re_version ) )
728  {
729  try
730  {
731  *aMajor = std::stoi( match[1].str() );
732  *aMinor = std::stoi( match[2].str() );
733  }
734  catch( ... )
735  {
736  return false;
737  }
738 
739  return true;
740  }
741 
742  return false;
743 }
744 
745 
746 bool SETTINGS_MANAGER::LoadProject( const wxString& aFullPath, bool aSetActive )
747 {
748  // Normalize path to new format even if migrating from a legacy file
749  wxFileName path( aFullPath );
750 
751  if( path.GetExt() == LegacyProjectFileExtension )
752  path.SetExt( ProjectFileExtension );
753 
754  wxString fullPath = path.GetFullPath();
755 
756  // If already loaded, we are all set. This might be called more than once over a project's
757  // lifetime in case the project is first loaded by the KiCad manager and then eeschema or
758  // pcbnew try to load it again when they are launched.
759  if( m_projects.count( fullPath ) )
760  return true;
761 
762  bool readOnly = false;
763  std::unique_ptr<wxSingleInstanceChecker> lockFile = ::LockFile( fullPath );
764 
765  if( !lockFile )
766  {
767  wxLogTrace( traceSettings, "Project %s is locked; opening read-only", fullPath );
768  readOnly = true;
769  }
770 
771  // No MDI yet
772  if( aSetActive && !m_projects.empty() )
773  {
774  PROJECT* oldProject = m_projects.begin()->second;
775  unloadProjectFile( oldProject, false );
776  m_projects.erase( m_projects.begin() );
777  }
778 
779  wxLogTrace( traceSettings, "Load project %s", fullPath );
780 
781  std::unique_ptr<PROJECT> project = std::make_unique<PROJECT>();
782  project->setProjectFullName( fullPath );
783 
784  bool success = loadProjectFile( *project );
785 
786  if( success )
787  {
788  project->SetReadOnly( readOnly || project->GetProjectFile().IsReadOnly() );
789 
790  if( lockFile )
791  m_project_lock.reset( lockFile.release() );
792  }
793 
794  m_projects_list.push_back( std::move( project ) );
795  m_projects[fullPath] = m_projects_list.back().get();
796 
797  wxString fn( path.GetName() );
798 
799  PROJECT_LOCAL_SETTINGS* settings = new PROJECT_LOCAL_SETTINGS( m_projects[fullPath], fn );
800 
801  if( aSetActive )
802  settings = static_cast<PROJECT_LOCAL_SETTINGS*>( RegisterSettings( settings ) );
803 
804  m_projects[fullPath]->setLocalSettings( settings );
805 
806  if( aSetActive && m_kiway )
808 
809  return success;
810 }
811 
812 
813 bool SETTINGS_MANAGER::UnloadProject( PROJECT* aProject, bool aSave )
814 {
815  if( !aProject || !m_projects.count( aProject->GetProjectFullName() ) )
816  return false;
817 
818  if( !unloadProjectFile( aProject, aSave ) )
819  return false;
820 
821  wxString projectPath = aProject->GetProjectFullName();
822  wxLogTrace( traceSettings, "Unload project %s", projectPath );
823 
824  PROJECT* toRemove = m_projects.at( projectPath );
825  auto it = std::find_if( m_projects_list.begin(), m_projects_list.end(),
826  [&]( const std::unique_ptr<PROJECT>& ptr )
827  {
828  return ptr.get() == toRemove;
829  } );
830 
831  wxASSERT( it != m_projects_list.end() );
832  m_projects_list.erase( it );
833 
834  m_projects.erase( projectPath );
835 
836  // Immediately reload a null project; this is required until the rest of the application
837  // is refactored to not assume that Prj() always works
838  if( m_projects.empty() )
839  LoadProject( "" );
840 
841  // Remove the reference in the environment to the previous project
842  wxSetEnv( PROJECT_VAR_NAME, "" );
843 
844  // Release lock on the file, in case we had one
845  m_project_lock = nullptr;
846 
847  if( m_kiway )
849 
850  return true;
851 }
852 
853 
855 {
856  // No MDI yet: First project in the list is the active project
857  return *m_projects.begin()->second;
858 }
859 
860 
862 {
863  return !m_projects.empty();
864 }
865 
866 
867 PROJECT* SETTINGS_MANAGER::GetProject( const wxString& aFullPath ) const
868 {
869  if( m_projects.count( aFullPath ) )
870  return m_projects.at( aFullPath );
871 
872  return nullptr;
873 }
874 
875 
876 std::vector<wxString> SETTINGS_MANAGER::GetOpenProjects() const
877 {
878  std::vector<wxString> ret;
879 
880  for( const std::pair<const wxString, PROJECT*>& pair : m_projects )
881  ret.emplace_back( pair.first );
882 
883  return ret;
884 }
885 
886 
887 bool SETTINGS_MANAGER::SaveProject( const wxString& aFullPath )
888 {
889  wxString path = aFullPath;
890 
891  if( path.empty() )
893 
894  // TODO: refactor for MDI
895  if( Prj().IsReadOnly() )
896  return false;
897 
898  if( !m_project_files.count( path ) )
899  return false;
900 
902  wxString projectPath = GetPathForSettingsFile( project );
903 
904  project->SaveToFile( projectPath );
905  Prj().GetLocalSettings().SaveToFile( projectPath );
906 
907  return true;
908 }
909 
910 
911 void SETTINGS_MANAGER::SaveProjectAs( const wxString& aFullPath )
912 {
913  wxString oldName = Prj().GetProjectFullName();
914 
915  // Changing this will cause UnloadProject to not save over the "old" project when loading below
916  Prj().setProjectFullName( aFullPath );
917 
918  wxFileName fn( aFullPath );
919 
920  PROJECT_FILE* project = m_project_files.at( oldName );
921  project->SetFilename( fn.GetName() );
922  project->SaveToFile( fn.GetPath() );
923 
924  Prj().GetLocalSettings().SetFilename( fn.GetName() );
925  Prj().GetLocalSettings().SaveToFile( fn.GetPath() );
926 
927  m_project_files[fn.GetFullPath()] = project;
928  m_project_files.erase( oldName );
929 
930  m_projects[fn.GetFullPath()] = m_projects[oldName];
931  m_projects.erase( oldName );
932 }
933 
934 
935 void SETTINGS_MANAGER::SaveProjectCopy( const wxString& aFullPath )
936 {
937  PROJECT_FILE* project = m_project_files.at( Prj().GetProjectFullName() );
938  wxString oldName = project->GetFilename();
939  wxFileName fn( aFullPath );
940 
941  project->SetFilename( fn.GetName() );
942  project->SaveToFile( fn.GetPath() );
943  project->SetFilename( oldName );
944 
945  Prj().GetLocalSettings().SetFilename( fn.GetName() );
946  Prj().GetLocalSettings().SaveToFile( fn.GetPath() );
947  Prj().GetLocalSettings().SetFilename( oldName );
948 }
949 
950 
952 {
953  wxFileName fullFn( aProject.GetProjectFullName() );
954  wxString fn( fullFn.GetName() );
955 
956  PROJECT_FILE* file = static_cast<PROJECT_FILE*>( RegisterSettings( new PROJECT_FILE( fn ),
957  false ) );
958 
959  m_project_files[aProject.GetProjectFullName()] = file;
960 
961  aProject.setProjectFile( file );
962  file->SetProject( &aProject );
963 
964  wxString path( fullFn.GetPath() );
965 
966  return file->LoadFromFile( path );
967 }
968 
969 
970 bool SETTINGS_MANAGER::unloadProjectFile( PROJECT* aProject, bool aSave )
971 {
972  if( !aProject )
973  return false;
974 
975  wxString name = aProject->GetProjectFullName();
976 
977  if( !m_project_files.count( name ) )
978  return false;
979 
981 
982  auto it = std::find_if( m_settings.begin(), m_settings.end(),
983  [&file]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
984  {
985  return aPtr.get() == file;
986  } );
987 
988  if( it != m_settings.end() )
989  {
990  wxString projectPath = GetPathForSettingsFile( it->get() );
991 
992  FlushAndRelease( &aProject->GetLocalSettings(), aSave );
993 
994  if( aSave )
995  ( *it )->SaveToFile( projectPath );
996 
997  m_settings.erase( it );
998  }
999 
1000  m_project_files.erase( name );
1001 
1002  return true;
1003 }
1004 
1005 
1007 {
1009 }
1010 
1011 
1012 wxString SETTINGS_MANAGER::backupDateTimeFormat = wxT( "%Y-%m-%d_%H%M%S" );
1013 
1014 
1016 {
1017  wxDateTime timestamp = wxDateTime::Now();
1018 
1019  wxString fileName = wxString::Format( wxT( "%s-%s" ), Prj().GetProjectName(),
1020  timestamp.Format( backupDateTimeFormat ) );
1021 
1022  wxFileName target;
1023  target.SetPath( GetProjectBackupsPath() );
1024  target.SetName( fileName );
1025  target.SetExt( ArchiveFileExtension );
1026 
1027  wxDir dir( target.GetPath() );
1028 
1029  if( !target.DirExists() && !wxMkdir( target.GetPath() ) )
1030  {
1031  wxLogTrace( traceSettings, "Could not create project backup path %s", target.GetPath() );
1032  return false;
1033  }
1034 
1035  if( !target.IsDirWritable() )
1036  {
1037  wxLogTrace( traceSettings, "Backup directory %s is not writeable", target.GetPath() );
1038  return false;
1039  }
1040 
1041  wxLogTrace( traceSettings, "Backing up project to %s", target.GetPath() );
1042 
1043  PROJECT_ARCHIVER archiver;
1044 
1045  return archiver.Archive( Prj().GetProjectPath(), target.GetFullPath(), aReporter );
1046 }
1047 
1048 
1049 class VECTOR_INSERT_TRAVERSER : public wxDirTraverser
1050 {
1051 public:
1052  VECTOR_INSERT_TRAVERSER( std::vector<wxString>& aVec,
1053  std::function<bool( const wxString& )> aCond ) :
1054  m_files( aVec ),
1055  m_condition( aCond )
1056  {
1057  }
1058 
1059  wxDirTraverseResult OnFile( const wxString& aFile ) override
1060  {
1061  if( m_condition( aFile ) )
1062  m_files.emplace_back( aFile );
1063 
1064  return wxDIR_CONTINUE;
1065  }
1066 
1067  wxDirTraverseResult OnDir( const wxString& aDirName ) override
1068  {
1069  return wxDIR_CONTINUE;
1070  }
1071 
1072 private:
1073  std::vector<wxString>& m_files;
1074 
1075  std::function<bool( const wxString& )> m_condition;
1076 };
1077 
1078 
1080 {
1082 
1083  if( !settings.enabled )
1084  return true;
1085 
1086  wxString prefix = Prj().GetProjectName() + '-';
1087 
1088  auto modTime =
1089  [&prefix]( const wxString& aFile )
1090  {
1091  wxDateTime dt;
1092  wxString fn( wxFileName( aFile ).GetName() );
1093  fn.Replace( prefix, "" );
1094  dt.ParseFormat( fn, backupDateTimeFormat );
1095  return dt;
1096  };
1097 
1098  wxFileName projectPath( Prj().GetProjectPath() );
1099 
1100  // Skip backup if project path isn't valid or writeable
1101  if( !projectPath.IsOk() || !projectPath.Exists() || !projectPath.IsDirWritable() )
1102  return true;
1103 
1104  wxString backupPath = GetProjectBackupsPath();
1105 
1106  if( !wxDirExists( backupPath ) )
1107  {
1108  wxLogTrace( traceSettings, "Backup path %s doesn't exist, creating it", backupPath );
1109 
1110  if( !wxMkdir( backupPath ) )
1111  {
1112  wxLogTrace( traceSettings, "Could not create backups path! Skipping backup" );
1113  return false;
1114  }
1115  }
1116 
1117  wxDir dir( backupPath );
1118 
1119  if( !dir.IsOpened() )
1120  {
1121  wxLogTrace( traceSettings, "Could not open project backups path %s", dir.GetName() );
1122  return false;
1123  }
1124 
1125  std::vector<wxString> files;
1126 
1127  VECTOR_INSERT_TRAVERSER traverser( files,
1128  [&modTime]( const wxString& aFile )
1129  {
1130  return modTime( aFile ).IsValid();
1131  } );
1132 
1133  dir.Traverse( traverser, wxT( "*.zip" ) );
1134 
1135  // Sort newest-first
1136  std::sort( files.begin(), files.end(),
1137  [&]( const wxString& aFirst, const wxString& aSecond ) -> bool
1138  {
1139  wxDateTime first = modTime( aFirst );
1140  wxDateTime second = modTime( aSecond );
1141 
1142  return first.GetTicks() > second.GetTicks();
1143  } );
1144 
1145  // Do we even need to back up?
1146  if( !files.empty() )
1147  {
1148  wxDateTime lastTime = modTime( files[0] );
1149 
1150  if( lastTime.IsValid() )
1151  {
1152  wxTimeSpan delta = wxDateTime::Now() - modTime( files[0] );
1153 
1154  if( delta.IsShorterThan( wxTimeSpan::Seconds( settings.min_interval ) ) )
1155  return true;
1156  }
1157  }
1158 
1159  // Now that we know a backup is needed, apply the retention policy
1160 
1161  // Step 1: if we're over the total file limit, remove the oldest
1162  if( !files.empty() && settings.limit_total_files > 0 )
1163  {
1164  while( files.size() > static_cast<size_t>( settings.limit_total_files ) )
1165  {
1166  wxRemoveFile( files.back() );
1167  files.pop_back();
1168  }
1169  }
1170 
1171  // Step 2: Stay under the total size limit
1172  if( settings.limit_total_size > 0 )
1173  {
1174  wxULongLong totalSize = 0;
1175 
1176  for( const wxString& file : files )
1177  totalSize += wxFileName::GetSize( file );
1178 
1179  while( !files.empty() && totalSize > static_cast<wxULongLong>( settings.limit_total_size ) )
1180  {
1181  totalSize -= wxFileName::GetSize( files.back() );
1182  wxRemoveFile( files.back() );
1183  files.pop_back();
1184  }
1185  }
1186 
1187  // Step 3: Stay under the daily limit
1188  if( settings.limit_daily_files > 0 && files.size() > 1 )
1189  {
1190  wxDateTime day = modTime( files[0] );
1191  int num = 1;
1192 
1193  wxASSERT( day.IsValid() );
1194 
1195  std::vector<wxString> filesToDelete;
1196 
1197  for( size_t i = 1; i < files.size(); i++ )
1198  {
1199  wxDateTime dt = modTime( files[i] );
1200 
1201  if( dt.IsSameDate( day ) )
1202  {
1203  num++;
1204 
1205  if( num > settings.limit_daily_files )
1206  filesToDelete.emplace_back( files[i] );
1207  }
1208  else
1209  {
1210  day = dt;
1211  num = 1;
1212  }
1213  }
1214 
1215  for( const wxString& file : filesToDelete )
1216  wxRemoveFile( file );
1217  }
1218 
1219  return BackupProject( aReporter );
1220 }
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...
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:265
This file is part of the common library TODO brief description.
#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)
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:66
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:363
std::unordered_map< size_t, JSON_SETTINGS * > m_app_settings_cache
Cache for app settings.
#define PROJECT_BACKUPS_DIR_SUFFIX
Project settings path will be <projectname> + this.
Definition: bitmap.cpp:63
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:41
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:123
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.
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:72
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:550
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
void registerColorSettings(const wxString &aFilename)
static void EnsureUserPathsExist()
Ensures/creates user default paths.
Definition: paths.cpp:263
virtual const wxString GetProjectFullName() const
Return the full path and name of the project.
Definition: project.cpp:117
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.
Definition: project.cpp:85
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.
wxString GetUserConfigPath()
Retrieves the operating system specific path for a user's configuration store.
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.
Definition: project.cpp:129
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:70
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
File locking utilities.