1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2020 Jon Evans <jon@craftyjon.com>
5 * Copyright (C) 2020-2021 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 <locale_io.h>
28 #include <gal/color4d.h>
29 #include <settings/json_settings.h>
30 #include <settings/json_settings_internals.h>
31 #include <settings/nested_settings.h>
32 #include <settings/parameters.h>
33 #include <wx/config.h>
34 #include <wx/debug.h>
35 #include <wx/fileconf.h>
36 #include <wx/filename.h>
37 #include <wx/log.h>
38 #include <wx/stdstream.h>
39 #include <wx/wfstream.h>
40
41 const wxChar* const traceSettings = wxT( "KICAD_SETTINGS" );
42
43
PointerFromString(std::string aPath)44 nlohmann::json::json_pointer JSON_SETTINGS_INTERNALS::PointerFromString( std::string aPath )
45 {
46 std::replace( aPath.begin(), aPath.end(), '.', '/' );
47 aPath.insert( 0, "/" );
48
49 nlohmann::json::json_pointer p;
50
51 try
52 {
53 p = nlohmann::json::json_pointer( aPath );
54 }
55 catch( ... )
56 {
57 wxASSERT_MSG( false, wxT( "Invalid pointer path in PointerFromString!" ) );
58 }
59
60 return p;
61 }
62
63
JSON_SETTINGS(const wxString & aFilename,SETTINGS_LOC aLocation,int aSchemaVersion,bool aCreateIfMissing,bool aCreateIfDefault,bool aWriteFile)64 JSON_SETTINGS::JSON_SETTINGS( const wxString& aFilename, SETTINGS_LOC aLocation,
65 int aSchemaVersion, bool aCreateIfMissing, bool aCreateIfDefault,
66 bool aWriteFile ) :
67 m_filename( aFilename ),
68 m_legacy_filename( "" ),
69 m_location( aLocation ),
70 m_createIfMissing( aCreateIfMissing ),
71 m_createIfDefault( aCreateIfDefault ),
72 m_writeFile( aWriteFile ),
73 m_deleteLegacyAfterMigration( true ),
74 m_resetParamsIfMissing( true ),
75 m_schemaVersion( aSchemaVersion ),
76 m_manager( nullptr )
77 {
78 m_internals = std::make_unique<JSON_SETTINGS_INTERNALS>();
79
80 try
81 {
82 m_internals->SetFromString( "meta.filename", GetFullFilename() );
83 }
84 catch( ... )
85 {
86 wxLogTrace( traceSettings, "Error: Could not create filename field for %s",
87 GetFullFilename() );
88 }
89
90
91 m_params.emplace_back(
92 new PARAM<int>( "meta.version", &m_schemaVersion, m_schemaVersion, true ) );
93 }
94
95
~JSON_SETTINGS()96 JSON_SETTINGS::~JSON_SETTINGS()
97 {
98 for( auto param: m_params )
99 delete param;
100
101 m_params.clear();
102 }
103
104
GetFullFilename() const105 wxString JSON_SETTINGS::GetFullFilename() const
106 {
107 return wxString( m_filename + "." + getFileExt() );
108 }
109
110
At(const std::string & aPath)111 nlohmann::json& JSON_SETTINGS::At( const std::string& aPath )
112 {
113 return m_internals->At( aPath );
114 }
115
116
Contains(const std::string & aPath) const117 bool JSON_SETTINGS::Contains( const std::string& aPath ) const
118 {
119 return m_internals->contains( JSON_SETTINGS_INTERNALS::PointerFromString( aPath ) );
120 }
121
122
Count(const std::string & aPath) const123 size_t JSON_SETTINGS::Count( const std::string& aPath ) const
124 {
125 return m_internals->count( JSON_SETTINGS_INTERNALS::PointerFromString( aPath ) );
126 }
127
128
Internals()129 JSON_SETTINGS_INTERNALS* JSON_SETTINGS::Internals()
130 {
131 return m_internals.get();
132 }
133
134
Load()135 void JSON_SETTINGS::Load()
136 {
137 for( auto param : m_params )
138 {
139 try
140 {
141 param->Load( this, m_resetParamsIfMissing );
142 }
143 catch( ... )
144 {
145 // Skip unreadable parameters in file
146 wxLogTrace( traceSettings, "param '%s' load err", param->GetJsonPath().c_str() );
147 }
148 }
149 }
150
151
LoadFromFile(const wxString & aDirectory)152 bool JSON_SETTINGS::LoadFromFile( const wxString& aDirectory )
153 {
154 // First, load all params to default values
155 m_internals->clear();
156 Load();
157
158 bool success = true;
159 bool migrated = false;
160 bool legacy_migrated = false;
161
162 LOCALE_IO locale;
163
164 auto migrateFromLegacy = [&] ( wxFileName& aPath ) {
165 // Backup and restore during migration so that the original can be mutated if convenient
166 bool backed_up = false;
167 wxFileName temp;
168
169 if( aPath.IsDirWritable() )
170 {
171 temp.AssignTempFileName( aPath.GetFullPath() );
172
173 if( !wxCopyFile( aPath.GetFullPath(), temp.GetFullPath() ) )
174 {
175 wxLogTrace( traceSettings, "%s: could not create temp file for migration",
176 GetFullFilename() );
177 }
178 else
179 backed_up = true;
180 }
181
182 // Silence popups if legacy file is read-only
183 wxLogNull doNotLog;
184
185 wxConfigBase::DontCreateOnDemand();
186 auto cfg = std::make_unique<wxFileConfig>( wxT( "" ), wxT( "" ), aPath.GetFullPath() );
187
188 // If migrate fails or is not implemented, fall back to built-in defaults that were
189 // already loaded above
190 if( !MigrateFromLegacy( cfg.get() ) )
191 {
192 wxLogTrace( traceSettings,
193 "%s: migrated; not all settings were found in legacy file",
194 GetFullFilename() );
195 }
196 else
197 {
198 wxLogTrace( traceSettings, "%s: migrated from legacy format", GetFullFilename() );
199 }
200
201 if( backed_up )
202 {
203 cfg.reset();
204
205 if( !wxCopyFile( temp.GetFullPath(), aPath.GetFullPath() ) )
206 {
207 wxLogTrace( traceSettings,
208 "migrate; copy temp file %s to %s failed",
209 temp.GetFullPath(), aPath.GetFullPath() );
210 }
211
212 if( !wxRemoveFile( temp.GetFullPath() ) )
213 {
214 wxLogTrace( traceSettings,
215 "migrate; failed to remove temp file %s",
216 temp.GetFullPath() );
217 }
218 }
219
220 // Either way, we want to clean up the old file afterwards
221 legacy_migrated = true;
222 };
223
224 wxFileName path;
225
226 if( aDirectory.empty() )
227 {
228 path.Assign( m_filename );
229 path.SetExt( getFileExt() );
230 }
231 else
232 {
233 wxString dir( aDirectory );
234 path.Assign( dir, m_filename, getFileExt() );
235 }
236
237 if( !path.Exists() )
238 {
239 // Case 1: legacy migration, no .json extension yet
240 path.SetExt( getLegacyFileExt() );
241
242 if( path.Exists() )
243 {
244 migrateFromLegacy( path );
245 }
246 // Case 2: legacy filename is different from new one
247 else if( !m_legacy_filename.empty() )
248 {
249 path.SetName( m_legacy_filename );
250
251 if( path.Exists() )
252 migrateFromLegacy( path );
253 }
254 else
255 {
256 success = false;
257 }
258 }
259 else
260 {
261 if( !path.IsFileWritable() )
262 m_writeFile = false;
263
264 try
265 {
266 wxFFileInputStream fp( path.GetFullPath(), wxT( "rt" ) );
267 wxStdInputStream fstream( fp );
268
269 if( fp.IsOk() )
270 {
271 *static_cast<nlohmann::json*>( m_internals.get() ) =
272 nlohmann::json::parse( fstream, nullptr,
273 /* allow_exceptions = */ true,
274 /* ignore_comments = */ true );
275
276 // If parse succeeds, check if schema migration is required
277 int filever = -1;
278
279 try
280 {
281 filever = m_internals->Get<int>( "meta.version" );
282 }
283 catch( ... )
284 {
285 wxLogTrace( traceSettings, "%s: file version could not be read!",
286 GetFullFilename() );
287 success = false;
288 }
289
290 if( filever >= 0 && filever < m_schemaVersion )
291 {
292 wxLogTrace( traceSettings, "%s: attempting migration from version %d to %d",
293 GetFullFilename(), filever, m_schemaVersion );
294
295 if( Migrate() )
296 {
297 migrated = true;
298 }
299 else
300 {
301 wxLogTrace( traceSettings, "%s: migration failed!", GetFullFilename() );
302 }
303 }
304 else if( filever > m_schemaVersion )
305 {
306 wxLogTrace( traceSettings,
307 "%s: warning: file version %d is newer than latest (%d)",
308 GetFullFilename(), filever, m_schemaVersion );
309 }
310 }
311 else
312 {
313 wxLogTrace( traceSettings, "%s exists but can't be opened for read",
314 GetFullFilename() );
315 }
316 }
317 catch( nlohmann::json::parse_error& error )
318 {
319 wxLogTrace( traceSettings, "Json parse error reading %s: %s",
320 path.GetFullPath(), error.what() );
321 wxLogTrace( traceSettings, "Attempting migration in case file is in legacy format" );
322 migrateFromLegacy( path );
323 }
324 }
325
326 // Now that we have new data in the JSON structure, load the params again
327 Load();
328
329 // And finally load any nested settings
330 for( auto settings : m_nested_settings )
331 settings->LoadFromFile();
332
333 wxLogTrace( traceSettings, "Loaded <%s> with schema %d", GetFullFilename(), m_schemaVersion );
334
335 // If we migrated, clean up the legacy file (with no extension)
336 if( legacy_migrated || migrated )
337 {
338 if( legacy_migrated && m_deleteLegacyAfterMigration && !wxRemoveFile( path.GetFullPath() ) )
339 {
340 wxLogTrace( traceSettings, "Warning: could not remove legacy file %s",
341 path.GetFullPath() );
342 }
343
344 // And write-out immediately so that we don't lose data if the program later crashes.
345 if( m_deleteLegacyAfterMigration )
346 SaveToFile( aDirectory, true );
347 }
348
349 return success;
350 }
351
352
Store()353 bool JSON_SETTINGS::Store()
354 {
355 bool modified = false;
356
357 for( auto param : m_params )
358 {
359 modified |= !param->MatchesFile( this );
360 param->Store( this );
361 }
362
363 return modified;
364 }
365
366
ResetToDefaults()367 void JSON_SETTINGS::ResetToDefaults()
368 {
369 for( auto param : m_params )
370 param->SetDefault();
371 }
372
373
SaveToFile(const wxString & aDirectory,bool aForce)374 bool JSON_SETTINGS::SaveToFile( const wxString& aDirectory, bool aForce )
375 {
376 if( !m_writeFile )
377 return false;
378
379 // Default PROJECT won't have a filename set
380 if( m_filename.IsEmpty() )
381 return false;
382
383 wxFileName path;
384
385 if( aDirectory.empty() )
386 {
387 path.Assign( m_filename );
388 path.SetExt( getFileExt() );
389 }
390 else
391 {
392 wxString dir( aDirectory );
393 path.Assign( dir, m_filename, getFileExt() );
394 }
395
396 if( !m_createIfMissing && !path.FileExists() )
397 {
398 wxLogTrace( traceSettings,
399 "File for %s doesn't exist and m_createIfMissing == false; not saving",
400 GetFullFilename() );
401 return false;
402 }
403
404 // Ensure the path exists, and create it if not.
405 if( !path.DirExists() && !path.Mkdir() )
406 {
407 wxLogTrace( traceSettings, "Warning: could not create path %s, can't save %s",
408 path.GetPath(), GetFullFilename() );
409 return false;
410 }
411
412 if( ( path.FileExists() && !path.IsFileWritable() ) ||
413 ( !path.FileExists() && !path.IsDirWritable() ) )
414 {
415 wxLogTrace( traceSettings, "File for %s is read-only; not saving", GetFullFilename() );
416 return false;
417 }
418
419 bool modified = false;
420
421 for( auto settings : m_nested_settings )
422 modified |= settings->SaveToFile();
423
424 modified |= Store();
425
426 if( !modified && !aForce && path.FileExists() )
427 {
428 wxLogTrace( traceSettings, "%s contents not modified, skipping save", GetFullFilename() );
429 return false;
430 }
431 else if( !modified && !aForce && !m_createIfDefault )
432 {
433 wxLogTrace( traceSettings,
434 "%s contents still default and m_createIfDefault == false; not saving",
435 GetFullFilename() );
436 return false;
437 }
438
439 wxLogTrace( traceSettings, "Saving %s", GetFullFilename() );
440
441 LOCALE_IO dummy;
442 bool success = true;
443
444 try
445 {
446 std::stringstream buffer;
447 buffer << std::setw( 2 ) << *m_internals << std::endl;
448
449 wxFFileOutputStream fileStream( path.GetFullPath(), "wb" );
450
451 if( !fileStream.IsOk()
452 || !fileStream.WriteAll( buffer.str().c_str(), buffer.str().size() ) )
453 {
454 wxLogTrace( traceSettings, "Warning: could not save %s", GetFullFilename() );
455 success = false;
456 }
457 }
458 catch( nlohmann::json::exception& error )
459 {
460 wxLogTrace( traceSettings, "Catch error: could not save %s. Json error %s",
461 GetFullFilename(), error.what() );
462 success = false;
463 }
464 catch( ... )
465 {
466 wxLogTrace( traceSettings, "Error: could not save %s." );
467 success = false;
468 }
469
470 return success;
471 }
472
473
GetJson(const std::string & aPath) const474 OPT<nlohmann::json> JSON_SETTINGS::GetJson( const std::string& aPath ) const
475 {
476 nlohmann::json::json_pointer ptr = m_internals->PointerFromString( aPath );
477
478 if( m_internals->contains( ptr ) )
479 {
480 try
481 {
482 return OPT<nlohmann::json>{ m_internals->at( ptr ) };
483 }
484 catch( ... )
485 {
486 }
487 }
488
489 return OPT<nlohmann::json>{};
490 }
491
492
493 template<typename ValueType>
Get(const std::string & aPath) const494 OPT<ValueType> JSON_SETTINGS::Get( const std::string& aPath ) const
495 {
496 if( OPT<nlohmann::json> ret = GetJson( aPath ) )
497 {
498 try
499 {
500 return ret->get<ValueType>();
501 }
502 catch( ... )
503 {
504 }
505 }
506
507 return NULLOPT;
508 }
509
510
511 // Instantiate all required templates here to allow reducing scope of json.hpp
512 template OPT<bool> JSON_SETTINGS::Get<bool>( const std::string& aPath ) const;
513 template OPT<double> JSON_SETTINGS::Get<double>( const std::string& aPath ) const;
514 template OPT<float> JSON_SETTINGS::Get<float>( const std::string& aPath ) const;
515 template OPT<int> JSON_SETTINGS::Get<int>( const std::string& aPath ) const;
516 template OPT<unsigned int> JSON_SETTINGS::Get<unsigned int>( const std::string& aPath ) const;
517 template OPT<unsigned long long> JSON_SETTINGS::Get<unsigned long long>( const std::string& aPath ) const;
518 template OPT<std::string> JSON_SETTINGS::Get<std::string>( const std::string& aPath ) const;
519 template OPT<nlohmann::json> JSON_SETTINGS::Get<nlohmann::json>( const std::string& aPath ) const;
520 template OPT<KIGFX::COLOR4D> JSON_SETTINGS::Get<KIGFX::COLOR4D>( const std::string& aPath ) const;
521
522
523 template<typename ValueType>
Set(const std::string & aPath,ValueType aVal)524 void JSON_SETTINGS::Set( const std::string& aPath, ValueType aVal )
525 {
526 m_internals->SetFromString( aPath, aVal );
527 }
528
529
530 // Instantiate all required templates here to allow reducing scope of json.hpp
531 template void JSON_SETTINGS::Set<bool>( const std::string& aPath, bool aValue );
532 template void JSON_SETTINGS::Set<double>( const std::string& aPath, double aValue );
533 template void JSON_SETTINGS::Set<float>( const std::string& aPath, float aValue );
534 template void JSON_SETTINGS::Set<int>( const std::string& aPath, int aValue );
535 template void JSON_SETTINGS::Set<unsigned int>( const std::string& aPath, unsigned int aValue );
536 template void JSON_SETTINGS::Set<unsigned long long>( const std::string& aPath, unsigned long long aValue );
537 template void JSON_SETTINGS::Set<const char*>( const std::string& aPath, const char* aValue );
538 template void JSON_SETTINGS::Set<std::string>( const std::string& aPath, std::string aValue );
539 template void JSON_SETTINGS::Set<nlohmann::json>( const std::string& aPath, nlohmann::json aValue );
540 template void JSON_SETTINGS::Set<KIGFX::COLOR4D>( const std::string& aPath, KIGFX::COLOR4D aValue );
541
542
registerMigration(int aOldSchemaVersion,int aNewSchemaVersion,std::function<bool ()> aMigrator)543 void JSON_SETTINGS::registerMigration( int aOldSchemaVersion, int aNewSchemaVersion,
544 std::function<bool()> aMigrator )
545 {
546 wxASSERT( aNewSchemaVersion > aOldSchemaVersion );
547 wxASSERT( aNewSchemaVersion <= m_schemaVersion );
548 m_migrators[aOldSchemaVersion] = std::make_pair( aNewSchemaVersion, aMigrator );
549 }
550
551
Migrate()552 bool JSON_SETTINGS::Migrate()
553 {
554 int filever = m_internals->Get<int>( "meta.version" );
555
556 while( filever < m_schemaVersion )
557 {
558 if( !m_migrators.count( filever ) )
559 {
560 wxLogTrace( traceSettings, "Migrator missing for %s version %d!",
561 typeid( *this ).name(), filever );
562 return false;
563 }
564
565 std::pair<int, std::function<bool()>> pair = m_migrators.at( filever );
566
567 if( pair.second() )
568 {
569 wxLogTrace( traceSettings, "Migrated %s from %d to %d", typeid( *this ).name(),
570 filever, pair.first );
571 filever = pair.first;
572 m_internals->At( "meta.version" ) = filever;
573 }
574 else
575 {
576 wxLogTrace( traceSettings, "Migration failed for %s from %d to %d",
577 typeid( *this ).name(), filever, pair.first );
578 return false;
579 }
580 }
581
582 return true;
583 }
584
585
MigrateFromLegacy(wxConfigBase * aLegacyConfig)586 bool JSON_SETTINGS::MigrateFromLegacy( wxConfigBase* aLegacyConfig )
587 {
588 wxLogTrace( traceSettings,
589 "MigrateFromLegacy() not implemented for %s", typeid( *this ).name() );
590 return false;
591 }
592
593
SetIfPresent(const nlohmann::json & aObj,const std::string & aPath,wxString & aTarget)594 bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
595 wxString& aTarget )
596 {
597 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
598
599 if( aObj.contains( ptr ) && aObj.at( ptr ).is_string() )
600 {
601 aTarget = aObj.at( ptr ).get<wxString>();
602 return true;
603 }
604
605 return false;
606 }
607
608
SetIfPresent(const nlohmann::json & aObj,const std::string & aPath,bool & aTarget)609 bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
610 bool& aTarget )
611 {
612 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
613
614 if( aObj.contains( ptr ) && aObj.at( ptr ).is_boolean() )
615 {
616 aTarget = aObj.at( ptr ).get<bool>();
617 return true;
618 }
619
620 return false;
621 }
622
623
SetIfPresent(const nlohmann::json & aObj,const std::string & aPath,int & aTarget)624 bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
625 int& aTarget )
626 {
627 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
628
629 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_integer() )
630 {
631 aTarget = aObj.at( ptr ).get<int>();
632 return true;
633 }
634
635 return false;
636 }
637
638
SetIfPresent(const nlohmann::json & aObj,const std::string & aPath,unsigned int & aTarget)639 bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
640 unsigned int& aTarget )
641 {
642 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
643
644 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_unsigned() )
645 {
646 aTarget = aObj.at( ptr ).get<unsigned int>();
647 return true;
648 }
649
650 return false;
651 }
652
653
654 template<typename ValueType>
fromLegacy(wxConfigBase * aConfig,const std::string & aKey,const std::string & aDest)655 bool JSON_SETTINGS::fromLegacy( wxConfigBase* aConfig, const std::string& aKey,
656 const std::string& aDest )
657 {
658 ValueType val;
659
660 if( aConfig->Read( aKey, &val ) )
661 {
662 try
663 {
664 ( *m_internals )[aDest] = val;
665 }
666 catch( ... )
667 {
668 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacy!" ) );
669 return false;
670 }
671
672 return true;
673 }
674
675 return false;
676 }
677
678
679 // Explicitly declare these because we only support a few types anyway, and it means we can keep
680 // wxConfig detail out of the header file
681 template bool JSON_SETTINGS::fromLegacy<int>( wxConfigBase*, const std::string&,
682 const std::string& );
683
684 template bool JSON_SETTINGS::fromLegacy<double>( wxConfigBase*, const std::string&,
685 const std::string& );
686
687 template bool JSON_SETTINGS::fromLegacy<bool>( wxConfigBase*, const std::string&,
688 const std::string& );
689
690
fromLegacyString(wxConfigBase * aConfig,const std::string & aKey,const std::string & aDest)691 bool JSON_SETTINGS::fromLegacyString( wxConfigBase* aConfig, const std::string& aKey,
692 const std::string& aDest )
693 {
694 wxString str;
695
696 if( aConfig->Read( aKey, &str ) )
697 {
698 try
699 {
700 ( *m_internals )[aDest] = str.ToUTF8();
701 }
702 catch( ... )
703 {
704 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyString!" ) );
705 return false;
706 }
707
708 return true;
709 }
710
711 return false;
712 }
713
714
fromLegacyColor(wxConfigBase * aConfig,const std::string & aKey,const std::string & aDest)715 bool JSON_SETTINGS::fromLegacyColor( wxConfigBase* aConfig, const std::string& aKey,
716 const std::string& aDest )
717 {
718 wxString str;
719
720 if( aConfig->Read( aKey, &str ) )
721 {
722 KIGFX::COLOR4D color;
723 color.SetFromWxString( str );
724
725 try
726 {
727 nlohmann::json js = nlohmann::json::array( { color.r, color.g, color.b, color.a } );
728 ( *m_internals )[aDest] = js;
729 }
730 catch( ... )
731 {
732 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyColor!" ) );
733 return false;
734 }
735
736 return true;
737 }
738
739 return false;
740 }
741
742
AddNestedSettings(NESTED_SETTINGS * aSettings)743 void JSON_SETTINGS::AddNestedSettings( NESTED_SETTINGS* aSettings )
744 {
745 wxLogTrace( traceSettings, "AddNestedSettings %s", aSettings->GetFilename() );
746 m_nested_settings.push_back( aSettings );
747 }
748
749
ReleaseNestedSettings(NESTED_SETTINGS * aSettings)750 void JSON_SETTINGS::ReleaseNestedSettings( NESTED_SETTINGS* aSettings )
751 {
752 if( !aSettings )
753 return;
754
755 auto it = std::find_if( m_nested_settings.begin(), m_nested_settings.end(),
756 [&aSettings]( const JSON_SETTINGS* aPtr ) {
757 return aPtr == aSettings;
758 } );
759
760 if( it != m_nested_settings.end() )
761 {
762 wxLogTrace( traceSettings, "Flush and release %s", ( *it )->GetFilename() );
763 ( *it )->SaveToFile();
764 m_nested_settings.erase( it );
765 }
766
767 aSettings->SetParent( nullptr );
768 }
769
770
771 // Specializations to allow conversion between wxString and std::string via JSON_SETTINGS API
772
Get(const std::string & aPath) const773 template<> OPT<wxString> JSON_SETTINGS::Get( const std::string& aPath ) const
774 {
775 if( OPT<nlohmann::json> opt_json = GetJson( aPath ) )
776 return wxString( opt_json->get<std::string>().c_str(), wxConvUTF8 );
777
778 return NULLOPT;
779 }
780
781
Set(const std::string & aPath,wxString aVal)782 template<> void JSON_SETTINGS::Set<wxString>( const std::string& aPath, wxString aVal )
783 {
784 ( *m_internals )[aPath] = aVal.ToUTF8();
785 }
786
787 // Specializations to allow directly reading/writing wxStrings from JSON
788
to_json(nlohmann::json & aJson,const wxString & aString)789 void to_json( nlohmann::json& aJson, const wxString& aString )
790 {
791 aJson = aString.ToUTF8();
792 }
793
794
from_json(const nlohmann::json & aJson,wxString & aString)795 void from_json( const nlohmann::json& aJson, wxString& aString )
796 {
797 aString = wxString( aJson.get<std::string>().c_str(), wxConvUTF8 );
798 }
799