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