KiCad PCB EDA Suite
Loading...
Searching...
No Matches
settings_manager.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2020 Jon Evans <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
22#include <regex>
23#include <set>
24#include <wx/debug.h>
25#include <wx/dir.h>
26#include <wx/filename.h>
27#include <wx/snglinst.h>
28#include <wx/stdpaths.h>
29#include <wx/utils.h>
30
31#include <build_version.h>
32#include <confirm.h>
33#include <gestfich.h>
35#include <kiplatform/io.h>
36#include <kiway.h>
37#include <lockfile.h>
38#include <macros.h>
39#include <pgm_base.h>
40#include <paths.h>
41#include <picosha2.h>
42
43#include <algorithm>
44#include <project.h>
54#include <env_vars.h>
56
57
59 m_kiway( nullptr ),
60 m_common_settings( nullptr ),
62{
63 wxFileName path( PATHS::GetUserSettingsPath(), wxS( "" ) );
64 wxLogTrace( traceSettings, wxT( "Using settings path %s" ), path.GetFullPath() );
65
66 if( !path.DirExists() )
67 {
68 wxLogTrace( traceSettings, wxT( "Path didn't exist; creating it" ) );
69 path.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
70 }
71
73 {
74 // This will be picked up by the first-run wizard later in application start,
75 // but we allow it for now because many things rely on being able to access the
76 // settings manager. For now, default settings in memory will be used.
77 wxLogTrace( traceSettings, wxT( "Note: no valid settings directory on disk" ) );
78 }
79
80 m_ok = true;
81
82 // create the common settings shared by all applications. Not loaded immediately
84
85 // Create the built-in color settings
86 // Here to allow the Python API to access the built-in colors
88}
89
90
92{
93 for( std::unique_ptr<PROJECT>& project : m_projects_list )
94 project.reset();
95
96 m_projects.clear();
97
98 for( std::unique_ptr<JSON_SETTINGS>& settings : m_settings )
99 settings.reset();
100
101 m_settings.clear();
102
103 m_color_settings.clear();
104}
105
106
108{
109 for( std::unique_ptr<JSON_SETTINGS>& settings : m_settings )
110 {
111 if( settings->GetLocation() == SETTINGS_LOC::USER || settings->GetLocation() == SETTINGS_LOC::COLORS )
112 {
113 std::map<std::string, nlohmann::json> fileHistories = settings->GetFileHistories();
114
115 settings->Internals()->clear();
116 settings->Load(); // load from nothing (ie: load defaults)
117
118 for( const auto& [path, history] : fileHistories )
119 settings->Set( path, history );
120
121 settings->SaveToFile( GetPathForSettingsFile( settings.get() ) );
122 }
123 }
124}
125
126
128{
129 for( std::unique_ptr<JSON_SETTINGS>& settings : m_settings )
130 {
131 if( settings->GetLocation() == SETTINGS_LOC::USER )
132 {
133 for( const auto& [path, history] : settings->GetFileHistories() )
134 settings->Set( path, nlohmann::json::array() );
135
136 settings->SaveToFile( GetPathForSettingsFile( settings.get() ) );
137 }
138 }
139}
140
141
143{
144 std::unique_ptr<JSON_SETTINGS> ptr( aSettings );
145
146 ptr->SetManager( this );
147
148 wxLogTrace( traceSettings, wxT( "Registered new settings object <%s>" ),
149 ptr->GetFullFilename() );
150
151 if( aLoadNow )
152 ptr->LoadFromFile( GetPathForSettingsFile( ptr.get() ) );
153
154 m_settings.push_back( std::move( ptr ) );
155 return m_settings.back().get();
156}
157
158
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
1003bool SETTINGS_MANAGER::LoadProject( const wxString& aFullPath, bool aSetActive )
1004{
1005 // Normalize path to current project extension. Users may open legacy .pro files,
1006 // or the OS may hand us a .kicad_sch/.kicad_pcb via file association or drag-and-drop.
1007 wxFileName path( aFullPath );
1008
1009 if( path.HasName() && path.GetExt() != FILEEXT::ProjectFileExtension )
1011
1012 wxString fullPath = path.GetFullPath();
1013
1014 // If already loaded, we are all set. This might be called more than once over a project's
1015 // lifetime in case the project is first loaded by the KiCad manager and then Eeschema or
1016 // Pcbnew try to load it again when they are launched.
1017 if( m_projects.count( fullPath ) )
1018 return true;
1019
1020 LOCKFILE lockFile( fullPath );
1021
1022 if( !lockFile.Valid() )
1023 {
1024 wxLogTrace( traceSettings, wxT( "Project %s is locked; opening read-only" ), fullPath );
1025 }
1026
1027 // No MDI yet
1028 if( aSetActive && !m_projects.empty() )
1029 {
1030 // Cancel any in-progress library preloads and wait for them to finish before
1031 // modifying m_projects_list. Background preload threads access Prj() which becomes
1032 // invalid when the project list is modified.
1033 if( m_kiway )
1034 {
1035 if( KIFACE* pcbFace = m_kiway->KiFACE( KIWAY::FACE_PCB, false ) )
1036 pcbFace->CancelPreload( true );
1037 }
1038
1039 // Abort any async library loads before modifying m_projects_list to prevent race
1040 // conditions where background threads try to access Prj() while the list is empty.
1041 if( PgmOrNull() )
1043
1044 PROJECT* oldProject = m_projects.begin()->second;
1045 unloadProjectFile( oldProject, false );
1046 m_projects.erase( m_projects.begin() );
1047
1048 auto it = std::find_if( m_projects_list.begin(), m_projects_list.end(),
1049 [&]( const std::unique_ptr<PROJECT>& ptr )
1050 {
1051 return ptr.get() == oldProject;
1052 } );
1053
1054 wxASSERT( it != m_projects_list.end() );
1055 m_projects_list.erase( it );
1056 }
1057
1058 wxLogTrace( traceSettings, wxT( "Load project %s" ), fullPath );
1059
1060 std::unique_ptr<PROJECT> project = std::make_unique<PROJECT>();
1061 project->setProjectFullName( fullPath );
1062
1063 if( aSetActive )
1064 {
1065 // until multiple projects are in play, set an environment variable for the
1066 // the project pointer.
1067 wxFileName projectPath( fullPath );
1068 wxSetEnv( PROJECT_VAR_NAME, projectPath.GetPath() );
1069
1070 // set the cwd but don't impact kicad-cli
1071 if( !projectPath.GetPath().IsEmpty() && wxTheApp && wxTheApp->IsGUI() )
1072 wxSetWorkingDirectory( projectPath.GetPath() );
1073
1074 // Anchor text_eval VCS lookups to the project directory. The GUI relies on cwd,
1075 // which is deliberately left untouched for kicad-cli; an explicit context is
1076 // required so repo-scoped queries resolve correctly from either entry point.
1077 // Force an absolute path because libgit2 resolves relative paths against the
1078 // process cwd, which is what we are working around. Clear the context for an
1079 // empty/null project load so VCS queries fall back to cwd rather than locking
1080 // onto whatever directory happens to be current.
1081 if( projectPath.GetPath().IsEmpty() )
1082 {
1083 TEXT_EVAL_VCS::SetContextPath( wxString() );
1084 }
1085 else
1086 {
1087 wxFileName vcsContext( projectPath );
1088 vcsContext.MakeAbsolute();
1089 TEXT_EVAL_VCS::SetContextPath( vcsContext.GetPath() );
1090 }
1091 }
1092
1093 bool success = loadProjectFile( *project );
1094
1095 if( success )
1096 {
1097 project->SetReadOnly( !lockFile.Valid() || project->GetProjectFile().IsReadOnly() );
1098
1099 if( lockFile && aSetActive )
1100 project->SetProjectLock( new LOCKFILE( std::move( lockFile ) ) );
1101 }
1102
1103 m_projects_list.push_back( std::move( project ) );
1104 m_projects[fullPath] = m_projects_list.back().get();
1105
1106 wxString fn( path.GetName() );
1107
1108 PROJECT_LOCAL_SETTINGS* settings = new PROJECT_LOCAL_SETTINGS( m_projects[fullPath], fn );
1109
1110 if( aSetActive )
1111 settings = RegisterSettings( settings );
1112 else
1113 settings->LoadFromFile( path.GetPath() );
1114
1115 m_projects[fullPath]->setLocalSettings( settings );
1116
1117 // If not running from SWIG; notify the library manager of the new project
1118 // TODO(JE) this maybe could be handled through kiway (below) in the future
1119 if( aSetActive && PgmOrNull() )
1121
1122 if( aSetActive && m_kiway )
1123 m_kiway->ProjectChanged();
1124
1125 return success;
1126}
1127
1128
1130{
1131 if( !aProject )
1132 return false;
1133
1134 return std::any_of( m_projects_list.begin(), m_projects_list.end(),
1135 [&]( const std::unique_ptr<PROJECT>& aPtr )
1136 {
1137 return aPtr.get() == aProject;
1138 } );
1139}
1140
1141
1142bool SETTINGS_MANAGER::UnloadProject( PROJECT* aProject, bool aSave )
1143{
1144 if( !aProject || !m_projects.count( aProject->GetProjectFullName() ) )
1145 return false;
1146
1147 wxString projectPath = aProject->GetProjectFullName();
1148 wxLogTrace( traceSettings, wxT( "Unload project %s" ), projectPath );
1149
1150 PROJECT* toRemove = m_projects.at( projectPath );
1151 bool wasActiveProject = m_projects_list.begin()->get() == toRemove;
1152
1153 // Cancel any in-progress library preloads and wait for them to finish before
1154 // modifying m_projects_list. Background preload threads access Prj() which becomes
1155 // invalid when the project list is modified.
1156 if( wasActiveProject && m_kiway )
1157 {
1158 if( KIFACE* pcbFace = m_kiway->KiFACE( KIWAY::FACE_PCB, false ) )
1159 pcbFace->CancelPreload( true );
1160 }
1161
1162 // Abort any async library loads before modifying m_projects_list to prevent race
1163 // conditions where background threads try to access Prj() while the list is empty.
1164 if( wasActiveProject && PgmOrNull() )
1166
1167 if( !unloadProjectFile( aProject, aSave ) )
1168 return false;
1169
1170 auto it = std::find_if( m_projects_list.begin(), m_projects_list.end(),
1171 [&]( const std::unique_ptr<PROJECT>& ptr )
1172 {
1173 return ptr.get() == toRemove;
1174 } );
1175
1176 wxASSERT( it != m_projects_list.end() );
1177 m_projects_list.erase( it );
1178
1179 m_projects.erase( projectPath );
1180
1181 if( wasActiveProject )
1182 {
1183 // Immediately reload a null project; this is required until the rest of the application
1184 // is refactored to not assume that Prj() always works
1185 if( m_projects_list.empty() )
1186 LoadProject( "" );
1187
1188 // Remove the reference in the environment to the previous project
1189 wxSetEnv( PROJECT_VAR_NAME, wxS( "" ) );
1190
1191 // Drop the VCS context so lingering text_eval queries don't probe a stale project dir.
1192 TEXT_EVAL_VCS::SetContextPath( wxString() );
1193
1194#ifdef _WIN32
1195 // On Windows, processes hold a handle to their current working directory, preventing
1196 // it from being deleted. Reset to the user settings path to release the project
1197 // directory. This mirrors the wxSetWorkingDirectory call in LoadProject.
1198 if( wxTheApp && wxTheApp->IsGUI() )
1199 wxSetWorkingDirectory( PATHS::GetUserSettingsPath() );
1200#endif
1201
1202 if( m_kiway )
1203 m_kiway->ProjectChanged();
1204 }
1205
1206 return true;
1207}
1208
1209
1211{
1212 // No MDI yet: First project in the list is the active project
1213 if( m_projects_list.empty() )
1214 {
1215 wxLogTrace( traceSettings, wxT( "Prj() called with no project loaded" ) );
1216
1217 static PROJECT s_emptyProject;
1218 return s_emptyProject;
1219 }
1220
1221 return *m_projects_list.begin()->get();
1222}
1223
1224
1226{
1227 return !m_projects.empty();
1228}
1229
1230
1232{
1233 return m_projects.size() > 1 || ( m_projects.size() == 1
1234 && !m_projects.begin()->second->GetProjectFullName().IsEmpty() );
1235}
1236
1237
1238PROJECT* SETTINGS_MANAGER::GetProject( const wxString& aFullPath ) const
1239{
1240 if( m_projects.count( aFullPath ) )
1241 return m_projects.at( aFullPath );
1242
1243 return nullptr;
1244}
1245
1246
1247std::vector<wxString> SETTINGS_MANAGER::GetOpenProjects() const
1248{
1249 std::vector<wxString> ret;
1250
1251 for( const std::pair<const wxString, PROJECT*>& pair : m_projects )
1252 {
1253 // Don't save empty projects (these are the default project settings)
1254 if( !pair.first.IsEmpty() )
1255 ret.emplace_back( pair.first );
1256 }
1257
1258 return ret;
1259}
1260
1261
1262bool SETTINGS_MANAGER::SaveProject( const wxString& aFullPath, PROJECT* aProject )
1263{
1264 if( !aProject )
1265 aProject = &Prj();
1266
1267 wxString path = aFullPath;
1268
1269 if( path.empty() )
1270 path = aProject->GetProjectFullName();
1271
1272 // TODO: refactor for MDI
1273 if( aProject->IsReadOnly() )
1274 return false;
1275
1276 if( !m_project_files.count( path ) )
1277 return false;
1278
1280 wxString projectPath = aProject->GetProjectPath();
1281
1282 project->SaveToFile( projectPath );
1283 aProject->GetLocalSettings().SaveToFile( projectPath );
1284
1285 return true;
1286}
1287
1288
1289void SETTINGS_MANAGER::SaveProjectAs( const wxString& aFullPath, PROJECT* aProject )
1290{
1291 if( !aProject )
1292 aProject = &Prj();
1293
1294 wxString oldName = aProject->GetProjectFullName();
1295
1296 if( aFullPath.IsSameAs( oldName ) )
1297 {
1298 SaveProject( aFullPath, aProject );
1299 return;
1300 }
1301
1302 // Changing this will cause UnloadProject to not save over the "old" project when loading below
1303 aProject->setProjectFullName( aFullPath );
1304
1305 wxFileName fn( aFullPath );
1306
1307 PROJECT_FILE* project = m_project_files.at( oldName );
1308
1309 // Ensure read-only flags are copied; this allows doing a "Save As" on a standalone board/sch
1310 // without creating project files if the checkbox is turned off
1311 project->SetReadOnly( aProject->IsReadOnly() );
1312 aProject->GetLocalSettings().SetReadOnly( aProject->IsReadOnly() );
1313
1314 project->SetFilename( fn.GetName() );
1315 project->SaveToFile( fn.GetPath() );
1316
1317 aProject->GetLocalSettings().SetFilename( fn.GetName() );
1318 aProject->GetLocalSettings().SaveToFile( fn.GetPath() );
1319
1320 m_project_files[fn.GetFullPath()] = project;
1321 m_project_files.erase( oldName );
1322
1323 m_projects[fn.GetFullPath()] = m_projects[oldName];
1324 m_projects.erase( oldName );
1325}
1326
1327
1328bool SETTINGS_MANAGER::SaveProjectCopy( const wxString& aFullPath, PROJECT* aProject )
1329{
1330 if( !aProject )
1331 aProject = &Prj();
1332
1334 wxString oldName = project->GetFilename();
1335 wxFileName fn( aFullPath );
1336
1337 bool readOnly = project->IsReadOnly();
1338 project->SetReadOnly( false );
1339
1340 project->SetFilename( fn.GetName() );
1341 const bool projectOk = project->SaveToFile( fn.GetPath() );
1342 project->SetFilename( oldName );
1343
1344 // PROJECT_LOCAL_SETTINGS save is best-effort: SaveToFile returns false for
1345 // benign skips (unchanged content, default settings with m_createIfDefault
1346 // == false), so requiring success would false-positive an error.
1347 PROJECT_LOCAL_SETTINGS& localSettings = aProject->GetLocalSettings();
1348
1349 localSettings.SetFilename( fn.GetName() );
1350 localSettings.SaveToFile( fn.GetPath() );
1351 localSettings.SetFilename( oldName );
1352
1353 project->SetReadOnly( readOnly );
1354
1355 return projectOk;
1356}
1357
1358
1360{
1361 wxFileName fullFn( aProject.GetProjectFullName() );
1362 wxString fn( fullFn.GetName() );
1363
1364 PROJECT_FILE* file = RegisterSettings( new PROJECT_FILE( fn ), false );
1365
1366 m_project_files[aProject.GetProjectFullName()] = file;
1367
1368 aProject.setProjectFile( file );
1369 file->SetProject( &aProject );
1370
1371 wxString path( fullFn.GetPath() );
1372
1373 return file->LoadFromFile( path );
1374}
1375
1376
1378{
1379 if( !aProject )
1380 return false;
1381
1382 wxString name = aProject->GetProjectFullName();
1383
1384 if( !m_project_files.count( name ) )
1385 return false;
1386
1388
1389 if( !file->ShouldAutoSave() )
1390 aSave = false;
1391
1392 auto it = std::find_if( m_settings.begin(), m_settings.end(),
1393 [&file]( const std::unique_ptr<JSON_SETTINGS>& aPtr )
1394 {
1395 return aPtr.get() == file;
1396 } );
1397
1398 if( it != m_settings.end() )
1399 {
1400 // Resolve from aProject directly; during a switch Prj() is no longer aProject.
1401 wxString projectPath = aProject->GetProjectPath();
1402
1403 bool saveLocalSettings = aSave && aProject->GetLocalSettings().ShouldAutoSave();
1404
1405 FlushAndRelease( &aProject->GetLocalSettings(), saveLocalSettings );
1406
1407 if( aSave )
1408 ( *it )->SaveToFile( projectPath );
1409
1410 m_settings.erase( it );
1411 }
1412
1413 m_project_files.erase( name );
1414
1415 return true;
1416}
1417
1418
1420{
1421 if( !aProject )
1422 return wxEmptyString;
1423
1424 wxString fullName = aProject->GetProjectFullName();
1425
1426 if( fullName.IsEmpty() )
1427 return wxEmptyString;
1428
1429 std::string hashHex;
1430 picosha2::hash256_hex_string( fullName.ToStdString( wxConvUTF8 ), hashHex );
1431
1432 return wxString::Format( wxS( "%s-%s" ), aProject->GetProjectName(),
1433 wxString::FromUTF8( hashHex.substr( 0, 12 ).c_str() ) );
1434}
1435
1436
1437const PROJECT& SETTINGS_MANAGER::resolveProject( const PROJECT* aProject ) const
1438{
1439 return aProject ? *aProject : Prj();
1440}
1441
1442
1443PROJECT* SETTINGS_MANAGER::GetProjectForPath( const wxString& aProjectPath ) const
1444{
1445 if( !IsProjectOpen() )
1446 return nullptr;
1447
1448 wxString activePath = Prj().GetProjectPath();
1449
1450 if( activePath.IsSameAs( aProjectPath ) || activePath.IsSameAs( aProjectPath + wxFILE_SEP_PATH ) )
1451 return &Prj();
1452
1453 return nullptr;
1454}
1455
1456
1458{
1459 return GetBackupRootForProject( nullptr );
1460}
1461
1462
1464{
1465 const PROJECT& project = resolveProject( aProject );
1467
1469 return project.GetProjectPath() + project.GetProjectName() + PROJECT_BACKUPS_DIR_SUFFIX;
1470
1471 wxFileName root( PATHS::GetUserSettingsPath(), wxEmptyString );
1472 root.AppendDir( wxS( "backups" ) );
1473
1474 wxString key = projectKeySuffix( &project );
1475
1476 if( !key.IsEmpty() )
1477 root.AppendDir( key );
1478
1479 return root.GetPathWithSep();
1480}
1481
1482
1484{
1485 const PROJECT& project = resolveProject( aProject );
1487
1489 {
1490 wxFileName p( project.GetProjectPath(), wxEmptyString );
1491 p.AppendDir( wxS( ".history" ) );
1492 return p.GetPath();
1493 }
1494
1495 wxFileName root( PATHS::GetUserSettingsPath(), wxEmptyString );
1496 root.AppendDir( wxS( "local_history" ) );
1497
1498 wxString key = projectKeySuffix( &project );
1499
1500 if( !key.IsEmpty() )
1501 root.AppendDir( key );
1502
1503 return root.GetPath();
1504}
1505
1506
1507wxString SETTINGS_MANAGER::GetLocalHistoryDirForPath( const wxString& aProjectPath ) const
1508{
1509 if( GetCommonSettings()->m_Backup.location == BACKUP_LOCATION::PROJECT_DIR )
1510 {
1511 wxFileName p( aProjectPath, wxEmptyString );
1512 p.AppendDir( wxS( ".history" ) );
1513 return p.GetPath();
1514 }
1515
1516 return GetLocalHistoryDirForProject( GetProjectForPath( aProjectPath ) );
1517}
1518
1519
1521{
1522 const PROJECT& project = resolveProject( aProject );
1524
1526 return project.GetProjectPath();
1527
1528 wxFileName root( PATHS::GetUserSettingsPath(), wxEmptyString );
1529 root.AppendDir( wxS( "autosave" ) );
1530
1531 wxString key = projectKeySuffix( &project );
1532
1533 if( !key.IsEmpty() )
1534 root.AppendDir( key );
1535
1536 return root.GetPathWithSep();
1537}
1538
1539
1540wxString SETTINGS_MANAGER::backupDateTimeFormat = wxT( "%Y-%m-%d_%H%M%S" );
1541
1542
1543bool SETTINGS_MANAGER::BackupProject( REPORTER& aReporter, wxFileName& aTarget ) const
1544{
1545 wxDateTime timestamp = wxDateTime::Now();
1546
1547 wxString fileName = wxString::Format( wxT( "%s-%s" ), Prj().GetProjectName(),
1548 timestamp.Format( backupDateTimeFormat ) );
1549
1550 if( !aTarget.IsOk() )
1551 {
1552 aTarget.SetPath( GetProjectBackupsPath() );
1553 aTarget.SetName( fileName );
1554 aTarget.SetExt( FILEEXT::ArchiveFileExtension );
1555 }
1556
1557 if( !aTarget.DirExists() && !PATHS::EnsurePathExists( aTarget.GetPath() ) )
1558 {
1559 wxLogTrace( traceSettings, wxT( "Could not create project backup path %s" ),
1560 aTarget.GetPath() );
1561 return false;
1562 }
1563
1564 if( !aTarget.IsDirWritable() )
1565 {
1566 wxLogTrace( traceSettings, wxT( "Backup directory %s is not writable" ),
1567 aTarget.GetPath() );
1568 return false;
1569 }
1570
1571 wxLogTrace( traceSettings, wxT( "Backing up project to %s" ), aTarget.GetPath() );
1572
1573 return PROJECT_ARCHIVER::Archive( Prj().GetProjectPath(), aTarget.GetFullPath(), aReporter );
1574}
1575
1576
1577class VECTOR_INSERT_TRAVERSER : public wxDirTraverser
1578{
1579public:
1580 VECTOR_INSERT_TRAVERSER( std::vector<wxString>& aVec,
1581 std::function<bool( const wxString& )> aCond ) :
1582 m_files( aVec ),
1583 m_condition( std::move( aCond ) )
1584 {
1585 }
1586
1587 wxDirTraverseResult OnFile( const wxString& aFile ) override
1588 {
1589 if( m_condition( aFile ) )
1590 m_files.emplace_back( aFile );
1591
1592 return wxDIR_CONTINUE;
1593 }
1594
1595 wxDirTraverseResult OnDir( const wxString& aDirName ) override
1596 {
1597 return wxDIR_CONTINUE;
1598 }
1599
1600private:
1601 std::vector<wxString>& m_files;
1602
1603 std::function<bool( const wxString& )> m_condition;
1604};
1605
1606
1608{
1610
1611 if( !settings.enabled )
1612 return true;
1613
1614 // The Format radio is exclusive: in INCREMENTAL mode the user has opted out of
1615 // timestamped zip archives entirely. Skip backup creation here so we do not
1616 // produce a zip on every eligible save in addition to the git history snapshot.
1617 if( settings.format != BACKUP_FORMAT::ZIP )
1618 return true;
1619
1620 wxString prefix = Prj().GetProjectName() + '-';
1621
1622 auto modTime =
1623 [&prefix]( const wxString& aFile )
1624 {
1625 wxDateTime dt;
1626 wxString fn( wxFileName( aFile ).GetName() );
1627 fn.Replace( prefix, wxS( "" ) );
1628 dt.ParseFormat( fn, backupDateTimeFormat );
1629 return dt;
1630 };
1631
1632 if( Prj().GetProjectFullName().IsEmpty() )
1633 return true;
1634
1635 wxString backupPath = GetProjectBackupsPath();
1636
1637 // Ensure the backup root exists; this also covers user-dir mode where the parent
1638 // directories may not yet have been created.
1639 if( !PATHS::EnsurePathExists( backupPath ) )
1640 {
1641 wxLogTrace( traceSettings, wxT( "Could not create backups path %s! Skipping backup" ),
1642 backupPath );
1643 return false;
1644 }
1645
1646 wxFileName backupRoot( backupPath, wxEmptyString, wxEmptyString );
1647
1648 // Skip backup if the resolved backup root isn't writable. In USER_DIR mode this gates
1649 // on the user data path; in PROJECT_DIR mode it gates on the project tree.
1650 if( !backupRoot.IsDirWritable() )
1651 {
1652 wxLogTrace( traceSettings, wxT( "Backup directory %s is not writable! Skipping backup" ),
1653 backupPath );
1654 return true;
1655 }
1656
1657 wxDir dir( backupPath );
1658
1659 if( !dir.IsOpened() )
1660 {
1661 wxLogTrace( traceSettings, wxT( "Could not open project backups path %s" ), dir.GetName() );
1662 return false;
1663 }
1664
1665 std::vector<wxString> files;
1666
1667 VECTOR_INSERT_TRAVERSER traverser( files,
1668 [&modTime]( const wxString& aFile )
1669 {
1670 return modTime( aFile ).IsValid();
1671 } );
1672
1673 dir.Traverse( traverser, wxT( "*.zip" ) );
1674
1675 // Sort newest-first
1676 std::sort( files.begin(), files.end(),
1677 [&]( const wxString& aFirst, const wxString& aSecond ) -> bool
1678 {
1679 wxDateTime first = modTime( aFirst );
1680 wxDateTime second = modTime( aSecond );
1681
1682 return first.GetTicks() > second.GetTicks();
1683 } );
1684
1685 // Do we even need to back up?
1686 if( !files.empty() )
1687 {
1688 wxDateTime lastTime = modTime( files[0] );
1689
1690 if( lastTime.IsValid() )
1691 {
1692 wxTimeSpan delta = wxDateTime::Now() - modTime( files[0] );
1693
1694 if( delta.IsShorterThan( wxTimeSpan::Seconds( settings.min_interval ) ) )
1695 return true;
1696 }
1697 }
1698
1699 // Backup
1700 wxFileName target;
1701 bool backupSuccessful = BackupProject( aReporter, target );
1702
1703 if( !backupSuccessful )
1704 return false;
1705
1706 // Update the file list
1707 files.insert( files.begin(), target.GetFullPath() );
1708
1709 // Are there any changes since the last backup?
1710 if( files.size() >= 2
1711 && PROJECT_ARCHIVER::AreZipArchivesIdentical( files[0], files[1], aReporter ) )
1712 {
1713 wxRemoveFile( files[0] );
1714 return true;
1715 }
1716
1717 // Now that we know a backup is needed, apply the retention policy
1718
1719 // Step 1: if we're over the total file limit, remove the oldest
1720 if( !files.empty() && settings.limit_total_files > 0 )
1721 {
1722 while( files.size() > static_cast<size_t>( settings.limit_total_files ) )
1723 {
1724 wxRemoveFile( files.back() );
1725 files.pop_back();
1726 }
1727 }
1728
1729 // Step 2: Stay under the total size limit
1730 if( settings.limit_total_size > 0 )
1731 {
1732 wxULongLong totalSize = 0;
1733
1734 for( const wxString& file : files )
1735 totalSize += wxFileName::GetSize( file );
1736
1737 while( !files.empty() && totalSize > static_cast<wxULongLong>( settings.limit_total_size ) )
1738 {
1739 totalSize -= wxFileName::GetSize( files.back() );
1740 wxRemoveFile( files.back() );
1741 files.pop_back();
1742 }
1743 }
1744
1745 // Step 3: Stay under the daily limit
1746 if( settings.limit_daily_files > 0 && files.size() > 1 )
1747 {
1748 wxDateTime day = modTime( files[0] );
1749 int num = 1;
1750
1751 wxASSERT( day.IsValid() );
1752
1753 std::vector<wxString> filesToDelete;
1754
1755 for( size_t i = 1; i < files.size(); i++ )
1756 {
1757 wxDateTime dt = modTime( files[i] );
1758
1759 if( dt.IsSameDate( day ) )
1760 {
1761 num++;
1762
1763 if( num > settings.limit_daily_files )
1764 filesToDelete.emplace_back( files[i] );
1765 }
1766 else
1767 {
1768 day = dt;
1769 num = 1;
1770 }
1771 }
1772
1773 for( const wxString& file : filesToDelete )
1774 wxRemoveFile( file );
1775 }
1776
1777 return true;
1778}
1779
1780
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:319
void AbortAsyncLoads()
Abort any async library loading operations in progress.
void ProjectChanged()
Notify all adapters that the project has changed.
bool Valid() const
Definition lockfile.h:264
wxDirTraverseResult OnDir(const wxString &dirPath) override
MIGRATION_TRAVERSER(const wxString &aSrcDir, const wxString &aDestDir, bool aMigrateTables)
wxDirTraverseResult OnFile(const wxString &aSrcFilePath) override
static wxString CalculateUserSettingsPath(bool aIncludeVer=true, bool aUseEnv=true)
Determines the base path for user settings files.
Definition paths.cpp:645
static wxString GetDefault3rdPartyPath()
Gets the default path for PCM packages.
Definition paths.cpp:126
static bool EnsurePathExists(const wxString &aPath, bool aPathToFile=false)
Attempts to create a given path if it does not exist.
Definition paths.cpp:518
static wxString GetStockDataPath(bool aRespectRunFromBuildDir=true)
Gets the stock (install) data path, which is the base path for things like scripting,...
Definition paths.cpp:233
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
Definition paths.cpp:634
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
Definition pgm_base.cpp:799
virtual LIBRARY_MANAGER & GetLibraryManager() const
Definition pgm_base.h:126
static bool Archive(const wxString &aSrcDir, const wxString &aDestFile, REPORTER &aReporter, bool aVerbose=true, bool aIncludeExtraFiles=false)
Create an archive of the project.
static bool AreZipArchivesIdentical(const wxString &aZipFileA, const wxString &aZipFileB, REPORTER &aReporter)
Compare the CRCs of all the files in zip archive to determine whether the archives are identical.
The backing store for a PROJECT, in JSON format.
bool ShouldAutoSave() const
void SetProject(PROJECT *aProject)
bool LoadFromFile(const wxString &aDirectory="") override
Loads the backing file from disk and then calls Load()
The project local settings are things that are attached to a particular project, but also might be pa...
bool SaveToFile(const wxString &aDirectory="", bool aForce=false) override
Calls Store() and then writes the contents of the JSON document to a file.
Container for project specific data.
Definition project.h:62
virtual void setProjectFile(PROJECT_FILE *aFile)
Set the backing store file for this project.
Definition project.h:338
virtual bool IsReadOnly() const
Definition project.h:158
virtual const wxString GetProjectFullName() const
Return the full path and name of the project.
Definition project.cpp:177
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:183
virtual const wxString GetProjectName() const
Return the short name of the project.
Definition project.cpp:195
virtual PROJECT_LOCAL_SETTINGS & GetLocalSettings() const
Definition project.h:206
virtual void setProjectFullName(const wxString &aFullPathAndName)
Set the full directory, basename, and extension of the project.
Definition project.cpp:147
A pure virtual class used to derive REPORTER objects from.
Definition reporter.h:71
static int compareVersions(const std::string &aFirst, const std::string &aSecond)
Compare two settings versions, like "5.99" and "6.0".
wxString GetBackupRootForProject(const PROJECT *aProject=nullptr) const
Resolve the backup root directory for a project, honoring the active BACKUP_LOCATION preference.
wxString GetPathForSettingsFile(JSON_SETTINGS *aSettings)
Return the path a given settings file should be loaded from / stored to.
static std::string GetSettingsVersion()
Parse the current KiCad build version and extracts the major and minor revision to use as the name of...
void SaveProjectAs(const wxString &aFullPath, PROJECT *aProject=nullptr)
Set the currently loaded project path and saves it (pointers remain valid).
JSON_SETTINGS * registerSettings(JSON_SETTINGS *aSettings, bool aLoadNow=true)
static wxString GetUserSettingsPath()
A proxy for PATHS::GetUserSettingsPath() rather than fighting swig.
T * RegisterSettings(T *aSettings, bool aLoadNow=true)
Take ownership of the pointer passed in.
void SaveColorSettings(COLOR_SETTINGS *aSettings, const std::string &aNamespace="")
Safely save a COLOR_SETTINGS to disk, preserving any changes outside the given namespace.
bool MigrateFromPreviousVersion(const wxString &aSourcePath)
Handle migration of the settings from previous KiCad versions.
std::map< wxString, PROJECT * > m_projects
Loaded projects, mapped according to project full name.
static bool extractVersion(const std::string &aVersionString, int *aMajor=nullptr, int *aMinor=nullptr)
Extract the numeric version from a given settings string.
COLOR_SETTINGS * registerColorSettings(const wxString &aFilename, bool aAbsolutePath=false)
COLOR_SETTINGS * GetColorSettings(const wxString &aName)
Retrieve a color settings object that applications can read colors from.
static wxString GetColorSettingsPath()
Return the path where color scheme files are stored; creating it if missing (normally .
COMMON_SETTINGS * GetCommonSettings() const
Retrieve the common settings shared by all applications.
bool SaveProject(const wxString &aFullPath=wxEmptyString, PROJECT *aProject=nullptr)
Save a loaded project.
bool SaveProjectCopy(const wxString &aFullPath, PROJECT *aProject=nullptr)
Save a copy of the current project under the given path.
wxString GetLocalHistoryDirForProject(const PROJECT *aProject=nullptr) const
Resolve the local-history (.history) storage directory for a project.
void ClearFileHistory()
Clear saved file history from all settings files.
wxString GetProjectBackupsPath() const
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
void ResetToDefaults()
Reset all program settings to defaults.
const PROJECT & resolveProject(const PROJECT *aProject) const
Pick the project to resolve a backup path against, falling back to Prj().
bool SettingsDirectoryValid() const
wxString GetAutosaveRootForProject(const PROJECT *aProject=nullptr) const
Resolve the autosave-files root for a project.
std::map< wxString, PROJECT_FILE * > m_project_files
Loaded project files, mapped according to project full name.
std::unordered_map< wxString, COLOR_SETTINGS * > m_color_settings
PROJECT * GetProjectForPath(const wxString &aProjectPath) const
Return the active project iff its path matches aProjectPath, else nullptr.
COLOR_SETTINGS * loadColorSettingsByName(const wxString &aName)
Attempt to load a color theme by name (the color theme directory and .json ext are assumed).
std::vector< COLOR_SETTINGS * > GetColorSettingsList()
bool IsProjectOpen() const
Helper for checking if we have a project open.
wxString GetLocalHistoryDirForPath(const wxString &aProjectPath) const
Resolve the local-history directory for a project given by its on-disk path.
static wxString GetToolbarSettingsPath()
Return the path where toolbar configuration files are stored; creating it if missing (normally .
bool GetPreviousVersionPaths(std::vector< wxString > *aName=nullptr)
Retrieve the name of the most recent previous KiCad version that can be found in the user settings di...
static bool IsSettingsPathValid(const wxString &aPath)
Check if a given path is probably a valid KiCad configuration directory.
bool IsProjectLoaded(PROJECT *aProject) const
True if aProject is still owned by the manager.
bool BackupProject(REPORTER &aReporter, wxFileName &aTarget) const
Create a backup archive of the current project.
std::vector< std::unique_ptr< PROJECT > > m_projects_list
Loaded projects (ownership here).
PROJECT * GetProject(const wxString &aFullPath) const
Retrieve a loaded project by name.
bool UnloadProject(PROJECT *aProject, bool aSave=true)
Save, unload and unregister the given PROJECT.
std::vector< wxString > GetOpenProjects() const
std::vector< std::unique_ptr< JSON_SETTINGS > > m_settings
bool TriggerBackupIfNeeded(REPORTER &aReporter) const
Call BackupProject() if a new backup is needed according to the current backup policy.
bool m_migrateLibraryTables
If true, the symbol and footprint library tables will be migrated from the previous version.
bool unloadProjectFile(PROJECT *aProject, bool aSave)
Optionally save, unload and unregister the given PROJECT_FILE.
COLOR_SETTINGS * GetMigratedColorSettings()
Return a color theme for storing colors migrated from legacy (5.x and earlier) settings,...
std::unordered_map< size_t, JSON_SETTINGS * > m_app_settings_cache
Cache for app settings.
COLOR_SETTINGS * AddNewColorSettings(const wxString &aFilename)
Register a new color settings object with the given filename.
bool m_ok
True if settings loaded successfully at construction.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
bool loadProjectFile(PROJECT &aProject)
Register a PROJECT_FILE and attempt to load it from disk.
bool IsProjectOpenNotDummy() const
Helper for checking if we have a project open that is not a dummy project.
static wxString backupDateTimeFormat
void ReloadColorSettings()
Re-scan the color themes directory, reloading any changes it finds.
COMMON_SETTINGS * m_common_settings
KIWAY * m_kiway
The kiway this settings manager interacts with.
void FlushAndRelease(JSON_SETTINGS *aSettings, bool aSave=true)
If the given settings object is registered, save it to disk and unregister it.
static wxString projectKeySuffix(const PROJECT *aProject)
Build "<projectname>-<sha256prefix>" suffix used to disambiguate per-project subdirectories under the...
wxDirTraverseResult OnFile(const wxString &aFile) override
wxDirTraverseResult OnDir(const wxString &aDirName) override
std::vector< wxString > & m_files
VECTOR_INSERT_TRAVERSER(std::vector< wxString > &aVec, std::function< bool(const wxString &)> aCond)
std::function< bool(const wxString &)> m_condition
@ ZIP
Zip archive snapshots; autosave uses recovery files.
BACKUP_LOCATION
@ PROJECT_DIR
Inside the project directory (default)
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
This file is part of the common library.
Functions related to environment variables, including help functions.
void KiCopyFile(const wxString &aSrcPath, const wxString &aDestPath, wxString &aErrors)
Definition gestfich.cpp:335
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 as the repository-discovery starting point for repo-scoped VCS queries (...
STL namespace.
PGM_BASE & Pgm()
The global program "get" accessor.
PGM_BASE * PgmOrNull()
Return a reference that can be nullptr when running a shared lib from a script, not from a kicad app.
see class PGM_BASE
#define PROJECT_VAR_NAME
A variable name whose value holds the current project directory.
Definition project.h:37
static void reloadFromFile(JSON_SETTINGS *aSettings, const wxString &aPath)
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:152
std::string path
VECTOR2I location
int delta
Definition of file extensions used in Kicad.