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