admin: Global settings can be specified on the command-line. Removed some previous, less thorough ways of configuring things. #102
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled

This commit is contained in:
Cory McWilliams 2025-02-16 13:37:25 -05:00
parent 6247529799
commit c794c1b885
14 changed files with 201 additions and 173 deletions

View File

@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🎛",
"previous": "&R49FywYF8CXPhoSEydLbSCgvCddeyTiBwGuDU/gqY+M=.sha256"
"previous": "&kmKNyb/uaXNb24gCinJtfS8iWx4cLUWdtl0y2DwEUas=.sha256"
}

View File

@ -72,7 +72,7 @@ ${description.value}</textarea
</button>
</li>
`;
} else {
} else if (description.type != 'hidden') {
return html`
<li class="w3-row">
<label class="w3-quarter" for=${'gs_' + key} style="font-weight: bold">${title_case(key)}</label>

View File

@ -470,7 +470,6 @@ async function getProcessBlob(blobId, key, options) {
imports.ssb = Object.fromEntries(
Object.keys(ssb).map((key) => [key, ssb[key].bind(ssb)])
);
imports.ssb.port = tildefriends.ssb_port;
imports.ssb.createIdentity = () => process.createIdentity();
imports.ssb.addIdentity = function (id) {
if (

View File

@ -2319,35 +2319,6 @@ static void _httpd_endpoint_app_socket(tf_http_request_t* request)
JS_FreeValue(context, global);
}
static int _tf_httpd_get_tildefriends_int(JSContext* context, const char* arg)
{
JSValue global = JS_GetGlobalObject(context);
JSValue tildefriends = JS_GetPropertyStr(context, global, "tildefriends");
JSValue arg_value = JS_GetPropertyStr(context, tildefriends, arg);
int value = 0;
JS_ToInt32(context, &value, arg_value);
JS_FreeValue(context, arg_value);
JS_FreeValue(context, tildefriends);
JS_FreeValue(context, global);
return value;
}
static const char* _tf_httpd_get_tildefriends_arg_string(JSContext* context, const char* arg)
{
JSValue global = JS_GetGlobalObject(context);
JSValue tildefriends = JS_GetPropertyStr(context, global, "tildefriends");
JSValue args = JS_GetPropertyStr(context, tildefriends, "args");
JSValue arg_value = JS_GetPropertyStr(context, args, arg);
const char* value = !JS_IsUndefined(arg_value) ? JS_ToCString(context, arg_value) : NULL;
const char* result = value ? tf_strdup(value) : NULL;
JS_FreeCString(context, value);
JS_FreeValue(context, arg_value);
JS_FreeValue(context, args);
JS_FreeValue(context, tildefriends);
JS_FreeValue(context, global);
return result;
}
static void _httpd_free_user_data(void* user_data)
{
tf_free(user_data);
@ -2394,10 +2365,6 @@ void tf_httpd_register(JSContext* context)
fprintf(stderr, "Failed to register Request.\n");
}
int http_port = _tf_httpd_get_tildefriends_int(context, "http_port");
int https_port = _tf_httpd_get_tildefriends_int(context, "https_port");
const char* out_http_port_file = _tf_httpd_get_tildefriends_arg_string(context, "out_http_port_file");
JSValue global = JS_GetGlobalObject(context);
JSValue httpd = JS_NewObjectClass(context, _httpd_class_id);
@ -2408,6 +2375,15 @@ void tf_httpd_register(JSContext* context)
tf_http_set_trace(http, tf_task_get_trace(task));
JS_SetOpaque(httpd, http);
int64_t http_port = 0;
int64_t https_port = 0;
char out_http_port_file[512] = "";
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
tf_ssb_db_get_global_setting_int64(db, "http_port", &http_port);
tf_ssb_db_get_global_setting_int64(db, "https_port", &https_port);
tf_ssb_db_get_global_setting_string(db, "out_http_port_file", out_http_port_file, sizeof(out_http_port_file));
tf_ssb_release_db_reader(ssb, db);
if (https_port)
{
http_user_data_t* user_data = tf_http_get_user_data(http);
@ -2463,14 +2439,14 @@ void tf_httpd_register(JSContext* context)
JS_SetPropertyStr(context, global, "httpd", httpd);
JS_FreeValue(context, global);
if (http_port > 0 || out_http_port_file)
if (http_port > 0 || *out_http_port_file)
{
httpd_listener_t* listener = tf_malloc(sizeof(httpd_listener_t));
*listener = (httpd_listener_t) { 0 };
int assigned_port = tf_http_listen(http, http_port, NULL, _httpd_listener_cleanup, listener);
tf_printf(CYAN "~😎 Tilde Friends" RESET " " YELLOW VERSION_NUMBER RESET " is now up at " MAGENTA "http://127.0.0.1:%d/" RESET ".\n", assigned_port);
if (out_http_port_file)
if (*out_http_port_file)
{
const char* actual_http_port_file = tf_task_get_path_with_root(task, out_http_port_file);
FILE* file = fopen(actual_http_port_file, "wb");
@ -2507,6 +2483,4 @@ void tf_httpd_register(JSContext* context)
tf_free((char*)private_key);
}
}
tf_free((void*)out_http_port_file);
}

View File

@ -1217,9 +1217,6 @@ typedef struct tf_run_args_t
{
const char* script;
const char* network_key;
int ssb_port;
int http_port;
int https_port;
const char* db_path;
int count;
const char* args;
@ -1244,9 +1241,6 @@ static int _tf_run_task(const tf_run_args_t* args, int index)
tf_printf("setting zip path to %s\n", args->zip);
tf_task_set_zip_path(task, args->zip);
tf_task_set_ssb_network_key(task, args->network_key);
tf_task_set_ssb_port(task, args->ssb_port ? args->ssb_port + index : 0);
tf_task_set_http_port(task, args->http_port ? args->http_port + index : 0);
tf_task_set_https_port(task, args->https_port ? args->https_port + index : 0);
tf_task_set_args(task, args->args);
tf_task_set_one_proc(task, args->one_proc);
const char* db_path = args->db_path;
@ -1301,7 +1295,14 @@ static int _tf_run_task(const tf_run_args_t* args, int index)
tf_task_activate(task);
tf_ssb_set_verbose(tf_task_get_ssb(task), args->verbose);
tf_ssb_start_periodic(tf_task_get_ssb(task));
if (args->http_port || args->https_port)
tf_ssb_t* ssb = tf_task_get_ssb(task);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
int64_t http_port = 0;
int64_t https_port = 0;
tf_ssb_db_get_global_setting_int64(db, "http_port", &http_port);
tf_ssb_db_get_global_setting_int64(db, "https_port", &https_port);
tf_ssb_release_db_reader(ssb, db);
if (http_port || https_port)
{
if (args->zip)
{
@ -1417,9 +1418,6 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
const char* default_db_path = _get_db_path();
tf_run_args_t args = {
.count = 1,
.http_port = 12345,
.https_port = 12346,
.ssb_port = 8008,
.db_path = default_db_path,
};
bool show_usage = false;
@ -1436,10 +1434,7 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
{
static const struct option k_options[] = {
{ "script", required_argument, NULL, 's' },
{ "ssb-port", required_argument, NULL, 'b' },
{ "ssb-network-key", required_argument, NULL, 'k' },
{ "http-port", required_argument, NULL, 'p' },
{ "https-port", required_argument, NULL, 'q' },
{ "db-path", required_argument, NULL, 'd' },
{ "count", required_argument, NULL, 'n' },
{ "args", required_argument, NULL, 'a' },
@ -1448,7 +1443,7 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
{ "verbose", no_argument, NULL, 'v' },
{ "help", no_argument, NULL, 'h' },
};
int c = getopt_long(argc, argv, "s:b:k:p:q:d:n:a:oz:vh", k_options, NULL);
int c = getopt_long(argc, argv, "s:k:d:n:a:oz:vh", k_options, NULL);
if (c == -1)
{
break;
@ -1467,15 +1462,6 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
case 'k':
args.network_key = optarg;
break;
case 'b':
args.ssb_port = atoi(optarg);
break;
case 'p':
args.http_port = atoi(optarg);
break;
case 'q':
args.https_port = atoi(optarg);
break;
case 'd':
args.db_path = optarg;
break;
@ -1502,13 +1488,11 @@ static int _tf_command_run(const char* file, int argc, char* argv[])
tf_printf("\n%s run [options]\n\n", file);
tf_printf("options\n");
tf_printf(" -s, --script script Script to run (default: core/core.js).\n");
tf_printf(" -b, --ssb-port port Port on which to run SSB (default: 8008, 0 disables).\n");
tf_printf(" -p, --http-port port Port on which to run Tilde Friends web server (default: 12345).\n");
tf_printf(" -q, --https-port port Port on which to run secure Tilde Friends web server (default: 12346).\n");
tf_printf(" -d, --db-path path SQLite database path (default: %s).\n", default_db_path);
tf_printf(" -k, --ssb-network-key key SSB network key to use.\n");
tf_printf(" -n, --count count Number of instances to run.\n");
tf_printf(" -a, --args args Arguments of the format key=value,foo=bar,verbose=true.\n");
tf_printf(" -a, --args args Arguments of the format key=value,foo=bar,verbose=true (note: these are persisted to the database).\n");
tf_util_document_settings(" ");
tf_printf(" -o, --one-proc Run everything in one process (unsafely!).\n");
tf_printf(" -z, --zip path Zip archive from which to load files.\n");
tf_printf(" -v, --verbose Log raw messages.\n");
@ -1847,9 +1831,6 @@ void tf_run_thread_start(const char* zip_path)
tf_run_thread_data_t* data = tf_malloc(sizeof(tf_run_thread_data_t));
tf_run_args_t args = {
.count = 1,
.http_port = 12345,
.https_port = 12346,
.ssb_port = 8008,
.db_path = "db.sqlite",
.one_proc = true,
.zip = zip_path,

View File

@ -2126,6 +2126,51 @@ bool tf_ssb_db_get_global_setting_string(sqlite3* db, const char* name, char* ou
return result;
}
bool tf_ssb_db_set_global_setting_from_string(sqlite3* db, const char* name, char* value)
{
tf_setting_kind_t kind = tf_util_get_global_setting_kind(name);
if (kind == k_kind_unknown)
{
return false;
}
bool result = false;
sqlite3_stmt* statement;
if (sqlite3_prepare(db,
"INSERT INTO properties (id, key, value) VALUES ('core', 'settings', json_object(?1, ?2)) ON CONFLICT DO UPDATE SET value = json_set(value, '$.' || ?1, ?2)", -1,
&statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK)
{
bool bound = false;
switch (kind)
{
case k_kind_bool:
bound = sqlite3_bind_int(statement, 2, value && (strcmp(value, "true") == 0 || atoi(value))) == SQLITE_OK;
break;
case k_kind_int:
bound = sqlite3_bind_int(statement, 2, atoi(value)) == SQLITE_OK;
break;
case k_kind_string:
bound = sqlite3_bind_text(statement, 2, value, -1, NULL) == SQLITE_OK;
break;
case k_kind_unknown:
break;
}
if (bound && sqlite3_step(statement) == SQLITE_DONE)
{
result = sqlite3_changes(db) != 0;
}
}
sqlite3_finalize(statement);
}
else
{
tf_printf("prepare failed: %s\n", sqlite3_errmsg(db));
}
return result;
}
const char* tf_ssb_db_get_profile(sqlite3* db, const char* id)
{
const char* result = NULL;

View File

@ -486,6 +486,15 @@ bool tf_ssb_db_get_global_setting_int64(sqlite3* db, const char* name, int64_t*
*/
bool tf_ssb_db_get_global_setting_string(sqlite3* db, const char* name, char* out_value, size_t size);
/**
** Set a global setting from a string representation of its value.
** @param db The database.
** @param name The setting name.
** @param value The settinv value.
** @return true if the setting was set.
*/
bool tf_ssb_db_set_global_setting_from_string(sqlite3* db, const char* name, char* value);
/**
** Get the latest profile information for the given identity.
** @param db The database.

View File

@ -2357,6 +2357,12 @@ static JSValue _tf_ssb_set_user_permission(JSContext* context, JSValueConst this
return result;
}
static JSValue _tf_ssb_port(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
return JS_NewInt32(context, tf_ssb_server_get_port(ssb));
}
void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
{
JS_NewClassID(&_tf_ssb_classId);
@ -2403,6 +2409,7 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
JS_SetPropertyStr(context, object, "createTunnel", JS_NewCFunction(context, _tf_ssb_createTunnel, "createTunnel", 3));
JS_SetPropertyStr(context, object, "following", JS_NewCFunction(context, _tf_ssb_following, "following", 2));
JS_SetPropertyStr(context, object, "sync", JS_NewCFunction(context, _tf_ssb_sync, "sync", 0));
JS_SetPropertyStr(context, object, "port", JS_NewCFunction(context, _tf_ssb_port, "port", 0));
/* Write. */
JS_SetPropertyStr(context, object, "storeMessage", JS_NewCFunction(context, _tf_ssb_storeMessage, "storeMessage", 1));
JS_SetPropertyStr(context, object, "blobStore", JS_NewCFunction(context, _tf_ssb_blobStore, "blobStore", 1));

View File

@ -913,7 +913,7 @@ static void _write_file(const char* path, const char* contents)
fclose(file);
}
#define TEST_ARGS " --ssb-port=0 --http-port=0 --https-port=0"
#define TEST_ARGS " --args=ssb_port=0,http_port=0,https_port=0"
void tf_ssb_test_encrypt(const tf_test_options_t* options)
{

View File

@ -9,6 +9,7 @@
#include "packetstream.h"
#include "serialize.h"
#include "socket.js.h"
#include "ssb.db.h"
#include "ssb.h"
#include "ssb.js.h"
#include "taskstub.js.h"
@ -154,9 +155,6 @@ typedef struct _tf_task_t
JSValue _loadedFiles;
const char* _network_key;
int _ssb_port;
int _http_port;
int _https_port;
char _db_path[256];
char _zip_path[256];
char _root_path[256];
@ -398,6 +396,7 @@ static JSValue _tf_task_exit(JSContext* context, JSValueConst this_val, int argc
int exitCode = 0;
JS_ToInt32(task->_context, &exitCode, argv[0]);
tf_trace_end(task->_trace);
tf_printf("EXIT %d\n", exitCode);
exit(exitCode);
return JS_UNDEFINED;
}
@ -1680,38 +1679,6 @@ void tf_task_activate(tf_task_t* task)
JS_DefinePropertyGetSet(context, global, atom, JS_NewCFunction(context, _tf_task_get_parent, "parent", 0), JS_UNDEFINED, 0);
JS_FreeAtom(context, atom);
JSValue tildefriends = JS_NewObject(context);
JSValue args = JS_NewObject(context);
JS_SetPropertyStr(context, tildefriends, "args", args);
if (task->_args)
{
char* saveptr = NULL;
char* copy = tf_strdup(task->_args);
char* start = copy;
while (true)
{
char* assignment = strtok_r(start, ",", &saveptr);
start = NULL;
if (!assignment)
{
break;
}
char* equals = strchr(assignment, '=');
if (equals)
{
*equals = '\0';
JS_SetPropertyStr(context, args, assignment, JS_NewString(context, equals + 1));
}
else
{
tf_printf("Assignment missing '=': %s.\n", assignment);
exit(1);
}
}
tf_free(copy);
}
JS_SetPropertyStr(context, global, "tildefriends", tildefriends);
task->_trace = tf_trace_create();
if (task->_trusted)
{
@ -1732,23 +1699,45 @@ void tf_task_activate(tf_task_t* task)
tf_ssb_register(context, task->_ssb);
tf_ssb_set_hitch_callback(task->_ssb, _tf_task_record_hitch, task);
int actual_ssb_port = task->_ssb_port;
if (task->_args)
{
sqlite3* db = tf_ssb_acquire_db_writer(task->_ssb);
char* saveptr = NULL;
char* copy = tf_strdup(task->_args);
char* start = copy;
while (true)
{
char* assignment = strtok_r(start, ",", &saveptr);
start = NULL;
if (!assignment)
{
break;
}
char* equals = strchr(assignment, '=');
if (equals)
{
*equals = '\0';
tf_ssb_db_set_global_setting_from_string(db, assignment, equals + 1);
}
else
{
tf_printf("Assignment missing '=': %s.\n", assignment);
exit(1);
}
}
tf_free(copy);
tf_ssb_release_db_writer(task->_ssb, db);
}
if (task->_ssb_port)
int64_t ssb_port = 0;
sqlite3* db = tf_ssb_acquire_db_reader(task->_ssb);
tf_ssb_db_get_global_setting_int64(db, "ssb_port", &ssb_port);
tf_ssb_release_db_reader(task->_ssb, db);
if (ssb_port)
{
tf_ssb_broadcast_listener_start(task->_ssb, false);
tf_ssb_broadcast_sender_start(task->_ssb);
actual_ssb_port = tf_ssb_server_open(task->_ssb, task->_ssb_port);
}
JS_SetPropertyStr(context, tildefriends, "ssb_port", JS_NewInt32(context, actual_ssb_port));
if (task->_http_port)
{
JS_SetPropertyStr(context, tildefriends, "http_port", JS_NewInt32(context, task->_http_port));
}
if (task->_https_port)
{
JS_SetPropertyStr(context, tildefriends, "https_port", JS_NewInt32(context, task->_https_port));
tf_ssb_server_open(task->_ssb, ssb_port);
}
JS_SetPropertyStr(context, global, "getStats", JS_NewCFunction(context, _tf_task_getStats, "getStats", 0));
@ -1999,21 +1988,6 @@ void tf_task_set_ssb_network_key(tf_task_t* task, const char* network_key)
task->_network_key = network_key;
}
void tf_task_set_ssb_port(tf_task_t* task, int port)
{
task->_ssb_port = port;
}
void tf_task_set_http_port(tf_task_t* task, int port)
{
task->_http_port = port;
}
void tf_task_set_https_port(tf_task_t* task, int port)
{
task->_https_port = port;
}
void tf_task_set_db_path(tf_task_t* task, const char* db_path)
{
snprintf(task->_db_path, sizeof(task->_db_path), "%s", db_path);

View File

@ -76,27 +76,6 @@ void tf_task_configure_from_fd(tf_task_t* task, int fd);
*/
void tf_task_set_ssb_network_key(tf_task_t* task, const char* network_key);
/**
** Set the port number on which to run an SSB secure handshake server.
** @param task The task.
** @param port The port number or 0 to disable.
*/
void tf_task_set_ssb_port(tf_task_t* task, int port);
/**
** Set the port number on which to run an HTTP server.
** @param task The task.
** @param port The port number of 0 to disable.
*/
void tf_task_set_http_port(tf_task_t* task, int port);
/**
** Set the port number on which to run an HTTPS server.
** @param task The task.
** @param port The port number of 0 to disable.
*/
void tf_task_set_https_port(tf_task_t* task, int port);
/**
** Set the path to the SQLite database.
** @param task The task.

View File

@ -32,7 +32,7 @@
#include <TargetConditionals.h>
#endif
#define TEST_ARGS " --ssb-port=0 --http-port=0 --https-port=0"
#define TEST_ARGS " --args=ssb_port=0,http_port=0,https_port=0"
#if !TARGET_OS_IPHONE
static void _write_file(const char* path, const char* contents)
@ -311,7 +311,7 @@ static void _test_database(const tf_test_options_t* options)
" print('Expected but did not find: ' + JSON.stringify(expected));\n"
" exit(4);\n"
" }\n"
" if (JSON.stringify(await databases.list('%')) != '[\"testdb\"]') {\n"
" if (JSON.stringify(await databases.list('%')) != '[\"core\",\"testdb\"]') {\n"
" exit(7);\n"
" }\n"
"}\n"
@ -864,9 +864,6 @@ static void _test_httpd(const tf_test_options_t* options)
uv_loop_init(&loop);
unlink("out/test_db0.sqlite");
char command[256];
snprintf(command, sizeof(command), "%s run -b 0 --db-path=out/test_db0.sqlite" TEST_ARGS, options->exe_path);
uv_stdio_container_t stdio[] = {
[STDIN_FILENO] = { .flags = UV_IGNORE },
[STDOUT_FILENO] = { .flags = UV_INHERIT_FD },
@ -876,7 +873,7 @@ static void _test_httpd(const tf_test_options_t* options)
uv_spawn(&loop, &process,
&(uv_process_options_t) {
.file = options->exe_path,
.args = (char*[]) { (char*)options->exe_path, "run", "-b0", "--db-path=out/test_db0.sqlite", "--http-port=8080", "--https-port=0", NULL },
.args = (char*[]) { (char*)options->exe_path, "run", "--db-path=out/test_db0.sqlite", "--args=ssb_port=0,http_port=8080,https_port=0", NULL },
.stdio_count = sizeof(stdio) / sizeof(*stdio),
.stdio = stdio,
});
@ -960,7 +957,7 @@ static void _test_auto(const tf_test_options_t* options)
unlink("out/selenium.sqlite-shm");
unlink("out/selenium.sqlite-wal");
char* args[] = { executable, "run", "-d", "out/selenium.sqlite", "-b", "0", "-p", "8888", NULL };
char* args[] = { executable, "run", "-d", "out/selenium.sqlite", "-a", "ssb_port=0,http_port=8888", NULL };
uv_stdio_container_t io[3] = {
{ .flags = UV_INHERIT_FD },

View File

@ -314,13 +314,6 @@ static JSValue _util_parseHttpResponse(JSContext* context, JSValueConst this_val
return result;
}
typedef enum _value_kind_t
{
k_kind_bool,
k_kind_int,
k_kind_string,
} value_kind_t;
static const char* k_kind_name[] = {
[k_kind_bool] = "bool",
[k_kind_int] = "int",
@ -329,7 +322,7 @@ static const char* k_kind_name[] = {
typedef struct _setting_value_t
{
value_kind_t kind;
tf_setting_kind_t kind;
union
{
bool bool_value;
@ -348,6 +341,13 @@ typedef struct _setting_t
static const setting_t k_settings[] = {
{ .name = "code_of_conduct", .type = "textarea", .description = "Code of conduct presented at sign-in.", .default_value = { .kind = k_kind_string, .string_value = NULL } },
{ .name = "ssb_port",
.type = "integer",
.description = "Port on which to listen for SSB secure handshake connections.",
.default_value = { .kind = k_kind_int, .int_value = 8008 } },
{ .name = "http_port", .type = "integer", .description = "Port on which to listen for HTTP connections.", .default_value = { .kind = k_kind_int, .int_value = 12345 } },
{ .name = "https_port", .type = "integer", .description = "Port on which to listen for secure HTTP connections.", .default_value = { .kind = k_kind_int, .int_value = 0 } },
{ .name = "out_http_port_file", .type = "hidden", .description = "File to which to write bound HTTP port.", .default_value = { .kind = k_kind_string, .string_value = NULL } },
{ .name = "blob_fetch_age_seconds",
.type = "integer",
.description = "Only blobs mentioned more recently than this age will be automatically fetched.",
@ -393,21 +393,31 @@ static const setting_t k_settings[] = {
.type = "boolean",
.description = "Whether connections are accepted from accounts that aren't in the replication range or otherwise already known.",
.default_value = { .kind = k_kind_bool, .bool_value = true } },
{ .name = "autologin", .type = "boolean", .description = "Whether mobile autologin is supported.", .default_value = { .kind = k_kind_bool, .bool_value = TF_IS_MOBILE != 0 } },
};
static const setting_t* _util_get_setting(const char* name, value_kind_t kind)
static const setting_t* _util_get_setting(const char* name, tf_setting_kind_t kind)
{
for (int i = 0; i < tf_countof(k_settings); i++)
{
if (strcmp(k_settings[i].name, name) == 0 && k_settings[i].default_value.kind == kind)
if (strcmp(k_settings[i].name, name) == 0 && (kind == k_kind_unknown || k_settings[i].default_value.kind == kind))
{
return &k_settings[i];
}
}
tf_printf("Did not find global setting of type %s: %s.\n", k_kind_name[kind], name);
if (kind != k_kind_unknown)
{
tf_printf("Did not find global setting of type %s: %s.\n", k_kind_name[kind], name);
}
return NULL;
}
tf_setting_kind_t tf_util_get_global_setting_kind(const char* name)
{
const setting_t* setting = _util_get_setting(name, k_kind_unknown);
return setting ? setting->default_value.kind : k_kind_unknown;
}
bool tf_util_get_default_global_setting_bool(const char* name)
{
const setting_t* setting = _util_get_setting(name, k_kind_bool);
@ -446,12 +456,41 @@ static JSValue _util_defaultGlobalSettings(JSContext* context, JSValueConst this
JS_SetPropertyStr(
context, entry, "default_value", k_settings[i].default_value.string_value ? JS_NewString(context, k_settings[i].default_value.string_value) : JS_UNDEFINED);
break;
case k_kind_unknown:
break;
}
JS_SetPropertyStr(context, settings, k_settings[i].name, entry);
}
return settings;
}
void tf_util_document_settings(const char* line_prefix)
{
char buffer[32];
for (int i = 0; i < tf_countof(k_settings); i++)
{
const char* default_value = NULL;
const char* quote = "";
switch (k_settings[i].default_value.kind)
{
case k_kind_bool:
default_value = k_settings[i].default_value.bool_value ? "true" : "false";
break;
case k_kind_string:
quote = "\"";
default_value = k_settings[i].default_value.string_value ? k_settings[i].default_value.string_value : "";
break;
case k_kind_int:
snprintf(buffer, sizeof(buffer), "%d", k_settings[i].default_value.int_value);
default_value = buffer;
break;
case k_kind_unknown:
break;
}
tf_printf("%s%s (default: %s%s%s): %s\n", line_prefix, k_settings[i].name, quote, default_value, quote, k_settings[i].description);
}
}
JSValue tf_util_new_uint8_array(JSContext* context, const uint8_t* data, size_t size)
{
JSValue array_buffer = JS_NewArrayBufferCopy(context, data, size);

View File

@ -10,6 +10,17 @@
#include <stdbool.h>
/**
** Type of a setting.
*/
typedef enum _tf_setting_kind_t
{
k_kind_unknown,
k_kind_bool,
k_kind_int,
k_kind_string,
} tf_setting_kind_t;
/** An event loop. */
typedef struct uv_loop_s uv_loop_t;
@ -194,6 +205,19 @@ int tf_util_get_default_global_setting_int(const char* name);
*/
const char* tf_util_get_default_global_setting_string(const char* name);
/**
** Get the expected kind of a global setting.
** @param name The setting name.
** @return The setting kind or unknown if nonexistent.
*/
tf_setting_kind_t tf_util_get_global_setting_kind(const char* name);
/**
** Log documentation for the available settings.
** @param line_prefix Text to prefix each line with."
*/
void tf_util_document_settings(const char* line_prefix);
/**
** Check if the app is running on a mobile device.
** @return true for iPhone/Android, false otherwise.