710 lines
20 KiB
C
710 lines
20 KiB
C
#include "db.h"
|
|
|
|
#include <errno.h>
|
|
#include <sqlite3.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
|
|
static sqlite3 *g_db = NULL;
|
|
static char g_local_value[8192];
|
|
|
|
static int db_bind_text_or_null(sqlite3_stmt *stmt, int index, const char *value) {
|
|
if (value == NULL) {
|
|
return sqlite3_bind_null(stmt, index);
|
|
}
|
|
return sqlite3_bind_text(stmt, index, value, -1, SQLITE_TRANSIENT);
|
|
}
|
|
|
|
static int db_exec_sql(const char *sql) {
|
|
char *errmsg = NULL;
|
|
int rc;
|
|
|
|
if (g_db == NULL) {
|
|
fprintf(stderr, "db_exec_sql: database is not open\n");
|
|
return -1;
|
|
}
|
|
|
|
rc = sqlite3_exec(g_db, sql, NULL, NULL, &errmsg);
|
|
if (rc != SQLITE_OK) {
|
|
fprintf(stderr, "sqlite exec error: %s\n", errmsg ? errmsg : "unknown error");
|
|
sqlite3_free(errmsg);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int is_regular_kind(int kind) {
|
|
if (kind == 1 || kind == 2) {
|
|
return 1;
|
|
}
|
|
if (kind >= 4 && kind <= 44) {
|
|
return 1;
|
|
}
|
|
if (kind >= 1000 && kind <= 9999) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int is_replaceable_kind(int kind) {
|
|
if (kind == 0 || kind == 3) {
|
|
return 1;
|
|
}
|
|
if (kind >= 10000 && kind <= 19999) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int is_addressable_kind(int kind) {
|
|
return (kind >= 30000 && kind <= 39999);
|
|
}
|
|
|
|
static int db_get_relay_urls_filtered(const char *where_clause, char ***urls, int *count) {
|
|
sqlite3_stmt *stmt = NULL;
|
|
int rc;
|
|
int n = 0;
|
|
int i = 0;
|
|
char sql_count[256];
|
|
char sql_select[256];
|
|
char **out_urls = NULL;
|
|
|
|
if (urls == NULL || count == NULL) {
|
|
fprintf(stderr, "db_get_relay_urls_filtered: invalid arguments\n");
|
|
return -1;
|
|
}
|
|
|
|
*urls = NULL;
|
|
*count = 0;
|
|
|
|
if (g_db == NULL) {
|
|
fprintf(stderr, "db_get_relay_urls_filtered: database is not open\n");
|
|
return -1;
|
|
}
|
|
|
|
snprintf(sql_count, sizeof(sql_count), "SELECT COUNT(*) FROM relays WHERE %s;", where_clause);
|
|
rc = sqlite3_prepare_v2(g_db, sql_count, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
fprintf(stderr, "sqlite prepare error: %s\n", sqlite3_errmsg(g_db));
|
|
return -1;
|
|
}
|
|
|
|
rc = sqlite3_step(stmt);
|
|
if (rc == SQLITE_ROW) {
|
|
n = sqlite3_column_int(stmt, 0);
|
|
} else {
|
|
fprintf(stderr, "sqlite step error: %s\n", sqlite3_errmsg(g_db));
|
|
sqlite3_finalize(stmt);
|
|
return -1;
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
stmt = NULL;
|
|
|
|
if (n <= 0) {
|
|
*count = 0;
|
|
return 0;
|
|
}
|
|
|
|
out_urls = (char **)calloc((size_t)n, sizeof(char *));
|
|
if (out_urls == NULL) {
|
|
fprintf(stderr, "memory allocation failed\n");
|
|
return -1;
|
|
}
|
|
|
|
snprintf(sql_select, sizeof(sql_select), "SELECT url FROM relays WHERE %s ORDER BY url;", where_clause);
|
|
rc = sqlite3_prepare_v2(g_db, sql_select, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
fprintf(stderr, "sqlite prepare error: %s\n", sqlite3_errmsg(g_db));
|
|
free(out_urls);
|
|
return -1;
|
|
}
|
|
|
|
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
|
|
const unsigned char *url = sqlite3_column_text(stmt, 0);
|
|
if (i >= n) {
|
|
break;
|
|
}
|
|
out_urls[i] = strdup((const char *)(url ? url : (const unsigned char *)""));
|
|
if (out_urls[i] == NULL) {
|
|
fprintf(stderr, "memory allocation failed\n");
|
|
sqlite3_finalize(stmt);
|
|
for (int j = 0; j < i; j++) {
|
|
free(out_urls[j]);
|
|
}
|
|
free(out_urls);
|
|
return -1;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (rc != SQLITE_DONE) {
|
|
fprintf(stderr, "sqlite step error: %s\n", sqlite3_errmsg(g_db));
|
|
sqlite3_finalize(stmt);
|
|
for (int j = 0; j < i; j++) {
|
|
free(out_urls[j]);
|
|
}
|
|
free(out_urls);
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
*urls = out_urls;
|
|
*count = i;
|
|
return 0;
|
|
}
|
|
|
|
int db_open(void) {
|
|
const char *home = getenv("HOME");
|
|
char dir_path[4096];
|
|
char db_path[4096];
|
|
int rc;
|
|
|
|
if (g_db != NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (home == NULL || home[0] == '\0') {
|
|
fprintf(stderr, "db_open: HOME is not set\n");
|
|
return -1;
|
|
}
|
|
|
|
snprintf(dir_path, sizeof(dir_path), "%s/.nostr", home);
|
|
if (mkdir(dir_path, 0700) != 0 && errno != EEXIST) {
|
|
fprintf(stderr, "db_open: failed to create directory '%s': %s\n", dir_path, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if (snprintf(db_path, sizeof(db_path), "%s/nostr.db", dir_path) >= (int)sizeof(db_path)) {
|
|
fprintf(stderr, "db_open: database path too long\n");
|
|
return -1;
|
|
}
|
|
rc = sqlite3_open(db_path, &g_db);
|
|
if (rc != SQLITE_OK) {
|
|
fprintf(stderr, "db_open: sqlite open failed: %s\n", sqlite3_errmsg(g_db));
|
|
if (g_db != NULL) {
|
|
sqlite3_close(g_db);
|
|
g_db = NULL;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
if (db_exec_sql("PRAGMA foreign_keys = ON;") != 0) {
|
|
db_close();
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void db_close(void) {
|
|
if (g_db != NULL) {
|
|
sqlite3_close(g_db);
|
|
g_db = NULL;
|
|
}
|
|
}
|
|
|
|
int db_init_schema(void) {
|
|
const char *schema_sql =
|
|
"CREATE TABLE IF NOT EXISTS regular_events ("
|
|
"id TEXT PRIMARY KEY CHECK(LENGTH(id) = 64),"
|
|
"kind INTEGER,"
|
|
"pubkey TEXT CHECK(LENGTH(pubkey) = 64),"
|
|
"created_at INTEGER NOT NULL,"
|
|
"sig TEXT CHECK(LENGTH(sig) = 128),"
|
|
"content TEXT,"
|
|
"read INTEGER DEFAULT 0,"
|
|
"tags TEXT"
|
|
");"
|
|
|
|
"CREATE TABLE IF NOT EXISTS replaceable_events ("
|
|
"id TEXT PRIMARY KEY CHECK(LENGTH(id) = 64),"
|
|
"kind INTEGER,"
|
|
"pubkey TEXT CHECK(LENGTH(pubkey) = 64),"
|
|
"created_at INTEGER NOT NULL,"
|
|
"sig TEXT CHECK(LENGTH(sig) = 128),"
|
|
"content TEXT,"
|
|
"tags TEXT"
|
|
");"
|
|
"CREATE UNIQUE INDEX IF NOT EXISTS idx_replaceable_pubkey_kind ON replaceable_events (pubkey, kind);"
|
|
|
|
"CREATE TABLE IF NOT EXISTS addressable_events ("
|
|
"id TEXT PRIMARY KEY CHECK(LENGTH(id) = 64),"
|
|
"kind INTEGER,"
|
|
"pubkey TEXT CHECK(LENGTH(pubkey) = 64),"
|
|
"created_at INTEGER NOT NULL,"
|
|
"sig TEXT CHECK(LENGTH(sig) = 128),"
|
|
"content TEXT,"
|
|
"tags TEXT,"
|
|
"d TEXT NOT NULL"
|
|
");"
|
|
"CREATE UNIQUE INDEX IF NOT EXISTS idx_addressable_pubkey_kind_d ON addressable_events (pubkey, kind, d);"
|
|
|
|
"CREATE TABLE IF NOT EXISTS relays ("
|
|
"url TEXT PRIMARY KEY,"
|
|
"name TEXT,"
|
|
"description TEXT,"
|
|
"read INTEGER DEFAULT 0,"
|
|
"write INTEGER DEFAULT 0,"
|
|
"supported_nips TEXT,"
|
|
"software TEXT,"
|
|
"version TEXT"
|
|
");"
|
|
|
|
"CREATE TABLE IF NOT EXISTS relay_posts ("
|
|
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
|
"relay_url TEXT,"
|
|
"event_id TEXT CHECK(LENGTH(event_id) = 64),"
|
|
"timestamp INTEGER,"
|
|
"posted INTEGER,"
|
|
"response TEXT"
|
|
");"
|
|
|
|
"CREATE TABLE IF NOT EXISTS people ("
|
|
"pubkey TEXT PRIMARY KEY,"
|
|
"name TEXT,"
|
|
"following INTEGER DEFAULT 0"
|
|
");"
|
|
|
|
"CREATE TABLE IF NOT EXISTS local ("
|
|
"key TEXT PRIMARY KEY,"
|
|
"value TEXT"
|
|
");";
|
|
|
|
return db_exec_sql(schema_sql);
|
|
}
|
|
|
|
int db_store_event(const char *id,
|
|
int kind,
|
|
const char *pubkey,
|
|
int created_at,
|
|
const char *sig,
|
|
const char *content,
|
|
const char *tags_json,
|
|
const char *d_tag) {
|
|
sqlite3_stmt *stmt = NULL;
|
|
int rc;
|
|
const char *sql = NULL;
|
|
|
|
if (g_db == NULL) {
|
|
fprintf(stderr, "db_store_event: database is not open\n");
|
|
return -1;
|
|
}
|
|
|
|
if (id == NULL || pubkey == NULL || sig == NULL) {
|
|
fprintf(stderr, "db_store_event: id/pubkey/sig are required\n");
|
|
return -1;
|
|
}
|
|
|
|
if (is_regular_kind(kind)) {
|
|
sql =
|
|
"INSERT OR IGNORE INTO regular_events "
|
|
"(id, kind, pubkey, created_at, sig, content, tags) "
|
|
"VALUES (?, ?, ?, ?, ?, ?, ?);";
|
|
|
|
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
fprintf(stderr, "sqlite prepare error: %s\n", sqlite3_errmsg(g_db));
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_bind_text(stmt, 1, id, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int(stmt, 2, kind);
|
|
sqlite3_bind_text(stmt, 3, pubkey, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int(stmt, 4, created_at);
|
|
sqlite3_bind_text(stmt, 5, sig, -1, SQLITE_TRANSIENT);
|
|
db_bind_text_or_null(stmt, 6, content);
|
|
db_bind_text_or_null(stmt, 7, tags_json);
|
|
} else if (is_replaceable_kind(kind)) {
|
|
sql =
|
|
"INSERT INTO replaceable_events "
|
|
"(id, kind, pubkey, created_at, sig, content, tags) "
|
|
"VALUES (?, ?, ?, ?, ?, ?, ?) "
|
|
"ON CONFLICT(pubkey, kind) DO UPDATE SET "
|
|
"id = excluded.id, "
|
|
"created_at = excluded.created_at, "
|
|
"sig = excluded.sig, "
|
|
"content = excluded.content, "
|
|
"tags = excluded.tags "
|
|
"WHERE excluded.created_at > replaceable_events.created_at;";
|
|
|
|
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
fprintf(stderr, "sqlite prepare error: %s\n", sqlite3_errmsg(g_db));
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_bind_text(stmt, 1, id, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int(stmt, 2, kind);
|
|
sqlite3_bind_text(stmt, 3, pubkey, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int(stmt, 4, created_at);
|
|
sqlite3_bind_text(stmt, 5, sig, -1, SQLITE_TRANSIENT);
|
|
db_bind_text_or_null(stmt, 6, content);
|
|
db_bind_text_or_null(stmt, 7, tags_json);
|
|
} else if (is_addressable_kind(kind)) {
|
|
if (d_tag == NULL || d_tag[0] == '\0') {
|
|
fprintf(stderr, "db_store_event: d_tag is required for addressable kinds\n");
|
|
return -1;
|
|
}
|
|
|
|
sql =
|
|
"INSERT INTO addressable_events "
|
|
"(id, kind, pubkey, created_at, sig, content, tags, d) "
|
|
"VALUES (?, ?, ?, ?, ?, ?, ?, ?) "
|
|
"ON CONFLICT(pubkey, kind, d) DO UPDATE SET "
|
|
"id = excluded.id, "
|
|
"created_at = excluded.created_at, "
|
|
"sig = excluded.sig, "
|
|
"content = excluded.content, "
|
|
"tags = excluded.tags "
|
|
"WHERE excluded.created_at > addressable_events.created_at;";
|
|
|
|
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
fprintf(stderr, "sqlite prepare error: %s\n", sqlite3_errmsg(g_db));
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_bind_text(stmt, 1, id, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int(stmt, 2, kind);
|
|
sqlite3_bind_text(stmt, 3, pubkey, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int(stmt, 4, created_at);
|
|
sqlite3_bind_text(stmt, 5, sig, -1, SQLITE_TRANSIENT);
|
|
db_bind_text_or_null(stmt, 6, content);
|
|
db_bind_text_or_null(stmt, 7, tags_json);
|
|
sqlite3_bind_text(stmt, 8, d_tag, -1, SQLITE_TRANSIENT);
|
|
} else {
|
|
fprintf(stderr, "db_store_event: unsupported kind %d\n", kind);
|
|
return -1;
|
|
}
|
|
|
|
rc = sqlite3_step(stmt);
|
|
if (rc != SQLITE_DONE) {
|
|
fprintf(stderr, "sqlite step error: %s\n", sqlite3_errmsg(g_db));
|
|
sqlite3_finalize(stmt);
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
return 0;
|
|
}
|
|
|
|
int db_store_relay(const char *url, int read, int write) {
|
|
sqlite3_stmt *stmt = NULL;
|
|
int rc;
|
|
const char *sql =
|
|
"INSERT INTO relays (url, read, write) VALUES (?, ?, ?) "
|
|
"ON CONFLICT(url) DO UPDATE SET read = excluded.read, write = excluded.write;";
|
|
|
|
if (g_db == NULL) {
|
|
fprintf(stderr, "db_store_relay: database is not open\n");
|
|
return -1;
|
|
}
|
|
|
|
if (url == NULL || url[0] == '\0') {
|
|
fprintf(stderr, "db_store_relay: url is required\n");
|
|
return -1;
|
|
}
|
|
|
|
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
fprintf(stderr, "sqlite prepare error: %s\n", sqlite3_errmsg(g_db));
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_bind_text(stmt, 1, url, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int(stmt, 2, read ? 1 : 0);
|
|
sqlite3_bind_int(stmt, 3, write ? 1 : 0);
|
|
|
|
rc = sqlite3_step(stmt);
|
|
if (rc != SQLITE_DONE) {
|
|
fprintf(stderr, "sqlite step error: %s\n", sqlite3_errmsg(g_db));
|
|
sqlite3_finalize(stmt);
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
return 0;
|
|
}
|
|
|
|
int db_get_relays(char ***urls, int **read_flags, int **write_flags, int *count) {
|
|
sqlite3_stmt *stmt = NULL;
|
|
sqlite3_stmt *count_stmt = NULL;
|
|
int rc;
|
|
int n = 0;
|
|
int i = 0;
|
|
char **out_urls = NULL;
|
|
int *out_read = NULL;
|
|
int *out_write = NULL;
|
|
const char *sql_count = "SELECT COUNT(*) FROM relays;";
|
|
const char *sql_select = "SELECT url, read, write FROM relays ORDER BY url;";
|
|
|
|
if (urls == NULL || read_flags == NULL || write_flags == NULL || count == NULL) {
|
|
fprintf(stderr, "db_get_relays: invalid arguments\n");
|
|
return -1;
|
|
}
|
|
|
|
*urls = NULL;
|
|
*read_flags = NULL;
|
|
*write_flags = NULL;
|
|
*count = 0;
|
|
|
|
if (g_db == NULL) {
|
|
fprintf(stderr, "db_get_relays: database is not open\n");
|
|
return -1;
|
|
}
|
|
|
|
rc = sqlite3_prepare_v2(g_db, sql_count, -1, &count_stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
fprintf(stderr, "sqlite prepare error: %s\n", sqlite3_errmsg(g_db));
|
|
return -1;
|
|
}
|
|
|
|
rc = sqlite3_step(count_stmt);
|
|
if (rc == SQLITE_ROW) {
|
|
n = sqlite3_column_int(count_stmt, 0);
|
|
} else {
|
|
fprintf(stderr, "sqlite step error: %s\n", sqlite3_errmsg(g_db));
|
|
sqlite3_finalize(count_stmt);
|
|
return -1;
|
|
}
|
|
sqlite3_finalize(count_stmt);
|
|
count_stmt = NULL;
|
|
|
|
if (n <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
out_urls = (char **)calloc((size_t)n, sizeof(char *));
|
|
out_read = (int *)calloc((size_t)n, sizeof(int));
|
|
out_write = (int *)calloc((size_t)n, sizeof(int));
|
|
if (out_urls == NULL || out_read == NULL || out_write == NULL) {
|
|
fprintf(stderr, "memory allocation failed\n");
|
|
free(out_urls);
|
|
free(out_read);
|
|
free(out_write);
|
|
return -1;
|
|
}
|
|
|
|
rc = sqlite3_prepare_v2(g_db, sql_select, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
fprintf(stderr, "sqlite prepare error: %s\n", sqlite3_errmsg(g_db));
|
|
free(out_urls);
|
|
free(out_read);
|
|
free(out_write);
|
|
return -1;
|
|
}
|
|
|
|
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
|
|
const unsigned char *url = sqlite3_column_text(stmt, 0);
|
|
|
|
if (i >= n) {
|
|
break;
|
|
}
|
|
|
|
out_urls[i] = strdup((const char *)(url ? url : (const unsigned char *)""));
|
|
if (out_urls[i] == NULL) {
|
|
fprintf(stderr, "memory allocation failed\n");
|
|
sqlite3_finalize(stmt);
|
|
for (int j = 0; j < i; j++) {
|
|
free(out_urls[j]);
|
|
}
|
|
free(out_urls);
|
|
free(out_read);
|
|
free(out_write);
|
|
return -1;
|
|
}
|
|
|
|
out_read[i] = sqlite3_column_int(stmt, 1);
|
|
out_write[i] = sqlite3_column_int(stmt, 2);
|
|
i++;
|
|
}
|
|
|
|
if (rc != SQLITE_DONE) {
|
|
fprintf(stderr, "sqlite step error: %s\n", sqlite3_errmsg(g_db));
|
|
sqlite3_finalize(stmt);
|
|
for (int j = 0; j < i; j++) {
|
|
free(out_urls[j]);
|
|
}
|
|
free(out_urls);
|
|
free(out_read);
|
|
free(out_write);
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
*urls = out_urls;
|
|
*read_flags = out_read;
|
|
*write_flags = out_write;
|
|
*count = i;
|
|
return 0;
|
|
}
|
|
|
|
int db_get_read_relays(char ***urls, int *count) {
|
|
return db_get_relay_urls_filtered("read = 1", urls, count);
|
|
}
|
|
|
|
int db_get_write_relays(char ***urls, int *count) {
|
|
return db_get_relay_urls_filtered("write = 1", urls, count);
|
|
}
|
|
|
|
int db_store_person(const char *pubkey, const char *name, int following) {
|
|
sqlite3_stmt *stmt = NULL;
|
|
int rc;
|
|
const char *sql =
|
|
"INSERT INTO people (pubkey, name, following) VALUES (?, ?, ?) "
|
|
"ON CONFLICT(pubkey) DO UPDATE SET name = excluded.name, following = excluded.following;";
|
|
|
|
if (g_db == NULL) {
|
|
fprintf(stderr, "db_store_person: database is not open\n");
|
|
return -1;
|
|
}
|
|
|
|
if (pubkey == NULL || pubkey[0] == '\0') {
|
|
fprintf(stderr, "db_store_person: pubkey is required\n");
|
|
return -1;
|
|
}
|
|
|
|
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
fprintf(stderr, "sqlite prepare error: %s\n", sqlite3_errmsg(g_db));
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_bind_text(stmt, 1, pubkey, -1, SQLITE_TRANSIENT);
|
|
db_bind_text_or_null(stmt, 2, name);
|
|
sqlite3_bind_int(stmt, 3, following ? 1 : 0);
|
|
|
|
rc = sqlite3_step(stmt);
|
|
if (rc != SQLITE_DONE) {
|
|
fprintf(stderr, "sqlite step error: %s\n", sqlite3_errmsg(g_db));
|
|
sqlite3_finalize(stmt);
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
return 0;
|
|
}
|
|
|
|
int db_log_relay_post(const char *relay_url, const char *event_id, int posted, const char *response) {
|
|
sqlite3_stmt *stmt = NULL;
|
|
int rc;
|
|
time_t now = time(NULL);
|
|
const char *sql =
|
|
"INSERT INTO relay_posts (relay_url, event_id, timestamp, posted, response) "
|
|
"VALUES (?, ?, ?, ?, ?);";
|
|
|
|
if (g_db == NULL) {
|
|
fprintf(stderr, "db_log_relay_post: database is not open\n");
|
|
return -1;
|
|
}
|
|
|
|
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
fprintf(stderr, "sqlite prepare error: %s\n", sqlite3_errmsg(g_db));
|
|
return -1;
|
|
}
|
|
|
|
db_bind_text_or_null(stmt, 1, relay_url);
|
|
db_bind_text_or_null(stmt, 2, event_id);
|
|
sqlite3_bind_int(stmt, 3, (int)now);
|
|
sqlite3_bind_int(stmt, 4, posted ? 1 : 0);
|
|
db_bind_text_or_null(stmt, 5, response);
|
|
|
|
rc = sqlite3_step(stmt);
|
|
if (rc != SQLITE_DONE) {
|
|
fprintf(stderr, "sqlite step error: %s\n", sqlite3_errmsg(g_db));
|
|
sqlite3_finalize(stmt);
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
return 0;
|
|
}
|
|
|
|
int db_set_local(const char *key, const char *value) {
|
|
sqlite3_stmt *stmt = NULL;
|
|
int rc;
|
|
const char *sql =
|
|
"INSERT INTO local (key, value) VALUES (?, ?) "
|
|
"ON CONFLICT(key) DO UPDATE SET value = excluded.value;";
|
|
|
|
if (g_db == NULL) {
|
|
fprintf(stderr, "db_set_local: database is not open\n");
|
|
return -1;
|
|
}
|
|
|
|
if (key == NULL || key[0] == '\0') {
|
|
fprintf(stderr, "db_set_local: key is required\n");
|
|
return -1;
|
|
}
|
|
|
|
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
fprintf(stderr, "sqlite prepare error: %s\n", sqlite3_errmsg(g_db));
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
|
|
db_bind_text_or_null(stmt, 2, value);
|
|
|
|
rc = sqlite3_step(stmt);
|
|
if (rc != SQLITE_DONE) {
|
|
fprintf(stderr, "sqlite step error: %s\n", sqlite3_errmsg(g_db));
|
|
sqlite3_finalize(stmt);
|
|
return -1;
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
return 0;
|
|
}
|
|
|
|
const char *db_get_local(const char *key) {
|
|
sqlite3_stmt *stmt = NULL;
|
|
int rc;
|
|
const unsigned char *value;
|
|
const char *sql = "SELECT value FROM local WHERE key = ? LIMIT 1;";
|
|
|
|
if (g_db == NULL) {
|
|
fprintf(stderr, "db_get_local: database is not open\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (key == NULL || key[0] == '\0') {
|
|
fprintf(stderr, "db_get_local: key is required\n");
|
|
return NULL;
|
|
}
|
|
|
|
rc = sqlite3_prepare_v2(g_db, sql, -1, &stmt, NULL);
|
|
if (rc != SQLITE_OK) {
|
|
fprintf(stderr, "sqlite prepare error: %s\n", sqlite3_errmsg(g_db));
|
|
return NULL;
|
|
}
|
|
|
|
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_TRANSIENT);
|
|
|
|
rc = sqlite3_step(stmt);
|
|
if (rc == SQLITE_ROW) {
|
|
value = sqlite3_column_text(stmt, 0);
|
|
if (value == NULL) {
|
|
sqlite3_finalize(stmt);
|
|
return NULL;
|
|
}
|
|
|
|
snprintf(g_local_value, sizeof(g_local_value), "%s", (const char *)value);
|
|
sqlite3_finalize(stmt);
|
|
return g_local_value;
|
|
}
|
|
|
|
if (rc != SQLITE_DONE) {
|
|
fprintf(stderr, "sqlite step error: %s\n", sqlite3_errmsg(g_db));
|
|
}
|
|
|
|
sqlite3_finalize(stmt);
|
|
return NULL;
|
|
}
|