haiku/src/kits/shared/SettingsHandler.cpp

501 lines
10 KiB
C++

/*
Open Tracker License
Terms and Conditions
Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice applies to all licensees
and shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of Be Incorporated shall not be
used in advertising or otherwise to promote the sale, use or other dealings in
this Software without prior written authorization from Be Incorporated.
Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
of Be Incorporated in the United States and other countries. Other brand product
names are registered trademarks or trademarks of their respective holders.
All rights reserved.
*/
#include <Debug.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <FindDirectory.h>
#include <Path.h>
#include <StopWatch.h>
#include <alloca.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "SettingsHandler.h"
// #pragma mark - ArgvParser
/*! \class ArgvParser
ArgvParser class opens a text file and passes the context in argv
format to a specified handler
*/
ArgvParser::ArgvParser(const char* name)
:
fFile(0),
fBuffer(NULL),
fPos(-1),
fArgc(0),
fCurrentArgv(0),
fCurrentArgsPos(-1),
fSawBackslash(false),
fEatComment(false),
fInDoubleQuote(false),
fInSingleQuote(false),
fLineNo(0),
fFileName(name)
{
fFile = fopen(fFileName, "r");
if (!fFile) {
PRINT(("Error opening %s\n", fFileName));
return;
}
fBuffer = new char [kBufferSize];
fCurrentArgv = new char * [1024];
}
ArgvParser::~ArgvParser()
{
delete[] fBuffer;
MakeArgvEmpty();
delete[] fCurrentArgv;
if (fFile)
fclose(fFile);
}
void
ArgvParser::MakeArgvEmpty()
{
// done with current argv, free it up
for (int32 index = 0; index < fArgc; index++)
delete fCurrentArgv[index];
fArgc = 0;
}
status_t
ArgvParser::SendArgv(ArgvHandler argvHandlerFunc, void* passThru)
{
if (fArgc) {
NextArgv();
fCurrentArgv[fArgc] = 0;
const char* result = (argvHandlerFunc)(fArgc, fCurrentArgv, passThru);
if (result != NULL) {
printf("File %s; Line %" B_PRId32 " # %s", fFileName, fLineNo,
result);
}
MakeArgvEmpty();
if (result != NULL)
return B_ERROR;
}
return B_OK;
}
void
ArgvParser::NextArgv()
{
if (fSawBackslash) {
fCurrentArgs[++fCurrentArgsPos] = '\\';
fSawBackslash = false;
}
fCurrentArgs[++fCurrentArgsPos] = '\0';
// terminate current arg pos
// copy it as a string to the current argv slot
fCurrentArgv[fArgc] = new char [strlen(fCurrentArgs) + 1];
strcpy(fCurrentArgv[fArgc], fCurrentArgs);
fCurrentArgsPos = -1;
fArgc++;
}
void
ArgvParser::NextArgvIfNotEmpty()
{
if (!fSawBackslash && fCurrentArgsPos < 0)
return;
NextArgv();
}
int
ArgvParser::GetCh()
{
if (fPos < 0 || fBuffer[fPos] == 0) {
if (fFile == 0)
return EOF;
if (fgets(fBuffer, kBufferSize, fFile) == 0)
return EOF;
fPos = 0;
}
return fBuffer[fPos++];
}
status_t
ArgvParser::EachArgv(const char* name, ArgvHandler argvHandlerFunc,
void* passThru)
{
ArgvParser parser(name);
return parser.EachArgvPrivate(name, argvHandlerFunc, passThru);
}
status_t
ArgvParser::EachArgvPrivate(const char* name, ArgvHandler argvHandlerFunc,
void* passThru)
{
status_t result;
for (;;) {
int ch = GetCh();
if (ch == EOF) {
// done with fFile
if (fInDoubleQuote || fInSingleQuote) {
printf("File %s # unterminated quote at end of file\n", name);
result = B_ERROR;
break;
}
result = SendArgv(argvHandlerFunc, passThru);
break;
}
if (ch == '\n' || ch == '\r') {
// handle new line
fEatComment = false;
if (!fSawBackslash && (fInDoubleQuote || fInSingleQuote)) {
printf("File %s ; Line %" B_PRId32 " # unterminated quote\n",
name, fLineNo);
result = B_ERROR;
break;
}
fLineNo++;
if (fSawBackslash) {
fSawBackslash = false;
continue;
}
// end of line, flush all argv
result = SendArgv(argvHandlerFunc, passThru);
continue;
}
if (fEatComment)
continue;
if (!fSawBackslash) {
if (!fInDoubleQuote && !fInSingleQuote) {
if (ch == ';') {
// semicolon is a command separator, pass on
// the whole argv
result = SendArgv(argvHandlerFunc, passThru);
if (result != B_OK)
break;
continue;
} else if (ch == '#') {
// ignore everything on this line after this character
fEatComment = true;
continue;
} else if (ch == ' ' || ch == '\t') {
// space or tab separates the individual arg strings
NextArgvIfNotEmpty();
continue;
} else if (!fSawBackslash && ch == '\\') {
// the next character is escaped
fSawBackslash = true;
continue;
}
}
if (!fInSingleQuote && ch == '"') {
// enter/exit double quote handling
fInDoubleQuote = !fInDoubleQuote;
continue;
}
if (!fInDoubleQuote && ch == '\'') {
// enter/exit single quote handling
fInSingleQuote = !fInSingleQuote;
continue;
}
} else {
// we just pass through the escape sequence as is
fCurrentArgs[++fCurrentArgsPos] = '\\';
fSawBackslash = false;
}
fCurrentArgs[++fCurrentArgsPos] = ch;
}
return result;
}
// #pragma mark - SettingsArgvDispatcher
SettingsArgvDispatcher::SettingsArgvDispatcher(const char* name)
:
fName(name)
{
}
void
SettingsArgvDispatcher::SaveSettings(Settings* settings,
bool onlyIfNonDefault)
{
if (!onlyIfNonDefault || NeedsSaving()) {
settings->Write("%s ", Name());
SaveSettingValue(settings);
settings->Write("\n");
}
}
bool
SettingsArgvDispatcher::HandleRectValue(BRect &result,
const char* const* argv, bool printError)
{
if (!*argv) {
if (printError)
printf("rect left expected");
return false;
}
result.left = atoi(*argv);
if (!*++argv) {
if (printError)
printf("rect top expected");
return false;
}
result.top = atoi(*argv);
if (!*++argv) {
if (printError)
printf("rect right expected");
return false;
}
result.right = atoi(*argv);
if (!*++argv) {
if (printError)
printf("rect bottom expected");
return false;
}
result.bottom = atoi(*argv);
return true;
}
void
SettingsArgvDispatcher::WriteRectValue(Settings* setting, BRect rect)
{
setting->Write("%d %d %d %d", (int32)rect.left, (int32)rect.top,
(int32)rect.right, (int32)rect.bottom);
}
/*! \class Settings
this class represents a list of all the settings handlers, reads and
saves the settings file
*/
Settings::Settings(const char* filename, const char* settingsDirName)
:
fFileName(filename),
fSettingsDir(settingsDirName),
fList(0),
fCount(0),
fListSize(30),
fCurrentSettings(0)
{
fList = (SettingsArgvDispatcher**)calloc((size_t)fListSize,
sizeof(SettingsArgvDispatcher*));
}
Settings::~Settings()
{
for (int32 index = 0; index < fCount; index++)
delete fList[index];
free(fList);
}
const char*
Settings::ParseUserSettings(int, const char* const* argv, void* castToThis)
{
if (!*argv)
return 0;
SettingsArgvDispatcher* handler = ((Settings*)castToThis)->Find(*argv);
if (!handler)
return "unknown command";
return handler->Handle(argv);
}
/*!
Returns false if argv dispatcher with the same name already
registered
*/
bool
Settings::Add(SettingsArgvDispatcher* setting)
{
// check for uniqueness
if (Find(setting->Name()))
return false;
if (fCount >= fListSize) {
fListSize += 30;
fList = (SettingsArgvDispatcher**)realloc(fList,
fListSize * sizeof(SettingsArgvDispatcher*));
}
fList[fCount++] = setting;
return true;
}
SettingsArgvDispatcher*
Settings::Find(const char* name)
{
for (int32 index = 0; index < fCount; index++)
if (strcmp(name, fList[index]->Name()) == 0)
return fList[index];
return NULL;
}
void
Settings::TryReadingSettings()
{
BPath prefsPath;
if (find_directory(B_USER_SETTINGS_DIRECTORY, &prefsPath, true) == B_OK) {
prefsPath.Append(fSettingsDir);
BPath path(prefsPath);
path.Append(fFileName);
ArgvParser::EachArgv(path.Path(), Settings::ParseUserSettings, this);
}
}
void
Settings::SaveSettings(bool onlyIfNonDefault)
{
SaveCurrentSettings(onlyIfNonDefault);
}
void
Settings::MakeSettingsDirectory(BDirectory* resultingSettingsDir)
{
BPath path;
if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true) != B_OK)
return;
// make sure there is a directory
// mkdir() will only make one leaf at a time, unfortunately
path.Append(fSettingsDir);
char* ptr = (char *)alloca(strlen(path.Path()) + 1);
strcpy(ptr, path.Path());
char* end = ptr+strlen(ptr);
char* mid = ptr+1;
while (mid < end) {
mid = strchr(mid, '/');
if (!mid) break;
*mid = 0;
mkdir(ptr, 0777);
*mid = '/';
mid++;
}
mkdir(ptr, 0777);
resultingSettingsDir->SetTo(path.Path());
}
void
Settings::SaveCurrentSettings(bool onlyIfNonDefault)
{
BDirectory settingsDir;
MakeSettingsDirectory(&settingsDir);
if (settingsDir.InitCheck() != B_OK)
return;
// nuke old settings
BEntry entry(&settingsDir, fFileName);
entry.Remove();
BFile prefs(&entry, O_RDWR | O_CREAT);
if (prefs.InitCheck() != B_OK)
return;
fCurrentSettings = &prefs;
for (int32 index = 0; index < fCount; index++)
fList[index]->SaveSettings(this, onlyIfNonDefault);
fCurrentSettings = NULL;
}
void
Settings::Write(const char* format, ...)
{
va_list args;
va_start(args, format);
VSWrite(format, args);
va_end(args);
}
void
Settings::VSWrite(const char* format, va_list arg)
{
char buffer[2048];
vsprintf(buffer, format, arg);
ASSERT(fCurrentSettings && fCurrentSettings->InitCheck() == B_OK);
fCurrentSettings->Write(buffer, strlen(buffer));
}