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 // If we migrated, clean up the legacy file (with no extension). Save the migrated
377 // contents FIRST so that if the save fails we still have the legacy file on disk to
378 // fall back to -- otherwise a crash mid-migration leaves the user with neither copy.
379 if( m_writeFile && ( legacy_migrated || migrated ) )
380 {
382 {
383 bool savedMigrated = SaveToFile( aDirectory, true );
384
385 if( savedMigrated && legacy_migrated && !wxRemoveFile( path.GetFullPath() ) )
386 {
387 wxLogTrace( traceSettings, wxT( "Warning: could not remove legacy file %s" ),
388 path.GetFullPath() );
389 }
390 else if( !savedMigrated )
391 {
392 wxLogTrace( traceSettings,
393 wxT( "Migrated save of %s failed; leaving legacy file intact" ),
394 GetFullFilename() );
395 }
396 }
397 }
398
399 return success;
400}
401
402
404{
405 for( PARAM_BASE* param : m_params )
406 {
407 try
408 {
409 m_modified |= !param->MatchesFile( *this );
410 param->Store( this );
411 }
412 catch( const std::exception& e )
413 {
414 wxLogTrace( traceSettings, wxT( "param '%s' store err: %s" ),
415 param->GetJsonPath().c_str(), e.what() );
416 }
417 }
418
419 return m_modified;
420}
421
422
424{
425 for( PARAM_BASE* param : m_params )
426 param->SetDefault();
427}
428
429
430std::map<std::string, nlohmann::json> JSON_SETTINGS::GetFileHistories()
431{
432 std::map<std::string, nlohmann::json> histories;
433
434 for( const std::string& candidate : { std::string( "system.file_history" ) } )
435 {
436 if( Contains( candidate ) )
437 histories[candidate] = GetJson( candidate ).value();
438 }
439
440 return histories;
441}
442
443
444bool JSON_SETTINGS::SaveToFile( const wxString& aDirectory, bool aForce )
445{
446 if( !m_writeFile )
447 return false;
448
449 // Default PROJECT won't have a filename set
450 if( m_filename.IsEmpty() )
451 return false;
452
453 wxFileName path;
454
455 if( aDirectory.empty() )
456 {
457 path.Assign( m_filename );
458 path.SetExt( getFileExt() );
459 }
460 else
461 {
462 wxString dir( aDirectory );
463 path.Assign( dir, m_filename, getFileExt() );
464 }
465
466 if( !m_createIfMissing && !path.FileExists() )
467 {
468 wxLogTrace( traceSettings,
469 wxT( "File for %s doesn't exist and m_createIfMissing == false; not saving" ),
470 GetFullFilename() );
471 return false;
472 }
473
474 // Ensure the path exists, and create it if not.
475 if( !path.DirExists() && !path.Mkdir() )
476 {
477 wxLogTrace( traceSettings, wxT( "Warning: could not create path %s, can't save %s" ),
478 path.GetPath(), GetFullFilename() );
479 return false;
480 }
481
482 if( ( path.FileExists() && !path.IsFileWritable() ) ||
483 ( !path.FileExists() && !path.IsDirWritable() ) )
484 {
485 wxLogTrace( traceSettings, wxT( "File for %s is read-only; not saving" ),
486 GetFullFilename() );
487 return false;
488 }
489
490 bool modified = false;
491
492 for( NESTED_SETTINGS* settings : m_nested_settings )
493 {
494 wxCHECK2( settings, continue );
495
496 modified |= settings->SaveToFile();
497 }
498
499 modified |= Store();
500
501 if( !modified && !aForce && path.FileExists() )
502 {
503 wxLogTrace( traceSettings, wxT( "%s contents not modified, skipping save" ),
504 GetFullFilename() );
505 return false;
506 }
507 else if( !modified && !aForce && !m_createIfDefault )
508 {
509 wxLogTrace( traceSettings,
510 wxT( "%s contents still default and m_createIfDefault == false; not saving" ),
511 GetFullFilename() );
512 return false;
513 }
514
515 wxLogTrace( traceSettings, wxT( "Saving %s" ), GetFullFilename() );
516
518 bool success = true;
519
520 nlohmann::json toSave = m_internals->m_original;
521
522
523 for( PARAM_BASE* param : m_params )
524 {
525 if( param->ClearUnknownKeys() )
526 {
527 nlohmann::json_pointer p = JSON_SETTINGS_INTERNALS::PointerFromString( param->GetJsonPath() );
528
529 toSave[p] = nlohmann::json( {} );
530 }
531 }
532
533 toSave.update( m_internals->begin(), m_internals->end(), /* merge_objects = */ true );
534
535 try
536 {
537 std::stringstream buffer;
538 buffer << std::setw( 2 ) << toSave << std::endl;
539
540 std::string payload = buffer.str();
541 wxString writeError;
542
543 // Last-chance skip for the case where the dirty heuristic fired but the serialized payload
544 // still equals the on-disk bytes (e.g. key reordering or normalization). Avoids bumping the
545 // project file timestamps for a no-op rewrite; see #24402. A genuine change yields a
546 // differing payload and is always written.
547 if( !aForce && path.FileExists() )
548 {
549 std::ifstream existing( path.GetFullPath().fn_str(), std::ios::in | std::ios::binary );
550
551 if( existing )
552 {
553 std::string current( ( std::istreambuf_iterator<char>( existing ) ),
554 std::istreambuf_iterator<char>() );
555
556 // Only trust an equal comparison from a clean read; on any read error fall through
557 // and write, preferring data safety over avoiding a rewrite.
558 if( !existing.bad() && current == payload )
559 {
560 wxLogTrace( traceSettings,
561 wxT( "%s on-disk contents match payload, skipping write" ),
562 GetFullFilename() );
563
564 m_modified = false;
565
566 return false;
567 }
568 }
569 }
570
571 if( !KIPLATFORM::IO::AtomicWriteFile( path.GetFullPath(), payload.data(), payload.size(),
572 &writeError ) )
573 {
574 wxLogTrace( traceSettings, wxT( "Warning: could not save %s: %s" ), GetFullFilename(),
575 writeError );
576 success = false;
577 }
578 }
579 catch( nlohmann::json::exception& error )
580 {
581 wxLogTrace( traceSettings, wxT( "Catch error: could not save %s. Json error %s" ),
582 GetFullFilename(), error.what() );
583 success = false;
584 }
585 catch( ... )
586 {
587 wxLogTrace( traceSettings, wxT( "Error: could not save %s." ) );
588 success = false;
589 }
590
591 if( success )
592 {
593 m_modified = false;
594 m_fileSynced = true;
595 }
596
597 return success;
598}
599
600
602{
603 Store();
604
606
607 std::stringstream buffer;
608 buffer << std::setw( 2 ) << *m_internals << std::endl;
609
610 return buffer.str();
611}
612
613
614bool JSON_SETTINGS::LoadFromRawFile( const wxString& aPath )
615{
616 try
617 {
618 wxFFileInputStream fp( aPath, wxT( "rt" ) );
619 wxStdInputStream fstream( fp );
620
621 if( fp.IsOk() )
622 {
623 *static_cast<nlohmann::json*>( m_internals.get() ) =
624 nlohmann::json::parse( fstream, nullptr,
625 /* allow_exceptions = */ true,
626 /* ignore_comments = */ true );
627 }
628 else
629 {
630 return false;
631 }
632 }
633 catch( nlohmann::json::parse_error& error )
634 {
635 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ), aPath, error.what() );
636
637 return false;
638 }
639
640 // Now that we have new data in the JSON structure, load the params again
641 Load();
642 return true;
643}
644
645
646std::optional<nlohmann::json> JSON_SETTINGS::GetJson( const std::string& aPath ) const
647{
648 nlohmann::json::json_pointer ptr = m_internals->PointerFromString( aPath );
649
650 if( m_internals->contains( ptr ) )
651 {
652 try
653 {
654 return std::optional<nlohmann::json>{ m_internals->at( ptr ) };
655 }
656 catch( ... )
657 {
658 }
659 }
660
661 return std::optional<nlohmann::json>{};
662}
663
664
665template<typename ValueType>
666std::optional<ValueType> JSON_SETTINGS::Get( const std::string& aPath ) const
667{
668 if( std::optional<nlohmann::json> ret = GetJson( aPath ) )
669 {
670 try
671 {
672 return ret->get<ValueType>();
673 }
674 catch( ... )
675 {
676 }
677 }
678
679 return std::nullopt;
680}
681
682
683// Instantiate all required templates here to allow reducing scope of json.hpp
684template KICOMMON_API std::optional<bool>
685 JSON_SETTINGS::Get<bool>( const std::string& aPath ) const;
686template KICOMMON_API std::optional<double>
687 JSON_SETTINGS::Get<double>( const std::string& aPath ) const;
688template KICOMMON_API std::optional<float>
689 JSON_SETTINGS::Get<float>( const std::string& aPath ) const;
690template KICOMMON_API std::optional<int> JSON_SETTINGS::Get<int>( const std::string& aPath ) const;
691template KICOMMON_API std::optional<unsigned int>
692 JSON_SETTINGS::Get<unsigned int>( const std::string& aPath ) const;
693template KICOMMON_API std::optional<unsigned long long>
694 JSON_SETTINGS::Get<unsigned long long>( const std::string& aPath ) const;
695template KICOMMON_API std::optional<std::string>
696 JSON_SETTINGS::Get<std::string>( const std::string& aPath ) const;
697template KICOMMON_API std::optional<nlohmann::json>
698 JSON_SETTINGS::Get<nlohmann::json>( const std::string& aPath ) const;
699template KICOMMON_API std::optional<KIGFX::COLOR4D>
700 JSON_SETTINGS::Get<KIGFX::COLOR4D>( const std::string& aPath ) const;
701template KICOMMON_API std::optional<BOM_FIELD>
702 JSON_SETTINGS::Get<BOM_FIELD>( const std::string& aPath ) const;
703template KICOMMON_API std::optional<BOM_PRESET>
704 JSON_SETTINGS::Get<BOM_PRESET>( const std::string& aPath ) const;
705template KICOMMON_API std::optional<BOM_FMT_PRESET>
706 JSON_SETTINGS::Get<BOM_FMT_PRESET>( const std::string& aPath ) const;
707template KICOMMON_API std::optional<GRID>
708 JSON_SETTINGS::Get<GRID>( const std::string& aPath ) const;
709template KICOMMON_API std::optional<wxPoint>
710 JSON_SETTINGS::Get<wxPoint>( const std::string& aPath ) const;
711template KICOMMON_API std::optional<wxSize>
712 JSON_SETTINGS::Get<wxSize>( const std::string& aPath ) const;
713template KICOMMON_API std::optional<wxRect>
714 JSON_SETTINGS::Get<wxRect>( const std::string& aPath ) const;
715template KICOMMON_API std::optional<wxAuiPaneInfo>
716 JSON_SETTINGS::Get<wxAuiPaneInfo>( const std::string& aPath ) const;
717template KICOMMON_API std::optional<KIGFX::CROSS_HAIR_MODE>
718 JSON_SETTINGS::Get<KIGFX::CROSS_HAIR_MODE>( const std::string& aPath ) const;
719
720template<typename ValueType>
721void JSON_SETTINGS::Set( const std::string& aPath, ValueType aVal )
722{
723 m_internals->SetFromString( aPath, std::move( aVal ) );
724}
725
726
727// Instantiate all required templates here to allow reducing scope of json.hpp
728template KICOMMON_API void JSON_SETTINGS::Set<bool>( const std::string& aPath, bool aValue );
729template KICOMMON_API void JSON_SETTINGS::Set<double>( const std::string& aPath, double aValue );
730template KICOMMON_API void JSON_SETTINGS::Set<float>( const std::string& aPath, float aValue );
731template KICOMMON_API void JSON_SETTINGS::Set<int>( const std::string& aPath, int aValue );
732template KICOMMON_API void JSON_SETTINGS::Set<unsigned int>( const std::string& aPath,
733 unsigned int aValue );
734template KICOMMON_API void JSON_SETTINGS::Set<unsigned long long>( const std::string& aPath,
735 unsigned long long aValue );
736template KICOMMON_API void JSON_SETTINGS::Set<const char*>( const std::string& aPath,
737 const char* aValue );
738template KICOMMON_API void JSON_SETTINGS::Set<std::string>( const std::string& aPath,
739 std::string aValue );
740template KICOMMON_API void JSON_SETTINGS::Set<nlohmann::json>( const std::string& aPath,
741 nlohmann::json aValue );
742template KICOMMON_API void JSON_SETTINGS::Set<KIGFX::COLOR4D>( const std::string& aPath,
743 KIGFX::COLOR4D aValue );
744template KICOMMON_API void JSON_SETTINGS::Set<BOM_FIELD>( const std::string& aPath,
745 BOM_FIELD aValue );
746template KICOMMON_API void JSON_SETTINGS::Set<BOM_PRESET>( const std::string& aPath,
747 BOM_PRESET aValue );
748template KICOMMON_API void JSON_SETTINGS::Set<BOM_FMT_PRESET>( const std::string& aPath,
749 BOM_FMT_PRESET aValue );
750template KICOMMON_API void JSON_SETTINGS::Set<GRID>( const std::string& aPath, GRID aValue );
751template KICOMMON_API void JSON_SETTINGS::Set<wxPoint>( const std::string& aPath, wxPoint aValue );
752template KICOMMON_API void JSON_SETTINGS::Set<wxSize>( const std::string& aPath, wxSize aValue );
753template KICOMMON_API void JSON_SETTINGS::Set<wxRect>( const std::string& aPath, wxRect aValue );
754template KICOMMON_API void JSON_SETTINGS::Set<wxAuiPaneInfo>( const std::string& aPath,
755 wxAuiPaneInfo aValue );
756template KICOMMON_API void JSON_SETTINGS::Set<KIGFX::CROSS_HAIR_MODE>( const std::string& aPath,
757 KIGFX::CROSS_HAIR_MODE aValue );
758
759
760void JSON_SETTINGS::registerMigration( int aOldSchemaVersion, int aNewSchemaVersion,
761 std::function<bool()> aMigrator )
762{
763 wxASSERT( aNewSchemaVersion > aOldSchemaVersion );
764 wxASSERT( aNewSchemaVersion <= m_schemaVersion );
765 m_migrators[aOldSchemaVersion] = std::make_pair( aNewSchemaVersion, aMigrator );
766}
767
768
770{
771 int filever = m_internals->Get<int>( "meta.version" );
772
773 while( filever < m_schemaVersion )
774 {
775 wxASSERT( m_migrators.count( filever ) > 0 );
776
777 if( !m_migrators.count( filever ) )
778 {
779 wxLogTrace( traceSettings, wxT( "Migrator missing for %s version %d!" ),
780 typeid( *this ).name(),
781 filever );
782 return false;
783 }
784
785 std::pair<int, std::function<bool()>> pair = m_migrators.at( filever );
786
787 if( pair.second() )
788 {
789 wxLogTrace( traceSettings, wxT( "Migrated %s from %d to %d" ),
790 typeid( *this ).name(),
791 filever,
792 pair.first );
793 filever = pair.first;
794 m_internals->At( "meta.version" ) = filever;
795 }
796 else
797 {
798 wxLogTrace( traceSettings, wxT( "Migration failed for %s from %d to %d" ),
799 typeid( *this ).name(),
800 filever,
801 pair.first );
802 return false;
803 }
804 }
805
806 return true;
807}
808
809
810bool JSON_SETTINGS::MigrateFromLegacy( wxConfigBase* aLegacyConfig )
811{
812 wxLogTrace( traceSettings, wxT( "MigrateFromLegacy() not implemented for %s" ),
813 typeid( *this ).name() );
814 return false;
815}
816
817
818bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
819 wxString& aTarget )
820{
821 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
822
823 if( aObj.contains( ptr ) && aObj.at( ptr ).is_string() )
824 {
825 aTarget = aObj.at( ptr ).get<wxString>();
826 return true;
827 }
828
829 return false;
830}
831
832
833bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
834 bool& aTarget )
835{
836 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
837
838 if( aObj.contains( ptr ) && aObj.at( ptr ).is_boolean() )
839 {
840 aTarget = aObj.at( ptr ).get<bool>();
841 return true;
842 }
843
844 return false;
845}
846
847
848bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
849 int& aTarget )
850{
851 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
852
853 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_integer() )
854 {
855 aTarget = aObj.at( ptr ).get<int>();
856 return true;
857 }
858
859 return false;
860}
861
862
863bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
864 unsigned int& aTarget )
865{
866 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
867
868 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_unsigned() )
869 {
870 aTarget = aObj.at( ptr ).get<unsigned int>();
871 return true;
872 }
873
874 return false;
875}
876
877
878template<typename ValueType>
879bool JSON_SETTINGS::fromLegacy( wxConfigBase* aConfig, const std::string& aKey,
880 const std::string& aDest )
881{
882 ValueType val;
883
884 if( aConfig->Read( aKey, &val ) )
885 {
886 try
887 {
888 ( *m_internals )[aDest] = val;
889 }
890 catch( ... )
891 {
892 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacy!" ) );
893 return false;
894 }
895
896 return true;
897 }
898
899 return false;
900}
901
902
903// Explicitly declare these because we only support a few types anyway, and it means we can keep
904// wxConfig detail out of the header file
905template
906KICOMMON_API bool JSON_SETTINGS::fromLegacy<int>( wxConfigBase*, const std::string&,
907 const std::string& );
908
909template
910KICOMMON_API bool JSON_SETTINGS::fromLegacy<double>( wxConfigBase*, const std::string&,
911 const std::string& );
912
913template
914KICOMMON_API bool JSON_SETTINGS::fromLegacy<bool>( wxConfigBase*, const std::string&,
915 const std::string& );
916
917
918bool JSON_SETTINGS::fromLegacyString( wxConfigBase* aConfig, const std::string& aKey,
919 const std::string& aDest )
920{
921 wxString str;
922
923 if( aConfig->Read( aKey, &str ) )
924 {
925 try
926 {
927 ( *m_internals )[aDest] = str.ToUTF8();
928 }
929 catch( ... )
930 {
931 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyString!" ) );
932 return false;
933 }
934
935 return true;
936 }
937
938 return false;
939}
940
941
942bool JSON_SETTINGS::fromLegacyColor( wxConfigBase* aConfig, const std::string& aKey,
943 const std::string& aDest )
944{
945 wxString str;
946
947 if( aConfig->Read( aKey, &str ) )
948 {
949 KIGFX::COLOR4D color;
950 color.SetFromWxString( str );
951
952 try
953 {
954 nlohmann::json js = nlohmann::json::array( { color.r, color.g, color.b, color.a } );
955 ( *m_internals )[aDest] = std::move( js );
956 }
957 catch( ... )
958 {
959 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyColor!" ) );
960 return false;
961 }
962
963 return true;
964 }
965
966 return false;
967}
968
969
971{
972 wxLogTrace( traceSettings, wxT( "AddNestedSettings %s" ), aSettings->GetFilename() );
973 m_nested_settings.push_back( aSettings );
974}
975
976
978{
979 if( !aSettings || !m_manager )
980 return;
981
982 auto it = std::find_if( m_nested_settings.begin(), m_nested_settings.end(),
983 [&aSettings]( const JSON_SETTINGS* aPtr )
984 {
985 return aPtr == aSettings;
986 } );
987
988 if( it != m_nested_settings.end() )
989 {
990 wxLogTrace( traceSettings, wxT( "Flush and release %s" ), ( *it )->GetFilename() );
991
992 // Flush the nested state into the parent and propagate genuine dirtiness so a later parent
993 // save persists it; the nested object is gone by then, so this is the parent's only signal.
994 // Default-fill of params absent from an older file no longer reports modified, so releasing
995 // an unchanged nested setting on editor close does not falsely dirty the parent (#24402).
996 m_modified |= ( *it )->SaveToFile();
997 m_nested_settings.erase( it );
998 }
999
1000 aSettings->SetParent( nullptr );
1001}
1002
1003
1004// Specializations to allow conversion between wxString and std::string via JSON_SETTINGS API
1005template<>
1006std::optional<wxString> JSON_SETTINGS::Get( const std::string& aPath ) const
1007{
1008 if( std::optional<nlohmann::json> opt_json = GetJson( aPath ) )
1009 return wxString( opt_json->get<std::string>().c_str(), wxConvUTF8 );
1010
1011 return std::nullopt;
1012}
1013
1014
1015template<>
1016void JSON_SETTINGS::Set<wxString>( const std::string& aPath, wxString aVal )
1017{
1018 ( *m_internals )[aPath] = aVal.ToUTF8();
1019}
1020
1021
1022template<typename ResultType>
1023ResultType JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson, const std::string& aKey,
1024 ResultType aDefault )
1025{
1026 ResultType ret = std::move( aDefault );
1027
1028 try
1029 {
1030 if( aJson.contains( aKey ) )
1031 ret = aJson.at( aKey ).get<ResultType>();
1032 }
1033 catch( ... )
1034 {
1035 }
1036
1037 return ret;
1038}
1039
1040
1041template
1042KICOMMON_API std::string JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson,
1043 const std::string& aKey,
1044 std::string aDefault );
1045
1046
1047template
1048KICOMMON_API bool JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson,
1049 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:389
double g
Green component.
Definition color4d.h:390
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:392
double b
Blue component.
Definition color4d.h:391
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