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 <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*/
60ArgvParser::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
85ArgvParser::~ArgvParser()
86{
87	delete[] fBuffer;
88
89	MakeArgvEmpty();
90	delete[] fCurrentArgv;
91
92	if (fFile)
93		fclose(fFile);
94}
95
96
97void
98ArgvParser::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
108status_t
109ArgvParser::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
128void
129ArgvParser::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
146void
147ArgvParser::NextArgvIfNotEmpty()
148{
149	if (!fSawBackslash && fCurrentArgsPos < 0)
150		return;
151
152	NextArgv();
153}
154
155
156int
157ArgvParser::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
171status_t
172ArgvParser::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
181status_t
182ArgvParser::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
273SettingsArgvDispatcher::SettingsArgvDispatcher(const char* name)
274	:
275	fName(name)
276{
277}
278
279
280void
281SettingsArgvDispatcher::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
292bool
293SettingsArgvDispatcher::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
328void
329SettingsArgvDispatcher::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*/
340Settings::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
354Settings::~Settings()
355{
356	for (int32 index = 0; index < fCount; index++)
357		delete fList[index];
358
359	free(fList);
360}
361
362
363const char*
364Settings::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*/
381bool
382Settings::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
398SettingsArgvDispatcher*
399Settings::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
409void
410Settings::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
423void
424Settings::SaveSettings(bool onlyIfNonDefault)
425{
426	SaveCurrentSettings(onlyIfNonDefault);
427}
428
429
430void
431Settings::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
457void
458Settings::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
482void
483Settings::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
493void
494Settings::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