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