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