Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
OptionsFile.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
12#include "Stroika/Foundation/Linguistics/MessageUtilities.h"
13#include "Stroika/Foundation/Streams/MemoryStream.h"
14
15#include "Variant/JSON/Reader.h"
16#include "Variant/JSON/Writer.h"
17#include "Variant/XML/Reader.h"
18#include "Variant/XML/Writer.h"
19#include "VariantValue.h"
20
21#include "OptionsFile.h"
22
23using std::byte;
24
25using namespace Stroika::Foundation;
28using namespace Stroika::Foundation::Memory;
29using namespace Stroika::Foundation::Streams;
30
31using Memory::BLOB;
32
33// Comment this in to turn on aggressive noisy DbgTrace in this module
34//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
35
36/*
37 ********************************************************************************
38 ***************** DataExchange::OptionsFile::LoggerMessage *********************
39 ********************************************************************************
40 */
41OptionsFile::LoggerMessage::LoggerMessage (Msg msg, const filesystem::path& fn, const optional<String>& details)
42 : fMsg{msg}
43 , fFileName{fn}
44 , fDetails{details}
45{
46}
47
48String OptionsFile::LoggerMessage::FormatMessage () const
49{
50 String details{};
51 if (fDetails) {
53 sb << "; "sv << *fDetails;
54 details = Linguistics::MessageUtilities::Manager::sThe.RemoveTrailingSentencePunctuation (sb).first;
55 }
56 switch (fMsg) {
57 case Msg::eSuccessfullyReadFile:
58 return "Successfully read configuration file {}."_f(fFileName);
59 case Msg::eFailedToWriteFile:
60 return "Failed to write file {}: {}."_f(fFileName, details);
61 case Msg::eFailedToReadFile:
62 return "Failed to read file {}: {}."_f(fFileName, details);
63 case Msg::eFailedToParseReadFile:
64 return "Error analyzing configuration file {} - using defaults{}."_f(fFileName, details);
65 case Msg::eFailedToParseReadFileBadFormat:
66 return "Error analyzing configuration file (because bad format) {} - using defaults{}."_f(fFileName, details);
67 case Msg::eFailedToCompareReadFile:
68 return "Failed to compare configuration file: {}{}."_f(fFileName, details);
69 case Msg::eWritingConfigFile_SoDefaultsEditable:
70 return "Writing configuration file {} because not found (and so defaults are more easily seen and editable){}."_f(fFileName, details);
71 case Msg::eWritingConfigFile_BecauseUpgraded:
72 return "Writing configuration file {} in a new location because the software has been upgraded{}."_f(fFileName, details);
73 case Msg::eWritingConfigFile_BecauseSomethingChanged:
74 return "Writing configuration file {} because something changed (e.g. a default, or field added/removed){}."_f(fFileName, details);
75 case Msg::eFailedToWriteInUseValues:
76 return "Failed to write default (in use) values to file: {}{}."_f(fFileName, details);
77 default:
79 return String{};
80 }
81}
82
83/*
84 ********************************************************************************
85 ************************** DataExchange::OptionsFile ***************************
86 ********************************************************************************
87 */
89 [] (const optional<Common::Version>& /*version*/, const VariantValue& rawVariantValue) -> VariantValue { return rawVariantValue; };
90
91const OptionsFile::LoggerType OptionsFile::kDefaultLogger = [] (const LoggerMessage& message) {
93 Logger::Priority priority = Logger::eError;
94 using Msg = OptionsFile::LoggerMessage::Msg;
95 switch (message.fMsg) {
96 case Msg::eSuccessfullyReadFile:
97 priority = Logger::eInfo;
98 break;
99 case Msg::eFailedToReadFile:
100 priority = Logger::eWarning; // could be just because new system, no file
101 break;
102 case Msg::eWritingConfigFile_SoDefaultsEditable:
103 case Msg::eWritingConfigFile_BecauseUpgraded:
104 case Msg::eWritingConfigFile_BecauseSomethingChanged:
105 priority = Logger::eInfo;
106 break;
107
108 case Msg::eFailedToParseReadFile:
109 case Msg::eFailedToParseReadFileBadFormat:
110 // Most likely very bad - as critical configuration data will be lost, and overwritten with 'defaults'
111 priority = Logger::eCriticalError;
112 break;
113 }
114 Logger::sThe.Log (priority, "{}"_f, message.FormatMessage ());
115};
116
117OptionsFile::ModuleNameToFileNameMapperType OptionsFile::mkFilenameMapper (const String& appName)
118{
119 return [appName] (const String& moduleName, const String& fileSuffix) -> filesystem::path {
120 return IO::FileSystem::WellKnownLocations::GetApplicationData () / appName.As<filesystem::path> () /
121 (moduleName + fileSuffix).As<filesystem::path> ();
122 };
123}
124
125const OptionsFile::ModuleNameToFileVersionMapperType OptionsFile::kDefaultModuleNameToFileVersionMapper =
126 [] ([[maybe_unused]] const String& /*moduleName*/) -> optional<Common::Version> {
127 return optional<Common::Version> (); // default to don't know
128};
129
130// Consider using XML by default when more mature
133
134OptionsFile::OptionsFile (const String& modName, const ObjectVariantMapper& mapper, ModuleDataUpgraderType moduleUpgrader,
135 ModuleNameToFileNameMapperType moduleNameToFileNameMapper, ModuleNameToFileVersionMapperType moduleNameToReadFileVersion,
136 LoggerType logger, Variant::Reader reader, Variant::Writer writer)
137 : OptionsFile{modName,
138 mapper,
139 moduleUpgrader,
140 moduleNameToFileNameMapper,
141 moduleNameToFileNameMapper,
142 moduleNameToReadFileVersion,
143 logger,
144 reader,
145 writer,
146 String{reader.GetDefaultFileSuffix ().value_or (""sv)}}
147{
148}
149
150OptionsFile::OptionsFile (const String& modName, const ObjectVariantMapper& mapper, ModuleDataUpgraderType moduleUpgrader,
151 ModuleNameToFileNameMapperType moduleNameToReadFileNameMapper, ModuleNameToFileNameMapperType moduleNameToWriteFileNameMapper,
152 ModuleNameToFileVersionMapperType moduleNameToReadFileVersion, LoggerType logger, Variant::Reader reader, Variant::Writer writer)
153 : OptionsFile{modName,
154 mapper,
155 moduleUpgrader,
156 moduleNameToReadFileNameMapper,
157 moduleNameToWriteFileNameMapper,
158 moduleNameToReadFileVersion,
159 logger,
160 reader,
161 writer,
162 String{reader.GetDefaultFileSuffix ().value_or (""sv)}}
163{
164}
165
166OptionsFile::OptionsFile (const String& modName, const ObjectVariantMapper& mapper, ModuleDataUpgraderType moduleUpgrader,
167 ModuleNameToFileNameMapperType moduleNameToReadFileNameMapper, ModuleNameToFileNameMapperType moduleNameToWriteFileNameMapper,
168 ModuleNameToFileVersionMapperType moduleNameToReadFileVersion, LoggerType logger, Variant::Reader reader,
169 Variant::Writer writer, const String& fileSuffix)
170 : fModuleName_{modName}
171 , fMapper_{mapper}
172 , fModuleDataUpgrader_{moduleUpgrader}
173 , fModuleNameToReadFileNameMapper_{moduleNameToReadFileNameMapper}
174 , fModuleNameToWriteFileNameMapper_{moduleNameToWriteFileNameMapper}
175 , fModuleNameToFileVersionMapper_{moduleNameToReadFileVersion}
176 , fLogger_{logger}
177 , fReader_{reader}
178 , fWriter_{writer}
179 , fFileSuffix_{fileSuffix}
180{
181}
182
184{
185 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("OptionsFile::ReadRaw", L"readfilename={}"_f, GetReadFilePath_ ())};
186 return IO::FileSystem::FileInputStream::New (GetReadFilePath_ ()).ReadAll ();
187}
188
189void OptionsFile::WriteRaw (const BLOB& blob)
190{
191 Debug::TraceContextBumper ctx{Stroika_Foundation_Debug_OptionalizeTraceArgs ("OptionsFile::WriteRaw", L"writefilename={}"_f, GetWriteFilePath_ ())};
192 if (GetReadFilePath_ () == GetWriteFilePath_ ()) {
193 try {
194 if (ReadRaw () == blob) {
195 return;
196 }
197 }
198 catch (...) {
199 // No matter why we fail, never-mind. Just fall through and write.
200 }
201 }
202 try {
203 IO::FileSystem::ThroughTmpFileWriter tmpFile{GetWriteFilePath_ ()};
204 IO::FileSystem::FileOutputStream::Ptr outStream = IO::FileSystem::FileOutputStream::New (tmpFile.GetFilePath ());
205 outStream.Write (blob);
206 outStream.Close (); // so any errors can be displayed as exceptions, and so closed before commit/rename
207 tmpFile.Commit ();
208 }
209 catch (...) {
210 fLogger_ (LoggerMessage{LoggerMessage::Msg::eFailedToWriteFile, GetWriteFilePath_ (), Characters::ToString (current_exception ())});
211 }
212}
213
214template <>
215optional<VariantValue> OptionsFile::Read ()
216{
217 Debug::TraceContextBumper ctx{"OptionsFile::Read"};
218 try {
219 optional<VariantValue> r = fReader_.Read (MemoryStream::New<byte> (ReadRaw ()));
220 if (r.has_value ()) {
221#if USE_NOISY_TRACE_IN_THIS_MODULE_
222 DbgTrace ("present: upgrading module {}"_f, fModuleName_);
223#endif
224 r = fModuleDataUpgrader_ (fModuleNameToFileVersionMapper_ (fModuleName_), *r);
225 }
226 fLogger_ (LoggerMessage{LoggerMessage::Msg::eSuccessfullyReadFile, GetReadFilePath_ ()});
227 return r;
228 }
229 catch (...) {
230#if USE_NOISY_TRACE_IN_THIS_MODULE_
231 DbgTrace ("exception");
232#endif
233 // @todo - check different exception cases and for some - like file not found - just no warning...
234 fLogger_ (LoggerMessage{LoggerMessage::Msg::eFailedToReadFile, GetReadFilePath_ (), Characters::ToString (current_exception ())});
235 return nullopt;
236 }
237}
238
239template <>
240void OptionsFile::Write (const VariantValue& optionsObject)
241{
242 Debug::TraceContextBumper ctx{"OptionsFile::Write"};
243 MemoryStream::Ptr<byte> tmp = MemoryStream::New<byte> ();
244 fWriter_.Write (optionsObject, tmp);
245 WriteRaw (tmp.As<BLOB> ());
246}
247
248filesystem::path OptionsFile::GetReadFilePath_ () const
249{
250 return fModuleNameToReadFileNameMapper_ (fModuleName_, fFileSuffix_);
251}
252
253filesystem::path OptionsFile::GetWriteFilePath_ () const
254{
255 return fModuleNameToWriteFileNameMapper_ (fModuleName_, fFileSuffix_);
256}
#define RequireNotReached()
Definition Assertions.h:385
#define DbgTrace
Definition Trace.h:309
#define Stroika_Foundation_Debug_OptionalizeTraceArgs(...)
Definition Trace.h:270
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
ObjectVariantMapper can be used to map C++ types to and from variant-union types, which can be transp...
nonvirtual void WriteRaw(const BLOB &blob)
static const Variant::Writer kDefaultWriter
function< optional< Common::Version >(const String &moduleName)> ModuleNameToFileVersionMapperType
nonvirtual void Write(const T &optionsObject)
static const ModuleDataUpgraderType kDefaultUpgrader
function< VariantValue(const optional< Common::Version > &version, const VariantValue &rawVariantValue)> ModuleDataUpgraderType
static const Variant::Reader kDefaultReader
abstract class specifying interface for readers that map a source like XML or JSON to a VariantValue ...
abstract class specifying interface for writers VariantValue objects to serialized formats like JSON,...
Definition Writer.h:37
nonvirtual void Write(const VariantValue &v, const Streams::OutputStream::Ptr< byte > &out) const
Definition Writer.inl:30
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
A simple/portable wrapper on syslog/log4j/WindowsEventlog, with features like throttling,...
Definition Logger.h:94
void Log(Priority logLevel, const wchar_t *format,...)
Definition Logger.inl:27
String ToString(T &&t, ARGS... args)
Return a debug-friendly, display version of the argument: not guaranteed parsable or usable except fo...
Definition ToString.inl:465
filesystem::path GetApplicationData(bool createIfNotPresent=true)