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