apple2ix/src/prefs.c

498 lines
13 KiB
C

/*
* Apple // emulator for *ix
*
* This software package is subject to the GNU General Public License
* version 3 or later (your choice) as published by the Free Software
* Foundation.
*
* Copyright 1994 Alexander Jean-Claude Bottema
* Copyright 1995 Stephen Lee
* Copyright 1997, 1998 Aaron Culliney
* Copyright 1998, 1999, 2000 Michael Deutschmann
* Copyright 2013-2015 Aaron Culliney
*
*/
#include "prefs.h"
#include "json_parse_private.h"
typedef struct prefs_listener_s {
prefs_change_callback_f prefsChanged;
struct prefs_listener_s *nextListener;
} prefs_listener_s;
typedef struct prefs_domain_s {
char *domain;
struct prefs_listener_s *listeners;
struct prefs_domain_s *nextDomain;
} prefs_domain_s;
static JSON_ref jsonPrefs = NULL;
static prefs_domain_s *domains = NULL;
static char *prefsFile = NULL;
static unsigned long listenerCount = 0;
static pthread_mutex_t prefsLock = PTHREAD_MUTEX_INITIALIZER;
// ----------------------------------------------------------------------------
static void _prefs_load(const char *filePath) {
pthread_mutex_lock(&prefsLock);
assert(filePath && "must specify a file path");
FREE(prefsFile);
prefsFile = STRDUP(filePath);
json_destroy(&jsonPrefs);
int tokCount = json_createFromFile(prefsFile, &jsonPrefs);
if (tokCount < 0) {
tokCount = json_createFromString("{}", &jsonPrefs);
assert(tokCount > 0);
}
pthread_mutex_unlock(&prefsLock);
}
void prefs_load(void) {
char *filePath = NULL;
const char *apple2JSON = getenv("APPLE2IX_JSON");
if (apple2JSON) {
filePath = STRDUP(apple2JSON);
} else {
ASPRINTF(&filePath, "%s/.apple2.json", HOMEDIR);
}
assert((uintptr_t)filePath);
_prefs_load(filePath);
FREE(filePath);
}
#if TESTING
void prefs_load_file(const char *filePath) {
_prefs_load(filePath);
}
#endif
void prefs_loadString(const char *jsonString) {
pthread_mutex_lock(&prefsLock);
json_destroy(&jsonPrefs);
int tokCount = json_createFromString(jsonString, &jsonPrefs);
if (tokCount < 0) {
tokCount = json_createFromString("{}", &jsonPrefs);
assert(tokCount > 0);
}
pthread_mutex_unlock(&prefsLock);
}
bool prefs_save(void) {
pthread_mutex_lock(&prefsLock);
int fd = -1;
bool success = false;
do {
if (!prefsFile) {
LOG("Not saving preferences, no file loaded...");
break;
}
if (!jsonPrefs) {
LOG("Not saving preferences, none loaded...");
break;
}
if (((JSON_s *)jsonPrefs)->numTokens <= 0) {
LOG("Not saving preferences, no preferences loaded...");
break;
}
assert(((JSON_s *)jsonPrefs)->jsonString && "string should be valid");
#define ERROR_SUBMENU_H 8
#define ERROR_SUBMENU_W 40
#if defined(INTERFACE_CLASSIC) && !TESTING
int ch = -1;
char submenu[ERROR_SUBMENU_H][ERROR_SUBMENU_W+1] =
//1. 5. 10. 15. 20. 25. 30. 35. 40.
{ "||||||||||||||||||||||||||||||||||||||||",
"| |",
"| |",
"| OOPS, could not open or write to the |",
"| .apple2.json preferences file |",
"| |",
"| |",
"||||||||||||||||||||||||||||||||||||||||" };
#endif
TEMP_FAILURE_RETRY(fd = open(prefsFile, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR));
if (fd == -1) {
LOG("Cannot open the .apple2.json preferences file for writing.\n"
"Make sure it has R/W permission in your home directory. (%s)", strerror(errno));
#if defined(INTERFACE_CLASSIC) && !TESTING
c_interface_print_submenu_centered(submenu[0], ERROR_SUBMENU_W, ERROR_SUBMENU_H);
while ((ch = c_mygetch(1)) == -1) {
// ...
}
#endif
break;
}
success = json_serialize(jsonPrefs, fd, /*pretty:*/true);
} while (0);
if (fd != -1) {
TEMP_FAILURE_RETRY(fsync(fd));
TEMP_FAILURE_RETRY(close(fd));
}
pthread_mutex_unlock(&prefsLock);
return success;
}
bool prefs_copyJSONValue(const char * _NONNULL domain, const char * _NONNULL key, INOUT JSON_ref *jsonVal) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount < 0) {
break;
}
ret = json_mapCopyJSON(jsonRef, key, jsonVal);
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
bool prefs_copyStringValue(const char *domain, const char *key, INOUT char **val) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount < 0) {
break;
}
ret = json_mapCopyStringValue(jsonRef, key, val);
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
bool prefs_parseLongValue(const char *domain, const char *key, INOUT long *val, const long base) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount < 0) {
break;
}
ret = json_mapParseLongValue(jsonRef, key, val, base);
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
bool prefs_parseBoolValue(const char *domain, const char *key, INOUT bool *val) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount < 0) {
break;
}
ret = json_mapParseBoolValue(jsonRef, key, val);
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
bool prefs_parseFloatValue(const char *domain, const char *key, INOUT float *val) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount < 0) {
break;
}
ret = json_mapParseFloatValue(jsonRef, key, val);
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
bool prefs_setStringValue(const char *domain, const char * _NONNULL key, const char * _NONNULL val) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount <= 0) {
errCount = json_createFromString("{}", &jsonRef);
assert(errCount > 0);
assert((uintptr_t)jsonRef);
}
ret = json_mapSetStringValue(jsonRef, key, val);
if (!ret) {
break;
}
ret = json_mapSetJSONValue(jsonPrefs, domain, jsonRef);
if (!ret) {
break;
}
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
bool prefs_setLongValue(const char * _NONNULL domain, const char * _NONNULL key, long val) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount <= 0) {
errCount = json_createFromString("{}", &jsonRef);
assert(errCount > 0);
assert((uintptr_t)jsonRef);
}
ret = json_mapSetLongValue(jsonRef, key, val);
if (!ret) {
break;
}
ret = json_mapSetJSONValue(jsonPrefs, domain, jsonRef);
if (!ret) {
break;
}
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
bool prefs_setBoolValue(const char * _NONNULL domain, const char * _NONNULL key, bool val) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount <= 0) {
errCount = json_createFromString("{}", &jsonRef);
assert(errCount > 0);
assert((uintptr_t)jsonRef);
}
ret = json_mapSetBoolValue(jsonRef, key, val);
if (!ret) {
break;
}
ret = json_mapSetJSONValue(jsonPrefs, domain, jsonRef);
if (!ret) {
break;
}
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
bool prefs_setFloatValue(const char * _NONNULL domain, const char * _NONNULL key, float val) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount <= 0) {
errCount = json_createFromString("{}", &jsonRef);
assert(errCount > 0);
assert((uintptr_t)jsonRef);
}
ret = json_mapSetFloatValue(jsonRef, key, val);
if (!ret) {
break;
}
ret = json_mapSetJSONValue(jsonPrefs, domain, jsonRef);
if (!ret) {
break;
}
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
void prefs_registerListener(const char *domain, prefs_change_callback_f callback) {
pthread_mutex_lock(&prefsLock);
assert(domain && "listener needs to specify non-NULL domain");
prefs_domain_s *dom = domains;
while (dom) {
if (strcmp(domain, dom->domain) == 0) {
break;
}
dom = dom->nextDomain;
}
if (!dom) {
dom = MALLOC(sizeof(*dom));
dom->domain = STRDUP(domain);
dom->nextDomain = domains;
dom->listeners = NULL;
domains = dom;
}
prefs_listener_s *newL = MALLOC(sizeof(*newL));
prefs_listener_s *oldL = dom->listeners;
dom->listeners = newL;
newL->nextListener = oldL;
newL->prefsChanged = callback;
++listenerCount;
pthread_mutex_unlock(&prefsLock);
}
void prefs_sync(const char *domain) {
static unsigned long syncCount = 0;
pthread_mutex_lock(&prefsLock);
++syncCount;
if (syncCount > 1) {
pthread_mutex_unlock(&prefsLock);
return;
}
pthread_mutex_unlock(&prefsLock);
void **alreadySynced = MALLOC(listenerCount * sizeof(void *));
unsigned long idx = 0;
prefs_domain_s *dom = domains;
do {
while (dom) {
if (domain && (strcmp(domain, dom->domain) != 0)) {
dom = dom->nextDomain;
continue;
}
prefs_listener_s *listener = dom->listeners;
while (listener) {
bool foundAlready = false;
for (unsigned long i = 0; i < idx; i++) {
if (alreadySynced[i] == (void *)listener->prefsChanged) {
LOG("ignoring already synced listener %p for domain %s", alreadySynced[i], dom->domain);
foundAlready = true;
break;
}
}
if (!foundAlready) {
alreadySynced[idx++] = (void *)listener->prefsChanged;
assert(idx <= listenerCount);
listener->prefsChanged(dom->domain);
}
listener = listener->nextListener;
}
if (domain) {
break;
}
dom = dom->nextDomain;
}
pthread_mutex_lock(&prefsLock);
--syncCount;
if (syncCount == 0) {
pthread_mutex_unlock(&prefsLock);
break;
}
pthread_mutex_unlock(&prefsLock);
} while (1);
FREE(alreadySynced);
}
void prefs_shutdown(void) {
if (!emulator_isShuttingDown()) {
return;
}
pthread_mutex_lock(&prefsLock);
FREE(prefsFile);
prefs_domain_s *dom = domains;
domains = NULL;
while (dom) {
prefs_listener_s *listener = dom->listeners;
while (listener) {
prefs_listener_s *dead = listener;
listener = listener->nextListener;
FREE(dead);
}
prefs_domain_s *dead = dom;
dom = dom->nextDomain;
FREE(dead->domain);
FREE(dead);
}
listenerCount = 0;
pthread_mutex_unlock(&prefsLock);
}