KiCad PCB EDA Suite
Loading...
Searching...
No Matches
json_settings.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
21#include <algorithm>
22#include <fstream>
23#include <iomanip>
24#include <utility>
25#include <sstream>
26
27#include <kiplatform/io.h>
28#include <locale_io.h>
29#include <gal/color4d.h>
33#include <settings/parameters.h>
37#include <wx/aui/framemanager.h>
38#include <wx/config.h>
39#include <wx/debug.h>
40#include <wx/fileconf.h>
41#include <wx/filename.h>
42#include <wx/gdicmn.h>
43#include <wx/log.h>
44#include <wx/stdstream.h>
45#include <wx/wfstream.h>
46
47
48nlohmann::json::json_pointer JSON_SETTINGS_INTERNALS::PointerFromString( std::string aPath )
49{
50 std::replace( aPath.begin(), aPath.end(), '.', '/' );
51 aPath.insert( 0, "/" );
52
53 nlohmann::json::json_pointer p;
54
55 try
56 {
57 p = nlohmann::json::json_pointer( aPath );
58 }
59 catch( ... )
60 {
61 wxASSERT_MSG( false, wxT( "Invalid pointer path in PointerFromString!" ) );
62 }
63
64 return p;
65}
66
67
68JSON_SETTINGS::JSON_SETTINGS( const wxString& aFilename, SETTINGS_LOC aLocation,
69 int aSchemaVersion, bool aCreateIfMissing, bool aCreateIfDefault,
70 bool aWriteFile ) :
71 m_filename( aFilename ),
73 m_location( aLocation ),
74 m_createIfMissing( aCreateIfMissing ),
75 m_createIfDefault( aCreateIfDefault ),
76 m_writeFile( aWriteFile ),
77 m_modified( false ),
78 m_fileSynced( false ),
81 m_schemaVersion( aSchemaVersion ),
82 m_isFutureFormat( false ),
83 m_manager( nullptr )
84{
85 m_internals = std::make_unique<JSON_SETTINGS_INTERNALS>();
86
87 try
88 {
89 m_internals->SetFromString( "meta.filename", GetFullFilename() );
90 }
91 catch( ... )
92 {
93 wxLogTrace( traceSettings, wxT( "Error: Could not create filename field for %s" ),
95 }
96
97
98 m_params.emplace_back( new PARAM<int>( "meta.version", &m_schemaVersion, m_schemaVersion,
99 true ) );
100}
101
102
104{
105 // ReleaseNestedSettings erases from m_nested_settings, so snapshot first to avoid
106 // iterating an invalidated container
107 std::vector<NESTED_SETTINGS*> nestedToRelease( m_nested_settings );
108
109 for( NESTED_SETTINGS* nested : nestedToRelease )
110 ReleaseNestedSettings( nested );
111
112 for( PARAM_BASE* param: m_params )
113 delete param;
114
115 m_params.clear();
116}
117
118
120{
121 if( m_filename.AfterLast( '.' ) == getFileExt() )
122 return m_filename;
123
124 return wxString( m_filename + "." + getFileExt() );
125}
126
127
128nlohmann::json& JSON_SETTINGS::At( const std::string& aPath )
129{
130 return m_internals->At( aPath );
131}
132
133
134bool JSON_SETTINGS::Contains( const std::string& aPath ) const
135{
136 return m_internals->contains( JSON_SETTINGS_INTERNALS::PointerFromString( aPath ) );
137}
138
139
144
145
147{
148 for( PARAM_BASE* param : m_params )
149 {
150 try
151 {
152 param->Load( *this, m_resetParamsIfMissing );
153 }
154 catch( ... )
155 {
156 // Skip unreadable parameters in file
157 wxLogTrace( traceSettings, wxT( "param '%s' load err" ), param->GetJsonPath().c_str() );
158 }
159 }
160}
161
162
163bool JSON_SETTINGS::LoadFromFile( const wxString& aDirectory )
164{
165 // First, load all params to default values
166 m_internals->clear();
167 Load();
168
169 bool success = true;
170 bool migrated = false;
171 bool legacy_migrated = false;
172
173 LOCALE_IO locale;
174
175 auto migrateFromLegacy =
176 [&] ( wxFileName& aPath )
177 {
178 // Backup and restore during migration so that the original can be mutated if
179 // convenient
180 bool backed_up = false;
181 wxFileName temp;
182
183 if( aPath.IsDirWritable() )
184 {
185 temp.AssignTempFileName( aPath.GetFullPath() );
186
187 if( !wxCopyFile( aPath.GetFullPath(), temp.GetFullPath() ) )
188 {
189 wxLogTrace( traceSettings,
190 wxT( "%s: could not create temp file for migration" ),
191 GetFullFilename() );
192 }
193 else
194 {
195 backed_up = true;
196 }
197 }
198
199 // Silence popups if legacy file is read-only
200 wxLogNull doNotLog;
201
202 wxConfigBase::DontCreateOnDemand();
203 auto cfg = std::make_unique<wxFileConfig>( wxT( "" ), wxT( "" ),
204 aPath.GetFullPath() );
205
206 // If migrate fails or is not implemented, fall back to built-in defaults that
207 // were already loaded above
208 if( !MigrateFromLegacy( cfg.get() ) )
209 {
210 success = false;
211 wxLogTrace( traceSettings,
212 wxT( "%s: migrated; not all settings were found in legacy file" ),
213 GetFullFilename() );
214 }
215 else
216 {
217 success = true;
218 wxLogTrace( traceSettings, wxT( "%s: migrated from legacy format" ),
219 GetFullFilename() );
220 }
221
222 if( backed_up )
223 {
224 cfg.reset();
225
226 if( !wxCopyFile( temp.GetFullPath(), aPath.GetFullPath() ) )
227 {
228 wxLogTrace( traceSettings,
229 wxT( "migrate; copy temp file %s to %s failed" ),
230 temp.GetFullPath(),
231 aPath.GetFullPath() );
232 }
233
234 if( !wxRemoveFile( temp.GetFullPath() ) )
235 {
236 wxLogTrace( traceSettings,
237 wxT( "migrate; failed to remove temp file %s" ),
238 temp.GetFullPath() );
239 }
240 }
241
242 // Either way, we want to clean up the old file afterwards
243 legacy_migrated = true;
244 };
245
246 wxFileName path;
247
248 if( aDirectory.empty() )
249 {
250 path.Assign( m_filename );
251 path.SetExt( getFileExt() );
252 }
253 else
254 {
255 path.Assign( aDirectory, m_filename, getFileExt() );
256 }
257
258 if( !path.Exists() )
259 {
260 // Case 1: legacy migration, no .json extension yet
261 path.SetExt( getLegacyFileExt() );
262
263 if( path.Exists() )
264 {
265 migrateFromLegacy( path );
266 }
267 // Case 2: legacy filename is different from new one
268 else if( !m_legacy_filename.empty() )
269 {
270 path.SetName( m_legacy_filename );
271
272 if( path.Exists() )
273 migrateFromLegacy( path );
274 }
275 else
276 {
277 success = false;
278 }
279 }
280 else
281 {
282 if( !path.IsFileWritable() )
283 m_writeFile = false;
284
285 try
286 {
287 wxFFileInputStream fp( path.GetFullPath(), wxT( "rt" ) );
288 wxStdInputStream fstream( fp );
289
290 if( fp.IsOk() )
291 {
292 *static_cast<nlohmann::json*>( m_internals.get() ) =
293 nlohmann::json::parse( fstream, nullptr,
294 /* allow_exceptions = */ true,
295 /* ignore_comments = */ true );
296
297 // Save whatever we loaded, before doing any migration etc
298 m_internals->m_original = *static_cast<nlohmann::json*>( m_internals.get() );
299
300 // If parse succeeds, check if schema migration is required
301 int filever = -1;
302
303 try
304 {
305 filever = m_internals->Get<int>( "meta.version" );
306 }
307 catch( ... )
308 {
309 wxLogTrace( traceSettings, wxT( "%s: file version could not be read!" ),
310 GetFullFilename() );
311 success = false;
312 }
313
314 if( filever >= 0 && filever < m_schemaVersion )
315 {
316 wxLogTrace( traceSettings, wxT( "%s: attempting migration from version "
317 "%d to %d" ),
319 filever,
321
322 if( Migrate() )
323 {
324 migrated = true;
325 }
326 else
327 {
328 wxLogTrace( traceSettings, wxT( "%s: migration failed!" ),
329 GetFullFilename() );
330 }
331 }
332 else if( filever > m_schemaVersion )
333 {
334 wxLogTrace( traceSettings,
335 wxT( "%s: warning: file version %d is newer than latest (%d)" ),
337 filever,
339 m_isFutureFormat = true;
340 }
341 }
342 else
343 {
344 wxLogTrace( traceSettings, wxT( "%s exists but can't be opened for read" ),
345 GetFullFilename() );
346 }
347 }
348 catch( nlohmann::json::parse_error& error )
349 {
350 success = false;
351 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ),
352 path.GetFullPath(), error.what() );
353 wxLogTrace( traceSettings, wxT( "Attempting migration in case file is in legacy "
354 "format" ) );
355 migrateFromLegacy( path );
356 }
357 }
358
359 // Now that we have new data in the JSON structure, load the params again
360 Load();
361
362 // And finally load any nested settings
363 for( NESTED_SETTINGS* settings : m_nested_settings )
364 settings->LoadFromFile();
365
366 wxLogTrace( traceSettings, wxT( "Loaded <%s> with schema %d" ),
369
370 m_modified = false;
371
372 // A missing backing file leaves the store empty, which must not count as synchronized
373 if( success )
374 m_fileSynced = true;
375
376 // Skip writeback when running QA tests
377 bool doWrite = m_writeFile && !wxGetEnv( wxT( "KICAD_INHIBIT_SETTINGS_WRITES" ), nullptr );
378
379 // If we migrated, clean up the legacy file (with no extension). Save the migrated
380 // contents FIRST so that if the save fails we still have the legacy file on disk to
381 // fall back to -- otherwise a crash mid-migration leaves the user with neither copy.
382 if( doWrite && ( legacy_migrated || migrated ) )
383 {
385 {
386 bool savedMigrated = SaveToFile( aDirectory, true );
387
388 if( savedMigrated && legacy_migrated && !wxRemoveFile( path.GetFullPath() ) )
389 {
390 wxLogTrace( traceSettings, wxT( "Warning: could not remove legacy file %s" ),
391 path.GetFullPath() );
392 }
393 else if( !savedMigrated )
394 {
395 wxLogTrace( traceSettings,
396 wxT( "Migrated save of %s failed; leaving legacy file intact" ),
397 GetFullFilename() );
398 }
399 }
400 }
401
402 return success;
403}
404
405
407{
408 for( PARAM_BASE* param : m_params )
409 {
410 try
411 {
412 m_modified |= !param->MatchesFile( *this );
413 param->Store( this );
414 }
415 catch( const std::exception& e )
416 {
417 wxLogTrace( traceSettings, wxT( "param '%s' store err: %s" ),
418 param->GetJsonPath().c_str(), e.what() );
419 }
420 }
421
422 return m_modified;
423}
424
425
427{
428 for( PARAM_BASE* param : m_params )
429 param->SetDefault();
430}
431
432
433std::map<std::string, nlohmann::json> JSON_SETTINGS::GetFileHistories()
434{
435 std::map<std::string, nlohmann::json> histories;
436
437 for( const std::string& candidate : { std::string( "system.file_history" ) } )
438 {
439 if( Contains( candidate ) )
440 histories[candidate] = GetJson( candidate ).value();
441 }
442
443 return histories;
444}
445
446
447bool JSON_SETTINGS::SaveToFile( const wxString& aDirectory, bool aForce )
448{
449 if( !m_writeFile )
450 return false;
451
452 // Default PROJECT won't have a filename set
453 if( m_filename.IsEmpty() )
454 return false;
455
456 wxFileName path;
457
458 if( aDirectory.empty() )
459 {
460 path.Assign( m_filename );
461 path.SetExt( getFileExt() );
462 }
463 else
464 {
465 wxString dir( aDirectory );
466 path.Assign( dir, m_filename, getFileExt() );
467 }
468
469 if( !m_createIfMissing && !path.FileExists() )
470 {
471 wxLogTrace( traceSettings,
472 wxT( "File for %s doesn't exist and m_createIfMissing == false; not saving" ),
473 GetFullFilename() );
474 return false;
475 }
476
477 // Ensure the path exists, and create it if not.
478 if( !path.DirExists() && !path.Mkdir() )
479 {
480 wxLogTrace( traceSettings, wxT( "Warning: could not create path %s, can't save %s" ),
481 path.GetPath(), GetFullFilename() );
482 return false;
483 }
484
485 if( ( path.FileExists() && !path.IsFileWritable() ) ||
486 ( !path.FileExists() && !path.IsDirWritable() ) )
487 {
488 wxLogTrace( traceSettings, wxT( "File for %s is read-only; not saving" ),
489 GetFullFilename() );
490 return false;
491 }
492
493 bool modified = false;
494
495 for( NESTED_SETTINGS* settings : m_nested_settings )
496 {
497 wxCHECK2( settings, continue );
498
499 modified |= settings->SaveToFile();
500 }
501
502 modified |= Store();
503
504 if( !modified && !aForce && path.FileExists() )
505 {
506 wxLogTrace( traceSettings, wxT( "%s contents not modified, skipping save" ),
507 GetFullFilename() );
508 return false;
509 }
510 else if( !modified && !aForce && !m_createIfDefault )
511 {
512 wxLogTrace( traceSettings,
513 wxT( "%s contents still default and m_createIfDefault == false; not saving" ),
514 GetFullFilename() );
515 return false;
516 }
517
518 wxLogTrace( traceSettings, wxT( "Saving %s" ), GetFullFilename() );
519
521 bool success = true;
522
523 nlohmann::json toSave = m_internals->m_original;
524
525
526 for( PARAM_BASE* param : m_params )
527 {
528 if( param->ClearUnknownKeys() )
529 {
530 nlohmann::json_pointer p = JSON_SETTINGS_INTERNALS::PointerFromString( param->GetJsonPath() );
531
532 toSave[p] = nlohmann::json::object();
533 }
534 }
535
536 toSave.update( m_internals->begin(), m_internals->end(), /* merge_objects = */ true );
537
538 try
539 {
540 std::stringstream buffer;
541 buffer << std::setw( 2 ) << toSave << std::endl;
542
543 std::string payload = buffer.str();
544 wxString writeError;
545
546 // Last-chance skip for the case where the dirty heuristic fired but the serialized payload
547 // still equals the on-disk bytes (e.g. key reordering or normalization). Avoids bumping the
548 // project file timestamps for a no-op rewrite; see #24402. A genuine change yields a
549 // differing payload and is always written.
550 if( !aForce && path.FileExists() )
551 {
552 std::ifstream existing( path.GetFullPath().fn_str(), std::ios::in | std::ios::binary );
553
554 if( existing )
555 {
556 std::string current( ( std::istreambuf_iterator<char>( existing ) ),
557 std::istreambuf_iterator<char>() );
558
559 // Only trust an equal comparison from a clean read; on any read error fall through
560 // and write, preferring data safety over avoiding a rewrite.
561 if( !existing.bad() && current == payload )
562 {
563 wxLogTrace( traceSettings,
564 wxT( "%s on-disk contents match payload, skipping write" ),
565 GetFullFilename() );
566
567 m_modified = false;
568
569 return false;
570 }
571 }
572 }
573
574 if( !KIPLATFORM::IO::AtomicWriteFile( path.GetFullPath(), payload.data(), payload.size(),
575 &writeError ) )
576 {
577 wxLogTrace( traceSettings, wxT( "Warning: could not save %s: %s" ), GetFullFilename(),
578 writeError );
579 success = false;
580 }
581 }
582 catch( nlohmann::json::exception& error )
583 {
584 wxLogTrace( traceSettings, wxT( "Catch error: could not save %s. Json error %s" ),
585 GetFullFilename(), error.what() );
586 success = false;
587 }
588 catch( ... )
589 {
590 wxLogTrace( traceSettings, wxT( "Error: could not save %s." ) );
591 success = false;
592 }
593
594 if( success )
595 {
596 m_modified = false;
597 m_fileSynced = true;
598 }
599
600 return success;
601}
602
603
605{
606 Store();
607
609
610 std::stringstream buffer;
611 buffer << std::setw( 2 ) << *m_internals << std::endl;
612
613 return buffer.str();
614}
615
616
617bool JSON_SETTINGS::LoadFromRawFile( const wxString& aPath )
618{
619 try
620 {
621 wxFFileInputStream fp( aPath, wxT( "rt" ) );
622 wxStdInputStream fstream( fp );
623
624 if( fp.IsOk() )
625 {
626 *static_cast<nlohmann::json*>( m_internals.get() ) =
627 nlohmann::json::parse( fstream, nullptr,
628 /* allow_exceptions = */ true,
629 /* ignore_comments = */ true );
630 }
631 else
632 {
633 return false;
634 }
635 }
636 catch( nlohmann::json::parse_error& error )
637 {
638 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ), aPath, error.what() );
639
640 return false;
641 }
642
643 // Now that we have new data in the JSON structure, load the params again
644 Load();
645 return true;
646}
647
648
649std::optional<nlohmann::json> JSON_SETTINGS::GetJson( const std::string& aPath ) const
650{
651 nlohmann::json::json_pointer ptr = m_internals->PointerFromString( aPath );
652
653 if( m_internals->contains( ptr ) )
654 {
655 try
656 {
657 return std::optional<nlohmann::json>{ m_internals->at( ptr ) };
658 }
659 catch( ... )
660 {
661 }
662 }
663
664 return std::optional<nlohmann::json>{};
665}
666
667
668template<typename ValueType>
669std::optional<ValueType> JSON_SETTINGS::Get( const std::string& aPath ) const
670{
671 if( std::optional<nlohmann::json> ret = GetJson( aPath ) )
672 {
673 try
674 {
675 return ret->get<ValueType>();
676 }
677 catch( ... )
678 {
679 }
680 }
681
682 return std::nullopt;
683}
684
685
686// Instantiate all required templates here to allow reducing scope of json.hpp
687template KICOMMON_API std::optional<bool>
688 JSON_SETTINGS::Get<bool>( const std::string& aPath ) const;
689template KICOMMON_API std::optional<double>
690 JSON_SETTINGS::Get<double>( const std::string& aPath ) const;
691template KICOMMON_API std::optional<float>
692 JSON_SETTINGS::Get<float>( const std::string& aPath ) const;
693template KICOMMON_API std::optional<int> JSON_SETTINGS::Get<int>( const std::string& aPath ) const;
694template KICOMMON_API std::optional<unsigned int>
695 JSON_SETTINGS::Get<unsigned int>( const std::string& aPath ) const;
696template KICOMMON_API std::optional<unsigned long long>
697 JSON_SETTINGS::Get<unsigned long long>( const std::string& aPath ) const;
698template KICOMMON_API std::optional<std::string>
699 JSON_SETTINGS::Get<std::string>( const std::string& aPath ) const;
700template KICOMMON_API std::optional<nlohmann::json>
701 JSON_SETTINGS::Get<nlohmann::json>( const std::string& aPath ) const;
702template KICOMMON_API std::optional<KIGFX::COLOR4D>
703 JSON_SETTINGS::Get<KIGFX::COLOR4D>( const std::string& aPath ) const;
704template KICOMMON_API std::optional<BOM_FIELD>
705 JSON_SETTINGS::Get<BOM_FIELD>( const std::string& aPath ) const;
706template KICOMMON_API std::optional<BOM_PRESET>
707 JSON_SETTINGS::Get<BOM_PRESET>( const std::string& aPath ) const;
708template KICOMMON_API std::optional<BOM_FMT_PRESET>
709 JSON_SETTINGS::Get<BOM_FMT_PRESET>( const std::string& aPath ) const;
710template KICOMMON_API std::optional<GRID>
711 JSON_SETTINGS::Get<GRID>( const std::string& aPath ) const;
712template KICOMMON_API std::optional<wxPoint>
713 JSON_SETTINGS::Get<wxPoint>( const std::string& aPath ) const;
714template KICOMMON_API std::optional<wxSize>
715 JSON_SETTINGS::Get<wxSize>( const std::string& aPath ) const;
716template KICOMMON_API std::optional<wxRect>
717 JSON_SETTINGS::Get<wxRect>( const std::string& aPath ) const;
718template KICOMMON_API std::optional<wxAuiPaneInfo>
719 JSON_SETTINGS::Get<wxAuiPaneInfo>( const std::string& aPath ) const;
720template KICOMMON_API std::optional<KIGFX::CROSS_HAIR_MODE>
721 JSON_SETTINGS::Get<KIGFX::CROSS_HAIR_MODE>( const std::string& aPath ) const;
722
723template<typename ValueType>
724void JSON_SETTINGS::Set( const std::string& aPath, ValueType aVal )
725{
726 m_internals->SetFromString( aPath, std::move( aVal ) );
727}
728
729
730// Instantiate all required templates here to allow reducing scope of json.hpp
731template KICOMMON_API void JSON_SETTINGS::Set<bool>( const std::string& aPath, bool aValue );
732template KICOMMON_API void JSON_SETTINGS::Set<double>( const std::string& aPath, double aValue );
733template KICOMMON_API void JSON_SETTINGS::Set<float>( const std::string& aPath, float aValue );
734template KICOMMON_API void JSON_SETTINGS::Set<int>( const std::string& aPath, int aValue );
735template KICOMMON_API void JSON_SETTINGS::Set<unsigned int>( const std::string& aPath,
736 unsigned int aValue );
737template KICOMMON_API void JSON_SETTINGS::Set<unsigned long long>( const std::string& aPath,
738 unsigned long long aValue );
739template KICOMMON_API void JSON_SETTINGS::Set<const char*>( const std::string& aPath,
740 const char* aValue );
741template KICOMMON_API void JSON_SETTINGS::Set<std::string>( const std::string& aPath,
742 std::string aValue );
743template KICOMMON_API void JSON_SETTINGS::Set<nlohmann::json>( const std::string& aPath,
744 nlohmann::json aValue );
745template KICOMMON_API void JSON_SETTINGS::Set<KIGFX::COLOR4D>( const std::string& aPath,
746 KIGFX::COLOR4D aValue );
747template KICOMMON_API void JSON_SETTINGS::Set<BOM_FIELD>( const std::string& aPath,
748 BOM_FIELD aValue );
749template KICOMMON_API void JSON_SETTINGS::Set<BOM_PRESET>( const std::string& aPath,
750 BOM_PRESET aValue );
751template KICOMMON_API void JSON_SETTINGS::Set<BOM_FMT_PRESET>( const std::string& aPath,
752 BOM_FMT_PRESET aValue );
753template KICOMMON_API void JSON_SETTINGS::Set<GRID>( const std::string& aPath, GRID aValue );
754template KICOMMON_API void JSON_SETTINGS::Set<wxPoint>( const std::string& aPath, wxPoint aValue );
755template KICOMMON_API void JSON_SETTINGS::Set<wxSize>( const std::string& aPath, wxSize aValue );
756template KICOMMON_API void JSON_SETTINGS::Set<wxRect>( const std::string& aPath, wxRect aValue );
757template KICOMMON_API void JSON_SETTINGS::Set<wxAuiPaneInfo>( const std::string& aPath,
758 wxAuiPaneInfo aValue );
759template KICOMMON_API void JSON_SETTINGS::Set<KIGFX::CROSS_HAIR_MODE>( const std::string& aPath,
760 KIGFX::CROSS_HAIR_MODE aValue );
761
762
763void JSON_SETTINGS::registerMigration( int aOldSchemaVersion, int aNewSchemaVersion,
764 std::function<bool()> aMigrator )
765{
766 wxASSERT( aNewSchemaVersion > aOldSchemaVersion );
767 wxASSERT( aNewSchemaVersion <= m_schemaVersion );
768 m_migrators[aOldSchemaVersion] = std::make_pair( aNewSchemaVersion, aMigrator );
769}
770
771
773{
774 int filever = m_internals->Get<int>( "meta.version" );
775
776 while( filever < m_schemaVersion )
777 {
778 wxASSERT( m_migrators.count( filever ) > 0 );
779
780 if( !m_migrators.count( filever ) )
781 {
782 wxLogTrace( traceSettings, wxT( "Migrator missing for %s version %d!" ),
783 typeid( *this ).name(),
784 filever );
785 return false;
786 }
787
788 std::pair<int, std::function<bool()>> pair = m_migrators.at( filever );
789
790 if( pair.second() )
791 {
792 wxLogTrace( traceSettings, wxT( "Migrated %s from %d to %d" ),
793 typeid( *this ).name(),
794 filever,
795 pair.first );
796 filever = pair.first;
797 m_internals->At( "meta.version" ) = filever;
798 }
799 else
800 {
801 wxLogTrace( traceSettings, wxT( "Migration failed for %s from %d to %d" ),
802 typeid( *this ).name(),
803 filever,
804 pair.first );
805 return false;
806 }
807 }
808
809 return true;
810}
811
812
813bool JSON_SETTINGS::MigrateFromLegacy( wxConfigBase* aLegacyConfig )
814{
815 wxLogTrace( traceSettings, wxT( "MigrateFromLegacy() not implemented for %s" ),
816 typeid( *this ).name() );
817 return false;
818}
819
820
821bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
822 wxString& aTarget )
823{
824 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
825
826 if( aObj.contains( ptr ) && aObj.at( ptr ).is_string() )
827 {
828 aTarget = aObj.at( ptr ).get<wxString>();
829 return true;
830 }
831
832 return false;
833}
834
835
836bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
837 bool& aTarget )
838{
839 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
840
841 if( aObj.contains( ptr ) && aObj.at( ptr ).is_boolean() )
842 {
843 aTarget = aObj.at( ptr ).get<bool>();
844 return true;
845 }
846
847 return false;
848}
849
850
851bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
852 int& aTarget )
853{
854 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
855
856 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_integer() )
857 {
858 aTarget = aObj.at( ptr ).get<int>();
859 return true;
860 }
861
862 return false;
863}
864
865
866bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
867 unsigned int& aTarget )
868{
869 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
870
871 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_unsigned() )
872 {
873 aTarget = aObj.at( ptr ).get<unsigned int>();
874 return true;
875 }
876
877 return false;
878}
879
880
881template<typename ValueType>
882bool JSON_SETTINGS::fromLegacy( wxConfigBase* aConfig, const std::string& aKey,
883 const std::string& aDest )
884{
885 ValueType val;
886
887 if( aConfig->Read( aKey, &val ) )
888 {
889 try
890 {
891 ( *m_internals )[aDest] = val;
892 }
893 catch( ... )
894 {
895 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacy!" ) );
896 return false;
897 }
898
899 return true;
900 }
901
902 return false;
903}
904
905
906// Explicitly declare these because we only support a few types anyway, and it means we can keep
907// wxConfig detail out of the header file
908template
909KICOMMON_API bool JSON_SETTINGS::fromLegacy<int>( wxConfigBase*, const std::string&,
910 const std::string& );
911
912template
913KICOMMON_API bool JSON_SETTINGS::fromLegacy<double>( wxConfigBase*, const std::string&,
914 const std::string& );
915
916template
917KICOMMON_API bool JSON_SETTINGS::fromLegacy<bool>( wxConfigBase*, const std::string&,
918 const std::string& );
919
920
921bool JSON_SETTINGS::fromLegacyString( wxConfigBase* aConfig, const std::string& aKey,
922 const std::string& aDest )
923{
924 wxString str;
925
926 if( aConfig->Read( aKey, &str ) )
927 {
928 try
929 {
930 ( *m_internals )[aDest] = str.ToUTF8();
931 }
932 catch( ... )
933 {
934 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyString!" ) );
935 return false;
936 }
937
938 return true;
939 }
940
941 return false;
942}
943
944
945bool JSON_SETTINGS::fromLegacyColor( wxConfigBase* aConfig, const std::string& aKey,
946 const std::string& aDest )
947{
948 wxString str;
949
950 if( aConfig->Read( aKey, &str ) )
951 {
952 KIGFX::COLOR4D color;
953 color.SetFromWxString( str );
954
955 try
956 {
957 nlohmann::json js = nlohmann::json::array( { color.r, color.g, color.b, color.a } );
958 ( *m_internals )[aDest] = std::move( js );
959 }
960 catch( ... )
961 {
962 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyColor!" ) );
963 return false;
964 }
965
966 return true;
967 }
968
969 return false;
970}
971
972
974{
975 wxLogTrace( traceSettings, wxT( "AddNestedSettings %s" ), aSettings->GetFilename() );
976 m_nested_settings.push_back( aSettings );
977}
978
979
981{
982 if( !aSettings || !m_manager )
983 return;
984
985 auto it = std::find_if( m_nested_settings.begin(), m_nested_settings.end(),
986 [&aSettings]( const JSON_SETTINGS* aPtr )
987 {
988 return aPtr == aSettings;
989 } );
990
991 if( it != m_nested_settings.end() )
992 {
993 wxLogTrace( traceSettings, wxT( "Flush and release %s" ), ( *it )->GetFilename() );
994
995 // Flush the nested state into the parent and propagate genuine dirtiness so a later parent
996 // save persists it; the nested object is gone by then, so this is the parent's only signal.
997 // Default-fill of params absent from an older file no longer reports modified, so releasing
998 // an unchanged nested setting on editor close does not falsely dirty the parent (#24402).
999 m_modified |= ( *it )->SaveToFile();
1000 m_nested_settings.erase( it );
1001 }
1002
1003 aSettings->SetParent( nullptr );
1004}
1005
1006
1007// Specializations to allow conversion between wxString and std::string via JSON_SETTINGS API
1008template<>
1009std::optional<wxString> JSON_SETTINGS::Get( const std::string& aPath ) const
1010{
1011 if( std::optional<nlohmann::json> opt_json = GetJson( aPath ) )
1012 return wxString( opt_json->get<std::string>().c_str(), wxConvUTF8 );
1013
1014 return std::nullopt;
1015}
1016
1017
1018template<>
1019void JSON_SETTINGS::Set<wxString>( const std::string& aPath, wxString aVal )
1020{
1021 ( *m_internals )[aPath] = aVal.ToUTF8();
1022}
1023
1024
1025template<typename ResultType>
1026ResultType JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson, const std::string& aKey,
1027 ResultType aDefault )
1028{
1029 ResultType ret = std::move( aDefault );
1030
1031 try
1032 {
1033 if( aJson.contains( aKey ) )
1034 ret = aJson.at( aKey ).get<ResultType>();
1035 }
1036 catch( ... )
1037 {
1038 }
1039
1040 return ret;
1041}
1042
1043
1044template
1045KICOMMON_API std::string JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson,
1046 const std::string& aKey,
1047 std::string aDefault );
1048
1049
1050template
1051KICOMMON_API bool JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson,
1052 const std::string& aKey, bool aDefault );
static nlohmann::json::json_pointer PointerFromString(std::string aPath)
Builds a JSON pointer based on a given string.
bool fromLegacyString(wxConfigBase *aConfig, const std::string &aKey, const std::string &aDest)
Translates a legacy wxConfig string value to a given JSON pointer value.
bool fromLegacy(wxConfigBase *aConfig, const std::string &aKey, const std::string &aDest)
Translates a legacy wxConfig value to a given JSON pointer value.
virtual wxString getFileExt() const
virtual std::map< std::string, nlohmann::json > GetFileHistories()
void Set(const std::string &aPath, ValueType aVal)
Stores a value into the JSON document Will throw an exception if ValueType isn't something that the l...
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....
wxString m_filename
The filename (not including path) of this settings file (inicode)
SETTINGS_MANAGER * m_manager
A pointer to the settings manager managing this file (may be null)
bool Contains(const std::string &aPath) const
bool m_fileSynced
True once the store has been synchronized with an on-disk file (loaded or saved)
bool LoadFromRawFile(const wxString &aPath)
bool m_isFutureFormat
Set to true if this settings is loaded from a file with a newer schema version than is known.
virtual ~JSON_SETTINGS()
std::vector< NESTED_SETTINGS * > m_nested_settings
Nested settings files that live inside this one, if any.
bool m_modified
True if the JSON data store has been written to since the last file write.
virtual bool LoadFromFile(const wxString &aDirectory="")
Loads the backing file from disk and then calls Load()
bool m_createIfDefault
Whether or not the backing store file should be created if all parameters are still at their default ...
bool m_writeFile
Whether or not the backing store file should be written.
static bool SetIfPresent(const nlohmann::json &aObj, const std::string &aPath, wxString &aTarget)
Sets the given string if the given key/path is present.
virtual void Load()
Updates the parameters of this object based on the current JSON document contents.
void ResetToDefaults()
Resets all parameters to default values.
bool fromLegacyColor(wxConfigBase *aConfig, const std::string &aKey, const std::string &aDest)
Translates a legacy COLOR4D stored in a wxConfig string to a given JSON pointer value.
wxString GetFullFilename() const
bool m_createIfMissing
Whether or not the backing store file should be created it if doesn't exist.
bool Migrate()
Migrates the schema of this settings from the version in the file to the latest version.
std::optional< ValueType > Get(const std::string &aPath) const
Fetches a value from within the JSON document.
std::vector< PARAM_BASE * > m_params
The list of parameters (owned by this object)
virtual bool MigrateFromLegacy(wxConfigBase *aLegacyConfig)
Migrates from wxConfig to JSON-based configuration.
const std::string FormatAsString()
void registerMigration(int aOldSchemaVersion, int aNewSchemaVersion, std::function< bool(void)> aMigrator)
Registers a migration from one schema version to another.
nlohmann::json & At(const std::string &aPath)
Wrappers for the underlying JSON API so that most consumers don't need json.hpp All of these function...
JSON_SETTINGS_INTERNALS * Internals()
static ResultType fetchOrDefault(const nlohmann::json &aJson, const std::string &aKey, ResultType aDefault=ResultType())
Helper to retrieve a value from a JSON object (dictionary) as a certain result type.
void ReleaseNestedSettings(NESTED_SETTINGS *aSettings)
Saves and frees a nested settings object, if it exists within this one.
JSON_SETTINGS(const wxString &aFilename, SETTINGS_LOC aLocation, int aSchemaVersion)
bool m_deleteLegacyAfterMigration
Whether or not to delete legacy file after migration.
std::unique_ptr< JSON_SETTINGS_INTERNALS > m_internals
friend class NESTED_SETTINGS
SETTINGS_LOC m_location
The location of this settings file (.
virtual bool SaveToFile(const wxString &aDirectory="", bool aForce=false)
Calls Store() and then writes the contents of the JSON document to a file.
virtual wxString getLegacyFileExt() const
bool m_resetParamsIfMissing
Whether or not to set parameters to their default value if missing from JSON on Load()
std::map< int, std::pair< int, std::function< bool()> > > m_migrators
A map of starting schema version to a pair of <ending version, migrator function>
int m_schemaVersion
Version of this settings schema.
wxString m_legacy_filename
The filename of the wxConfig legacy file (if different from m_filename)
void AddNestedSettings(NESTED_SETTINGS *aSettings)
Transfers ownership of a given NESTED_SETTINGS to this object.
virtual bool Store()
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
wxString GetFilename() const
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
double r
Red component.
Definition color4d.h:390
double g
Green component.
Definition color4d.h:391
bool SetFromWxString(const wxString &aColorString)
Set color values by parsing a string using wxColour::Set().
Definition color4d.cpp:127
double a
Alpha component.
Definition color4d.h:393
double b
Blue component.
Definition color4d.h:392
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:37
void SetParent(JSON_SETTINGS *aParent, bool aLoadFromFile=true)
SETTINGS_LOC
#define traceSettings
#define KICOMMON_API
Definition kicommon.h:27
bool AtomicWriteFile(const wxString &aTargetPath, const void *aData, size_t aSize, wxString *aError=nullptr)
Writes aData to aTargetPath via a sibling temp file, fsyncs the data and directory,...
std::vector< FAB_LAYER_COLOR > dummy
Common grid settings, available to every frame.
std::string path