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