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 */ 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 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 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 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 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 147 ArgvParser::NextArgvIfNotEmpty() 148 { 149 if (!fSawBackslash && fCurrentArgsPos < 0) 150 return; 151 152 NextArgv(); 153 } 154 155 156 int 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 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 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 273 SettingsArgvDispatcher::SettingsArgvDispatcher(const char* name) 274 : 275 fName(name) 276 { 277 } 278 279 280 void 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 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 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 */ 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 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* 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 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* 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 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 424 Settings::SaveSettings(bool onlyIfNonDefault) 425 { 426 SaveCurrentSettings(onlyIfNonDefault); 427 } 428 429 430 void 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 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 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 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