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