-
Notifications
You must be signed in to change notification settings - Fork 0
/
io.cpccSettings.h
313 lines (231 loc) · 11.5 KB
/
io.cpccSettings.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
/* *****************************************
* File: io.cpccSettings.h
* Version: see function getClassVersion()
* Purpose: Portable (cross-platform), light-weight library
* to save/load application settings from an INI-like file
* *****************************************
* Library: Cross Platform C++ Classes (cpcc)
* Copyright: 2013 StarMessage software.
* License: Free for opensource projects.
* Commercial license for closed source projects.
* Web: http://www.StarMessageSoftware.com/cpcclibrary
* https://github.com/starmessage/cpcc
* email: sales -at- starmessage.info
* *****************************************
*/
#pragma once
#include <string>
#include <map>
#include <assert.h>
#include <sstream>
#include <cmath>
#include "core.cpccIdeMacros.h"
#include "cpccUnicodeSupport.h"
#include "core.cpccKeyValue.h"
#include "cpccTesting.h"
#include "fs.cpccPathHelper.h"
#include "io.cpccFileSystemMini.h"
/** A small and portable (cross platform) C++ class
to save/load application settings from an INI-like file
The file is a variable=value text file.
No [sectionName] headings are used.
*/
/*
ini file location tests:
MAC OS X
info: The User Domain values accessed by NSUserDefaults are serialized to a file ~/Library/Preferences/application.plist.
user settings:
non-sandboxed:
/Users/cto/Library/Preferences/com.StarMessageSoftware.StarMessage.ini (ok)
/Users/cto/Library/Preferences/com.StarMessageSoftware.StarMessage/com.StarMessageSoftware.StarMessage.ini (ok)
sandboxed:
system settings (app-wide, like common APPDATA):
non-sandboxed:
/users/shared/com.StarMessageSoftware.StarMessage.ini (ok)
/Library/Preferences/com.StarMessageSoftware.StarMessage.ini (failed)
/Library/Preferences/com.StarMessageSoftware.StarMessage/ (failed to create folder)
If you need to create a directory in /Library/Application Support for your application to use then you need to do privilege elevation.
Applications in the App Store cannot use privilege elevation
sandboxed:
*/
// //////////////////////////////////////////////////////////////////////////////
//
// class cpccSettings declaration
//
// //////////////////////////////////////////////////////////////////////////////
class cpccSettings: public cpccKeyValue
{
private:
cpcc_string mFilename;
public: // class metadata and selftest
enum class settingsScope { scopeCurrentUser=0, scopeAllUsers };
public: // data
bool instantSaving = true;
bool m_needsSaving = false;
public: // ctors
// explicit cpccSettings(const settingsScope aScope=scopeCurrentUser);
explicit cpccSettings(const cpcc_char *aFilename);
virtual ~cpccSettings();
public: // functions
static cpcc_string getAutoFilename(const settingsScope aScope, const cpcc_char* aCompanyName, const cpcc_char* aAppName, const cpcc_char* aBundleID);
virtual void dataHasChanged(void) override;
cpcc_string getFilename(void) const { return mFilename; }
bool load(void);
bool save(void);
void pauseInstantSaving(void) { instantSaving = false; }
void resumeInstantSaving(void);
};
// //////////////////////////////////////////////////////////////////////////////
//
// class cpccSettings implementation
//
// //////////////////////////////////////////////////////////////////////////////
inline void cpccSettings::dataHasChanged(void)
{
m_needsSaving = true;
if (!instantSaving)
return;
if (!save())
cpcc_cerr << _T("Error #1352p: saving cpccSettings to file:") << mFilename << std::endl;
else
m_needsSaving = false;
}
// /////////////////////////////////////////////////////////////////////////////////////////////////
//
// class cpccPersistentVar
//
// /////////////////////////////////////////////////////////////////////////////////////////////////
template <typename T>
class cpccPersistentVar
{
private:
cpccSettings & mIniRef;
cpcc_string mKey;
const T mDefaultValue;
const cpcc_string createIndexedKey(const int index) const
{
cpcc_ostringstream s;
s << mKey << index;
return cpcc_string(s.str());
}
public:
explicit cpccPersistentVar(cpccSettings &aIniPtr, const cpcc_char *aKey, const T aDefaultValue):
mIniRef(aIniPtr),
mKey(aKey),
mDefaultValue(aDefaultValue)
{ }
// get value
inline const T getValue(void) const { return mIniRef.get(mKey.c_str(), mDefaultValue); }
inline operator const T(void) const { return mIniRef.get(mKey.c_str(), mDefaultValue); }
// T read(void) const { return mIniRef.read(mKey.c_str(), mDefaultValue); }
// T readAtIndex(const int index) const { return mIniRef.read(createIndexedKey(index).c_str(), mDefaultValue); }
inline const T operator[](const int index) const { return mIniRef.get(createIndexedKey(index).c_str(), mDefaultValue); }
// set value
inline void setValue(const T &aValue) { mIniRef.set(mKey.c_str(), aValue); }
// void operator=(T &aValue) const { mIniRef.write(mKey.c_str(), aValue); } // try a non-const version
void operator=(const T &aValue) const { mIniRef.set(mKey.c_str(), aValue); }
//void write(const T aValue) const { mIniRef.write(mKey.c_str(), aValue); }
void writeAtIndex(const int index, const T aValue) { mIniRef.set(createIndexedKey(index).c_str(), aValue); }
};
// //////////////////////////////////////////////////////////////////////////////
//
// class cpccSettings testing
//
// //////////////////////////////////////////////////////////////////////////////
TEST_RUN(cpccSettings_testNew)
{
const bool skipThisTest = false;
if (skipThisTest)
{
TEST_ADDNOTE("Test skipped");
return;
}
const double piFloat = 3.14159265359;
const float bigFloat = 13456798.43e9f;
const cpcc_char* tmpTestString = _T("abc-καλημέρα=good\n\rmorning to all.");
cpcc_string fnameCurrentUser(cpccSettings::getAutoFilename(cpccSettings::settingsScope::scopeCurrentUser, _T("Test Company Name"), _T("Test App Name"), _T("com.StarMessageSoftware.SelfTestBundleID")));
cpcc_string fnameAllUsers(cpccSettings::getAutoFilename(cpccSettings::settingsScope::scopeAllUsers, _T("Test Company Name"), _T("Test App Name"), _T("com.StarMessageSoftware.SelfTestBundleID")));
TEST_ADDNOTE(_T("ini file for current user: ") << fnameCurrentUser);
TEST_ADDNOTE(_T("ini file for all users: ") << fnameAllUsers);
// clean up
if (cpccFileSystemMini::fileExists(fnameCurrentUser.c_str()))
cpccFileSystemMini::deleteFile(fnameCurrentUser.c_str());
if (cpccFileSystemMini::fileExists(fnameAllUsers.c_str()))
cpccFileSystemMini::deleteFile(fnameAllUsers.c_str());
/*
cpcc_string folderCurrentUser = cpccPathHelper::getParentFolderOf(fnameCurrentUser);
if (cpccFileSystemMini::folderExists(folderCurrentUser.c_str()))
cpccFileSystemMini::deleteFolder(folderCurrentUser.c_str());
cpcc_string folderAllUsers = cpccPathHelper::getParentFolderOf(fnameCurrentUser);
if (cpccFileSystemMini::folderExists(folderAllUsers.c_str()))
cpccFileSystemMini::deleteFolder(folderAllUsers.c_str());
*/
{
// writing
cpccSettings settingsUser(fnameCurrentUser.c_str());
#ifndef OSX_SANDBOXED // define this is your app is Sandboxed for the OSX apple store
cpccSettings settingsApp(fnameAllUsers.c_str());
#else
cpccSettings& settingsApp = settingsUser;
#endif
settingsUser.set(_T("testStringKeyA"), _T("testStringValueA"));
settingsUser.set(_T("testStringKeyB"), _T("tmpValue"));
settingsUser.set(_T("testStringKeyB"), _T("B"));
settingsUser.set(_T("testTrueKey"), true);
settingsUser.set(_T("testFalseKey"), false);
settingsUser.set(_T("pi"), piFloat);
settingsUser.set(_T("twentythree"), 23);
settingsUser.set(_T("bigFloat"), bigFloat);
settingsApp.set(_T("AppSettingsOfSoftware"), _T("testSoftwareName"));
settingsApp.set(_T("extremeString"), tmpTestString);
cpccPersistentVar<int> tmpPersistentInt(settingsApp, _T("tmpPersInt"), 98);
tmpPersistentInt = 456;
tmpPersistentInt.writeAtIndex(3, 678);
}
//assert(cpccFileSystemMini::fileExists(fnameCurrentUser.c_str()) && _T("SelfTest #7712a: file does not exist"));
TEST_EXPECT(cpccFileSystemMini::fileExists(fnameCurrentUser.c_str()) , _T("SelfTest #7712a: file does not exist"));
#ifndef OSX_SANDBOXED
// the following fails under Catalina
TEST_EXPECT(cpccFileSystemMini::fileExists(fnameAllUsers.c_str()) , _T("SelfTest #7712b: file does not exist"));
#endif
if (true) // turn ON/OFF the reading tests
{
// separate reading
cpccSettings settingsUser(fnameCurrentUser.c_str());
#ifndef OSX_SANDBOXED
cpccSettings settingsSystem(fnameAllUsers.c_str());
#else
cpccSettings& settingsSystem = settingsUser;
#endif
cpcc_string tmp = settingsUser.get(_T("testStringKeyA"), _T("default"));
TEST_EXPECT(tmp.compare(_T("testStringValueA")) == 0 , _T("SelfTest #7711b: readString error"));
tmp = settingsUser.get(_T("NonExistingKey"), _T("default"));
TEST_EXPECT(tmp.compare(_T("default")) == 0 , _T("SelfTest #7711c: readString error on default value"));
tmp = settingsUser.get(_T("testStringKeyB"), _T("default"));
TEST_EXPECT(tmp.compare(_T("B")) == 0 , _T("SelfTest #7711d: writeString error: does not update values"));
TEST_EXPECT(settingsUser.get(_T("testTrueKey"), false) , _T("SelfTest #7711e: readBool error 1"));
TEST_EXPECT(!settingsUser.get(_T("testFalseKey"), true) , _T("SelfTest #7711e: readBool error 2"));
TEST_EXPECT(settingsUser.get(_T("testMissingKey1"), true) , _T("SelfTest #7711e: readBool error 3"));
TEST_EXPECT(!settingsUser.get(_T("testMissingKey2"), false) , _T("SelfTest #7711e: readBool error 4"));
TEST_EXPECT(settingsUser.get(_T("pi"), 1.0) == piFloat , _T("SelfTest #7711f: readReal"));
// todo: this has problems to solve
float aFloat = settingsUser.get(_T("bigFloat"), 2.0f);
aFloat -= bigFloat;
if (bigFloat != 0)
aFloat /= bigFloat;
//cpcc_cout << _T("aFloat read=") << aFloat << std::endl;
TEST_EXPECT((std::fabs(aFloat) < 0.000001f) , _T("SelfTest #7711k: readReal bigFloat"));
TEST_EXPECT(settingsUser.get(_T("twentythree"), 2) == 23 , _T("SelfTest #7711g: readLongint"));
cpccPersistentVar<int> tmpPersistentInt(settingsSystem, _T("tmpPersInt"), 92);
TEST_EXPECT((tmpPersistentInt == 456) , _T("SelfTest #7711r: tmpPersistentInt error 1"));
// assert((tmpPersistentInt.readAtIndex(3) == 678) && "SelfTest #7711j: tmpPersistentInt error 2");
TEST_EXPECT((tmpPersistentInt[3] == 678) , _T("SelfTest #7711j: tmpPersistentInt error 2"));
tmp = settingsSystem.get(_T("extremeString"), _T("----"));
TEST_EXPECT((tmp.compare(tmpTestString) == 0) , _T("SelfTest #7711w: readString error"));
}
// clean up
cpccFileSystemMini::deleteFile(fnameCurrentUser.c_str());
cpccFileSystemMini::deleteFile(fnameAllUsers.c_str());
// cpccFileSystemMini::deleteFolder(folderCurrentUser.c_str());
// cpccFileSystemMini::deleteFolder(folderAllUsers.c_str());
}