-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathio.cpccSettings.cpp
336 lines (253 loc) · 11.3 KB
/
io.cpccSettings.cpp
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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
/* *****************************************
* File: cpccSettings.cpp
* 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
* *****************************************
*/
#include <cassert>
#include <iostream>
#include <sstream>
#include <fstream>
#include <cerrno>
#include <locale>
#include <codecvt>
#include "core.cpccStringUtil.h"
#include "io.cpccSettings.h"
#include "fs.cpccSystemFolders.h" // todo: remove the system (and user folders) The INI class does not have somemthing to do with them.
#include "fs.cpccUserFolders.h"
/*
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 cSerialCodec declaration
//
// /////////////////////////////////////////////////////////////////////////////////////////////////
class cSerialCodec
{
private:
typedef cpcc_char* tEncodingsTable[5][2];
static const tEncodingsTable& encondingTable(void);
public:
static void encode(cpcc_string& str);
static void decode(cpcc_string& str);
};
// /////////////////////////////////////////////////////////////////////////////////////////////////
//
// class cSerialCodec implementation
//
// /////////////////////////////////////////////////////////////////////////////////////////////////
inline const cSerialCodec::tEncodingsTable& cSerialCodec::encondingTable(void)
{
// static variable
// encode characters to fit to the INI file (e.g. multiline text)
static const tEncodingsTable encodedINIcharacterTable =
{
// normal -> encoded
{ (cpcc_char*)_T("="), (cpcc_char*)_T("\\=") },
{ (cpcc_char*)_T("\n"), (cpcc_char*)_T("\\n") },
{ (cpcc_char*)_T("\r"), (cpcc_char*)_T("\\r") },
{ (cpcc_char*)_T("\t"), (cpcc_char*)_T("\\t") },
// this must be the last rule for the decoding and first rule for the encoding
{ (cpcc_char*)_T("\\"), (cpcc_char*)_T("\\\\") }
};
return encodedINIcharacterTable;
}
inline void cSerialCodec::encode(cpcc_string& str)
{
const tEncodingsTable& table = encondingTable();
// traver backwards
for (int i = (sizeof(table) / sizeof(table[0])) - 1; i >= 0; --i)
stringUtils::findAndReplaceAll(str, table[i][0], table[i][1]);
}
inline void cSerialCodec::decode(cpcc_string& str)
{
const tEncodingsTable& table = encondingTable();
for (unsigned int i = 0; i < sizeof(table) / sizeof(table[0]); ++i)
stringUtils::findAndReplaceAll(str, table[i][1], table[i][0]);
}
// /////////////////////////////////////////////////////////////////////////////////////////////////
// cpccSettings
// /////////////////////////////////////////////////////////////////////////////////////////////////
cpccSettings::cpccSettings(const cpcc_char *aFilename)
{
if (!aFilename)
return;
mFilename = aFilename;
cpccPathString folder(cpccPathHelper::getParentFolderOf(aFilename));
// make sure the folder for the INI file exists
if (! folder.pathExists())
if (!cpccFileSystemMini::createFolder(folder.c_str()))
cpcc_cerr << _T("#5813: During cpccSettings constructor could not create folder:") << folder << std::endl;
if (!folder.pathExists())
cpcc_cerr << _T("#7712i: folder for INI was not created") << std::endl;
if (!load())
cpcc_cerr << _T("Error #1351: loading cpccSettings from file:") << mFilename << std::endl;
}
cpcc_string cpccSettings::getAutoFilename(const settingsScope aScope, const cpcc_char* aCompanyName, const cpcc_char* aAppName, const cpcc_char* aBundleID)
{
cpccPathString fname(aScope== settingsScope::scopeAllUsers ? cpccSystemFolders::getCommonAppData() : cpccUserFolders::getUserData());
if (!cpccFileSystemMini::folderExists(fname.c_str()))
cpcc_cerr << _T("#5381: folder for saving the settings file does not exist") << std::endl;
#ifdef __APPLE__
/*
at this point, fname contains:
- "/users/shared" for scopeAllUsers,
- "~/Library/Preferences" for scopeCurrentUser
For the scopeAllUsers, we need to create a subfolder with the bundleID
*/
// fname("Preferences");
if (aScope==settingsScope::scopeAllUsers)
if (aBundleID)
if (cpcc_strlen(aBundleID)>0)
fname.appendPathSegment(aBundleID);
#else
if (aCompanyName)
if (cpcc_strlen(aCompanyName)>0)
fname.appendPathSegment(aCompanyName);
#endif
// now the fname contains the containing folder for the INI file.
// add the appName as part of the filename
// operator && evaluates left operand first
if (aAppName && (cpcc_strlen(aAppName)>0))
fname.appendPathSegment(aAppName);
else
fname.appendPathSegment(_T("no AppName cpccSettings error no.7511"));
fname.append(_T(".ini"));
return std::move(fname);
}
/*
cpccSettings::cpccSettings(const settingsScope aScope):
instantSaving(true)
{
// std::cout << "cpccSettings constructor\n";
assert(cpcc_strlen(config_getCompanyName())>0 && _T("#5351: cpccSettings: blank company name"));
assert(cpcc_strlen(config_getAppName())>0 && _T("#5351: cpccSettings: blank Software name"));
cpccPathString _settingsFilename(aScope==scopeAllUsers ? cpccSystemFolders::getFolder_CommonAppData() : cpccSystemFolders::getFolder_UserData());
assert(cpccFileSystemMini::folderExists(_settingsFilename.c_str()) && _T("#5381: folder for saving the settings file does not exist"));
#ifdef __APPLE__
// _settingsFilename.appendPathSegment("Preferences");
if (aScope==scopeAllUsers)
_settingsFilename.appendPathSegment(config_getAppBundleID());
#else
_settingsFilename.appendPathSegment(config_getCompanyName());
#endif
// make sure the folder for the INI file exists
if (! _settingsFilename.pathExists())
if (!cpccFileSystemMini::createFolder(_settingsFilename.c_str()))
cpcc_cerr << _T("During cpccSettings constructor could not create folder:") << _settingsFilename << _T("\n");
assert((_settingsFilename.pathExists()) && _T("#7712i: folder for INI was not created"));
_settingsFilename.appendPathSegment(config_getAppName());
_settingsFilename.append(_T(".ini"));
mFilename = _settingsFilename;
if (!load())
cpcc_cerr << _T("Error #1351: loading cpccSettings from file:") << mFilename << std::endl;
//std::cout << "cpccSettings construction point 2\n";
}
*/
cpccSettings::~cpccSettings()
{
if (m_needsSaving)
if (!save())
cpcc_cerr << _T("Error #1352: saving cpccSettings to file:") << mFilename << std::endl;
}
bool cpccSettings::load(void)
{
if (!cpccFileSystemMini::fileExists(mFilename.c_str()))
{
clear();
return true; // consider the INI loaded (empty file)
}
cpcc_ifstream iniFile(mFilename);
if (!iniFile.is_open())
{
cpcc_cerr << _T("#8551: Could not open file:") << mFilename << _T("\n");
return false;
}
// todo: is this needed for MacOS ?
std::locale my_utf8_locale(std::locale(), new std::codecvt_utf8<wchar_t>);
std::locale namedObject_loc = iniFile.imbue(my_utf8_locale); // namedObject_loc is to suppress warning C26444
// directly manipulate the map so that the save-to-file is not triggered
clear();
cpcc_string key, value;
// http://forums.codeguru.com/showthread.php?511066-RESOLVED-Is-it-possible-to-use-getline-with-unicode
while (getline(iniFile, key, _T('=')))
{
if (key.compare(0, 3, _T(UTF8_BOM)) == 0) // the file has a UTF-8 BOM
key.erase(0, 3); // Now get rid of the BOM.
getline(iniFile, value);
cSerialCodec::decode(value);
// set(key.c_str(), value);
// directly add into the map so that the save to file is not triggered
m_map[key] = value;
}
return true;
}
#ifdef _WIN32
std::string helper_to_utf8(const wchar_t* buffer, int len)
{
std::string result;
int nChars = ::WideCharToMultiByte( CP_UTF8, 0, buffer, len, NULL, 0, NULL, NULL);
if (nChars > 0)
{
result.resize(nChars);
// ::WideCharToMultiByte( CP_UTF8, 0, buffer, len, const_cast<char*>(result.c_str()), nChars, NULL, NULL);
::WideCharToMultiByte(CP_UTF8, 0, buffer, len, &result[0], nChars, NULL, NULL);
}
return result;
}
#endif
bool cpccSettings::save(void)
{
cpcc_ostringstream ss;
// for (cpccKeyValue::tKeysAndValues::const_iterator it = m_map.begin(); it != m_map.end(); ++it)
for (auto element : getMap())
{
const cpcc_char* key = element.first.c_str();
cpcc_string value = element.second; // todo: value( it->second);
cSerialCodec::encode(value);
ss << key << _T("=") << value << std::endl;
}
#ifdef DEBUG
cpcc_string test = serialize(_T("\n"), true, cSerialCodec::encode);
if (test.compare(ss.str())!=0)
cpcc_cerr << _T("#9582: error in save() serialize*()") << std::endl;
#endif
#ifdef UNICODE // write the BOM if UTF-8
//const cpcc_string tmpTxt(ss.str());
//const cpcc_string tmpTxtAfterUTF8(helper_to_utf8(tmpTxt.c_str(), tmpTxt.length()));
//return saveStringToFile(mFilename.c_str(), tmpTxtAfterUTF8.c_str(), true);
return cpccFileSystemMini::writeTextFile(mFilename.c_str(), ss.str().c_str(), true);
#else
return cpccFileSystemMini::writeTextFile(mFilename.c_str(), ss.str().c_str(), false);
#endif
}
void cpccSettings::resumeInstantSaving(void)
{
instantSaving = true;
if (!save())
cpcc_cerr << _T("Error #1353: saving cpccSettings to file:") << mFilename << std::endl;
}