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 for( PARAM_BASE* param: m_params )
106 delete param;
107
108 m_params.clear();
109}
110
111
113{
114 if( m_filename.AfterLast( '.' ) == getFileExt() )
115 return m_filename;
116
117 return wxString( m_filename + "." + getFileExt() );
118}
119
120
121nlohmann::json& JSON_SETTINGS::At( const std::string& aPath )
122{
123 return m_internals->At( aPath );
124}
125
126
127bool JSON_SETTINGS::Contains( const std::string& aPath ) const
128{
129 return m_internals->contains( JSON_SETTINGS_INTERNALS::PointerFromString( aPath ) );
130}
131
132
137
138
140{
141 for( PARAM_BASE* param : m_params )
142 {
143 try
144 {
145 param->Load( *this, m_resetParamsIfMissing );
146 }
147 catch( ... )
148 {
149 // Skip unreadable parameters in file
150 wxLogTrace( traceSettings, wxT( "param '%s' load err" ), param->GetJsonPath().c_str() );
151 }
152 }
153}
154
155
156bool JSON_SETTINGS::LoadFromFile( const wxString& aDirectory )
157{
158 // First, load all params to default values
159 m_internals->clear();
160 Load();
161
162 bool success = true;
163 bool migrated = false;
164 bool legacy_migrated = false;
165
166 LOCALE_IO locale;
167
168 auto migrateFromLegacy =
169 [&] ( wxFileName& aPath )
170 {
171 // Backup and restore during migration so that the original can be mutated if
172 // convenient
173 bool backed_up = false;
174 wxFileName temp;
175
176 if( aPath.IsDirWritable() )
177 {
178 temp.AssignTempFileName( aPath.GetFullPath() );
179
180 if( !wxCopyFile( aPath.GetFullPath(), temp.GetFullPath() ) )
181 {
182 wxLogTrace( traceSettings,
183 wxT( "%s: could not create temp file for migration" ),
184 GetFullFilename() );
185 }
186 else
187 {
188 backed_up = true;
189 }
190 }
191
192 // Silence popups if legacy file is read-only
193 wxLogNull doNotLog;
194
195 wxConfigBase::DontCreateOnDemand();
196 auto cfg = std::make_unique<wxFileConfig>( wxT( "" ), wxT( "" ),
197 aPath.GetFullPath() );
198
199 // If migrate fails or is not implemented, fall back to built-in defaults that
200 // were already loaded above
201 if( !MigrateFromLegacy( cfg.get() ) )
202 {
203 success = false;
204 wxLogTrace( traceSettings,
205 wxT( "%s: migrated; not all settings were found in legacy file" ),
206 GetFullFilename() );
207 }
208 else
209 {
210 success = true;
211 wxLogTrace( traceSettings, wxT( "%s: migrated from legacy format" ),
212 GetFullFilename() );
213 }
214
215 if( backed_up )
216 {
217 cfg.reset();
218
219 if( !wxCopyFile( temp.GetFullPath(), aPath.GetFullPath() ) )
220 {
221 wxLogTrace( traceSettings,
222 wxT( "migrate; copy temp file %s to %s failed" ),
223 temp.GetFullPath(),
224 aPath.GetFullPath() );
225 }
226
227 if( !wxRemoveFile( temp.GetFullPath() ) )
228 {
229 wxLogTrace( traceSettings,
230 wxT( "migrate; failed to remove temp file %s" ),
231 temp.GetFullPath() );
232 }
233 }
234
235 // Either way, we want to clean up the old file afterwards
236 legacy_migrated = true;
237 };
238
239 wxFileName path;
240
241 if( aDirectory.empty() )
242 {
243 path.Assign( m_filename );
244 path.SetExt( getFileExt() );
245 }
246 else
247 {
248 path.Assign( aDirectory, m_filename, getFileExt() );
249 }
250
251 if( !path.Exists() )
252 {
253 // Case 1: legacy migration, no .json extension yet
254 path.SetExt( getLegacyFileExt() );
255
256 if( path.Exists() )
257 {
258 migrateFromLegacy( path );
259 }
260 // Case 2: legacy filename is different from new one
261 else if( !m_legacy_filename.empty() )
262 {
263 path.SetName( m_legacy_filename );
264
265 if( path.Exists() )
266 migrateFromLegacy( path );
267 }
268 else
269 {
270 success = false;
271 }
272 }
273 else
274 {
275 if( !path.IsFileWritable() )
276 m_writeFile = false;
277
278 try
279 {
280 wxFFileInputStream fp( path.GetFullPath(), wxT( "rt" ) );
281 wxStdInputStream fstream( fp );
282
283 if( fp.IsOk() )
284 {
285 *static_cast<nlohmann::json*>( m_internals.get() ) =
286 nlohmann::json::parse( fstream, nullptr,
287 /* allow_exceptions = */ true,
288 /* ignore_comments = */ true );
289
290 // Save whatever we loaded, before doing any migration etc
291 m_internals->m_original = *static_cast<nlohmann::json*>( m_internals.get() );
292
293 // If parse succeeds, check if schema migration is required
294 int filever = -1;
295
296 try
297 {
298 filever = m_internals->Get<int>( "meta.version" );
299 }
300 catch( ... )
301 {
302 wxLogTrace( traceSettings, wxT( "%s: file version could not be read!" ),
303 GetFullFilename() );
304 success = false;
305 }
306
307 if( filever >= 0 && filever < m_schemaVersion )
308 {
309 wxLogTrace( traceSettings, wxT( "%s: attempting migration from version "
310 "%d to %d" ),
312 filever,
314
315 if( Migrate() )
316 {
317 migrated = true;
318 }
319 else
320 {
321 wxLogTrace( traceSettings, wxT( "%s: migration failed!" ),
322 GetFullFilename() );
323 }
324 }
325 else if( filever > m_schemaVersion )
326 {
327 wxLogTrace( traceSettings,
328 wxT( "%s: warning: file version %d is newer than latest (%d)" ),
330 filever,
332 m_isFutureFormat = true;
333 }
334 }
335 else
336 {
337 wxLogTrace( traceSettings, wxT( "%s exists but can't be opened for read" ),
338 GetFullFilename() );
339 }
340 }
341 catch( nlohmann::json::parse_error& error )
342 {
343 success = false;
344 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ),
345 path.GetFullPath(), error.what() );
346 wxLogTrace( traceSettings, wxT( "Attempting migration in case file is in legacy "
347 "format" ) );
348 migrateFromLegacy( path );
349 }
350 }
351
352 // Now that we have new data in the JSON structure, load the params again
353 Load();
354
355 // And finally load any nested settings
356 for( NESTED_SETTINGS* settings : m_nested_settings )
357 settings->LoadFromFile();
358
359 wxLogTrace( traceSettings, wxT( "Loaded <%s> with schema %d" ),
362
363 m_modified = false;
364
365 // A missing backing file leaves the store empty, which must not count as synchronized
366 if( success )
367 m_fileSynced = true;
368
369 // If we migrated, clean up the legacy file (with no extension). Save the migrated
370 // contents FIRST so that if the save fails we still have the legacy file on disk to
371 // fall back to -- otherwise a crash mid-migration leaves the user with neither copy.
372 if( m_writeFile && ( legacy_migrated || migrated ) )
373 {
375 {
376 bool savedMigrated = SaveToFile( aDirectory, true );
377
378 if( savedMigrated && legacy_migrated && !wxRemoveFile( path.GetFullPath() ) )
379 {
380 wxLogTrace( traceSettings, wxT( "Warning: could not remove legacy file %s" ),
381 path.GetFullPath() );
382 }
383 else if( !savedMigrated )
384 {
385 wxLogTrace( traceSettings,
386 wxT( "Migrated save of %s failed; leaving legacy file intact" ),
387 GetFullFilename() );
388 }
389 }
390 }
391
392 return success;
393}
394
395
397{
398 for( PARAM_BASE* param : m_params )
399 {
400 try
401 {
402 m_modified |= !param->MatchesFile( *this );
403 param->Store( this );
404 }
405 catch( const std::exception& e )
406 {
407 wxLogTrace( traceSettings, wxT( "param '%s' store err: %s" ),
408 param->GetJsonPath().c_str(), e.what() );
409 }
410 }
411
412 return m_modified;
413}
414
415
417{
418 for( PARAM_BASE* param : m_params )
419 param->SetDefault();
420}
421
422
423std::map<std::string, nlohmann::json> JSON_SETTINGS::GetFileHistories()
424{
425 std::map<std::string, nlohmann::json> histories;
426
427 for( const std::string& candidate : { std::string( "system.file_history" ) } )
428 {
429 if( Contains( candidate ) )
430 histories[candidate] = GetJson( candidate ).value();
431 }
432
433 return histories;
434}
435
436
437bool JSON_SETTINGS::SaveToFile( const wxString& aDirectory, bool aForce )
438{
439 if( !m_writeFile )
440 return false;
441
442 // Default PROJECT won't have a filename set
443 if( m_filename.IsEmpty() )
444 return false;
445
446 wxFileName path;
447
448 if( aDirectory.empty() )
449 {
450 path.Assign( m_filename );
451 path.SetExt( getFileExt() );
452 }
453 else
454 {
455 wxString dir( aDirectory );
456 path.Assign( dir, m_filename, getFileExt() );
457 }
458
459 if( !m_createIfMissing && !path.FileExists() )
460 {
461 wxLogTrace( traceSettings,
462 wxT( "File for %s doesn't exist and m_createIfMissing == false; not saving" ),
463 GetFullFilename() );
464 return false;
465 }
466
467 // Ensure the path exists, and create it if not.
468 if( !path.DirExists() && !path.Mkdir() )
469 {
470 wxLogTrace( traceSettings, wxT( "Warning: could not create path %s, can't save %s" ),
471 path.GetPath(), GetFullFilename() );
472 return false;
473 }
474
475 if( ( path.FileExists() && !path.IsFileWritable() ) ||
476 ( !path.FileExists() && !path.IsDirWritable() ) )
477 {
478 wxLogTrace( traceSettings, wxT( "File for %s is read-only; not saving" ),
479 GetFullFilename() );
480 return false;
481 }
482
483 bool modified = false;
484
485 for( NESTED_SETTINGS* settings : m_nested_settings )
486 {
487 wxCHECK2( settings, continue );
488
489 modified |= settings->SaveToFile();
490 }
491
492 modified |= Store();
493
494 if( !modified && !aForce && path.FileExists() )
495 {
496 wxLogTrace( traceSettings, wxT( "%s contents not modified, skipping save" ),
497 GetFullFilename() );
498 return false;
499 }
500 else if( !modified && !aForce && !m_createIfDefault )
501 {
502 wxLogTrace( traceSettings,
503 wxT( "%s contents still default and m_createIfDefault == false; not saving" ),
504 GetFullFilename() );
505 return false;
506 }
507
508 wxLogTrace( traceSettings, wxT( "Saving %s" ), GetFullFilename() );
509
511 bool success = true;
512
513 nlohmann::json toSave = m_internals->m_original;
514
515
516 for( PARAM_BASE* param : m_params )
517 {
518 if( param->ClearUnknownKeys() )
519 {
520 nlohmann::json_pointer p = JSON_SETTINGS_INTERNALS::PointerFromString( param->GetJsonPath() );
521
522 toSave[p] = nlohmann::json( {} );
523 }
524 }
525
526 toSave.update( m_internals->begin(), m_internals->end(), /* merge_objects = */ true );
527
528 try
529 {
530 std::stringstream buffer;
531 buffer << std::setw( 2 ) << toSave << std::endl;
532
533 std::string payload = buffer.str();
534 wxString writeError;
535
536 if( !KIPLATFORM::IO::AtomicWriteFile( path.GetFullPath(), payload.data(), payload.size(),
537 &writeError ) )
538 {
539 wxLogTrace( traceSettings, wxT( "Warning: could not save %s: %s" ), GetFullFilename(),
540 writeError );
541 success = false;
542 }
543 }
544 catch( nlohmann::json::exception& error )
545 {
546 wxLogTrace( traceSettings, wxT( "Catch error: could not save %s. Json error %s" ),
547 GetFullFilename(), error.what() );
548 success = false;
549 }
550 catch( ... )
551 {
552 wxLogTrace( traceSettings, wxT( "Error: could not save %s." ) );
553 success = false;
554 }
555
556 if( success )
557 {
558 m_modified = false;
559 m_fileSynced = true;
560 }
561
562 return success;
563}
564
565
567{
568 Store();
569
571
572 std::stringstream buffer;
573 buffer << std::setw( 2 ) << *m_internals << std::endl;
574
575 return buffer.str();
576}
577
578
579bool JSON_SETTINGS::LoadFromRawFile( const wxString& aPath )
580{
581 try
582 {
583 wxFFileInputStream fp( aPath, wxT( "rt" ) );
584 wxStdInputStream fstream( fp );
585
586 if( fp.IsOk() )
587 {
588 *static_cast<nlohmann::json*>( m_internals.get() ) =
589 nlohmann::json::parse( fstream, nullptr,
590 /* allow_exceptions = */ true,
591 /* ignore_comments = */ true );
592 }
593 else
594 {
595 return false;
596 }
597 }
598 catch( nlohmann::json::parse_error& error )
599 {
600 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ), aPath, error.what() );
601
602 return false;
603 }
604
605 // Now that we have new data in the JSON structure, load the params again
606 Load();
607 return true;
608}
609
610
611std::optional<nlohmann::json> JSON_SETTINGS::GetJson( const std::string& aPath ) const
612{
613 nlohmann::json::json_pointer ptr = m_internals->PointerFromString( aPath );
614
615 if( m_internals->contains( ptr ) )
616 {
617 try
618 {
619 return std::optional<nlohmann::json>{ m_internals->at( ptr ) };
620 }
621 catch( ... )
622 {
623 }
624 }
625
626 return std::optional<nlohmann::json>{};
627}
628
629
630template<typename ValueType>
631std::optional<ValueType> JSON_SETTINGS::Get( const std::string& aPath ) const
632{
633 if( std::optional<nlohmann::json> ret = GetJson( aPath ) )
634 {
635 try
636 {
637 return ret->get<ValueType>();
638 }
639 catch( ... )
640 {
641 }
642 }
643
644 return std::nullopt;
645}
646
647
648// Instantiate all required templates here to allow reducing scope of json.hpp
649template KICOMMON_API std::optional<bool>
650 JSON_SETTINGS::Get<bool>( const std::string& aPath ) const;
651template KICOMMON_API std::optional<double>
652 JSON_SETTINGS::Get<double>( const std::string& aPath ) const;
653template KICOMMON_API std::optional<float>
654 JSON_SETTINGS::Get<float>( const std::string& aPath ) const;
655template KICOMMON_API std::optional<int> JSON_SETTINGS::Get<int>( const std::string& aPath ) const;
656template KICOMMON_API std::optional<unsigned int>
657 JSON_SETTINGS::Get<unsigned int>( const std::string& aPath ) const;
658template KICOMMON_API std::optional<unsigned long long>
659 JSON_SETTINGS::Get<unsigned long long>( const std::string& aPath ) const;
660template KICOMMON_API std::optional<std::string>
661 JSON_SETTINGS::Get<std::string>( const std::string& aPath ) const;
662template KICOMMON_API std::optional<nlohmann::json>
663 JSON_SETTINGS::Get<nlohmann::json>( const std::string& aPath ) const;
664template KICOMMON_API std::optional<KIGFX::COLOR4D>
665 JSON_SETTINGS::Get<KIGFX::COLOR4D>( const std::string& aPath ) const;
666template KICOMMON_API std::optional<BOM_FIELD>
667 JSON_SETTINGS::Get<BOM_FIELD>( const std::string& aPath ) const;
668template KICOMMON_API std::optional<BOM_PRESET>
669 JSON_SETTINGS::Get<BOM_PRESET>( const std::string& aPath ) const;
670template KICOMMON_API std::optional<BOM_FMT_PRESET>
671 JSON_SETTINGS::Get<BOM_FMT_PRESET>( const std::string& aPath ) const;
672template KICOMMON_API std::optional<GRID>
673 JSON_SETTINGS::Get<GRID>( const std::string& aPath ) const;
674template KICOMMON_API std::optional<wxPoint>
675 JSON_SETTINGS::Get<wxPoint>( const std::string& aPath ) const;
676template KICOMMON_API std::optional<wxSize>
677 JSON_SETTINGS::Get<wxSize>( const std::string& aPath ) const;
678template KICOMMON_API std::optional<wxRect>
679 JSON_SETTINGS::Get<wxRect>( const std::string& aPath ) const;
680template KICOMMON_API std::optional<wxAuiPaneInfo>
681 JSON_SETTINGS::Get<wxAuiPaneInfo>( const std::string& aPath ) const;
682template KICOMMON_API std::optional<KIGFX::CROSS_HAIR_MODE>
683 JSON_SETTINGS::Get<KIGFX::CROSS_HAIR_MODE>( const std::string& aPath ) const;
684
685template<typename ValueType>
686void JSON_SETTINGS::Set( const std::string& aPath, ValueType aVal )
687{
688 m_internals->SetFromString( aPath, std::move( aVal ) );
689}
690
691
692// Instantiate all required templates here to allow reducing scope of json.hpp
693template KICOMMON_API void JSON_SETTINGS::Set<bool>( const std::string& aPath, bool aValue );
694template KICOMMON_API void JSON_SETTINGS::Set<double>( const std::string& aPath, double aValue );
695template KICOMMON_API void JSON_SETTINGS::Set<float>( const std::string& aPath, float aValue );
696template KICOMMON_API void JSON_SETTINGS::Set<int>( const std::string& aPath, int aValue );
697template KICOMMON_API void JSON_SETTINGS::Set<unsigned int>( const std::string& aPath,
698 unsigned int aValue );
699template KICOMMON_API void JSON_SETTINGS::Set<unsigned long long>( const std::string& aPath,
700 unsigned long long aValue );
701template KICOMMON_API void JSON_SETTINGS::Set<const char*>( const std::string& aPath,
702 const char* aValue );
703template KICOMMON_API void JSON_SETTINGS::Set<std::string>( const std::string& aPath,
704 std::string aValue );
705template KICOMMON_API void JSON_SETTINGS::Set<nlohmann::json>( const std::string& aPath,
706 nlohmann::json aValue );
707template KICOMMON_API void JSON_SETTINGS::Set<KIGFX::COLOR4D>( const std::string& aPath,
708 KIGFX::COLOR4D aValue );
709template KICOMMON_API void JSON_SETTINGS::Set<BOM_FIELD>( const std::string& aPath,
710 BOM_FIELD aValue );
711template KICOMMON_API void JSON_SETTINGS::Set<BOM_PRESET>( const std::string& aPath,
712 BOM_PRESET aValue );
713template KICOMMON_API void JSON_SETTINGS::Set<BOM_FMT_PRESET>( const std::string& aPath,
714 BOM_FMT_PRESET aValue );
715template KICOMMON_API void JSON_SETTINGS::Set<GRID>( const std::string& aPath, GRID aValue );
716template KICOMMON_API void JSON_SETTINGS::Set<wxPoint>( const std::string& aPath, wxPoint aValue );
717template KICOMMON_API void JSON_SETTINGS::Set<wxSize>( const std::string& aPath, wxSize aValue );
718template KICOMMON_API void JSON_SETTINGS::Set<wxRect>( const std::string& aPath, wxRect aValue );
719template KICOMMON_API void JSON_SETTINGS::Set<wxAuiPaneInfo>( const std::string& aPath,
720 wxAuiPaneInfo aValue );
721template KICOMMON_API void JSON_SETTINGS::Set<KIGFX::CROSS_HAIR_MODE>( const std::string& aPath,
722 KIGFX::CROSS_HAIR_MODE aValue );
723
724
725void JSON_SETTINGS::registerMigration( int aOldSchemaVersion, int aNewSchemaVersion,
726 std::function<bool()> aMigrator )
727{
728 wxASSERT( aNewSchemaVersion > aOldSchemaVersion );
729 wxASSERT( aNewSchemaVersion <= m_schemaVersion );
730 m_migrators[aOldSchemaVersion] = std::make_pair( aNewSchemaVersion, aMigrator );
731}
732
733
735{
736 int filever = m_internals->Get<int>( "meta.version" );
737
738 while( filever < m_schemaVersion )
739 {
740 wxASSERT( m_migrators.count( filever ) > 0 );
741
742 if( !m_migrators.count( filever ) )
743 {
744 wxLogTrace( traceSettings, wxT( "Migrator missing for %s version %d!" ),
745 typeid( *this ).name(),
746 filever );
747 return false;
748 }
749
750 std::pair<int, std::function<bool()>> pair = m_migrators.at( filever );
751
752 if( pair.second() )
753 {
754 wxLogTrace( traceSettings, wxT( "Migrated %s from %d to %d" ),
755 typeid( *this ).name(),
756 filever,
757 pair.first );
758 filever = pair.first;
759 m_internals->At( "meta.version" ) = filever;
760 }
761 else
762 {
763 wxLogTrace( traceSettings, wxT( "Migration failed for %s from %d to %d" ),
764 typeid( *this ).name(),
765 filever,
766 pair.first );
767 return false;
768 }
769 }
770
771 return true;
772}
773
774
775bool JSON_SETTINGS::MigrateFromLegacy( wxConfigBase* aLegacyConfig )
776{
777 wxLogTrace( traceSettings, wxT( "MigrateFromLegacy() not implemented for %s" ),
778 typeid( *this ).name() );
779 return false;
780}
781
782
783bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
784 wxString& aTarget )
785{
786 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
787
788 if( aObj.contains( ptr ) && aObj.at( ptr ).is_string() )
789 {
790 aTarget = aObj.at( ptr ).get<wxString>();
791 return true;
792 }
793
794 return false;
795}
796
797
798bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
799 bool& aTarget )
800{
801 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
802
803 if( aObj.contains( ptr ) && aObj.at( ptr ).is_boolean() )
804 {
805 aTarget = aObj.at( ptr ).get<bool>();
806 return true;
807 }
808
809 return false;
810}
811
812
813bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
814 int& aTarget )
815{
816 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
817
818 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_integer() )
819 {
820 aTarget = aObj.at( ptr ).get<int>();
821 return true;
822 }
823
824 return false;
825}
826
827
828bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
829 unsigned int& aTarget )
830{
831 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
832
833 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_unsigned() )
834 {
835 aTarget = aObj.at( ptr ).get<unsigned int>();
836 return true;
837 }
838
839 return false;
840}
841
842
843template<typename ValueType>
844bool JSON_SETTINGS::fromLegacy( wxConfigBase* aConfig, const std::string& aKey,
845 const std::string& aDest )
846{
847 ValueType val;
848
849 if( aConfig->Read( aKey, &val ) )
850 {
851 try
852 {
853 ( *m_internals )[aDest] = val;
854 }
855 catch( ... )
856 {
857 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacy!" ) );
858 return false;
859 }
860
861 return true;
862 }
863
864 return false;
865}
866
867
868// Explicitly declare these because we only support a few types anyway, and it means we can keep
869// wxConfig detail out of the header file
870template
871KICOMMON_API bool JSON_SETTINGS::fromLegacy<int>( wxConfigBase*, const std::string&,
872 const std::string& );
873
874template
875KICOMMON_API bool JSON_SETTINGS::fromLegacy<double>( wxConfigBase*, const std::string&,
876 const std::string& );
877
878template
879KICOMMON_API bool JSON_SETTINGS::fromLegacy<bool>( wxConfigBase*, const std::string&,
880 const std::string& );
881
882
883bool JSON_SETTINGS::fromLegacyString( wxConfigBase* aConfig, const std::string& aKey,
884 const std::string& aDest )
885{
886 wxString str;
887
888 if( aConfig->Read( aKey, &str ) )
889 {
890 try
891 {
892 ( *m_internals )[aDest] = str.ToUTF8();
893 }
894 catch( ... )
895 {
896 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyString!" ) );
897 return false;
898 }
899
900 return true;
901 }
902
903 return false;
904}
905
906
907bool JSON_SETTINGS::fromLegacyColor( wxConfigBase* aConfig, const std::string& aKey,
908 const std::string& aDest )
909{
910 wxString str;
911
912 if( aConfig->Read( aKey, &str ) )
913 {
914 KIGFX::COLOR4D color;
915 color.SetFromWxString( str );
916
917 try
918 {
919 nlohmann::json js = nlohmann::json::array( { color.r, color.g, color.b, color.a } );
920 ( *m_internals )[aDest] = std::move( js );
921 }
922 catch( ... )
923 {
924 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyColor!" ) );
925 return false;
926 }
927
928 return true;
929 }
930
931 return false;
932}
933
934
936{
937 wxLogTrace( traceSettings, wxT( "AddNestedSettings %s" ), aSettings->GetFilename() );
938 m_nested_settings.push_back( aSettings );
939}
940
941
943{
944 if( !aSettings || !m_manager )
945 return;
946
947 auto it = std::find_if( m_nested_settings.begin(), m_nested_settings.end(),
948 [&aSettings]( const JSON_SETTINGS* aPtr )
949 {
950 return aPtr == aSettings;
951 } );
952
953 if( it != m_nested_settings.end() )
954 {
955 wxLogTrace( traceSettings, wxT( "Flush and release %s" ), ( *it )->GetFilename() );
956 m_modified |= ( *it )->SaveToFile();
957 m_nested_settings.erase( it );
958 }
959
960 aSettings->SetParent( nullptr );
961}
962
963
964// Specializations to allow conversion between wxString and std::string via JSON_SETTINGS API
965template<>
966std::optional<wxString> JSON_SETTINGS::Get( const std::string& aPath ) const
967{
968 if( std::optional<nlohmann::json> opt_json = GetJson( aPath ) )
969 return wxString( opt_json->get<std::string>().c_str(), wxConvUTF8 );
970
971 return std::nullopt;
972}
973
974
975template<>
976void JSON_SETTINGS::Set<wxString>( const std::string& aPath, wxString aVal )
977{
978 ( *m_internals )[aPath] = aVal.ToUTF8();
979}
980
981
982template<typename ResultType>
983ResultType JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson, const std::string& aKey,
984 ResultType aDefault )
985{
986 ResultType ret = std::move( aDefault );
987
988 try
989 {
990 if( aJson.contains( aKey ) )
991 ret = aJson.at( aKey ).get<ResultType>();
992 }
993 catch( ... )
994 {
995 }
996
997 return ret;
998}
999
1000
1001template
1002KICOMMON_API std::string JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson,
1003 const std::string& aKey,
1004 std::string aDefault );
1005
1006
1007template
1008KICOMMON_API bool JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson,
1009 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