xref: /haiku/src/kits/shared/SettingsHandler.cpp (revision 319c399d61497d2e6dbca0ffae8d5d2a2d72d866)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 All rights reserved.
33 */
34 
35 #include <Debug.h>
36 #include <Directory.h>
37 #include <Entry.h>
38 #include <File.h>
39 #include <FindDirectory.h>
40 #include <Path.h>
41 #include <StopWatch.h>
42 
43 #include <alloca.h>
44 #include <stdlib.h>
45 #include <stdio.h>
46 #include <string.h>
47 #include <stdarg.h>
48 
49 
50 #include "SettingsHandler.h"
51 
52 
53 //	#pragma mark - ArgvParser
54 
55 
56 /*! \class ArgvParser
57 	ArgvParser class opens a text file and passes the context in argv
58 	format to a specified handler
59 */
ArgvParser(const char * name)60 ArgvParser::ArgvParser(const char* name)
61 	:
62 	fFile(0),
63 	fBuffer(NULL),
64 	fPos(-1),
65 	fArgc(0),
66 	fCurrentArgv(0),
67 	fCurrentArgsPos(-1),
68 	fSawBackslash(false),
69 	fEatComment(false),
70 	fInDoubleQuote(false),
71 	fInSingleQuote(false),
72 	fLineNo(0),
73 	fFileName(name)
74 {
75 	fFile = fopen(fFileName, "r");
76 	if (!fFile) {
77 		PRINT(("Error opening %s\n", fFileName));
78 		return;
79 	}
80 	fBuffer = new char [kBufferSize];
81 	fCurrentArgv = new char * [1024];
82 }
83 
84 
~ArgvParser()85 ArgvParser::~ArgvParser()
86 {
87 	delete[] fBuffer;
88 
89 	MakeArgvEmpty();
90 	delete[] fCurrentArgv;
91 
92 	if (fFile)
93 		fclose(fFile);
94 }
95 
96 
97 void
MakeArgvEmpty()98 ArgvParser::MakeArgvEmpty()
99 {
100 	// done with current argv, free it up
101 	for (int32 index = 0; index < fArgc; index++)
102 		delete fCurrentArgv[index];
103 
104 	fArgc = 0;
105 }
106 
107 
108 status_t
SendArgv(ArgvHandler argvHandlerFunc,void * passThru)109 ArgvParser::SendArgv(ArgvHandler argvHandlerFunc, void* passThru)
110 {
111 	if (fArgc) {
112 		NextArgv();
113 		fCurrentArgv[fArgc] = 0;
114 		const char* result = (argvHandlerFunc)(fArgc, fCurrentArgv, passThru);
115 		if (result != NULL) {
116 			printf("File %s; Line %" B_PRId32 " # %s", fFileName, fLineNo,
117 				result);
118 		}
119 		MakeArgvEmpty();
120 		if (result != NULL)
121 			return B_ERROR;
122 	}
123 
124 	return B_OK;
125 }
126 
127 
128 void
NextArgv()129 ArgvParser::NextArgv()
130 {
131 	if (fSawBackslash) {
132 		fCurrentArgs[++fCurrentArgsPos] = '\\';
133 		fSawBackslash = false;
134 	}
135 	fCurrentArgs[++fCurrentArgsPos] = '\0';
136 		// terminate current arg pos
137 
138 	// copy it as a string to the current argv slot
139 	fCurrentArgv[fArgc] = new char [strlen(fCurrentArgs) + 1];
140 	strcpy(fCurrentArgv[fArgc], fCurrentArgs);
141 	fCurrentArgsPos = -1;
142 	fArgc++;
143 }
144 
145 
146 void
NextArgvIfNotEmpty()147 ArgvParser::NextArgvIfNotEmpty()
148 {
149 	if (!fSawBackslash && fCurrentArgsPos < 0)
150 		return;
151 
152 	NextArgv();
153 }
154 
155 
156 int
GetCh()157 ArgvParser::GetCh()
158 {
159 	if (fPos < 0 || fBuffer[fPos] == 0) {
160 		if (fFile == 0)
161 			return EOF;
162 		if (fgets(fBuffer, kBufferSize, fFile) == 0)
163 			return EOF;
164 		fPos = 0;
165 	}
166 
167 	return fBuffer[fPos++];
168 }
169 
170 
171 status_t
EachArgv(const char * name,ArgvHandler argvHandlerFunc,void * passThru)172 ArgvParser::EachArgv(const char* name, ArgvHandler argvHandlerFunc,
173 	void* passThru)
174 {
175 	ArgvParser parser(name);
176 
177 	return parser.EachArgvPrivate(name, argvHandlerFunc, passThru);
178 }
179 
180 
181 status_t
EachArgvPrivate(const char * name,ArgvHandler argvHandlerFunc,void * passThru)182 ArgvParser::EachArgvPrivate(const char* name, ArgvHandler argvHandlerFunc,
183 	void* passThru)
184 {
185 	status_t result;
186 
187 	for (;;) {
188 		int ch = GetCh();
189 		if (ch == EOF) {
190 			// done with fFile
191 			if (fInDoubleQuote || fInSingleQuote) {
192 				printf("File %s # unterminated quote at end of file\n", name);
193 				result = B_ERROR;
194 				break;
195 			}
196 			result = SendArgv(argvHandlerFunc, passThru);
197 			break;
198 		}
199 
200 		if (ch == '\n' || ch == '\r') {
201 			// handle new line
202 			fEatComment = false;
203 			if (!fSawBackslash && (fInDoubleQuote || fInSingleQuote)) {
204 				printf("File %s ; Line %" B_PRId32 " # unterminated quote\n",
205 					name, fLineNo);
206 				result = B_ERROR;
207 				break;
208 			}
209 
210 			fLineNo++;
211 			if (fSawBackslash) {
212 				fSawBackslash = false;
213 				continue;
214 			}
215 
216 			// end of line, flush all argv
217 			result = SendArgv(argvHandlerFunc, passThru);
218 
219 			continue;
220 		}
221 
222 		if (fEatComment)
223 			continue;
224 
225 		if (!fSawBackslash) {
226 			if (!fInDoubleQuote && !fInSingleQuote) {
227 				if (ch == ';') {
228 					// semicolon is a command separator, pass on
229 					// the whole argv
230 					result = SendArgv(argvHandlerFunc, passThru);
231 					if (result != B_OK)
232 						break;
233 					continue;
234 				} else if (ch == '#') {
235 					// ignore everything on this line after this character
236 					fEatComment = true;
237 					continue;
238 				} else if (ch == ' ' || ch == '\t') {
239 					// space or tab separates the individual arg strings
240 					NextArgvIfNotEmpty();
241 					continue;
242 				} else if (!fSawBackslash && ch == '\\') {
243 					// the next character is escaped
244 					fSawBackslash = true;
245 					continue;
246 				}
247 			}
248 			if (!fInSingleQuote && ch == '"') {
249 				// enter/exit double quote handling
250 				fInDoubleQuote = !fInDoubleQuote;
251 				continue;
252 			}
253 			if (!fInDoubleQuote && ch == '\'') {
254 				// enter/exit single quote handling
255 				fInSingleQuote = !fInSingleQuote;
256 				continue;
257 			}
258 		} else {
259 			// we just pass through the escape sequence as is
260 			fCurrentArgs[++fCurrentArgsPos] = '\\';
261 			fSawBackslash = false;
262 		}
263 		fCurrentArgs[++fCurrentArgsPos] = ch;
264 	}
265 
266 	return result;
267 }
268 
269 
270 //	#pragma mark - SettingsArgvDispatcher
271 
272 
SettingsArgvDispatcher(const char * name)273 SettingsArgvDispatcher::SettingsArgvDispatcher(const char* name)
274 	:
275 	fName(name)
276 {
277 }
278 
279 
280 void
SaveSettings(Settings * settings,bool onlyIfNonDefault)281 SettingsArgvDispatcher::SaveSettings(Settings* settings,
282 	bool onlyIfNonDefault)
283 {
284 	if (!onlyIfNonDefault || NeedsSaving()) {
285 		settings->Write("%s ", Name());
286 		SaveSettingValue(settings);
287 		settings->Write("\n");
288 	}
289 }
290 
291 
292 bool
HandleRectValue(BRect & result,const char * const * argv,bool printError)293 SettingsArgvDispatcher::HandleRectValue(BRect &result,
294 	const char* const* argv, bool printError)
295 {
296 	if (!*argv) {
297 		if (printError)
298 			printf("rect left expected");
299 		return false;
300 	}
301 	result.left = atoi(*argv);
302 
303 	if (!*++argv) {
304 		if (printError)
305 			printf("rect top expected");
306 		return false;
307 	}
308 	result.top = atoi(*argv);
309 
310 	if (!*++argv) {
311 		if (printError)
312 			printf("rect right expected");
313 		return false;
314 	}
315 	result.right = atoi(*argv);
316 
317 	if (!*++argv) {
318 		if (printError)
319 			printf("rect bottom expected");
320 		return false;
321 	}
322 	result.bottom = atoi(*argv);
323 
324 	return true;
325 }
326 
327 
328 void
WriteRectValue(Settings * setting,BRect rect)329 SettingsArgvDispatcher::WriteRectValue(Settings* setting, BRect rect)
330 {
331 	setting->Write("%d %d %d %d", (int32)rect.left, (int32)rect.top,
332 		(int32)rect.right, (int32)rect.bottom);
333 }
334 
335 
336 /*!	\class Settings
337 	this class represents a list of all the settings handlers, reads and
338 	saves the settings file
339 */
Settings(const char * filename,const char * settingsDirName)340 Settings::Settings(const char* filename, const char* settingsDirName)
341 	:
342 	fFileName(filename),
343 	fSettingsDir(settingsDirName),
344 	fList(0),
345 	fCount(0),
346 	fListSize(30),
347 	fCurrentSettings(0)
348 {
349 	fList = (SettingsArgvDispatcher**)calloc((size_t)fListSize,
350 		sizeof(SettingsArgvDispatcher*));
351 }
352 
353 
~Settings()354 Settings::~Settings()
355 {
356 	for (int32 index = 0; index < fCount; index++)
357 		delete fList[index];
358 
359 	free(fList);
360 }
361 
362 
363 const char*
ParseUserSettings(int,const char * const * argv,void * castToThis)364 Settings::ParseUserSettings(int, const char* const* argv, void* castToThis)
365 {
366 	if (!*argv)
367 		return 0;
368 
369 	SettingsArgvDispatcher* handler = ((Settings*)castToThis)->Find(*argv);
370 	if (!handler)
371 		return "unknown command";
372 
373 	return handler->Handle(argv);
374 }
375 
376 
377 /*!
378 	Returns false if argv dispatcher with the same name already
379 	registered
380 */
381 bool
Add(SettingsArgvDispatcher * setting)382 Settings::Add(SettingsArgvDispatcher* setting)
383 {
384 	// check for uniqueness
385 	if (Find(setting->Name()))
386 		return false;
387 
388 	if (fCount >= fListSize) {
389 		fListSize += 30;
390 		fList = (SettingsArgvDispatcher**)realloc(fList,
391 			fListSize * sizeof(SettingsArgvDispatcher*));
392 	}
393 	fList[fCount++] = setting;
394 	return true;
395 }
396 
397 
398 SettingsArgvDispatcher*
Find(const char * name)399 Settings::Find(const char* name)
400 {
401 	for (int32 index = 0; index < fCount; index++)
402 		if (strcmp(name, fList[index]->Name()) == 0)
403 			return fList[index];
404 
405 	return NULL;
406 }
407 
408 
409 void
TryReadingSettings()410 Settings::TryReadingSettings()
411 {
412 	BPath prefsPath;
413 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &prefsPath, true) == B_OK) {
414 		prefsPath.Append(fSettingsDir);
415 
416 		BPath path(prefsPath);
417 		path.Append(fFileName);
418 		ArgvParser::EachArgv(path.Path(), Settings::ParseUserSettings, this);
419 	}
420 }
421 
422 
423 void
SaveSettings(bool onlyIfNonDefault)424 Settings::SaveSettings(bool onlyIfNonDefault)
425 {
426 	SaveCurrentSettings(onlyIfNonDefault);
427 }
428 
429 
430 void
MakeSettingsDirectory(BDirectory * resultingSettingsDir)431 Settings::MakeSettingsDirectory(BDirectory* resultingSettingsDir)
432 {
433 	BPath path;
434 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true) != B_OK)
435 		return;
436 
437 	// make sure there is a directory
438 	// mkdir() will only make one leaf at a time, unfortunately
439 	path.Append(fSettingsDir);
440 	char* ptr = (char *)alloca(strlen(path.Path()) + 1);
441 	strcpy(ptr, path.Path());
442 	char* end = ptr+strlen(ptr);
443 	char* mid = ptr+1;
444 	while (mid < end) {
445 		mid = strchr(mid, '/');
446 		if (!mid) break;
447 		*mid = 0;
448 		mkdir(ptr, 0777);
449 		*mid = '/';
450 		mid++;
451 	}
452 	mkdir(ptr, 0777);
453 	resultingSettingsDir->SetTo(path.Path());
454 }
455 
456 
457 void
SaveCurrentSettings(bool onlyIfNonDefault)458 Settings::SaveCurrentSettings(bool onlyIfNonDefault)
459 {
460 	BDirectory settingsDir;
461 	MakeSettingsDirectory(&settingsDir);
462 
463 	if (settingsDir.InitCheck() != B_OK)
464 		return;
465 
466 	// nuke old settings
467 	BEntry entry(&settingsDir, fFileName);
468 	entry.Remove();
469 
470 	BFile prefs(&entry, O_RDWR | O_CREAT);
471 	if (prefs.InitCheck() != B_OK)
472 		return;
473 
474 	fCurrentSettings = &prefs;
475 	for (int32 index = 0; index < fCount; index++)
476 		fList[index]->SaveSettings(this, onlyIfNonDefault);
477 
478 	fCurrentSettings = NULL;
479 }
480 
481 
482 void
Write(const char * format,...)483 Settings::Write(const char* format, ...)
484 {
485 	va_list args;
486 
487 	va_start(args, format);
488 	VSWrite(format, args);
489 	va_end(args);
490 }
491 
492 
493 void
VSWrite(const char * format,va_list arg)494 Settings::VSWrite(const char* format, va_list arg)
495 {
496 	char buffer[2048];
497 	vsprintf(buffer, format, arg);
498 	ASSERT(fCurrentSettings && fCurrentSettings->InitCheck() == B_OK);
499 	fCurrentSettings->Write(buffer, strlen(buffer));
500 }
501