apple2ix/src/prefs.c
Aaron Culliney db816ad502 Refactor Android variant to use common shared JSON preferences
- Eliminates a whole bunch of boilerplate setter/getter code in various places
    - Accesses preferences directly from JSON data through custom json_parse.h API
    - Improves code locality for Java menus/settings
2016-04-11 22:01:30 -07:00

493 lines
12 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;
// ----------------------------------------------------------------------------
void prefs_load(void) {
pthread_mutex_lock(&prefsLock);
FREE(prefsFile);
const char *apple2JSON = getenv("APPLE2IX_JSON");
if (apple2JSON) {
ASPRINTF(&prefsFile, "%s", apple2JSON);
}
if (!prefsFile) {
#ifdef ANDROID
ASPRINTF(&prefsFile, "%s/.apple2.json", data_dir);
#else
ASPRINTF(&prefsFile, "%s/.apple2.json", getenv("HOME"));
#endif
}
assert(prefsFile);
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_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) {
ERRLOG("Not saving preferences, no file loaded...");
break;
}
if (!jsonPrefs) {
ERRLOG("Not saving preferences, none loaded...");
break;
}
if (((JSON_s *)jsonPrefs)->numTokens <= 0) {
ERRLOG("Not saving preferences, no preferences loaded...");
break;
}
assert(((JSON_s *)jsonPrefs)->jsonString && "string should be valid");
#define PREFS_ERRPRINT() \
ERRLOG( \
"Cannot open the .apple2.json preferences file for writing.\n" \
"Make sure it has R/W permission in your home directory.")
#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) {
PREFS_ERRPRINT();
#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(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(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(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(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(bool emulatorShuttingDown) {
if (!emulatorShuttingDown) {
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);
}