KiCad PCB EDA Suite
settings_manager.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2020 Jon Evans <jon@craftyjon.com>
5  * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software: you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation, either version 3 of the License, or (at your
10  * option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <regex>
22 #include <wx/debug.h>
23 #include <wx/filename.h>
24 #include <wx/stdpaths.h>
25 #include <wx/utils.h>
26 
27 #include <build_version.h>
28 #include <confirm.h>
30 #include <gestfich.h>
31 #include <macros.h>
32 #include <project.h>
34 #include <project/project_file.h>
36 #include <settings/app_settings.h>
41 
42 
47 const char* traceSettings = "SETTINGS";
48 
49 
51 #define PROJECT_BACKUPS_DIR_SUFFIX wxT( "-backups" )
52 
53 
55  m_headless( aHeadless ),
56  m_common_settings( nullptr ),
57  m_migration_source()
58 {
59  // Check if the settings directory already exists, and if not, perform a migration if possible
60  if( !MigrateIfNeeded() )
61  {
62  m_ok = false;
63  return;
64  }
65 
66  m_ok = true;
67 
68  // create the common settings shared by all applications. Not loaded immediately
70  static_cast<COMMON_SETTINGS*>( RegisterSettings( new COMMON_SETTINGS, false ) );
71 
73 }
74 
76 {
77  m_settings.clear();
78  m_color_settings.clear();
79  m_projects.clear();
80 }
81 
82 
84 {
85  std::unique_ptr<JSON_SETTINGS> ptr( aSettings );
86 
87  ptr->SetManager( this );
88 
89  wxLogTrace( traceSettings, "Registered new settings object <%s>", ptr->GetFullFilename() );
90 
91  if( aLoadNow )
92  ptr->LoadFromFile( GetPathForSettingsFile( ptr.get() ) );
93 
94  m_settings.push_back( std::move( ptr ) );
95  return m_settings.back().get();
96 }
97 
98 
100 {
101  // TODO(JE) We should check for dirty settings here and write them if so, because
102  // Load() could be called late in the application lifecycle
103 
104  for( auto&& settings : m_settings )
105  settings->LoadFromFile( GetPathForSettingsFile( settings.get() ) );
106 }
107 
108 
110 {
111  auto it = std::find_if( m_settings.begin(), m_settings.end(),
112  [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
113  {
114  return aPtr.get() == aSettings;
115  } );
116 
117  if( it != m_settings.end() )
118  ( *it )->LoadFromFile( GetPathForSettingsFile( it->get() ) );
119 }
120 
121 
123 {
124  for( auto&& settings : m_settings )
125  {
126  // Never automatically save color settings, caller should use SaveColorSettings
127  if( dynamic_cast<COLOR_SETTINGS*>( settings.get() ) )
128  continue;
129 
130  settings->SaveToFile( GetPathForSettingsFile( settings.get() ) );
131  }
132 }
133 
134 
136 {
137  auto it = std::find_if( m_settings.begin(), m_settings.end(),
138  [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
139  {
140  return aPtr.get() == aSettings;
141  } );
142 
143  if( it != m_settings.end() )
144  {
145  wxLogTrace( traceSettings, "Saving %s", ( *it )->GetFullFilename() );
146  ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
147  }
148 }
149 
150 
151 void SETTINGS_MANAGER::FlushAndRelease( JSON_SETTINGS* aSettings, bool aSave )
152 {
153  auto it = std::find_if( m_settings.begin(), m_settings.end(),
154  [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
155  {
156  return aPtr.get() == aSettings;
157  } );
158 
159  if( it != m_settings.end() )
160  {
161  wxLogTrace( traceSettings, "Flush and release %s", ( *it )->GetFullFilename() );
162 
163  if( aSave )
164  ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
165 
166  m_settings.erase( it );
167  }
168 }
169 
170 
172 {
173  if( m_color_settings.count( aName ) )
174  return m_color_settings.at( aName );
175 
176  COLOR_SETTINGS* ret = nullptr;
177 
178  if( !aName.empty() )
179  ret = loadColorSettingsByName( aName );
180 
181  // This had better work
182  if( !ret )
183  ret = m_color_settings.at( "user" );
184 
185  return ret;
186 }
187 
188 
190 {
191  wxLogTrace( traceSettings, "Attempting to load color theme %s", aName );
192 
193  wxFileName fn( GetColorSettingsPath(), aName, "json" );
194 
195  if( !fn.IsOk() || !fn.Exists() )
196  {
197  wxLogTrace( traceSettings, "Theme file %s.json not found, falling back to user", aName );
198  return nullptr;
199  }
200 
201  auto cs = static_cast<COLOR_SETTINGS*>(
202  RegisterSettings( new COLOR_SETTINGS( aName.ToStdString() ) ) );
203 
204  if( cs->GetFilename() != aName.ToStdString() )
205  {
206  wxLogTrace( traceSettings, "Warning: stored filename is actually %s, ", cs->GetFilename() );
207  }
208 
209  m_color_settings[aName] = cs;
210 
211  return cs;
212 }
213 
214 
215 class COLOR_SETTINGS_LOADER : public wxDirTraverser
216 {
217 private:
218  std::function<void( const wxString& )> m_action;
219 
220 public:
221  explicit COLOR_SETTINGS_LOADER( std::function<void( const wxString& )> aAction )
222  : m_action( std::move( aAction ) )
223  {
224  }
225 
226  wxDirTraverseResult OnFile( const wxString& aFilePath ) override
227  {
228  wxFileName file( aFilePath );
229 
230  if( file.GetExt() != "json" )
231  return wxDIR_CONTINUE;
232 
233  if( file.GetName() == "user" )
234  return wxDIR_CONTINUE;
235 
236  m_action( file.GetName() );
237 
238  return wxDIR_CONTINUE;
239  }
240 
241  wxDirTraverseResult OnDir( const wxString& dirPath ) override
242  {
243  return wxDIR_IGNORE;
244  }
245 };
246 
247 
248 void SETTINGS_MANAGER::registerColorSettings( const wxString& aFilename )
249 {
250  if( m_color_settings.count( aFilename ) )
251  return;
252 
253  m_color_settings[aFilename] = static_cast<COLOR_SETTINGS*>(
254  RegisterSettings( new COLOR_SETTINGS( aFilename ) ) );
255 }
256 
257 
259 {
260  wxString filename = aFilename;
261 
262  if( filename.EndsWith( wxT( ".json" ) ) )
263  filename = filename.BeforeLast( '.' );
264 
265  registerColorSettings( filename );
266  return m_color_settings[filename];
267 }
268 
269 
271 {
272  // Create the default color settings
273  registerColorSettings( "user" );
274 
275  // Search for and load any other settings
276  COLOR_SETTINGS_LOADER loader( [&]( const wxString& aFilename )
277  {
278  registerColorSettings( aFilename );
279  } );
280 
281  wxDir colors_dir( GetColorSettingsPath() );
282 
283  if( colors_dir.IsOpened() )
284  colors_dir.Traverse( loader );
285 }
286 
287 
289 {
290  m_color_settings.clear();
292 }
293 
294 
295 void SETTINGS_MANAGER::SaveColorSettings( COLOR_SETTINGS* aSettings, const std::string& aNamespace )
296 {
297  // The passed settings should already be managed
298  wxASSERT( std::find_if( m_color_settings.begin(), m_color_settings.end(),
299  [aSettings] ( const std::pair<wxString, COLOR_SETTINGS*>& el )
300  {
301  return el.second->GetFilename() == aSettings->GetFilename();
302  }
303  ) != m_color_settings.end() );
304 
305  nlohmann::json::json_pointer ptr = JSON_SETTINGS::PointerFromString( aNamespace );
306 
307  if( !aSettings->Store() )
308  {
309  wxLogTrace( traceSettings, "Color scheme %s not modified; skipping save",
310  aSettings->GetFilename(), aNamespace );
311  return;
312  }
313 
314  wxASSERT( aSettings->contains( ptr ) );
315 
316  wxLogTrace( traceSettings, "Saving color scheme %s, preserving %s", aSettings->GetFilename(),
317  aNamespace );
318 
319  nlohmann::json backup = aSettings->at( ptr );
320  wxString path = GetColorSettingsPath();
321 
322  aSettings->LoadFromFile( path );
323 
324  ( *aSettings )[ptr].update( backup );
325  aSettings->Load();
326 
327  aSettings->SaveToFile( path, true );
328 }
329 
330 
332 {
333  wxASSERT( aSettings );
334 
335  switch( aSettings->GetLocation() )
336  {
337  case SETTINGS_LOC::USER:
338  return GetUserSettingsPath();
339 
341  return Prj().GetProjectPath();
342 
344  return GetColorSettingsPath();
345 
346  case SETTINGS_LOC::NONE:
347  return "";
348 
349  default:
350  wxASSERT_MSG( false, "Unknown settings location!" );
351  }
352 
353  return "";
354 }
355 
356 
357 class MIGRATION_TRAVERSER : public wxDirTraverser
358 {
359 private:
360  wxString m_src;
361  wxString m_dest;
362  wxString m_errors;
363 
364 public:
365  MIGRATION_TRAVERSER( const wxString& aSrcDir, const wxString& aDestDir ) :
366  m_src( aSrcDir ), m_dest( aDestDir )
367  {
368  }
369 
370  wxString GetErrors() { return m_errors; }
371 
372  virtual wxDirTraverseResult OnFile( const wxString& aSrcFilePath ) override
373  {
374  wxFileName file( aSrcFilePath );
375  wxString path = file.GetPath();
376 
377  path.Replace( m_src, m_dest, false );
378  file.SetPath( path );
379 
380  wxLogTrace( traceSettings, "Copying %s to %s", aSrcFilePath, file.GetFullPath() );
381 
382  // For now, just copy everything
383  CopyFile( aSrcFilePath, file.GetFullPath(), m_errors );
384 
385  return wxDIR_CONTINUE;
386  }
387 
388  virtual wxDirTraverseResult OnDir( const wxString& dirPath ) override
389  {
390  wxFileName dir( dirPath );
391 
392  // Whitelist of directories to migrate
393  if( dir.GetName() == "colors" ||
394  dir.GetName() == "3d" )
395  {
396 
397  wxString path = dir.GetPath();
398 
399  path.Replace( m_src, m_dest, false );
400  dir.SetPath( path );
401 
402  wxMkdir( dir.GetFullPath() );
403 
404  return wxDIR_CONTINUE;
405  }
406  else
407  {
408  return wxDIR_IGNORE;
409  }
410  }
411 };
412 
413 
415 {
416  wxFileName path( GetUserSettingsPath(), "" );
417  wxLogTrace( traceSettings, "Using settings path %s", path.GetFullPath() );
418 
419  if( path.DirExists() )
420  {
421  wxFileName common = path;
422  common.SetName( "kicad_common" );
423  common.SetExt( "json" );
424 
425  if( common.Exists() )
426  {
427  wxLogTrace( traceSettings, "Path exists and has a kicad_common, continuing!" );
428  return true;
429  }
430  }
431 
432  if( m_headless )
433  {
434  wxLogTrace( traceSettings, "Manual settings migration required but running headless!" );
435  return false;
436  }
437 
438  // Now we have an empty path, let's figure out what to put in it
439  DIALOG_MIGRATE_SETTINGS dlg( this );
440 
441  if( dlg.ShowModal() != wxID_OK )
442  {
443  wxLogTrace( traceSettings, "Migration dialog canceled; exiting" );
444  return false;
445  }
446 
447  if( !path.DirExists() )
448  {
449  wxLogTrace( traceSettings, "Path didn't exist; creating it" );
450  path.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
451  }
452 
453  if( m_migration_source.IsEmpty() )
454  {
455  wxLogTrace( traceSettings, "No migration source given; starting with defaults" );
456  return true;
457  }
458 
459  MIGRATION_TRAVERSER traverser( m_migration_source, path.GetFullPath() );
460  wxDir source_dir( m_migration_source );
461 
462  source_dir.Traverse( traverser );
463 
464  if( !traverser.GetErrors().empty() )
465  DisplayErrorMessage( nullptr, traverser.GetErrors() );
466 
467  return true;
468 }
469 
470 
471 bool SETTINGS_MANAGER::GetPreviousVersionPaths( std::vector<wxString>* aPaths )
472 {
473  wxASSERT( aPaths );
474 
475  aPaths->clear();
476 
477  wxDir dir;
478  std::vector<wxFileName> base_paths;
479 
480  base_paths.emplace_back( wxFileName( calculateUserSettingsPath( false ), "" ) );
481 
482  // If the env override is set, also check the default paths
483  if( wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), nullptr ) )
484  base_paths.emplace_back( wxFileName( calculateUserSettingsPath( false, false ), "" ) );
485 
486  wxString subdir;
487  std::string mine = GetSettingsVersion();
488 
489  auto check_dir = [&] ( const wxString& aSubDir )
490  {
491  // Only older versions are valid for migration
492  if( compareVersions( aSubDir.ToStdString(), mine ) <= 0 )
493  {
494  wxString sub_path = dir.GetNameWithSep() + aSubDir;
495 
496  if( IsSettingsPathValid( sub_path ) )
497  {
498  aPaths->push_back( sub_path );
499  wxLogTrace( traceSettings, "GetPreviousVersionName: %s is valid", sub_path );
500  }
501  }
502  };
503 
504  for( auto base_path : base_paths )
505  {
506  if( !dir.Open( base_path.GetFullPath() ) )
507  {
508  wxLogTrace( traceSettings, "GetPreviousVersionName: could not open base path %s",
509  base_path.GetFullPath() );
510  continue;
511  }
512 
513  wxLogTrace( traceSettings, "GetPreviousVersionName: checking base path %s",
514  base_path.GetFullPath() );
515 
516  if( dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS ) )
517  {
518  if( subdir != mine )
519  check_dir( subdir );
520 
521  while( dir.GetNext( &subdir ) )
522  {
523  if( subdir != mine )
524  check_dir( subdir );
525  }
526  }
527 
528  // If we didn't find one yet, check for legacy settings without a version directory
529  if( IsSettingsPathValid( dir.GetNameWithSep() ) )
530  {
531  wxLogTrace( traceSettings,
532  "GetPreviousVersionName: root path %s is valid", dir.GetName() );
533  aPaths->push_back( dir.GetName() );
534  }
535  }
536 
537  return aPaths->size() > 0;
538 }
539 
540 
541 bool SETTINGS_MANAGER::IsSettingsPathValid( const wxString& aPath )
542 {
543  wxFileName test( aPath, "kicad_common" );
544  return test.Exists();
545 }
546 
547 
549 {
550  wxFileName path;
551 
552  path.AssignDir( GetUserSettingsPath() );
553  path.AppendDir( "colors" );
554 
555  return path.GetPath();
556 }
557 
558 
560 {
561  static wxString user_settings_path;
562 
563  if( user_settings_path.empty() )
564  user_settings_path = calculateUserSettingsPath();
565 
566  return user_settings_path;
567 }
568 
569 
570 wxString SETTINGS_MANAGER::calculateUserSettingsPath( bool aIncludeVer, bool aUseEnv )
571 {
572  wxFileName cfgpath;
573 
574  // http://docs.wxwidgets.org/3.0/classwx_standard_paths.html#a7c7cf595d94d29147360d031647476b0
575  cfgpath.AssignDir( wxStandardPaths::Get().GetUserConfigDir() );
576 
577  // GetUserConfigDir() does not default to ~/.config which is the current standard
578  // configuration file location on Linux. This has been fixed in later versions of wxWidgets.
579 #if !defined( __WXMSW__ ) && !defined( __WXMAC__ )
580  wxArrayString dirs = cfgpath.GetDirs();
581 
582  if( dirs.Last() != ".config" )
583  cfgpath.AppendDir( ".config" );
584 #endif
585 
586  wxString envstr;
587 
588  // This shouldn't cause any issues on Windows or MacOS.
589  if( wxGetEnv( wxT( "XDG_CONFIG_HOME" ), &envstr ) && !envstr.IsEmpty() )
590  {
591  // Override the assignment above with XDG_CONFIG_HOME
592  cfgpath.AssignDir( envstr );
593  }
594 
595  cfgpath.AppendDir( TO_STR( KICAD_CONFIG_DIR ) );
596 
597  // Use KICAD_CONFIG_HOME to allow the user to force a specific configuration path.
598  if( aUseEnv && wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), &envstr ) && !envstr.IsEmpty() )
599  {
600  // Override the assignment above with KICAD_CONFIG_HOME
601  cfgpath.AssignDir( envstr );
602  }
603 
604  if( aIncludeVer )
605  cfgpath.AppendDir( GetSettingsVersion() );
606 
607  return cfgpath.GetPath();
608 }
609 
610 
612 {
613  // CMake computes the major.minor string for us.
614  return GetMajorMinorVersion().ToStdString();
615 }
616 
617 
618 int SETTINGS_MANAGER::compareVersions( const std::string& aFirst, const std::string& aSecond )
619 {
620  int a_maj = 0;
621  int a_min = 0;
622  int b_maj = 0;
623  int b_min = 0;
624 
625  if( !extractVersion( aFirst, &a_maj, &a_min ) || !extractVersion( aSecond, &b_maj, &b_min ) )
626  {
627  wxLogTrace( traceSettings, "compareSettingsVersions: bad input (%s, %s)", aFirst, aSecond );
628  return -1;
629  }
630 
631  if( a_maj < b_maj )
632  {
633  return -1;
634  }
635  else if( a_maj > b_maj )
636  {
637  return 1;
638  }
639  else
640  {
641  if( a_min < b_min )
642  {
643  return -1;
644  }
645  else if( a_min > b_min )
646  {
647  return 1;
648  }
649  else
650  {
651  return 0;
652  }
653  }
654 }
655 
656 
657 bool SETTINGS_MANAGER::extractVersion( const std::string& aVersionString, int* aMajor, int* aMinor )
658 {
659  std::regex re_version( "(\\d+)\\.(\\d+)" );
660  std::smatch match;
661 
662  if( std::regex_match( aVersionString, match, re_version ) )
663  {
664  try
665  {
666  *aMajor = std::stoi( match[1].str() );
667  *aMinor = std::stoi( match[2].str() );
668  }
669  catch( ... )
670  {
671  return false;
672  }
673 
674  return true;
675  }
676 
677  return false;
678 }
679 
680 
681 bool SETTINGS_MANAGER::LoadProject( const wxString& aFullPath, bool aSetActive )
682 {
683  // Normalize path to new format even if migrating from a legacy file
684  wxFileName path( aFullPath );
685 
686  if( path.GetExt() == LegacyProjectFileExtension )
687  path.SetExt( ProjectFileExtension );
688 
689  wxString fullPath = path.GetFullPath();
690 
691  // If already loaded, we are all set. This might be called more than once over a project's
692  // lifetime in case the project is first loaded by the KiCad manager and then eeschema or
693  // pcbnew try to load it again when they are launched.
694  if( m_projects.count( fullPath ) )
695  return true;
696 
697  // No MDI yet
698  if( aSetActive && !m_projects.empty() && !UnloadProject( m_projects.begin()->second.get() ) )
699  return false;
700 
701  wxLogTrace( traceSettings, "Load project %s", fullPath );
702 
703  std::unique_ptr<PROJECT> project = std::make_unique<PROJECT>();
704  project->setProjectFullName( fullPath );
705 
706  bool success = loadProjectFile( *project );
707 
708  m_projects[fullPath].reset( project.release() );
709 
710  wxString fn( path.GetName() );
711 
712  PROJECT_LOCAL_SETTINGS* settings = static_cast<PROJECT_LOCAL_SETTINGS*>(
714 
715  m_projects[fullPath]->setLocalSettings( settings );
716  settings->SetProject( m_projects[fullPath].get() );
717 
718  return success;
719 }
720 
721 
722 bool SETTINGS_MANAGER::UnloadProject( PROJECT* aProject, bool aSave )
723 {
724  if( !aProject || !m_projects.count( aProject->GetProjectFullName() ) )
725  return false;
726 
727  if( !unloadProjectFile( aProject, aSave ) )
728  return false;
729 
730  wxLogTrace( traceSettings, "Unload project %s", aProject->GetProjectFullName() );
731 
732  m_projects.erase( aProject->GetProjectFullName() );
733 
734  return true;
735 }
736 
737 
739 {
740  // No MDI yet: First project in the list is the active project
741  return *m_projects.begin()->second;
742 }
743 
744 
745 PROJECT* SETTINGS_MANAGER::GetProject( const wxString& aFullPath ) const
746 {
747  if( m_projects.count( aFullPath ) )
748  return m_projects.at( aFullPath ).get();
749 
750  return nullptr;
751 }
752 
753 
754 bool SETTINGS_MANAGER::SaveProject( const wxString& aFullPath )
755 {
756  wxString path = aFullPath;
757 
758  if( path.empty() )
759  path = Prj().GetProjectFullName();
760 
761  if( !m_project_files.count( path ) )
762  return false;
763 
764  PROJECT_FILE* project = m_project_files.at( path );
765  wxString projectPath = GetPathForSettingsFile( project );
766 
767  project->SaveToFile( projectPath );
768  Prj().GetLocalSettings().SaveToFile( projectPath );
769 
770  return true;
771 }
772 
773 
775 {
776  wxFileName fullFn( aProject.GetProjectFullName() );
777  wxString fn( fullFn.GetName() );
778 
779  PROJECT_FILE* file =
780  static_cast<PROJECT_FILE*>( RegisterSettings( new PROJECT_FILE( fn ), false ) );
781 
782  m_project_files[aProject.GetProjectFullName()] = file;
783 
784  aProject.setProjectFile( file );
785  file->SetProject( &aProject );
786 
787  wxString path( fullFn.GetPath() );
788 
789  return file->LoadFromFile( path );
790 }
791 
792 
793 bool SETTINGS_MANAGER::unloadProjectFile( PROJECT* aProject, bool aSave )
794 {
795  if( !aProject )
796  return false;
797 
798  wxString name = aProject->GetProjectFullName();
799 
800  if( !m_project_files.count( name ) )
801  return false;
802 
804 
805  auto it = std::find_if( m_settings.begin(), m_settings.end(),
806  [&file]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
807  {
808  return aPtr.get() == file;
809  } );
810 
811  if( it != m_settings.end() )
812  {
813  wxString projectPath = GetPathForSettingsFile( it->get() );
814 
815  FlushAndRelease( &aProject->GetLocalSettings(), aSave );
816 
817  if( aSave )
818  ( *it )->SaveToFile( projectPath );
819 
820  m_settings.erase( it );
821  }
822 
823  m_project_files.erase( name );
824 
825  return true;
826 }
827 
828 
830 {
832 }
833 
834 
835 wxString SETTINGS_MANAGER::backupDateTimeFormat = wxT( "%Y-%m-%d_%H%M%S" );
836 
837 
839 {
840  wxDateTime timestamp = wxDateTime::Now();
841 
842  wxString fileName = wxString::Format( wxT( "%s-%s" ), Prj().GetProjectName(),
843  timestamp.Format( backupDateTimeFormat ) );
844 
845  wxFileName target;
846  target.SetPath( GetProjectBackupsPath() );
847  target.SetName( fileName );
848  target.SetExt( ArchiveFileExtension );
849 
850  wxDir dir( target.GetPath() );
851 
852  if( !target.DirExists() && !wxMkdir( target.GetPath() ) )
853  {
854  wxLogTrace( traceSettings, "Could not create project backup path %s", target.GetPath() );
855  return false;
856  }
857 
858  if( !target.IsDirWritable() )
859  {
860  wxLogTrace( traceSettings, "Backup directory %s is not writeable", target.GetPath() );
861  return false;
862  }
863 
864  wxLogTrace( traceSettings, "Backing up project to %s", target.GetPath() );
865 
866  PROJECT_ARCHIVER archiver;
867 
868  return archiver.Archive( Prj().GetProjectPath(), target.GetFullPath(), aReporter );
869 }
870 
871 
872 class VECTOR_INSERT_TRAVERSER : public wxDirTraverser
873 {
874 public:
875  VECTOR_INSERT_TRAVERSER( std::vector<wxString>& aVec,
876  std::function<bool( const wxString& )> aCond ) :
877  m_files( aVec ),
878  m_condition( aCond )
879  {
880  }
881 
882  wxDirTraverseResult OnFile( const wxString& aFile ) override
883  {
884  if( m_condition( aFile ) )
885  m_files.emplace_back( aFile );
886 
887  return wxDIR_CONTINUE;
888  }
889 
890  wxDirTraverseResult OnDir( const wxString& aDirName ) override
891  {
892  return wxDIR_CONTINUE;
893  }
894 
895 private:
896  std::vector<wxString>& m_files;
897 
898  std::function<bool( const wxString& )> m_condition;
899 };
900 
901 
903 {
905 
906  if( !settings.enabled )
907  return true;
908 
909  wxString prefix = Prj().GetProjectName() + '-';
910 
911  auto modTime =
912  [&prefix]( const wxString& aFile )
913  {
914  wxDateTime dt;
915  wxString fn( wxFileName( aFile ).GetName() );
916  fn.Replace( prefix, "" );
917  dt.ParseFormat( fn, backupDateTimeFormat );
918  return dt;
919  };
920 
921  // Project not saved yet
922  if( Prj().GetProjectPath().empty() )
923  return true;
924 
925  wxString backupPath = GetProjectBackupsPath();
926 
927  if( !wxDirExists( backupPath ) )
928  {
929  wxLogTrace( traceSettings, "Backup path %s doesn't exist, creating it", backupPath );
930 
931  if( !wxMkdir( backupPath ) )
932  {
933  wxLogTrace( traceSettings, "Could not create backups path! Skipping backup" );
934  return false;
935  }
936  }
937 
938  wxDir dir( backupPath );
939 
940  if( !dir.IsOpened() )
941  {
942  wxLogTrace( traceSettings, "Could not open project backups path %s", dir.GetName() );
943  return false;
944  }
945 
946  std::vector<wxString> files;
947 
948  VECTOR_INSERT_TRAVERSER traverser( files,
949  [&modTime]( const wxString& aFile )
950  {
951  return modTime( aFile ).IsValid();
952  } );
953 
954  dir.Traverse( traverser, wxT( "*.zip" ) );
955 
956  // Sort newest-first
957  std::sort( files.begin(), files.end(),
958  [&]( const wxString& aFirst, const wxString& aSecond ) -> bool
959  {
960  wxDateTime first = modTime( aFirst );
961  wxDateTime second = modTime( aSecond );
962 
963  return first.GetTicks() > second.GetTicks();
964  } );
965 
966  // Do we even need to back up?
967  if( !files.empty() )
968  {
969  wxDateTime lastTime = modTime( files[0] );
970 
971  if( lastTime.IsValid() )
972  {
973  wxTimeSpan delta = wxDateTime::Now() - modTime( files[0] );
974 
975  if( delta.IsShorterThan( wxTimeSpan::Seconds( settings.min_interval ) ) )
976  return true;
977  }
978  }
979 
980  // Now that we know a backup is needed, apply the retention policy
981 
982  // Step 1: if we're over the total file limit, remove the oldest
983  if( !files.empty() && settings.limit_total_files > 0 )
984  {
985  while( files.size() > static_cast<size_t>( settings.limit_total_files ) )
986  {
987  wxRemoveFile( files.back() );
988  files.pop_back();
989  }
990  }
991 
992  // Step 2: Stay under the total size limit
993  if( settings.limit_total_size > 0 )
994  {
995  wxULongLong totalSize = 0;
996 
997  for( const wxString& file : files )
998  totalSize += wxFileName::GetSize( file );
999 
1000  while( !files.empty() && totalSize > static_cast<wxULongLong>( settings.limit_total_size ) )
1001  {
1002  totalSize -= wxFileName::GetSize( files.back() );
1003  wxRemoveFile( files.back() );
1004  files.pop_back();
1005  }
1006  }
1007 
1008  // Step 3: Stay under the daily limit
1009  if( settings.limit_daily_files > 0 && files.size() > 1 )
1010  {
1011  wxDateTime day = modTime( files[0] );
1012  int num = 1;
1013 
1014  wxASSERT( day.IsValid() );
1015 
1016  std::vector<wxString> filesToDelete;
1017 
1018  for( size_t i = 1; i < files.size(); i++ )
1019  {
1020  wxDateTime dt = modTime( files[i] );
1021 
1022  if( dt.IsSameDate( day ) )
1023  {
1024  num++;
1025 
1026  if( num > settings.limit_daily_files )
1027  filesToDelete.emplace_back( files[i] );
1028  }
1029  else
1030  {
1031  day = dt;
1032  num = 1;
1033  }
1034  }
1035 
1036  for( const wxString& file : filesToDelete )
1037  wxRemoveFile( file );
1038  }
1039 
1040  return BackupProject( aReporter );
1041 }
#define TO_STR(x)
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.
PROJECT holds project specific data.
Definition: project.h:61
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:252
This file is part of the common library TODO brief description.
This file is part of the common library.
static bool IsSettingsPathValid(const wxString &aPath)
Checks if a given path is probably a valid KiCad configuration directory.
wxDirTraverseResult OnFile(const wxString &aFilePath) override
VTBL_ENTRY void setProjectFile(PROJECT_FILE *aFile)
Sets the backing store file for this project Should only be called by SETTINGS_MANGER on load.
Definition: project.h:313
const std::string ProjectFileExtension
VTBL_ENTRY PROJECT_LOCAL_SETTINGS & GetLocalSettings() const
Definition: project.h:143
wxString GetFilename() const
Definition: json_settings.h:56
bool Archive(const wxString &aSrcDir, const wxString &aDestFile, REPORTER &aReporter, bool aVerbose=true)
Creates an archive of the project.
bool SaveToFile(const wxString &aDirectory="", bool aForce=false) override
bool enabled
Automatically back up the project when files are saved.
AUTO_BACKUP m_Backup
void CopyFile(const wxString &aSrcPath, const wxString &aDestPath, wxString &aErrors)
Function CopyFile.
Definition: gestfich.cpp:363
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
#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:56
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 ...
REPORTER is 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.
VTBL_ENTRY const wxString GetProjectPath() const
Function GetProjectPath returns the full path of the project.
Definition: project.cpp:123
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.
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.
std::map< wxString, std::unique_ptr< PROJECT > > m_projects
Loaded projects, mapped according to project full name.
virtual bool SaveToFile(const wxString &aDirectory="", bool aForce=false)
MIGRATION_TRAVERSER(const wxString &aSrcDir, const wxString &aDestDir)
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:60
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.
Definition of file extensions used in Kicad.
bool m_headless
True if running outside a UI context.
wxDirTraverseResult OnFile(const wxString &aFile) override
void registerColorSettings(const wxString &aFilename)
VTBL_ENTRY const wxString GetProjectFullName() const
Function GetProjectFullName returns 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)
SETTINGS_MANAGER(bool aHeadless=false)
virtual wxDirTraverseResult OnFile(const wxString &aSrcFilePath) override
static wxString GetColorSettingsPath()
Returns the path where color scheme files are stored (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.
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:60
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, CPTREE &aTree)
Function Format outputs a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:205
virtual wxDirTraverseResult OnDir(const wxString &dirPath) override
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.
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...
VTBL_ENTRY const wxString GetProjectName() const
Function GetProjectName returns 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
COMMON_SETTINGS * m_common_settings
const char * traceSettings
Flag to enable settings tracing.
static nlohmann::json::json_pointer PointerFromString(std::string aPath)
Builds a JSON pointer based on a given string.
std::function< void(const wxString &)> m_action
void FlushAndRelease(JSON_SETTINGS *aSettings, bool aSave=true)
If the given settings object is registered, save it to disk and unregister it.
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