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