KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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 The 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>
32#include <gestfich.h>
34#include <kiplatform/io.h>
35#include <kiway.h>
36#include <lockfile.h>
37#include <macros.h>
38#include <pgm_base.h>
39#include <paths.h>
40#include <picosha2.h>
41
42#include <algorithm>
43#include <project.h>
53#include <env_vars.h>
55
56
58 m_kiway( nullptr ),
59 m_common_settings( nullptr ),
61{
62 wxFileName path( PATHS::GetUserSettingsPath(), wxS( "" ) );
63 wxLogTrace( traceSettings, wxT( "Using settings path %s" ), path.GetFullPath() );
64
65 if( !path.DirExists() )
66 {
67 wxLogTrace( traceSettings, wxT( "Path didn't exist; creating it" ) );
68 path.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
69 }
70
72 {
73 // This will be picked up by the first-run wizard later in application start,
74 // but we allow it for now because many things rely on being able to access the
75 // settings manager. For now, default settings in memory will be used.
76 wxLogTrace( traceSettings, wxT( "Note: no valid settings directory on disk" ) );
77 }
78
79 m_ok = true;
80
81 // create the common settings shared by all applications. Not loaded immediately
83
84 // Create the built-in color settings
85 // Here to allow the Python API to access the built-in colors
87}
88
89
91{
92 for( std::unique_ptr<PROJECT>& project : m_projects_list )
93 project.reset();
94
95 m_projects.clear();
96
97 for( std::unique_ptr<JSON_SETTINGS>& settings : m_settings )
98 settings.reset();
99
100 m_settings.clear();
101
102 m_color_settings.clear();
103}
104
105
107{
108 for( std::unique_ptr<JSON_SETTINGS>& settings : m_settings )
109 {
110 if( settings->GetLocation() == SETTINGS_LOC::USER || settings->GetLocation() == SETTINGS_LOC::COLORS )
111 {
112 std::map<std::string, nlohmann::json> fileHistories = settings->GetFileHistories();
113
114 settings->Internals()->clear();
115 settings->Load(); // load from nothing (ie: load defaults)
116
117 for( const auto& [path, history] : fileHistories )
118 settings->Set( path, history );
119
120 settings->SaveToFile( GetPathForSettingsFile( settings.get() ) );
121 }
122 }
123}
124
125
127{
128 for( std::unique_ptr<JSON_SETTINGS>& settings : m_settings )
129 {
130 if( settings->GetLocation() == SETTINGS_LOC::USER )
131 {
132 for( const auto& [path, history] : settings->GetFileHistories() )
133 settings->Set( path, nlohmann::json::array() );
134
135 settings->SaveToFile( GetPathForSettingsFile( settings.get() ) );
136 }
137 }
138}
139
140
142{
143 std::unique_ptr<JSON_SETTINGS> ptr( aSettings );
144
145 ptr->SetManager( this );
146
147 wxLogTrace( traceSettings, wxT( "Registered new settings object <%s>" ),
148 ptr->GetFullFilename() );
149
150 if( aLoadNow )
151 ptr->LoadFromFile( GetPathForSettingsFile( ptr.get() ) );
152
153 m_settings.push_back( std::move( ptr ) );
154 return m_settings.back().get();
155}
156
157
159{
160 // TODO(JE) We should check for dirty settings here and write them if so, because
161 // Load() could be called late in the application lifecycle
162 std::vector<JSON_SETTINGS*> toLoad;
163
164 // Cache a copy of raw pointers; m_settings may be modified during the load loop
165 std::transform( m_settings.begin(), m_settings.end(), std::back_inserter( toLoad ),
166 []( std::unique_ptr<JSON_SETTINGS>& aSettings )
167 {
168 return aSettings.get();
169 } );
170
171 for( JSON_SETTINGS* settings : toLoad )
172 settings->LoadFromFile( GetPathForSettingsFile( settings ) );
173}
174
175
177{
178 auto it = std::find_if( m_settings.begin(), m_settings.end(),
179 [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
180 {
181 return aPtr.get() == aSettings;
182 } );
183
184 if( it != m_settings.end() )
185 ( *it )->LoadFromFile( GetPathForSettingsFile( it->get() ) );
186}
187
188
190{
191 for( auto&& settings : m_settings )
192 {
193 // Never automatically save color settings, caller should use SaveColorSettings
194 if( dynamic_cast<COLOR_SETTINGS*>( settings.get() ) )
195 continue;
196
197 // Never automatically save project file, caller should use SaveProject or UnloadProject
198 // We do want to save the project local settings, though because they are generally view
199 // settings that should persist even if the project is not saved
200 if( dynamic_cast<PROJECT_FILE*>( settings.get() ) )
201 {
202 continue;
203 }
204
205 settings->SaveToFile( GetPathForSettingsFile( settings.get() ) );
206 }
207}
208
209
211{
212 auto it = std::find_if( m_settings.begin(), m_settings.end(),
213 [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
214 {
215 return aPtr.get() == aSettings;
216 } );
217
218 if( it != m_settings.end() )
219 {
220 wxLogTrace( traceSettings, wxT( "Saving %s" ), ( *it )->GetFullFilename() );
221 ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
222 }
223}
224
225
227{
228 auto it = std::find_if( m_settings.begin(), m_settings.end(),
229 [&aSettings]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
230 {
231 return aPtr.get() == aSettings;
232 } );
233
234 if( it != m_settings.end() )
235 {
236 wxLogTrace( traceSettings, wxT( "Flush and release %s" ), ( *it )->GetFullFilename() );
237
238 if( aSave )
239 ( *it )->SaveToFile( GetPathForSettingsFile( it->get() ) );
240
241 JSON_SETTINGS* tmp = it->get(); // We use a temporary to suppress a Clang warning
242 size_t typeHash = typeid( *tmp ).hash_code();
243
244 if( m_app_settings_cache.count( typeHash ) )
245 m_app_settings_cache.erase( typeHash );
246
247 m_settings.erase( it );
248 }
249}
250
251
253{
254 // Find settings the fast way
255 if( m_color_settings.count( aName ) )
256 return m_color_settings.at( aName );
257
258 // Maybe it's the display name (cli is one method of invoke)
259 auto it = std::find_if( m_color_settings.begin(), m_color_settings.end(),
260 [&aName]( const std::pair<wxString, COLOR_SETTINGS*>& p )
261 {
262 return p.second->GetName().Lower() == aName.Lower();
263 } );
264
265 if( it != m_color_settings.end() )
266 {
267 return it->second;
268 }
269
270 // No match? See if we can load it
271 if( !aName.empty() )
272 {
274
275 if( !ret )
276 {
277 ret = registerColorSettings( aName );
280 ret->SetReadOnly( false );
281 }
282
283 return ret;
284 }
285
286 // This had better work
288}
289
290
291std::vector<COLOR_SETTINGS*> SETTINGS_MANAGER::GetColorSettingsList()
292{
293 std::vector<COLOR_SETTINGS*> ret;
294
295 for( const std::pair<const wxString, COLOR_SETTINGS*>& entry : m_color_settings )
296 ret.push_back( entry.second );
297
298 std::sort( ret.begin(), ret.end(), []( COLOR_SETTINGS* a, COLOR_SETTINGS* b )
299 { return a->GetName() < b->GetName(); } );
300
301 return ret;
302}
303
304
306{
307 wxLogTrace( traceSettings, wxT( "Attempting to load color theme %s" ), aName );
308
309 wxFileName fn( GetColorSettingsPath(), aName, wxS( "json" ) );
310
311 if( !fn.IsOk() || !fn.Exists() )
312 {
313 wxLogTrace( traceSettings, wxT( "Theme file %s.json not found, falling back to user" ),
314 aName );
315 return nullptr;
316 }
317
318 COLOR_SETTINGS* settings = RegisterSettings( new COLOR_SETTINGS( aName ) );
319
320 if( settings->GetFilename() != aName.ToStdString() )
321 {
322 wxLogTrace( traceSettings, wxT( "Warning: stored filename is actually %s, " ),
323 settings->GetFilename() );
324 }
325
326 m_color_settings[aName] = settings;
327
328 return settings;
329}
330
331
332class JSON_DIR_TRAVERSER : public wxDirTraverser
333{
334private:
335 std::function<void( const wxFileName& )> m_action;
336
337public:
338 explicit JSON_DIR_TRAVERSER( std::function<void( const wxFileName& )> aAction )
339 : m_action( std::move( aAction ) )
340 {
341 }
342
343 wxDirTraverseResult OnFile( const wxString& aFilePath ) override
344 {
345 wxFileName file( aFilePath );
346
347 if( file.GetExt() == wxS( "json" ) )
348 m_action( file );
349
350 return wxDIR_CONTINUE;
351 }
352
353 wxDirTraverseResult OnDir( const wxString& dirPath ) override
354 {
355 return wxDIR_CONTINUE;
356 }
357};
358
359
360COLOR_SETTINGS* SETTINGS_MANAGER::registerColorSettings( const wxString& aName, bool aAbsolutePath )
361{
362 if( !m_color_settings.count( aName ) )
363 {
364 COLOR_SETTINGS* colorSettings = RegisterSettings( new COLOR_SETTINGS( aName,
365 aAbsolutePath ) );
366 m_color_settings[aName] = colorSettings;
367 }
368
369 return m_color_settings.at( aName );
370}
371
372
374{
375 if( aName.EndsWith( wxT( ".json" ) ) )
376 return registerColorSettings( aName.BeforeLast( '.' ) );
377 else
378 return registerColorSettings( aName );
379}
380
381
383{
384 if( !m_color_settings.count( "user" ) )
385 {
386 COLOR_SETTINGS* settings = registerColorSettings( wxT( "user" ) );
387 settings->SetName( wxT( "User" ) );
388 Save( settings );
389 }
390
391 return m_color_settings.at( "user" );
392}
393
394
396{
398 m_color_settings[settings->GetFilename()] = RegisterSettings( settings, false );
399}
400
401
403{
404 // Create the built-in color settings
406
407 wxFileName third_party_path;
408 const ENV_VAR_MAP& env = Pgm().GetLocalEnvVariables();
409 auto it = env.find( ENV_VAR::GetVersionedEnvVarName( wxS( "3RD_PARTY" ) ) );
410
411 if( it != env.end() && !it->second.GetValue().IsEmpty() )
412 third_party_path.SetPath( it->second.GetValue() );
413 else
414 third_party_path.SetPath( PATHS::GetDefault3rdPartyPath() );
415
416 third_party_path.AppendDir( wxS( "colors" ) );
417
418 // PCM-managed themes
419 wxDir third_party_colors_dir( third_party_path.GetFullPath() );
420
421 // System-installed themes
422 wxDir system_colors_dir( PATHS::GetStockDataPath( false ) + "/colors" );
423
424 // User-created themes
425 wxDir colors_dir( GetColorSettingsPath() );
426
427 // Search for and load any other settings
428 JSON_DIR_TRAVERSER loader( [&]( const wxFileName& aFilename )
429 {
430 registerColorSettings( aFilename.GetName() );
431 } );
432
433 JSON_DIR_TRAVERSER readOnlyLoader(
434 [&]( const wxFileName& aFilename )
435 {
436 COLOR_SETTINGS* settings = registerColorSettings( aFilename.GetFullPath(), true );
437 settings->SetReadOnly( true );
438 } );
439
440 if( system_colors_dir.IsOpened() )
441 system_colors_dir.Traverse( readOnlyLoader );
442
443 if( third_party_colors_dir.IsOpened() )
444 third_party_colors_dir.Traverse( readOnlyLoader );
445
446 if( colors_dir.IsOpened() )
447 colors_dir.Traverse( loader );
448}
449
450
456
457
458void SETTINGS_MANAGER::SaveColorSettings( COLOR_SETTINGS* aSettings, const std::string& aNamespace )
459{
460 // The passed settings should already be managed
461 wxASSERT( std::find_if( m_color_settings.begin(), m_color_settings.end(),
462 [aSettings] ( const std::pair<wxString, COLOR_SETTINGS*>& el )
463 {
464 return el.second->GetFilename() == aSettings->GetFilename();
465 }
466 ) != m_color_settings.end() );
467
468 if( aSettings->IsReadOnly() )
469 return;
470
471 if( !aSettings->Store() )
472 {
473 wxLogTrace( traceSettings, wxT( "Color scheme %s not modified; skipping save" ),
474 aNamespace );
475 return;
476 }
477
478 wxASSERT( aSettings->Contains( aNamespace ) );
479
480 wxLogTrace( traceSettings, wxT( "Saving color scheme %s, preserving %s" ),
481 aSettings->GetFilename(),
482 aNamespace );
483
484 std::optional<nlohmann::json> backup = aSettings->GetJson( aNamespace );
485 wxString path = GetColorSettingsPath();
486
487 aSettings->LoadFromFile( path );
488
489 if( backup )
490 ( *aSettings->Internals() )[aNamespace].update( *backup );
491
492 aSettings->Load();
493
494 aSettings->SaveToFile( path, true );
495}
496
497
499{
500 wxASSERT( aSettings );
501
502 switch( aSettings->GetLocation() )
503 {
506
508 // TODO: MDI support
509 return Prj().GetProjectPath();
510
512 return GetColorSettingsPath();
513
515 return GetToolbarSettingsPath();
516
518 return "";
519
520 default:
521 wxASSERT_MSG( false, wxT( "Unknown settings location!" ) );
522 }
523
524 return "";
525}
526
527
528class MIGRATION_TRAVERSER : public wxDirTraverser
529{
530private:
531 wxString m_src;
532 wxString m_dest;
533 wxString m_errors;
535
536public:
537 MIGRATION_TRAVERSER( const wxString& aSrcDir, const wxString& aDestDir, bool aMigrateTables ) :
538 m_src( aSrcDir ),
539 m_dest( aDestDir ),
540 m_migrateTables( aMigrateTables )
541 {
542 }
543
544 wxString GetErrors() { return m_errors; }
545
546 wxDirTraverseResult OnFile( const wxString& aSrcFilePath ) override
547 {
548 wxFileName file( aSrcFilePath );
549
550 if( !m_migrateTables && ( file.GetName() == FILEEXT::SymbolLibraryTableFileName ||
551 file.GetName() == FILEEXT::FootprintLibraryTableFileName ) )
552 {
553 return wxDIR_CONTINUE;
554 }
555
556 // Skip migrating PCM installed packages as packages themselves are not moved
557 if( file.GetFullName() == wxT( "installed_packages.json" ) )
558 return wxDIR_CONTINUE;
559
560 // Don't migrate hotkeys config files; we don't have a reasonable migration handler for them
561 // and so there is no way to resolve conflicts at the moment
562 if( file.GetExt() == wxT( "hotkeys" ) )
563 return wxDIR_CONTINUE;
564
565 wxString path = file.GetPath();
566
567 path.Replace( m_src, m_dest, false );
568 file.SetPath( path );
569
570 wxLogTrace( traceSettings, wxT( "Copying %s to %s" ), aSrcFilePath, file.GetFullPath() );
571
572 // For now, just copy everything
573 KiCopyFile( aSrcFilePath, file.GetFullPath(), m_errors );
574
575 return wxDIR_CONTINUE;
576 }
577
578 wxDirTraverseResult OnDir( const wxString& dirPath ) override
579 {
580 wxFileName dir( dirPath );
581
582 // Whitelist of directories to migrate
583 if( dir.GetName() == wxS( "colors" ) ||
584 dir.GetName() == wxS( "3d" ) )
585 {
586
587 wxString path = dir.GetPath();
588
589 path.Replace( m_src, m_dest, false );
590 dir.SetPath( path );
591
592 if( !wxDirExists( dir.GetPath() ) )
593 wxMkdir( dir.GetPath() );
594
595 return wxDIR_CONTINUE;
596 }
597 else
598 {
599 return wxDIR_IGNORE;
600 }
601 }
602};
603
604
606{
607 wxFileName path( PATHS::GetUserSettingsPath(), wxS( "" ) );
608
609 if( path.DirExists() )
610 {
611 wxFileName common = path;
612 common.SetName( wxS( "kicad_common" ) );
613 common.SetExt( wxS( "json" ) );
614
615 if( common.Exists() )
616 {
617 wxLogTrace( traceSettings, wxT( "Path exists and has a kicad_common, continuing!" ) );
618 return true;
619 }
620 }
621
622 return false;
623}
624
625
626bool SETTINGS_MANAGER::MigrateFromPreviousVersion( const wxString& aSourcePath )
627{
628 wxFileName path( PATHS::GetUserSettingsPath(), wxS( "" ) );
629
630 if( aSourcePath.IsEmpty() )
631 return false;
632
633 wxLogTrace( traceSettings, wxT( "Migrating from path %s" ), aSourcePath );
634
635 // TODO(JE) library tables - move library table migration out of here probably
636 MIGRATION_TRAVERSER traverser( aSourcePath, path.GetFullPath(), m_migrateLibraryTables );
637 wxDir source_dir( aSourcePath );
638
639 source_dir.Traverse( traverser );
640
641 if( !traverser.GetErrors().empty() )
642 DisplayErrorMessage( nullptr, traverser.GetErrors() );
643
644 // Remove any library configuration if we didn't choose to import
646 {
647 COMMON_SETTINGS common;
648 wxString commonPath = GetPathForSettingsFile( &common );
649 common.LoadFromFile( commonPath );
650
651 const std::vector<wxString> libKeys = {
652 wxT( "KICAD6_SYMBOL_DIR" ),
653 wxT( "KICAD6_3DMODEL_DIR" ),
654 wxT( "KICAD6_FOOTPRINT_DIR" ),
655 wxT( "KICAD6_TEMPLATE_DIR" ), // Stores the default library table to be copied
656 wxT( "KICAD7_SYMBOL_DIR" ),
657 wxT( "KICAD7_3DMODEL_DIR" ),
658 wxT( "KICAD7_FOOTPRINT_DIR" ),
659 wxT( "KICAD7_TEMPLATE_DIR" ),
660 wxT( "KICAD8_SYMBOL_DIR" ),
661 wxT( "KICAD8_3DMODEL_DIR" ),
662 wxT( "KICAD8_FOOTPRINT_DIR" ),
663 wxT( "KICAD8_TEMPLATE_DIR" ),
664
665 // Deprecated keys
666 wxT( "KICAD_PTEMPLATES" ),
667 wxT( "KISYS3DMOD" ),
668 wxT( "KISYSMOD" ),
669 wxT( "KICAD_SYMBOL_DIR" ),
670 };
671
672 for( const wxString& key : libKeys )
673 common.m_Env.vars.erase( key );
674
675 common.SaveToFile( commonPath );
676 }
677
678 return true;
679}
680
681
682bool SETTINGS_MANAGER::GetPreviousVersionPaths( std::vector<wxString>* aPaths )
683{
684 wxASSERT( aPaths );
685
686 aPaths->clear();
687
688 wxDir dir;
689 std::vector<wxFileName> base_paths;
690
691 base_paths.emplace_back( wxFileName( PATHS::CalculateUserSettingsPath( false ), wxS( "" ) ) );
692
693 // If the env override is set, also check the default paths
694 if( wxGetEnv( wxT( "KICAD_CONFIG_HOME" ), nullptr ) )
695 base_paths.emplace_back( wxFileName( PATHS::CalculateUserSettingsPath( false, false ),
696 wxS( "" ) ) );
697
698#ifdef __WXGTK__
699 // When running inside FlatPak, KIPLATFORM::ENV::GetUserConfigPath() will return a sandboxed
700 // path. In case the user wants to move from non-FlatPak KiCad to FlatPak KiCad, let's add our
701 // best guess as to the non-FlatPak config path. Unfortunately FlatPak also hides the host
702 // XDG_CONFIG_HOME, so if the user customizes their config path, they will have to browse
703 // for it.
704 {
705 wxFileName wxGtkPath;
706 wxGtkPath.AssignDir( wxS( "~/.config/kicad" ) );
707 wxGtkPath.MakeAbsolute();
708 base_paths.emplace_back( wxGtkPath );
709
710 // We also want to pick up regular flatpak if we are nightly
711 wxGtkPath.AssignDir( wxS( "~/.var/app/org.kicad.KiCad/config/kicad" ) );
712 wxGtkPath.MakeAbsolute();
713 base_paths.emplace_back( wxGtkPath );
714 }
715#endif
716
717 wxString subdir;
718 std::string mine = GetSettingsVersion();
719
720 auto check_dir =
721 [&] ( const wxString& aSubDir )
722 {
723 // Only older versions are valid for migration
724 if( compareVersions( aSubDir.ToStdString(), mine ) <= 0 )
725 {
726 wxString sub_path = dir.GetNameWithSep() + aSubDir;
727
728 if( IsSettingsPathValid( sub_path ) )
729 {
730 aPaths->push_back( sub_path );
731 wxLogTrace( traceSettings, wxT( "GetPreviousVersionName: %s is valid" ), sub_path );
732 }
733 }
734 };
735
736 std::set<wxString> checkedPaths;
737
738 for( const wxFileName& base_path : base_paths )
739 {
740 if( checkedPaths.count( base_path.GetFullPath() ) )
741 continue;
742
743 checkedPaths.insert( base_path.GetFullPath() );
744
745 if( !dir.Open( base_path.GetFullPath() ) )
746 {
747 wxLogTrace( traceSettings, wxT( "GetPreviousVersionName: could not open base path %s" ),
748 base_path.GetFullPath() );
749 continue;
750 }
751
752 wxLogTrace( traceSettings, wxT( "GetPreviousVersionName: checking base path %s" ),
753 base_path.GetFullPath() );
754
755 if( dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS ) )
756 {
757 if( subdir != mine )
758 check_dir( subdir );
759
760 while( dir.GetNext( &subdir ) )
761 {
762 if( subdir != mine )
763 check_dir( subdir );
764 }
765 }
766
767 // If we didn't find one yet, check for legacy settings without a version directory
768 if( IsSettingsPathValid( dir.GetNameWithSep() ) )
769 {
770 wxLogTrace( traceSettings,
771 wxT( "GetPreviousVersionName: root path %s is valid" ), dir.GetName() );
772 aPaths->push_back( dir.GetName() );
773 }
774 }
775
776 std::erase_if( *aPaths,
777 []( const wxString& aPath ) -> bool
778 {
779 wxFileName fulldir = wxFileName::DirName( aPath );
780 const wxArrayString& dirs = fulldir.GetDirs();
781
782 if( dirs.empty() || !fulldir.IsDirReadable() )
783 return true;
784
785 std::string ver = dirs.back().ToStdString();
786
787 if( !extractVersion( ver ) )
788 return true;
789
790 return false;
791 } );
792
793 std::sort( aPaths->begin(), aPaths->end(),
794 [&]( const wxString& a, const wxString& b ) -> bool
795 {
796 wxFileName aPath = wxFileName::DirName( a );
797 wxFileName bPath = wxFileName::DirName( b );
798
799 const wxArrayString& aDirs = aPath.GetDirs();
800 const wxArrayString& bDirs = bPath.GetDirs();
801
802 if( aDirs.empty() )
803 return false;
804
805 if( bDirs.empty() )
806 return true;
807
808 std::string verA = aDirs.back().ToStdString();
809 std::string verB = bDirs.back().ToStdString();
810
811 if( !extractVersion( verA ) )
812 return false;
813
814 if( !extractVersion( verB ) )
815 return true;
816
817 return compareVersions( verA, verB ) > 0;
818 } );
819
820 return aPaths->size() > 0;
821}
822
823
824bool SETTINGS_MANAGER::IsSettingsPathValid( const wxString& aPath )
825{
826 wxFileName test( aPath, wxS( "kicad_common" ) );
827
828 if( test.Exists() )
829 return true;
830
831 test.SetExt( "json" );
832
833 return test.Exists();
834}
835
836
838{
839 wxFileName path;
840
841 path.AssignDir( PATHS::GetUserSettingsPath() );
842 path.AppendDir( wxS( "colors" ) );
843
844 if( !path.DirExists() )
845 {
846 if( !wxMkdir( path.GetPath() ) )
847 {
848 wxLogTrace( traceSettings,
849 wxT( "GetColorSettingsPath(): Path %s missing and could not be created!" ),
850 path.GetPath() );
851 }
852 }
853
854 return path.GetPath();
855}
856
857
859{
860 wxFileName path;
861
862 path.AssignDir( PATHS::GetUserSettingsPath() );
863 path.AppendDir( wxS( "toolbars" ) );
864
865 if( !path.DirExists() )
866 {
867 if( !wxMkdir( path.GetPath() ) )
868 {
869 wxLogTrace( traceSettings,
870 wxT( "GetToolbarSettingsPath(): Path %s missing and could not be created!" ),
871 path.GetPath() );
872 }
873 }
874
875 return path.GetPath();
876}
877
878
880{
881 // CMake computes the major.minor string for us.
882 return GetMajorMinorVersion().ToStdString();
883}
884
885
886int SETTINGS_MANAGER::compareVersions( const std::string& aFirst, const std::string& aSecond )
887{
888 int a_maj = 0;
889 int a_min = 0;
890 int b_maj = 0;
891 int b_min = 0;
892
893 if( !extractVersion( aFirst, &a_maj, &a_min ) || !extractVersion( aSecond, &b_maj, &b_min ) )
894 {
895 wxLogTrace( traceSettings, wxT( "compareSettingsVersions: bad input (%s, %s)" ),
896 aFirst, aSecond );
897 return -1;
898 }
899
900 if( a_maj < b_maj )
901 {
902 return -1;
903 }
904 else if( a_maj > b_maj )
905 {
906 return 1;
907 }
908 else
909 {
910 if( a_min < b_min )
911 {
912 return -1;
913 }
914 else if( a_min > b_min )
915 {
916 return 1;
917 }
918 else
919 {
920 return 0;
921 }
922 }
923}
924
925
926bool SETTINGS_MANAGER::extractVersion( const std::string& aVersionString, int* aMajor, int* aMinor )
927{
928 std::regex re_version( "(\\d+)\\.(\\d+)" );
929 std::smatch match;
930
931 if( std::regex_match( aVersionString, match, re_version ) )
932 {
933 try
934 {
935 int major = std::stoi( match[1].str() );
936 int minor = std::stoi( match[2].str() );
937
938 if( aMajor )
939 *aMajor = major;
940
941 if( aMinor )
942 *aMinor = minor;
943 }
944 catch( ... )
945 {
946 return false;
947 }
948
949 return true;
950 }
951
952 return false;
953}
954
955
956bool SETTINGS_MANAGER::LoadProject( const wxString& aFullPath, bool aSetActive )
957{
958 // Normalize path to current project extension. Users may open legacy .pro files,
959 // or the OS may hand us a .kicad_sch/.kicad_pcb via file association or drag-and-drop.
960 wxFileName path( aFullPath );
961
962 if( path.HasName() && path.GetExt() != FILEEXT::ProjectFileExtension )
964
965 wxString fullPath = path.GetFullPath();
966
967 // If already loaded, we are all set. This might be called more than once over a project's
968 // lifetime in case the project is first loaded by the KiCad manager and then Eeschema or
969 // Pcbnew try to load it again when they are launched.
970 if( m_projects.count( fullPath ) )
971 return true;
972
973 LOCKFILE lockFile( fullPath );
974
975 if( !lockFile.Valid() )
976 {
977 wxLogTrace( traceSettings, wxT( "Project %s is locked; opening read-only" ), fullPath );
978 }
979
980 // No MDI yet
981 if( aSetActive && !m_projects.empty() )
982 {
983 // Cancel any in-progress library preloads and wait for them to finish before
984 // modifying m_projects_list. Background preload threads access Prj() which becomes
985 // invalid when the project list is modified.
986 if( m_kiway )
987 {
988 if( KIFACE* pcbFace = m_kiway->KiFACE( KIWAY::FACE_PCB, false ) )
989 pcbFace->CancelPreload( true );
990 }
991
992 // Abort any async library loads before modifying m_projects_list to prevent race
993 // conditions where background threads try to access Prj() while the list is empty.
994 if( PgmOrNull() )
996
997 PROJECT* oldProject = m_projects.begin()->second;
998 unloadProjectFile( oldProject, false );
999 m_projects.erase( m_projects.begin() );
1000
1001 auto it = std::find_if( m_projects_list.begin(), m_projects_list.end(),
1002 [&]( const std::unique_ptr<PROJECT>& ptr )
1003 {
1004 return ptr.get() == oldProject;
1005 } );
1006
1007 wxASSERT( it != m_projects_list.end() );
1008 m_projects_list.erase( it );
1009 }
1010
1011 wxLogTrace( traceSettings, wxT( "Load project %s" ), fullPath );
1012
1013 std::unique_ptr<PROJECT> project = std::make_unique<PROJECT>();
1014 project->setProjectFullName( fullPath );
1015
1016 if( aSetActive )
1017 {
1018 // until multiple projects are in play, set an environment variable for the
1019 // the project pointer.
1020 wxFileName projectPath( fullPath );
1021 wxSetEnv( PROJECT_VAR_NAME, projectPath.GetPath() );
1022
1023 // set the cwd but don't impact kicad-cli
1024 if( !projectPath.GetPath().IsEmpty() && wxTheApp && wxTheApp->IsGUI() )
1025 wxSetWorkingDirectory( projectPath.GetPath() );
1026
1027 // Anchor text_eval VCS lookups to the project directory. The GUI relies on cwd,
1028 // which is deliberately left untouched for kicad-cli; an explicit context is
1029 // required so repo-scoped queries resolve correctly from either entry point.
1030 // Force an absolute path because libgit2 resolves relative paths against the
1031 // process cwd, which is what we are working around. Clear the context for an
1032 // empty/null project load so VCS queries fall back to cwd rather than locking
1033 // onto whatever directory happens to be current.
1034 if( projectPath.GetPath().IsEmpty() )
1035 {
1036 TEXT_EVAL_VCS::SetContextPath( wxString() );
1037 }
1038 else
1039 {
1040 wxFileName vcsContext( projectPath );
1041 vcsContext.MakeAbsolute();
1042 TEXT_EVAL_VCS::SetContextPath( vcsContext.GetPath() );
1043 }
1044 }
1045
1046 bool success = loadProjectFile( *project );
1047
1048 if( success )
1049 {
1050 project->SetReadOnly( !lockFile.Valid() || project->GetProjectFile().IsReadOnly() );
1051
1052 if( lockFile && aSetActive )
1053 project->SetProjectLock( new LOCKFILE( std::move( lockFile ) ) );
1054 }
1055
1056 m_projects_list.push_back( std::move( project ) );
1057 m_projects[fullPath] = m_projects_list.back().get();
1058
1059 wxString fn( path.GetName() );
1060
1061 PROJECT_LOCAL_SETTINGS* settings = new PROJECT_LOCAL_SETTINGS( m_projects[fullPath], fn );
1062
1063 if( aSetActive )
1064 settings = RegisterSettings( settings );
1065 else
1066 settings->LoadFromFile( path.GetPath() );
1067
1068 m_projects[fullPath]->setLocalSettings( settings );
1069
1070 // If not running from SWIG; notify the library manager of the new project
1071 // TODO(JE) this maybe could be handled through kiway (below) in the future
1072 if( aSetActive && PgmOrNull() )
1074
1075 if( aSetActive && m_kiway )
1076 m_kiway->ProjectChanged();
1077
1078 return success;
1079}
1080
1081
1082bool SETTINGS_MANAGER::UnloadProject( PROJECT* aProject, bool aSave )
1083{
1084 if( !aProject || !m_projects.count( aProject->GetProjectFullName() ) )
1085 return false;
1086
1087 wxString projectPath = aProject->GetProjectFullName();
1088 wxLogTrace( traceSettings, wxT( "Unload project %s" ), projectPath );
1089
1090 PROJECT* toRemove = m_projects.at( projectPath );
1091 bool wasActiveProject = m_projects_list.begin()->get() == toRemove;
1092
1093 // Cancel any in-progress library preloads and wait for them to finish before
1094 // modifying m_projects_list. Background preload threads access Prj() which becomes
1095 // invalid when the project list is modified.
1096 if( wasActiveProject && m_kiway )
1097 {
1098 if( KIFACE* pcbFace = m_kiway->KiFACE( KIWAY::FACE_PCB, false ) )
1099 pcbFace->CancelPreload( true );
1100 }
1101
1102 // Abort any async library loads before modifying m_projects_list to prevent race
1103 // conditions where background threads try to access Prj() while the list is empty.
1104 if( wasActiveProject && PgmOrNull() )
1106
1107 if( !unloadProjectFile( aProject, aSave ) )
1108 return false;
1109
1110 auto it = std::find_if( m_projects_list.begin(), m_projects_list.end(),
1111 [&]( const std::unique_ptr<PROJECT>& ptr )
1112 {
1113 return ptr.get() == toRemove;
1114 } );
1115
1116 wxASSERT( it != m_projects_list.end() );
1117 m_projects_list.erase( it );
1118
1119 m_projects.erase( projectPath );
1120
1121 if( wasActiveProject )
1122 {
1123 // Immediately reload a null project; this is required until the rest of the application
1124 // is refactored to not assume that Prj() always works
1125 if( m_projects_list.empty() )
1126 LoadProject( "" );
1127
1128 // Remove the reference in the environment to the previous project
1129 wxSetEnv( PROJECT_VAR_NAME, wxS( "" ) );
1130
1131 // Drop the VCS context so lingering text_eval queries don't probe a stale project dir.
1132 TEXT_EVAL_VCS::SetContextPath( wxString() );
1133
1134#ifdef _WIN32
1135 // On Windows, processes hold a handle to their current working directory, preventing
1136 // it from being deleted. Reset to the user settings path to release the project
1137 // directory. This mirrors the wxSetWorkingDirectory call in LoadProject.
1138 if( wxTheApp && wxTheApp->IsGUI() )
1139 wxSetWorkingDirectory( PATHS::GetUserSettingsPath() );
1140#endif
1141
1142 if( m_kiway )
1143 m_kiway->ProjectChanged();
1144 }
1145
1146 return true;
1147}
1148
1149
1151{
1152 // No MDI yet: First project in the list is the active project
1153 if( m_projects_list.empty() )
1154 {
1155 wxLogTrace( traceSettings, wxT( "Prj() called with no project loaded" ) );
1156
1157 static PROJECT s_emptyProject;
1158 return s_emptyProject;
1159 }
1160
1161 return *m_projects_list.begin()->get();
1162}
1163
1164
1166{
1167 return !m_projects.empty();
1168}
1169
1170
1172{
1173 return m_projects.size() > 1 || ( m_projects.size() == 1
1174 && !m_projects.begin()->second->GetProjectFullName().IsEmpty() );
1175}
1176
1177
1178PROJECT* SETTINGS_MANAGER::GetProject( const wxString& aFullPath ) const
1179{
1180 if( m_projects.count( aFullPath ) )
1181 return m_projects.at( aFullPath );
1182
1183 return nullptr;
1184}
1185
1186
1187std::vector<wxString> SETTINGS_MANAGER::GetOpenProjects() const
1188{
1189 std::vector<wxString> ret;
1190
1191 for( const std::pair<const wxString, PROJECT*>& pair : m_projects )
1192 {
1193 // Don't save empty projects (these are the default project settings)
1194 if( !pair.first.IsEmpty() )
1195 ret.emplace_back( pair.first );
1196 }
1197
1198 return ret;
1199}
1200
1201
1202bool SETTINGS_MANAGER::SaveProject( const wxString& aFullPath, PROJECT* aProject )
1203{
1204 if( !aProject )
1205 aProject = &Prj();
1206
1207 wxString path = aFullPath;
1208
1209 if( path.empty() )
1210 path = aProject->GetProjectFullName();
1211
1212 // TODO: refactor for MDI
1213 if( aProject->IsReadOnly() )
1214 return false;
1215
1216 if( !m_project_files.count( path ) )
1217 return false;
1218
1220 wxString projectPath = aProject->GetProjectPath();
1221
1222 project->SaveToFile( projectPath );
1223 aProject->GetLocalSettings().SaveToFile( projectPath );
1224
1225 return true;
1226}
1227
1228
1229void SETTINGS_MANAGER::SaveProjectAs( const wxString& aFullPath, PROJECT* aProject )
1230{
1231 if( !aProject )
1232 aProject = &Prj();
1233
1234 wxString oldName = aProject->GetProjectFullName();
1235
1236 if( aFullPath.IsSameAs( oldName ) )
1237 {
1238 SaveProject( aFullPath, aProject );
1239 return;
1240 }
1241
1242 // Changing this will cause UnloadProject to not save over the "old" project when loading below
1243 aProject->setProjectFullName( aFullPath );
1244
1245 wxFileName fn( aFullPath );
1246
1247 PROJECT_FILE* project = m_project_files.at( oldName );
1248
1249 // Ensure read-only flags are copied; this allows doing a "Save As" on a standalone board/sch
1250 // without creating project files if the checkbox is turned off
1251 project->SetReadOnly( aProject->IsReadOnly() );
1252 aProject->GetLocalSettings().SetReadOnly( aProject->IsReadOnly() );
1253
1254 project->SetFilename( fn.GetName() );
1255 project->SaveToFile( fn.GetPath() );
1256
1257 aProject->GetLocalSettings().SetFilename( fn.GetName() );
1258 aProject->GetLocalSettings().SaveToFile( fn.GetPath() );
1259
1260 m_project_files[fn.GetFullPath()] = project;
1261 m_project_files.erase( oldName );
1262
1263 m_projects[fn.GetFullPath()] = m_projects[oldName];
1264 m_projects.erase( oldName );
1265}
1266
1267
1268void SETTINGS_MANAGER::SaveProjectCopy( const wxString& aFullPath, PROJECT* aProject )
1269{
1270 if( !aProject )
1271 aProject = &Prj();
1272
1274 wxString oldName = project->GetFilename();
1275 wxFileName fn( aFullPath );
1276
1277 bool readOnly = project->IsReadOnly();
1278 project->SetReadOnly( false );
1279
1280 project->SetFilename( fn.GetName() );
1281 project->SaveToFile( fn.GetPath() );
1282 project->SetFilename( oldName );
1283
1284 PROJECT_LOCAL_SETTINGS& localSettings = aProject->GetLocalSettings();
1285
1286 localSettings.SetFilename( fn.GetName() );
1287 localSettings.SaveToFile( fn.GetPath() );
1288 localSettings.SetFilename( oldName );
1289
1290 project->SetReadOnly( readOnly );
1291}
1292
1293
1295{
1296 wxFileName fullFn( aProject.GetProjectFullName() );
1297 wxString fn( fullFn.GetName() );
1298
1299 PROJECT_FILE* file = RegisterSettings( new PROJECT_FILE( fn ), false );
1300
1301 m_project_files[aProject.GetProjectFullName()] = file;
1302
1303 aProject.setProjectFile( file );
1304 file->SetProject( &aProject );
1305
1306 wxString path( fullFn.GetPath() );
1307
1308 return file->LoadFromFile( path );
1309}
1310
1311
1313{
1314 if( !aProject )
1315 return false;
1316
1317 wxString name = aProject->GetProjectFullName();
1318
1319 if( !m_project_files.count( name ) )
1320 return false;
1321
1323
1324 if( !file->ShouldAutoSave() )
1325 aSave = false;
1326
1327 auto it = std::find_if( m_settings.begin(), m_settings.end(),
1328 [&file]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
1329 {
1330 return aPtr.get() == file;
1331 } );
1332
1333 if( it != m_settings.end() )
1334 {
1335 wxString projectPath = GetPathForSettingsFile( it->get() );
1336
1337 bool saveLocalSettings = aSave && aProject->GetLocalSettings().ShouldAutoSave();
1338
1339 FlushAndRelease( &aProject->GetLocalSettings(), saveLocalSettings );
1340
1341 if( aSave )
1342 ( *it )->SaveToFile( projectPath );
1343
1344 m_settings.erase( it );
1345 }
1346
1347 m_project_files.erase( name );
1348
1349 return true;
1350}
1351
1352
1354{
1355 if( !aProject )
1356 return wxEmptyString;
1357
1358 wxString fullName = aProject->GetProjectFullName();
1359
1360 if( fullName.IsEmpty() )
1361 return wxEmptyString;
1362
1363 std::string hashHex;
1364 picosha2::hash256_hex_string( fullName.ToStdString( wxConvUTF8 ), hashHex );
1365
1366 return wxString::Format( wxS( "%s-%s" ), aProject->GetProjectName(),
1367 wxString::FromUTF8( hashHex.substr( 0, 12 ).c_str() ) );
1368}
1369
1370
1371const PROJECT& SETTINGS_MANAGER::resolveProject( const PROJECT* aProject ) const
1372{
1373 return aProject ? *aProject : Prj();
1374}
1375
1376
1377PROJECT* SETTINGS_MANAGER::GetProjectForPath( const wxString& aProjectPath ) const
1378{
1379 if( !IsProjectOpen() )
1380 return nullptr;
1381
1382 wxString activePath = Prj().GetProjectPath();
1383
1384 if( activePath.IsSameAs( aProjectPath ) || activePath.IsSameAs( aProjectPath + wxFILE_SEP_PATH ) )
1385 return &Prj();
1386
1387 return nullptr;
1388}
1389
1390
1392{
1393 return GetBackupRootForProject( nullptr );
1394}
1395
1396
1398{
1399 const PROJECT& project = resolveProject( aProject );
1401
1403 return project.GetProjectPath() + project.GetProjectName() + PROJECT_BACKUPS_DIR_SUFFIX;
1404
1405 wxFileName root( PATHS::GetUserSettingsPath(), wxEmptyString );
1406 root.AppendDir( wxS( "backups" ) );
1407
1408 wxString key = projectKeySuffix( &project );
1409
1410 if( !key.IsEmpty() )
1411 root.AppendDir( key );
1412
1413 return root.GetPathWithSep();
1414}
1415
1416
1418{
1419 const PROJECT& project = resolveProject( aProject );
1421
1423 {
1424 wxFileName p( project.GetProjectPath(), wxEmptyString );
1425 p.AppendDir( wxS( ".history" ) );
1426 return p.GetPath();
1427 }
1428
1429 wxFileName root( PATHS::GetUserSettingsPath(), wxEmptyString );
1430 root.AppendDir( wxS( "local_history" ) );
1431
1432 wxString key = projectKeySuffix( &project );
1433
1434 if( !key.IsEmpty() )
1435 root.AppendDir( key );
1436
1437 return root.GetPath();
1438}
1439
1440
1441wxString SETTINGS_MANAGER::GetLocalHistoryDirForPath( const wxString& aProjectPath ) const
1442{
1443 if( GetCommonSettings()->m_Backup.location == BACKUP_LOCATION::PROJECT_DIR )
1444 {
1445 wxFileName p( aProjectPath, wxEmptyString );
1446 p.AppendDir( wxS( ".history" ) );
1447 return p.GetPath();
1448 }
1449
1450 return GetLocalHistoryDirForProject( GetProjectForPath( aProjectPath ) );
1451}
1452
1453
1455{
1456 const PROJECT& project = resolveProject( aProject );
1458
1460 return project.GetProjectPath();
1461
1462 wxFileName root( PATHS::GetUserSettingsPath(), wxEmptyString );
1463 root.AppendDir( wxS( "autosave" ) );
1464
1465 wxString key = projectKeySuffix( &project );
1466
1467 if( !key.IsEmpty() )
1468 root.AppendDir( key );
1469
1470 return root.GetPathWithSep();
1471}
1472
1473
1474wxString SETTINGS_MANAGER::backupDateTimeFormat = wxT( "%Y-%m-%d_%H%M%S" );
1475
1476
1477bool SETTINGS_MANAGER::BackupProject( REPORTER& aReporter, wxFileName& aTarget ) const
1478{
1479 wxDateTime timestamp = wxDateTime::Now();
1480
1481 wxString fileName = wxString::Format( wxT( "%s-%s" ), Prj().GetProjectName(),
1482 timestamp.Format( backupDateTimeFormat ) );
1483
1484 if( !aTarget.IsOk() )
1485 {
1486 aTarget.SetPath( GetProjectBackupsPath() );
1487 aTarget.SetName( fileName );
1488 aTarget.SetExt( FILEEXT::ArchiveFileExtension );
1489 }
1490
1491 if( !aTarget.DirExists() && !PATHS::EnsurePathExists( aTarget.GetPath() ) )
1492 {
1493 wxLogTrace( traceSettings, wxT( "Could not create project backup path %s" ),
1494 aTarget.GetPath() );
1495 return false;
1496 }
1497
1498 if( !aTarget.IsDirWritable() )
1499 {
1500 wxLogTrace( traceSettings, wxT( "Backup directory %s is not writable" ),
1501 aTarget.GetPath() );
1502 return false;
1503 }
1504
1505 wxLogTrace( traceSettings, wxT( "Backing up project to %s" ), aTarget.GetPath() );
1506
1507 return PROJECT_ARCHIVER::Archive( Prj().GetProjectPath(), aTarget.GetFullPath(), aReporter );
1508}
1509
1510
1511class VECTOR_INSERT_TRAVERSER : public wxDirTraverser
1512{
1513public:
1514 VECTOR_INSERT_TRAVERSER( std::vector<wxString>& aVec,
1515 std::function<bool( const wxString& )> aCond ) :
1516 m_files( aVec ),
1517 m_condition( std::move( aCond ) )
1518 {
1519 }
1520
1521 wxDirTraverseResult OnFile( const wxString& aFile ) override
1522 {
1523 if( m_condition( aFile ) )
1524 m_files.emplace_back( aFile );
1525
1526 return wxDIR_CONTINUE;
1527 }
1528
1529 wxDirTraverseResult OnDir( const wxString& aDirName ) override
1530 {
1531 return wxDIR_CONTINUE;
1532 }
1533
1534private:
1535 std::vector<wxString>& m_files;
1536
1537 std::function<bool( const wxString& )> m_condition;
1538};
1539
1540
1542{
1544
1545 if( !settings.enabled )
1546 return true;
1547
1548 // The Format radio is exclusive: in INCREMENTAL mode the user has opted out of
1549 // timestamped zip archives entirely. Skip backup creation here so we do not
1550 // produce a zip on every eligible save in addition to the git history snapshot.
1551 if( settings.format != BACKUP_FORMAT::ZIP )
1552 return true;
1553
1554 wxString prefix = Prj().GetProjectName() + '-';
1555
1556 auto modTime =
1557 [&prefix]( const wxString& aFile )
1558 {
1559 wxDateTime dt;
1560 wxString fn( wxFileName( aFile ).GetName() );
1561 fn.Replace( prefix, wxS( "" ) );
1562 dt.ParseFormat( fn, backupDateTimeFormat );
1563 return dt;
1564 };
1565
1566 if( Prj().GetProjectFullName().IsEmpty() )
1567 return true;
1568
1569 wxString backupPath = GetProjectBackupsPath();
1570
1571 // Ensure the backup root exists; this also covers user-dir mode where the parent
1572 // directories may not yet have been created.
1573 if( !PATHS::EnsurePathExists( backupPath ) )
1574 {
1575 wxLogTrace( traceSettings, wxT( "Could not create backups path %s! Skipping backup" ),
1576 backupPath );
1577 return false;
1578 }
1579
1580 wxFileName backupRoot( backupPath, wxEmptyString, wxEmptyString );
1581
1582 // Skip backup if the resolved backup root isn't writable. In USER_DIR mode this gates
1583 // on the user data path; in PROJECT_DIR mode it gates on the project tree.
1584 if( !backupRoot.IsDirWritable() )
1585 {
1586 wxLogTrace( traceSettings, wxT( "Backup directory %s is not writable! Skipping backup" ),
1587 backupPath );
1588 return true;
1589 }
1590
1591 wxDir dir( backupPath );
1592
1593 if( !dir.IsOpened() )
1594 {
1595 wxLogTrace( traceSettings, wxT( "Could not open project backups path %s" ), dir.GetName() );
1596 return false;
1597 }
1598
1599 std::vector<wxString> files;
1600
1601 VECTOR_INSERT_TRAVERSER traverser( files,
1602 [&modTime]( const wxString& aFile )
1603 {
1604 return modTime( aFile ).IsValid();
1605 } );
1606
1607 dir.Traverse( traverser, wxT( "*.zip" ) );
1608
1609 // Sort newest-first
1610 std::sort( files.begin(), files.end(),
1611 [&]( const wxString& aFirst, const wxString& aSecond ) -> bool
1612 {
1613 wxDateTime first = modTime( aFirst );
1614 wxDateTime second = modTime( aSecond );
1615
1616 return first.GetTicks() > second.GetTicks();
1617 } );
1618
1619 // Do we even need to back up?
1620 if( !files.empty() )
1621 {
1622 wxDateTime lastTime = modTime( files[0] );
1623
1624 if( lastTime.IsValid() )
1625 {
1626 wxTimeSpan delta = wxDateTime::Now() - modTime( files[0] );
1627
1628 if( delta.IsShorterThan( wxTimeSpan::Seconds( settings.min_interval ) ) )
1629 return true;
1630 }
1631 }
1632
1633 // Backup
1634 wxFileName target;
1635 bool backupSuccessful = BackupProject( aReporter, target );
1636
1637 if( !backupSuccessful )
1638 return false;
1639
1640 // Update the file list
1641 files.insert( files.begin(), target.GetFullPath() );
1642
1643 // Are there any changes since the last backup?
1644 if( files.size() >= 2
1645 && PROJECT_ARCHIVER::AreZipArchivesIdentical( files[0], files[1], aReporter ) )
1646 {
1647 wxRemoveFile( files[0] );
1648 return true;
1649 }
1650
1651 // Now that we know a backup is needed, apply the retention policy
1652
1653 // Step 1: if we're over the total file limit, remove the oldest
1654 if( !files.empty() && settings.limit_total_files > 0 )
1655 {
1656 while( files.size() > static_cast<size_t>( settings.limit_total_files ) )
1657 {
1658 wxRemoveFile( files.back() );
1659 files.pop_back();
1660 }
1661 }
1662
1663 // Step 2: Stay under the total size limit
1664 if( settings.limit_total_size > 0 )
1665 {
1666 wxULongLong totalSize = 0;
1667
1668 for( const wxString& file : files )
1669 totalSize += wxFileName::GetSize( file );
1670
1671 while( !files.empty() && totalSize > static_cast<wxULongLong>( settings.limit_total_size ) )
1672 {
1673 totalSize -= wxFileName::GetSize( files.back() );
1674 wxRemoveFile( files.back() );
1675 files.pop_back();
1676 }
1677 }
1678
1679 // Step 3: Stay under the daily limit
1680 if( settings.limit_daily_files > 0 && files.size() > 1 )
1681 {
1682 wxDateTime day = modTime( files[0] );
1683 int num = 1;
1684
1685 wxASSERT( day.IsValid() );
1686
1687 std::vector<wxString> filesToDelete;
1688
1689 for( size_t i = 1; i < files.size(); i++ )
1690 {
1691 wxDateTime dt = modTime( files[i] );
1692
1693 if( dt.IsSameDate( day ) )
1694 {
1695 num++;
1696
1697 if( num > settings.limit_daily_files )
1698 filesToDelete.emplace_back( files[i] );
1699 }
1700 else
1701 {
1702 day = dt;
1703 num = 1;
1704 }
1705 }
1706
1707 for( const wxString& file : filesToDelete )
1708 wxRemoveFile( file );
1709 }
1710
1711 return true;
1712}
1713
1714
const char * name
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.
static const wxString COLOR_BUILTIN_DEFAULT
AUTO_BACKUP m_Backup
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
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
void SetReadOnly(bool aReadOnly)
JSON_SETTINGS_INTERNALS * Internals()
void SetFilename(const wxString &aFilename)
virtual bool SaveToFile(const wxString &aDirectory="", bool aForce=false)
Calls Store() and then writes the contents of the JSON document to a file.
virtual bool Store()
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
wxString GetFilename() const
@ FACE_PCB
pcbnew DSO
Definition kiway.h:323
void AbortAsyncLoads()
Abort any async library loading operations in progress.
void ProjectChanged()
Notify all adapters that the project has changed.
bool Valid() const
Definition lockfile.h:254
wxDirTraverseResult OnDir(const wxString &dirPath) override
MIGRATION_TRAVERSER(const wxString &aSrcDir, const wxString &aDestDir, bool aMigrateTables)
wxDirTraverseResult OnFile(const wxString &aSrcFilePath) override
static wxString CalculateUserSettingsPath(bool aIncludeVer=true, bool aUseEnv=true)
Determines the base path for user settings files.
Definition paths.cpp:645
static wxString GetDefault3rdPartyPath()
Gets the default path for PCM packages.
Definition paths.cpp:126
static bool EnsurePathExists(const wxString &aPath, bool aPathToFile=false)
Attempts to create a given path if it does not exist.
Definition paths.cpp:518
static wxString GetStockDataPath(bool aRespectRunFromBuildDir=true)
Gets the stock (install) data path, which is the base path for things like scripting,...
Definition paths.cpp:233
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
Definition paths.cpp:634
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
Definition pgm_base.cpp:787
virtual LIBRARY_MANAGER & GetLibraryManager() const
Definition pgm_base.h:132
static bool Archive(const wxString &aSrcDir, const wxString &aDestFile, REPORTER &aReporter, bool aVerbose=true, bool aIncludeExtraFiles=false)
Create an archive of the project.
static bool AreZipArchivesIdentical(const wxString &aZipFileA, const wxString &aZipFileB, REPORTER &aReporter)
Compare the CRCs of all the files in zip archive to determine whether the archives are identical.
The backing store for a PROJECT, in JSON format.
bool ShouldAutoSave() const
void SetProject(PROJECT *aProject)
bool LoadFromFile(const wxString &aDirectory="") override
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
Calls Store() and then writes the contents of the JSON document to a file.
Container for project specific data.
Definition project.h:66
virtual void setProjectFile(PROJECT_FILE *aFile)
Set the backing store file for this project.
Definition project.h:342
virtual bool IsReadOnly() const
Definition project.h:162
virtual const wxString GetProjectFullName() const
Return the full path and name of the project.
Definition project.cpp:181
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:187
virtual const wxString GetProjectName() const
Return the short name of the project.
Definition project.cpp:199
virtual PROJECT_LOCAL_SETTINGS & GetLocalSettings() const
Definition project.h:210
virtual void setProjectFullName(const wxString &aFullPathAndName)
Set the full directory, basename, and extension of the project.
Definition project.cpp:151
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:75
static int compareVersions(const std::string &aFirst, const std::string &aSecond)
Compare two settings versions, like "5.99" and "6.0".
wxString GetBackupRootForProject(const PROJECT *aProject=nullptr) const
Resolve the backup root directory for a project, honoring the active BACKUP_LOCATION preference.
wxString GetPathForSettingsFile(JSON_SETTINGS *aSettings)
Return the path a given settings file should be loaded from / stored to.
static std::string GetSettingsVersion()
Parse the current KiCad build version and extracts the major and minor revision to use as the name of...
void SaveProjectAs(const wxString &aFullPath, PROJECT *aProject=nullptr)
Set the currently loaded project path and saves it (pointers remain valid).
JSON_SETTINGS * registerSettings(JSON_SETTINGS *aSettings, bool aLoadNow=true)
static wxString GetUserSettingsPath()
A proxy for PATHS::GetUserSettingsPath() rather than fighting swig.
T * RegisterSettings(T *aSettings, bool aLoadNow=true)
Take ownership of the pointer passed in.
void SaveProjectCopy(const wxString &aFullPath, PROJECT *aProject=nullptr)
Save a copy of the current project under the given path.
void SaveColorSettings(COLOR_SETTINGS *aSettings, const std::string &aNamespace="")
Safely save a COLOR_SETTINGS to disk, preserving any changes outside the given namespace.
bool MigrateFromPreviousVersion(const wxString &aSourcePath)
Handle migration of the settings from previous KiCad versions.
std::map< wxString, PROJECT * > m_projects
Loaded projects, mapped according to project full name.
static bool extractVersion(const std::string &aVersionString, int *aMajor=nullptr, int *aMinor=nullptr)
Extract the numeric version from a given settings string.
COLOR_SETTINGS * registerColorSettings(const wxString &aFilename, bool aAbsolutePath=false)
COLOR_SETTINGS * GetColorSettings(const wxString &aName)
Retrieve a color settings object that applications can read colors from.
static wxString GetColorSettingsPath()
Return the path where color scheme files are stored; creating it if missing (normally .
COMMON_SETTINGS * GetCommonSettings() const
Retrieve the common settings shared by all applications.
bool SaveProject(const wxString &aFullPath=wxEmptyString, PROJECT *aProject=nullptr)
Save a loaded project.
wxString GetLocalHistoryDirForProject(const PROJECT *aProject=nullptr) const
Resolve the local-history (.history) storage directory for a project.
void ClearFileHistory()
Clear saved file history from all settings files.
wxString GetProjectBackupsPath() const
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
void ResetToDefaults()
Reset all program settings to defaults.
const PROJECT & resolveProject(const PROJECT *aProject) const
Pick the project to resolve a backup path against, falling back to Prj().
bool SettingsDirectoryValid() const
wxString GetAutosaveRootForProject(const PROJECT *aProject=nullptr) const
Resolve the autosave-files root for a project.
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
PROJECT * GetProjectForPath(const wxString &aProjectPath) const
Return the active project iff its path matches aProjectPath, else nullptr.
COLOR_SETTINGS * loadColorSettingsByName(const wxString &aName)
Attempt to load a color theme by name (the color theme directory and .json ext are assumed).
std::vector< COLOR_SETTINGS * > GetColorSettingsList()
bool IsProjectOpen() const
Helper for checking if we have a project open.
wxString GetLocalHistoryDirForPath(const wxString &aProjectPath) const
Resolve the local-history directory for a project given by its on-disk path.
static wxString GetToolbarSettingsPath()
Return the path where toolbar configuration files are stored; creating it if missing (normally .
bool GetPreviousVersionPaths(std::vector< wxString > *aName=nullptr)
Retrieve the name of the most recent previous KiCad version that can be found in the user settings di...
static bool IsSettingsPathValid(const wxString &aPath)
Check if a given path is probably a valid KiCad configuration directory.
bool BackupProject(REPORTER &aReporter, wxFileName &aTarget) const
Create a backup archive of the current project.
std::vector< std::unique_ptr< PROJECT > > m_projects_list
Loaded projects (ownership here).
PROJECT * GetProject(const wxString &aFullPath) const
Retrieve a loaded project by name.
bool UnloadProject(PROJECT *aProject, bool aSave=true)
Save, unload and unregister the given PROJECT.
std::vector< wxString > GetOpenProjects() const
std::vector< std::unique_ptr< JSON_SETTINGS > > m_settings
bool TriggerBackupIfNeeded(REPORTER &aReporter) const
Call 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 save, unload and unregister the given PROJECT_FILE.
COLOR_SETTINGS * GetMigratedColorSettings()
Return a color theme for storing colors migrated from legacy (5.x and earlier) settings,...
std::unordered_map< size_t, JSON_SETTINGS * > m_app_settings_cache
Cache for app settings.
COLOR_SETTINGS * AddNewColorSettings(const wxString &aFilename)
Register 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)
Register a PROJECT_FILE and attempt to load it from disk.
bool IsProjectOpenNotDummy() const
Helper for checking if we have a project open that is not a dummy project.
static wxString backupDateTimeFormat
void ReloadColorSettings()
Re-scan 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.
static wxString projectKeySuffix(const PROJECT *aProject)
Build "<projectname>-<sha256prefix>" suffix used to disambiguate per-project subdirectories under the...
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
@ ZIP
Zip archive snapshots; autosave uses recovery files.
BACKUP_LOCATION
@ PROJECT_DIR
Inside the project directory (default)
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:221
This file is part of the common library.
Functions related to environment variables, including help functions.
void KiCopyFile(const wxString &aSrcPath, const wxString &aDestPath, wxString &aErrors)
Definition gestfich.cpp:308
static const std::string SymbolLibraryTableFileName
static const std::string ProjectFileExtension
static const std::string FootprintLibraryTableFileName
static const std::string ArchiveFileExtension
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
@ TOOLBARS
The toolbar directory (e.g. ~/.config/kicad/toolbars/)
@ 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)
#define traceSettings
File locking utilities.
This file contains miscellaneous commonly used macros and functions.
KICOMMON_API wxString GetVersionedEnvVarName(const wxString &aBaseName)
Construct a versioned environment variable based on this KiCad major version.
Definition env_vars.cpp:77
void SetContextPath(const wxString &aPath)
Set the filesystem path used as the repository-discovery starting point for repo-scoped VCS queries (...
STL namespace.
PGM_BASE & Pgm()
The global program "get" accessor.
PGM_BASE * PgmOrNull()
Return a reference that can be nullptr when running a shared lib from a script, not from a kicad app.
see class PGM_BASE
#define PROJECT_VAR_NAME
A variable name whose value holds the current project directory.
Definition project.h:41
#define DEFAULT_THEME
#define PROJECT_BACKUPS_DIR_SUFFIX
Project settings path will be <projectname> + this.
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.
BACKUP_LOCATION location
Where backups, history, and autosave files live.
int limit_daily_files
Maximum files to keep per day, 0 for unlimited.
BACKUP_FORMAT format
Backup format (incremental git history vs zip archives)
bool enabled
Automatically back up the project when files are saved.
Implement a participant in the KIWAY alchemy.
Definition kiway.h:156
std::string path
VECTOR2I location
int delta
Definition of file extensions used in Kicad.