File: ConfigSource.cpp

package info (click to toggle)
spring 88.0%2Bdfsg1-1.1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 41,524 kB
  • sloc: cpp: 343,114; ansic: 38,414; python: 12,257; java: 12,203; awk: 5,748; sh: 1,204; xml: 997; perl: 405; objc: 192; makefile: 181; php: 134; sed: 2
file content (239 lines) | stat: -rwxr-xr-x 6,495 bytes parent folder | download
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
/* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */

#include "ConfigSource.h"
#include "ConfigVariable.h"
#include "System/Log/ILog.h"
#include "System/Platform/ScopedFileLock.h"

#ifdef WIN32
	#include <io.h>
#endif
#include <string.h>
#include <stdexcept>
#include <boost/bind.hpp>

/******************************************************************************/

using std::map;
using std::string;

typedef map<string, string> StringMap;

/******************************************************************************/

bool ReadOnlyConfigSource::IsSet(const string& key) const
{
	return data.find(key) != data.end();
}

string ReadOnlyConfigSource::GetString(const string& key) const
{
	StringMap::const_iterator pos = data.find(key);
	if (pos == data.end()) {
		throw std::runtime_error("ConfigSource: Error: Key does not exist: " + key);
	}
	return pos->second;
}

/******************************************************************************/

void ReadWriteConfigSource::SetString(const string& key, const string& value)
{
	data[key] = value;
}

void ReadWriteConfigSource::Delete(const string& key)
{
	data.erase(key);
}

/******************************************************************************/

FileConfigSource::FileConfigSource(const string& filename) : filename(filename)
{
	FILE* file;

	if ((file = fopen(filename.c_str(), "r"))) {
		ScopedFileLock scoped_lock(fileno(file), false);
		Read(file);
		fclose(file);
	}
	else if ((file = fopen(filename.c_str(), "a"))) {
		// TODO: write some initial contents into the config file?
		fclose(file);
	}
	else {
		LOG_L(L_ERROR, "FileConfigSource: Error: Could not write to config file \"%s\"", filename.c_str());
	}
}

void FileConfigSource::SetStringInternal(const std::string& key, const std::string& value)
{
	ReadWriteConfigSource::SetString(key, value);
}

void FileConfigSource::SetString(const string& key, const string& value)
{
	ReadModifyWrite(boost::bind(&FileConfigSource::SetStringInternal, this, key, value));
}

void FileConfigSource::DeleteInternal(const string& key)
{
	ReadWriteConfigSource::Delete(key);
	// note: comments intentionally not deleted (see Write)
	// comments.erase(key);
}

void FileConfigSource::Delete(const string& key)
{
	ReadModifyWrite(boost::bind(&FileConfigSource::DeleteInternal, this, key));
}

void FileConfigSource::ReadModifyWrite(boost::function<void ()> modify) {
	FILE* file = fopen(filename.c_str(), "r+");

	if (file) {
		ScopedFileLock scoped_lock(fileno(file), true);
		Read(file);
		modify();
		Write(file);
	}
	else {
		modify();
	}

	// must be outside above 'if (file)' block because of the lock.
	if (file) {
		fclose(file);
	}
}

/**
 * @brief strip whitespace
 *
 * Strips whitespace off the string [begin, end] by setting the last
 * whitespace character from the end to 0 and returning a pointer
 * to the first non-whitespace character from the beginning.
 *
 * Precondition: end must point to the last character of the string,
 * i.e. the one before the terminating '\0'.
 */
char* FileConfigSource::Strip(char* begin, char* end) {
	while (end >= begin && isspace(*end)) --end;
	while (begin <= end && isspace(*begin)) ++begin;
	*(end + 1) = '\0';
	return begin;
}

/**
 * @brief Rewind file and re-read it.
 */
void FileConfigSource::Read(FILE* file)
{
	std::ostringstream commentBuffer;
	char line[500];
	rewind(file);
	while (fgets(line, sizeof(line), file)) {
		char* line_stripped = Strip(line, strchr(line, '\0') - 1);
		if (*line_stripped == '\0' || *line_stripped == '#') {
			// an empty line or a comment line
			// note: trailing whitespace has been removed by Strip
			commentBuffer << line << "\n";
		}
		else {
			char* eq = strchr(line_stripped, '=');
			if (eq) {
				// a "key=value" line
				char* key = Strip(line_stripped, eq - 1);
				char* value = Strip(eq + 1, strchr(eq + 1, '\0') - 1);
				data[key] = value;
				if (commentBuffer.tellp() > 0) {
					comments[key] = commentBuffer.str();
					// reset the ostringstream
					commentBuffer.clear();
					commentBuffer.str("");
				}
			}
			else {
				// neither a comment nor an empty line nor a key=value line
				LOG_L(L_ERROR, "ConfigSource: Error: Can not parse line: %s\n", line);
			}
		}
	}

	if (commentBuffer.tellp() > 0) {
		// Must be sorted after all valid config variable names.
		const std::string AT_THE_END = "\x7f";
		comments[AT_THE_END] = commentBuffer.str();
	}
}

/**
 * @brief Truncate file and write data to it.
 */
void FileConfigSource::Write(FILE* file)
{
	rewind(file);
#ifdef WIN32
	int err = _chsize(fileno(file), 0);
#else
	int err = ftruncate(fileno(file), 0);
#endif
	if (err != 0) {
		LOG_L(L_ERROR, "FileConfigSource: Error: Failed truncating config file.");
	}

	// Iterate through both containers at once, interspersing
	// comment lines with key value pair lines.
	StringMap::const_iterator iter = data.begin();
	StringMap::const_iterator commentIter = comments.begin();
	while (iter != data.end()) {
		while (commentIter != comments.end() && commentIter->first <= iter->first) {
			fputs(commentIter->second.c_str(), file);
			++commentIter;
		}
		fprintf(file, "%s = %s\n", iter->first.c_str(), iter->second.c_str());
		++iter;
	}

	// Finish by writing all new remaining comments.
	while (commentIter != comments.end()) {
		fputs(commentIter->second.c_str(), file);
		++commentIter;
	}
}

/******************************************************************************/

/**
 * @brief Fill with default values of declared configuration variables.
 */
DefaultConfigSource::DefaultConfigSource()
{
	const ConfigVariable::MetaDataMap& vars = ConfigVariable::GetMetaDataMap();

	for (ConfigVariable::MetaDataMap::const_iterator it = vars.begin(); it != vars.end(); ++it) {
		const ConfigVariableMetaData* metadata = it->second;
		if (metadata->GetDefaultValue().IsSet()) {
			data[metadata->GetKey()] = metadata->GetDefaultValue().ToString();
		}
	}
}


/**
 * @brief Fill with safemode values of declared configuration variables.
 */
SafemodeConfigSource::SafemodeConfigSource()
{
	const ConfigVariable::MetaDataMap& vars = ConfigVariable::GetMetaDataMap();

	for (ConfigVariable::MetaDataMap::const_iterator it = vars.begin(); it != vars.end(); ++it) {
		const ConfigVariableMetaData* metadata = it->second;
		if (metadata->GetSafemodeValue().IsSet()) {
			data[metadata->GetKey()] = metadata->GetSafemodeValue().ToString();
		}
	}
}

/******************************************************************************/