#include <CoreFoundation/CoreFoundation.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "prefs.h"
static CFTypeRef PrefsCopyPreference(const char *domain, const char *key);
static bool PrefsParseBoolString(const char *value, bool *out_value);
static bool PrefsGetBoolValue(CFTypeRef value, bool default_value);
static int64_t PrefsGetInt64Value(CFTypeRef value, int64_t default_value);
static double PrefsGetDoubleValue(CFTypeRef value, double default_value);
static char *PrefsCopyUTF8String(CFStringRef value);
static CFStringRef PrefsCreateCFString(const char *value);
char *prefs_copy_string(const char *domain, const char *key) {
CFTypeRef value = PrefsCopyPreference(domain, key);
char *result = NULL;
if (value != NULL && CFGetTypeID(value) == CFStringGetTypeID()) {
result = PrefsCopyUTF8String((CFStringRef)value);
}
if (value != NULL) {
CFRelease(value);
}
return result;
}
PrefsBoolResult prefs_get_bool_optional(const char *domain, const char *key) {
CFTypeRef value = PrefsCopyPreference(domain, key);
PrefsBoolResult result = { .found = value != NULL, .value = false };
if (value != NULL) {
result.value = PrefsGetBoolValue(value, false);
CFRelease(value);
}
return result;
}
PrefsInt64Result prefs_get_i64_optional(const char *domain, const char *key) {
CFTypeRef value = PrefsCopyPreference(domain, key);
PrefsInt64Result result = { .found = value != NULL, .value = 0 };
if (value != NULL) {
result.value = PrefsGetInt64Value(value, 0);
CFRelease(value);
}
return result;
}
PrefsDoubleResult prefs_get_f64_optional(const char *domain, const char *key) {
CFTypeRef value = PrefsCopyPreference(domain, key);
PrefsDoubleResult result = { .found = value != NULL, .value = 0.0 };
if (value != NULL) {
result.value = PrefsGetDoubleValue(value, 0.0);
CFRelease(value);
}
return result;
}
PrefsStringArray prefs_copy_string_array(const char *domain, const char *key) {
PrefsStringArray result = { .found = false, .values = NULL, .len = 0 };
CFTypeRef raw_value = PrefsCopyPreference(domain, key);
if (raw_value == NULL) {
return result;
}
result.found = true;
CFTypeID type = CFGetTypeID(raw_value);
if (type != CFArrayGetTypeID()) {
CFRelease(raw_value);
return result;
}
CFIndex count = CFArrayGetCount((CFArrayRef)raw_value);
if (count <= 0) {
CFRelease(raw_value);
return result;
}
result.len = (uintptr_t)count;
result.values = calloc((size_t)count, sizeof(char *));
if (result.values == NULL) {
result.len = 0;
CFRelease(raw_value);
return result;
}
for (CFIndex index = 0; index < count; index += 1) {
CFTypeRef entry = CFArrayGetValueAtIndex((CFArrayRef)raw_value, index);
if (entry == NULL || CFGetTypeID(entry) != CFStringGetTypeID()) {
continue;
}
result.values[index] = PrefsCopyUTF8String((CFStringRef)entry);
}
CFRelease(raw_value);
return result;
}
bool prefs_set_bool(const char *domain, const char *key, bool value) {
CFStringRef domain_ref = PrefsCreateCFString(domain);
CFStringRef key_ref = PrefsCreateCFString(key);
if (domain_ref == NULL || key_ref == NULL) {
if (domain_ref != NULL) CFRelease(domain_ref);
if (key_ref != NULL) CFRelease(key_ref);
return false;
}
CFBooleanRef bool_ref = value ? kCFBooleanTrue : kCFBooleanFalse;
CFPreferencesSetAppValue(key_ref, bool_ref, domain_ref);
bool result = CFPreferencesAppSynchronize(domain_ref);
CFRelease(key_ref);
CFRelease(domain_ref);
return result;
}
bool prefs_set_i64(const char *domain, const char *key, int64_t value) {
CFStringRef domain_ref = PrefsCreateCFString(domain);
CFStringRef key_ref = PrefsCreateCFString(key);
if (domain_ref == NULL || key_ref == NULL) {
if (domain_ref != NULL) CFRelease(domain_ref);
if (key_ref != NULL) CFRelease(key_ref);
return false;
}
CFNumberRef num_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &value);
if (num_ref == NULL) {
CFRelease(key_ref);
CFRelease(domain_ref);
return false;
}
CFPreferencesSetAppValue(key_ref, num_ref, domain_ref);
bool result = CFPreferencesAppSynchronize(domain_ref);
CFRelease(num_ref);
CFRelease(key_ref);
CFRelease(domain_ref);
return result;
}
bool prefs_set_f64(const char *domain, const char *key, double value) {
CFStringRef domain_ref = PrefsCreateCFString(domain);
CFStringRef key_ref = PrefsCreateCFString(key);
if (domain_ref == NULL || key_ref == NULL) {
if (domain_ref != NULL) CFRelease(domain_ref);
if (key_ref != NULL) CFRelease(key_ref);
return false;
}
CFNumberRef num_ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &value);
if (num_ref == NULL) {
CFRelease(key_ref);
CFRelease(domain_ref);
return false;
}
CFPreferencesSetAppValue(key_ref, num_ref, domain_ref);
bool result = CFPreferencesAppSynchronize(domain_ref);
CFRelease(num_ref);
CFRelease(key_ref);
CFRelease(domain_ref);
return result;
}
bool prefs_set_string(const char *domain, const char *key, const char *value) {
CFStringRef domain_ref = PrefsCreateCFString(domain);
CFStringRef key_ref = PrefsCreateCFString(key);
CFStringRef value_ref = PrefsCreateCFString(value);
if (domain_ref == NULL || key_ref == NULL || value_ref == NULL) {
if (domain_ref != NULL) CFRelease(domain_ref);
if (key_ref != NULL) CFRelease(key_ref);
if (value_ref != NULL) CFRelease(value_ref);
return false;
}
CFPreferencesSetAppValue(key_ref, value_ref, domain_ref);
bool result = CFPreferencesAppSynchronize(domain_ref);
CFRelease(value_ref);
CFRelease(key_ref);
CFRelease(domain_ref);
return result;
}
bool prefs_set_string_array(const char *domain, const char *key, const char **values, uintptr_t len) {
CFStringRef domain_ref = PrefsCreateCFString(domain);
CFStringRef key_ref = PrefsCreateCFString(key);
if (domain_ref == NULL || key_ref == NULL) {
if (domain_ref != NULL) CFRelease(domain_ref);
if (key_ref != NULL) CFRelease(key_ref);
return false;
}
bool result = false;
if (len == 0) {
CFPreferencesSetAppValue(key_ref, NULL, domain_ref);
result = CFPreferencesAppSynchronize(domain_ref);
CFRelease(key_ref);
CFRelease(domain_ref);
return result;
}
CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, (CFIndex)len, &kCFTypeArrayCallBacks);
if (array == NULL) {
CFRelease(key_ref);
CFRelease(domain_ref);
return false;
}
for (uintptr_t i = 0; i < len; i++) {
CFStringRef str_ref = PrefsCreateCFString(values[i]);
if (str_ref != NULL) {
CFArrayAppendValue(array, str_ref);
CFRelease(str_ref);
}
}
CFPreferencesSetAppValue(key_ref, array, domain_ref);
result = CFPreferencesAppSynchronize(domain_ref);
CFRelease(array);
CFRelease(key_ref);
CFRelease(domain_ref);
return result;
}
bool prefs_remove(const char *domain, const char *key) {
CFStringRef domain_ref = PrefsCreateCFString(domain);
CFStringRef key_ref = PrefsCreateCFString(key);
if (domain_ref == NULL || key_ref == NULL) {
if (domain_ref != NULL) CFRelease(domain_ref);
if (key_ref != NULL) CFRelease(key_ref);
return false;
}
CFPreferencesSetAppValue(key_ref, NULL, domain_ref);
bool result = CFPreferencesAppSynchronize(domain_ref);
CFRelease(key_ref);
CFRelease(domain_ref);
return result;
}
bool prefs_contains(const char *domain, const char *key) {
CFStringRef domain_ref = PrefsCreateCFString(domain);
CFStringRef key_ref = PrefsCreateCFString(key);
if (domain_ref == NULL || key_ref == NULL) {
if (domain_ref != NULL) CFRelease(domain_ref);
if (key_ref != NULL) CFRelease(key_ref);
return false;
}
CFPreferencesAppSynchronize(domain_ref);
CFTypeRef value = CFPreferencesCopyAppValue(key_ref, domain_ref);
bool result = value != NULL;
if (value != NULL) {
CFRelease(value);
}
CFRelease(key_ref);
CFRelease(domain_ref);
return result;
}
void prefs_free_string(char *value) {
free(value);
}
void prefs_free_string_array(PrefsStringArray *array) {
if (array == NULL || array->values == NULL) {
return;
}
for (uintptr_t index = 0; index < array->len; index += 1) {
free(array->values[index]);
}
free(array->values);
memset(array, 0, sizeof(*array));
}
static CFTypeRef PrefsCopyPreference(const char *domain, const char *key) {
if (domain == NULL || key == NULL) {
return NULL;
}
CFStringRef domain_ref = CFStringCreateWithCString(kCFAllocatorDefault, domain, kCFStringEncodingUTF8);
CFStringRef key_ref = CFStringCreateWithCString(kCFAllocatorDefault, key, kCFStringEncodingUTF8);
if (domain_ref == NULL || key_ref == NULL) {
if (domain_ref != NULL) {
CFRelease(domain_ref);
}
if (key_ref != NULL) {
CFRelease(key_ref);
}
return NULL;
}
CFPreferencesAppSynchronize(domain_ref);
CFTypeRef value = CFPreferencesCopyAppValue(key_ref, domain_ref);
CFRelease(key_ref);
CFRelease(domain_ref);
return value;
}
static bool PrefsParseBoolString(const char *value, bool *out_value) {
char *end = NULL;
long long numeric = strtoll(value, &end, 10);
if (end != value && *end == '\0') {
*out_value = numeric != 0;
return true;
}
if (strcasecmp(value, "true") == 0 || strcasecmp(value, "yes") == 0) {
*out_value = true;
return true;
}
if (strcasecmp(value, "false") == 0 || strcasecmp(value, "no") == 0) {
*out_value = false;
return true;
}
return false;
}
static bool PrefsGetBoolValue(CFTypeRef value, bool default_value) {
if (value == NULL) {
return default_value;
}
CFTypeID type = CFGetTypeID(value);
if (type == CFBooleanGetTypeID()) {
return CFBooleanGetValue((CFBooleanRef)value);
}
if (type == CFNumberGetTypeID()) {
int64_t numeric = 0;
if (CFNumberGetValue((CFNumberRef)value, kCFNumberSInt64Type, &numeric)) {
return numeric != 0;
}
return default_value;
}
if (type == CFStringGetTypeID()) {
char *buffer = PrefsCopyUTF8String((CFStringRef)value);
if (buffer == NULL) {
return default_value;
}
bool parsed = false;
bool result = default_value;
if (PrefsParseBoolString(buffer, &result)) {
parsed = true;
}
free(buffer);
return parsed ? result : default_value;
}
return default_value;
}
static int64_t PrefsGetInt64Value(CFTypeRef value, int64_t default_value) {
if (value == NULL) {
return default_value;
}
CFTypeID type = CFGetTypeID(value);
if (type == CFNumberGetTypeID()) {
int64_t numeric = 0;
if (CFNumberGetValue((CFNumberRef)value, kCFNumberSInt64Type, &numeric)) {
return numeric;
}
return default_value;
}
if (type == CFStringGetTypeID()) {
char *buffer = PrefsCopyUTF8String((CFStringRef)value);
if (buffer == NULL) {
return default_value;
}
char *end = NULL;
long long numeric = strtoll(buffer, &end, 10);
const bool parsed = end != buffer && *end == '\0';
free(buffer);
if (parsed) {
return (int64_t)numeric;
}
}
return default_value;
}
static double PrefsGetDoubleValue(CFTypeRef value, double default_value) {
if (value == NULL) {
return default_value;
}
CFTypeID type = CFGetTypeID(value);
if (type == CFNumberGetTypeID()) {
double numeric = 0.0;
if (CFNumberGetValue((CFNumberRef)value, kCFNumberDoubleType, &numeric)) {
return numeric;
}
return default_value;
}
if (type == CFStringGetTypeID()) {
char *buffer = PrefsCopyUTF8String((CFStringRef)value);
if (buffer == NULL) {
return default_value;
}
char *end = NULL;
double numeric = strtod(buffer, &end);
const bool parsed = end != buffer && *end == '\0';
free(buffer);
if (parsed) {
return numeric;
}
}
return default_value;
}
static char *PrefsCopyUTF8String(CFStringRef value) {
if (value == NULL) {
return NULL;
}
CFIndex length = CFStringGetLength(value);
CFIndex capacity = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
char *buffer = calloc((size_t)capacity, sizeof(char));
if (buffer == NULL) {
return NULL;
}
if (!CFStringGetCString(value, buffer, capacity, kCFStringEncodingUTF8)) {
free(buffer);
return NULL;
}
return buffer;
}
static CFStringRef PrefsCreateCFString(const char *value) {
if (value == NULL) {
return NULL;
}
return CFStringCreateWithCString(kCFAllocatorDefault, value, kCFStringEncodingUTF8);
}