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