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