AmiTimeKeeper/setting.c

679 lines
21 KiB
C

/*-
* Copyright (c) 2017-2020 Carsten Sonne Larsen <cs@innolan.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "setting.h"
#include "global.h"
#include "mem.h"
#include "logmod.h"
#define MODULENAME "Settings"
struct AppSettings *Settings;
struct AppSettingKeys *SettingKeys;
static const struct AppSettingKeys SettingKeyStruct = {
.DestinationAddress = KEYWORD_SERVER,
.DestinationPort = KEYWORD_PORT,
.Threshold = KEYWORD_THRESHOLD,
.Interval = KEYWORD_INTERVAL,
.Priority = KEYWORD_PRIORITY,
.PopKey = KEYWORD_POPKEY,
.Popup = KEYWORD_POPUP,
.Readonly = KEYWORD_READONLY,
.Expert = KEYWORD_EXPERT,
.Timeout = KEYWORD_TIMEOUT};
const struct AppSettings DefaultSettings = {
.Type = DefaultSettingType,
.DestinationAddress = (char *)SERVER_DEF,
.DestinationPort = (char *)PORT_DEF,
.Timeout = TIMEOUT_DEF,
.Interval = INTERVAL_DEF,
.PopKey = POPKEY_DEF,
.Popup = POPUP_DEF,
.Readonly = READONLY_DEF,
.Expert = EXPERT_DEF,
.Priority = PRIORITY_DEF,
.Threshold = THRESHOLD_DEF,
.Values = 0xFFFF};
static void SetPrioritySetting(struct AppSettings *, void *);
static void SetIntervalSetting(struct AppSettings *, void *);
static void SetTimeoutSetting(struct AppSettings *, void *);
static void SetThresholdSetting(struct AppSettings *, void *);
static void SetDestinationAddressSetting(struct AppSettings *, void *);
static void SetDestinationPortSetting(struct AppSettings *, void *);
static void SetReadOnlySetting(struct AppSettings *, void *);
static void SetExpertSetting(struct AppSettings *, void *);
static void SetPopupSetting(struct AppSettings *, void *);
static void SetPopKeySetting(struct AppSettings *, void *);
// Keyword order in settingFunctions needs to match order in keyword template
const struct SettingFunc settingFunctions[] = {
{KEYWORD_READONLY, SetReadOnlySetting},
{KEYWORD_EXPERT, SetExpertSetting},
{KEYWORD_SERVER, SetDestinationAddressSetting},
{KEYWORD_PORT, SetDestinationPortSetting},
{KEYWORD_TIMEOUT, SetTimeoutSetting},
{KEYWORD_THRESHOLD, SetThresholdSetting},
{KEYWORD_INTERVAL, SetIntervalSetting},
{KEYWORD_PRIORITY, SetPrioritySetting},
{KEYWORD_POPKEY, SetPopKeySetting},
{KEYWORD_POPUP, SetPopupSetting}};
static const char *prefsFile = "ENV:timekeeper.prefs";
static const char *persistentPrefsFile = "ENVARC:timekeeper.prefs";
static const char *prefsFileSearch = "Searching for preference in %s";
static const char *prefsFileFound = "Found preference file";
static const char *prefsFileNotFound = "Preference file not found";
static const char *prefsFileSave = "Saving preferences in %s";
static const char *fileOpenError = "Could not open preference file";
static const char *fileSaveError = "Could not save preference file";
static const char *fileReadError = "Error while reading file";
static const char *fileWriteError = "Error while writing file";
static const char *unknownSetting = "Found unknown setting in preference file: %s";
static const char *foundSetting = "Found %s in preference file";
static const char *foundWbSetting = "Found tooltype from icon: %s";
static const char *foundCliSetting = "Got %s from CLI";
static const char *integerError = "Value should be an integer: %s";
static const char *yesNoError = "Value should be YES or NO: %s";
static const char *applyDefaultSettings = "Applying default values";
static const char *applyFileSettings = "Applying values from preference file";
static const char *applyCliSettings = "Applying values from CLI";
static const char *applyWbSettings = "Applying values from tooltypes";
static const char *effectiveSettings = "Listing runtime values";
static const char *settingChangedLong = "%s changed: %ld -> %ld";
static const char *settingChangedString = "%s changed: %s -> %s";
static const char *settingSetLong = "%s is already set to %ld";
static const char *settingSetString = "%s is already set to %s";
static const char *settingValueLong = "%s=%ld";
static const char *settingValueString = "%s=%s";
static const char *saveValueLong = "%s=%ld\n";
static const char *saveValueString = "%s=%s\n";
static const char *noValueString = "NO";
static const char *yesValueString = "YES";
#define MAXSETTINGLINELEN 256
static struct AppSettings *fileSettings;
static struct AppSettings *cachedSettings;
void InitSettings(void)
{
Settings = CreateSettings(GlobalSettingType);
SettingKeys = (struct AppSettingKeys *)&SettingKeyStruct;
}
void CleanupSettings(void)
{
FreeSettings(Settings);
}
static char *BooleanAsText(bool value)
{
return (char *)(value ? yesValueString : noValueString);
}
void ShowAppSettings(struct AppSettings *settings)
{
char low[MAXLONGLONGCHARSIZE];
LongLongToStr(settings->Threshold, low);
LogDebug(settingValueString, SettingKeys->Popup, BooleanAsText(settings->Popup));
LogDebug(settingValueString, SettingKeys->PopKey, settings->PopKey);
LogDebug(settingValueLong, SettingKeys->Priority, settings->Priority);
LogDebug(settingValueString, SettingKeys->Threshold, low);
LogDebug(settingValueString, SettingKeys->DestinationAddress, settings->DestinationAddress);
LogDebug(settingValueString, SettingKeys->DestinationPort, settings->DestinationPort);
LogDebug(settingValueLong, SettingKeys->Timeout, settings->Timeout);
LogDebug(settingValueLong, SettingKeys->Interval, settings->Interval);
LogDebug(settingValueString, SettingKeys->Readonly, BooleanAsText(settings->Readonly));
LogDebug(settingValueString, SettingKeys->Expert, BooleanAsText(settings->Expert));
}
void ShowSettings(void)
{
LogDebug(effectiveSettings);
ShowAppSettings(Settings);
}
void LogFoundSetting(long type, const char *name)
{
switch (type)
{
case PrefsSettingType:
LogDebug(foundSetting, name);
break;
case CliSettingType:
LogDebug(foundCliSetting, name);
break;
case WbSettingType:
LogDebug(foundWbSetting, name);
break;
default:
break;
}
}
static void ParseLongSetting(
struct AppSettings *settings,
long flag,
const char *keyword,
long *valueField,
void *value)
{
LogFoundSetting(settings->Type, keyword);
if (settings->Type == CliSettingType)
{
*valueField = *(long *)value;
settings->Values |= flag;
return;
}
if (TryParseLong((char *)value, valueField))
{
settings->Values |= flag;
return;
}
LogWarn(integerError, value);
}
static void ParseBooleanSetting(
struct AppSettings *settings,
long flag,
const char *keyword,
long *valueField,
void *value,
bool yesNo)
{
LogFoundSetting(settings->Type, keyword);
// CLI switch is always a long value
if (settings->Type == CliSettingType && !yesNo)
{
*valueField = (*valueField != 0 ? true : false);
settings->Values |= flag;
return;
}
if (value == NULL || *((const char *)value) == '\0')
{
LogWarn(yesNoError, '\0');
return;
}
if (Stricmp((CONST_STRPTR)noValueString, (CONST_STRPTR)value) == 0 ||
Stricmp((CONST_STRPTR) "0", (CONST_STRPTR)value) == 0)
{
*valueField = false;
settings->Values |= flag;
return;
}
if (Stricmp((CONST_STRPTR)yesValueString, (CONST_STRPTR)value) == 0 ||
Stricmp((CONST_STRPTR) "1", (CONST_STRPTR)value) == 0)
{
*valueField = true;
settings->Values |= flag;
return;
}
LogWarn(yesNoError, value);
}
static void SetPrioritySetting(struct AppSettings *settings, void *value)
{
ParseLongSetting(settings, PrioritySet, SettingKeys->Priority,
&settings->Priority, value);
}
static void SetIntervalSetting(struct AppSettings *settings, void *value)
{
ParseLongSetting(settings, IntervalSet, SettingKeys->Interval,
&settings->Interval, value);
}
static void SetTimeoutSetting(struct AppSettings *settings, void *value)
{
ParseLongSetting(settings, TimeoutSet, SettingKeys->Timeout,
&settings->Timeout, value);
}
static void SetThresholdSetting(struct AppSettings *settings, void *value)
{
LogFoundSetting(settings->Type, SettingKeys->Threshold);
if (TryParseLongLong((char *)value, &settings->Threshold))
{
settings->Values |= ThresholdSet;
return;
}
LogWarn(integerError, value);
}
static void SetDestinationAddressSetting(struct AppSettings *settings, void *value)
{
LogFoundSetting(settings->Type, SettingKeys->DestinationAddress);
settings->DestinationAddress = StrDupSafe((const char *)value);
settings->Values |= DestinationAddressSet;
}
static void SetPopKeySetting(struct AppSettings *settings, void *value)
{
LogFoundSetting(settings->Type, SettingKeys->PopKey);
settings->PopKey = StrDupSafe((const char *)value);
settings->Values |= PopKeySet;
}
static void SetDestinationPortSetting(struct AppSettings *settings, void *value)
{
LogFoundSetting(settings->Type, SettingKeys->DestinationPort);
settings->DestinationPort = StrDupSafe((const char *)value);
settings->Values |= DestinationPortSet;
}
static void SetReadOnlySetting(struct AppSettings *settings, void *value)
{
ParseBooleanSetting(settings, ReadonlySet, SettingKeys->Readonly,
&settings->Readonly, value, true);
}
static void SetExpertSetting(struct AppSettings *settings, void *value)
{
ParseBooleanSetting(settings, ExpertSet, SettingKeys->Expert,
&settings->Expert, value, true);
}
static void SetPopupSetting(struct AppSettings *settings, void *value)
{
ParseBooleanSetting(settings, PopUpSet, SettingKeys->Popup,
&settings->Popup, value, false);
}
static void ParseSetting(struct AppSettings *settings, char *line)
{
char *value;
char *end;
int i;
value = StrChr(line, '=', MAXSETTINGLINELEN);
if (value == NULL)
{
LogWarn(unknownSetting, "No assignment");
return;
}
end = StrChr(value, '\n', MAXSETTINGLINELEN);
if (end == NULL)
{
LogWarn(unknownSetting, "No end of line");
return;
}
*value++ = '\0';
*end = '\0';
for (i = 0; i < KEYWORD_COUNT; i++)
{
if (Stricmp((STRPTR)settingFunctions[i].Name, (STRPTR)line) == 0)
{
settingFunctions[i].Function(settings, (void *)value);
return;
}
}
LogWarn(unknownSetting, line);
}
void LoadSettings(void)
{
struct AppSettings *settings;
const int maxLines = 25;
char line[MAXSETTINGLINELEN];
char message[MAXDOSERRORLEN];
bool eof = false;
int count = 0;
long error;
BPTR file;
LogDebug(prefsFileSearch, prefsFile);
file = Open((STRPTR)prefsFile, MODE_OLDFILE);
if (!file)
{
error = IoErr();
if (error == ERROR_OBJECT_NOT_FOUND)
{
LogWarn(prefsFileNotFound);
}
else
{
Fault(error, (STRPTR)fileOpenError, (STRPTR)message, MAXDOSERRORLEN);
LogWarn(message);
}
return;
}
LogInfo(prefsFileFound);
settings = CreateSettings(PrefsSettingType);
do
{
char *c = (char *)FGets(file, (STRPTR)line, MAXSETTINGLINELEN);
eof = (c == NULL);
if (!eof)
{
ParseSetting(settings, line);
count++;
}
} while (!eof && count < maxLines);
// If NULL is returned for an EOF, IoErr() will return 0.
error = IoErr();
if (error != 0)
{
Fault(error, (STRPTR)fileReadError, (STRPTR)message, MAXDOSERRORLEN);
LogError(message);
}
Close(file);
fileSettings = settings;
}
static void WriteSetting(BPTR file, const char *format, ...)
{
long count;
va_list args;
va_start(args, format);
count = VFPrintf(file, (void *)format, (void *)args);
va_end(args);
if (count <= 0)
{
long error = IoErr();
if (error != 0)
{
char message[MAXDOSERRORLEN];
Fault(error, (STRPTR)fileWriteError, (STRPTR)message, MAXDOSERRORLEN);
LogError(message);
}
}
}
void SaveSettings(bool persist)
{
char low[MAXLONGLONGCHARSIZE];
const char *fileName = persist ? persistentPrefsFile : prefsFile;
BPTR file = Open((STRPTR)fileName, MODE_NEWFILE);
if (!file)
{
char message[MAXDOSERRORLEN];
long error = IoErr();
Fault(error, (STRPTR)fileSaveError, (STRPTR)message, MAXDOSERRORLEN);
LogWarn(message);
return;
}
LogInfo(prefsFileSave, fileName);
LongLongToStr(Settings->Threshold, low);
WriteSetting(file, saveValueString, SettingKeys->Popup, BooleanAsText(Settings->Popup));
WriteSetting(file, saveValueString, SettingKeys->PopKey, Settings->PopKey);
WriteSetting(file, saveValueLong, SettingKeys->Priority, Settings->Priority);
WriteSetting(file, saveValueString, SettingKeys->Threshold, low);
WriteSetting(file, saveValueString, SettingKeys->DestinationAddress, Settings->DestinationAddress);
WriteSetting(file, saveValueString, SettingKeys->DestinationPort, Settings->DestinationPort);
WriteSetting(file, saveValueLong, SettingKeys->Timeout, Settings->Timeout);
WriteSetting(file, saveValueLong, SettingKeys->Interval, Settings->Interval);
WriteSetting(file, saveValueString, SettingKeys->Readonly, BooleanAsText(Settings->Readonly));
WriteSetting(file, saveValueString, SettingKeys->Expert, BooleanAsText(Settings->Expert));
Close(file);
}
struct AppSettings *CreateSettings(long type)
{
struct AppSettings *settings;
settings = AllocStructSafe(struct AppSettings);
settings->Type = type;
return settings;
}
struct AppSettings *CopySettings(const struct AppSettings *settings)
{
struct AppSettings *s = CreateSettings(settings->Type);
CopyMem((void *)settings, s, sizeof(struct AppSettings));
s->DestinationAddress = StrDupSafe(settings->DestinationAddress);
s->DestinationPort = StrDupSafe(settings->DestinationPort);
s->PopKey = StrDupSafe(settings->PopKey);
s->Values = 0xFFFF;
return s;
}
void FreeSettings(struct AppSettings *settings)
{
if (settings->DestinationAddress != NULL)
{
FreeMemSafe(settings->DestinationAddress);
}
if (settings->DestinationPort != NULL)
{
FreeMemSafe(settings->DestinationPort);
}
if (settings->PopKey != NULL)
{
FreeMemSafe(settings->PopKey);
}
FreeMemSafe(settings);
}
static void ApplyLongSetting(
struct AppSettings *settings,
long flag,
const char *keyword,
long *curValue,
long *newValue,
bool quiet)
{
if ((settings->Values & flag) == flag)
{
if (settings->Type == DefaultSettingType)
{
LogDebug(settingValueLong, keyword, *newValue);
}
else if (*curValue != *newValue)
{
LogInfo(settingChangedLong, keyword, *curValue, *newValue);
}
else if (!quiet)
{
LogDebug(settingSetLong, keyword, *newValue);
}
*curValue = *newValue;
}
}
static void ApplyBooleanSetting(
struct AppSettings *settings,
long flag,
const char *keyword,
long *curValue,
long *newValue,
bool quiet)
{
if ((settings->Values & flag) == flag)
{
if (settings->Type == DefaultSettingType)
{
LogDebug(settingValueString, keyword, BooleanAsText(*newValue));
}
else if (*curValue != *newValue)
{
LogInfo(settingChangedString, keyword, BooleanAsText(*curValue), BooleanAsText(*newValue));
}
else if (!quiet)
{
LogDebug(settingSetString, keyword, BooleanAsText(*newValue));
}
*curValue = *newValue;
}
}
static void ApplyStringSetting(
struct AppSettings *settings,
long flag,
const char *keyword,
char **curValue,
char *newValue,
bool quiet)
{
if ((settings->Values & flag) == flag)
{
if (settings->Type == DefaultSettingType)
{
LogDebug(settingValueString, keyword, newValue);
}
else if (Stricmp((STRPTR)*curValue, (STRPTR)newValue) != 0)
{
LogInfo(settingChangedString, keyword, *curValue, newValue);
}
else if (!quiet)
{
LogDebug(settingSetString, keyword, newValue);
}
if (*curValue != NULL)
{
FreeMemSafe(*curValue);
}
*curValue = StrDupSafe(newValue);
}
}
void ApplyAppSettings(struct AppSettings *settings, bool quiet)
{
switch (settings->Type)
{
case DefaultSettingType:
LogInfo(applyDefaultSettings);
break;
case PrefsSettingType:
LogInfo(applyFileSettings);
break;
case CliSettingType:
LogInfo(applyCliSettings);
break;
case WbSettingType:
LogInfo(applyWbSettings);
break;
default:
break;
}
ApplyBooleanSetting(settings, PopUpSet, SettingKeys->Popup,
&Settings->Popup, &settings->Popup, quiet);
ApplyStringSetting(settings, PopKeySet, SettingKeys->PopKey,
&Settings->PopKey, settings->PopKey, quiet);
ApplyLongSetting(settings, PrioritySet, SettingKeys->Priority,
&Settings->Priority, &settings->Priority, quiet);
ApplyLongSetting(settings, TimeoutSet, SettingKeys->Timeout,
&Settings->Timeout, &settings->Timeout, quiet);
ApplyLongSetting(settings, IntervalSet, SettingKeys->Interval,
&Settings->Interval, &settings->Interval, quiet);
ApplyBooleanSetting(settings, ReadonlySet, SettingKeys->Readonly,
&Settings->Readonly, &settings->Readonly, quiet);
ApplyBooleanSetting(settings, ExpertSet, SettingKeys->Expert,
&Settings->Expert, &settings->Expert, quiet);
ApplyStringSetting(settings, DestinationAddressSet, SettingKeys->DestinationAddress,
&Settings->DestinationAddress, settings->DestinationAddress, quiet);
ApplyStringSetting(settings, DestinationPortSet, SettingKeys->DestinationPort,
&Settings->DestinationPort, settings->DestinationPort, quiet);
if ((settings->Values & ThresholdSet) == ThresholdSet)
{
char before[MAXLONGLONGCHARSIZE];
char after[MAXLONGLONGCHARSIZE];
if (settings->Type == DefaultSettingType)
{
LongLongToStr(settings->Threshold, after);
LogDebug(settingValueString, SettingKeys->Threshold, after);
}
else if (Settings->Threshold != settings->Threshold)
{
LongLongToStr(Settings->Threshold, before);
LongLongToStr(settings->Threshold, after);
LogInfo(settingChangedString, SettingKeys->Threshold, before, after);
}
else if (!quiet)
{
LongLongToStr(Settings->Threshold, before);
LogDebug(settingSetString, SettingKeys->Threshold, before);
}
Settings->Threshold = settings->Threshold;
}
}
void CacheSettings(struct AppSettings *settings)
{
if (cachedSettings != NULL)
{
FreeSettings(cachedSettings);
}
cachedSettings = settings;
}
void ApplySettings()
{
ApplyAppSettings((struct AppSettings *)&DefaultSettings, false);
if (fileSettings != NULL)
{
ApplyAppSettings(fileSettings, false);
FreeSettings(fileSettings);
}
if (cachedSettings != NULL)
{
ApplyAppSettings(cachedSettings, false);
FreeSettings(cachedSettings);
}
}