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