Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bc2305f22 | ||
|
|
c08fae7ba8 |
@@ -31,6 +31,17 @@ int nt_publish(const char *event_json,
|
||||
int relay_count,
|
||||
int timeout_ms);
|
||||
|
||||
/*
|
||||
* Same as nt_publish_detailed(), but skips db relay-post logging.
|
||||
* Intended for background worker usage when DB handle is thread-affine.
|
||||
*/
|
||||
int nt_publish_detailed_nodb(const char *event_json,
|
||||
const char **relay_urls,
|
||||
int relay_count,
|
||||
int timeout_ms,
|
||||
nt_publish_result_t **results_out,
|
||||
int *result_count_out);
|
||||
|
||||
/* Free result array returned by nt_publish_detailed(). */
|
||||
void nt_publish_results_free(nt_publish_result_t *results, int result_count);
|
||||
|
||||
|
||||
@@ -13,4 +13,14 @@ int publish_signed_event_with_report(const char *label,
|
||||
cJSON *tags,
|
||||
int timeout_ms);
|
||||
|
||||
/*
|
||||
* Fire-and-forget publish wrapper.
|
||||
* Returns 0 when background job is started, -1 on immediate setup failure.
|
||||
*/
|
||||
int publish_signed_event_with_report_async(const char *label,
|
||||
int kind,
|
||||
const char *content,
|
||||
cJSON *tags,
|
||||
int timeout_ms);
|
||||
|
||||
#endif /* PUBLISH_H */
|
||||
|
||||
@@ -34,6 +34,7 @@ extern nt_state_t g_state;
|
||||
void state_init(void);
|
||||
void state_cleanup(void);
|
||||
int state_load_user_info(void);
|
||||
int state_load_user_info_async(void);
|
||||
void state_parse_relay_list(void);
|
||||
const char **state_get_read_relays(int *count);
|
||||
const char **state_get_write_relays(int *count);
|
||||
|
||||
52
src/main.c
52
src/main.c
@@ -11,8 +11,8 @@
|
||||
*/
|
||||
#define NT_VERSION_MAJOR 0
|
||||
#define NT_VERSION_MINOR 0
|
||||
#define NT_VERSION_PATCH 14
|
||||
#define NT_VERSION "v0.0.14"
|
||||
#define NT_VERSION_PATCH 16
|
||||
#define NT_VERSION "v0.0.16"
|
||||
|
||||
#include <ncurses.h>
|
||||
#include <stdio.h>
|
||||
@@ -183,31 +183,41 @@ int main(int argc, char *argv[]) {
|
||||
nt_set_app_info("NOSTR TERMINAL", NT_VERSION);
|
||||
nt_set_window_title("Nostr Terminal");
|
||||
|
||||
/* Splash screen */
|
||||
/* Splash screen (no header/footer, centered text) */
|
||||
{
|
||||
TuiFrame frame = nt_frame("> Splash");
|
||||
TuiStatus status = {"Press any key to continue"};
|
||||
WINDOW *body = (WINDOW *)tuin_body_window();
|
||||
int h = 0;
|
||||
int w = 0;
|
||||
const char *title = "NOSTR TERMINAL";
|
||||
int len = (int)strlen(title);
|
||||
int col;
|
||||
int row;
|
||||
const char *version = NT_VERSION;
|
||||
int title_len = (int)strlen(title);
|
||||
int version_len = (int)strlen(version);
|
||||
int title_col;
|
||||
int version_col;
|
||||
int title_row;
|
||||
|
||||
tuin_render_header(&frame);
|
||||
getmaxyx(body, h, w);
|
||||
col = (w - len) / 2;
|
||||
if (col < 0) {
|
||||
col = 0;
|
||||
getmaxyx(stdscr, h, w);
|
||||
|
||||
title_col = (w - title_len) / 2;
|
||||
if (title_col < 0) {
|
||||
title_col = 0;
|
||||
}
|
||||
row = h / 2;
|
||||
werase(body);
|
||||
mvwprintw(body, row, col, "%s", title);
|
||||
mvwprintw(body, row + 1, col, "%s", NT_VERSION);
|
||||
wnoutrefresh(body);
|
||||
tuin_render_footer(&status);
|
||||
doupdate();
|
||||
|
||||
version_col = (w - version_len) / 2;
|
||||
if (version_col < 0) {
|
||||
version_col = 0;
|
||||
}
|
||||
|
||||
title_row = (h / 2) - 1;
|
||||
if (title_row < 0) {
|
||||
title_row = 0;
|
||||
}
|
||||
|
||||
werase(stdscr);
|
||||
mvprintw(title_row, title_col, "%s", title);
|
||||
if (title_row + 1 < h) {
|
||||
mvprintw(title_row + 1, version_col, "%s", version);
|
||||
}
|
||||
refresh();
|
||||
tuin_get_key();
|
||||
}
|
||||
|
||||
|
||||
@@ -364,12 +364,10 @@ void menu_login(void) {
|
||||
free(local_items);
|
||||
free(labels);
|
||||
|
||||
(void)state_load_user_info();
|
||||
|
||||
if (g_state.kind0_json && strcmp(g_state.kind0_json, "{}") == 0) {
|
||||
nt_log("No profile found on relays. Setting up new user...");
|
||||
menu_add_new_user();
|
||||
(void)state_load_user_info();
|
||||
if (state_load_user_info_async() != 0) {
|
||||
nt_log("Failed to start background user-info load.");
|
||||
} else {
|
||||
nt_log("Loading profile/relay data in background...");
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -418,7 +416,7 @@ void menu_login(void) {
|
||||
snprintf(endpoint_url, sizeof(endpoint_url), "%s", (saved_endpoint && saved_endpoint[0] != '\0') ? saved_endpoint : "");
|
||||
(void)menu_prompt("> Login > URL Signer",
|
||||
"Signer URL (http://<npub>.fips:8080)",
|
||||
endpoint_url,
|
||||
(saved_endpoint && saved_endpoint[0] != '\0') ? saved_endpoint : "",
|
||||
endpoint_url,
|
||||
sizeof(endpoint_url));
|
||||
|
||||
@@ -486,12 +484,10 @@ void menu_login(void) {
|
||||
|
||||
nt_log("Signed in via n_signer URL (%s) npub: %s", endpoint_url, g_state.npub_bech32);
|
||||
|
||||
(void)state_load_user_info();
|
||||
|
||||
if (g_state.kind0_json && strcmp(g_state.kind0_json, "{}") == 0) {
|
||||
nt_log("No profile found on relays. Setting up new user...");
|
||||
menu_add_new_user();
|
||||
(void)state_load_user_info();
|
||||
if (state_load_user_info_async() != 0) {
|
||||
nt_log("Failed to start background user-info load.");
|
||||
} else {
|
||||
nt_log("Loading profile/relay data in background...");
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -511,7 +507,7 @@ void menu_login(void) {
|
||||
|
||||
(void)menu_prompt("> Login > Qrexec Signer",
|
||||
"Target signer qube (nsigner-vault)",
|
||||
target_qube,
|
||||
(saved_target && saved_target[0] != '\0') ? saved_target : "nsigner-vault",
|
||||
target_qube,
|
||||
sizeof(target_qube));
|
||||
if (target_qube[0] == '\0') {
|
||||
@@ -520,7 +516,7 @@ void menu_login(void) {
|
||||
|
||||
(void)menu_prompt("> Login > Qrexec Signer",
|
||||
"Qrexec service (qubes.NsignerRpc)",
|
||||
service_name,
|
||||
"qubes.NsignerRpc",
|
||||
service_name,
|
||||
sizeof(service_name));
|
||||
if (service_name[0] == '\0') {
|
||||
@@ -567,12 +563,10 @@ void menu_login(void) {
|
||||
|
||||
nt_log("Signed in via qrexec n_signer (%s:%s) npub: %s", target_qube, service_name, g_state.npub_bech32);
|
||||
|
||||
(void)state_load_user_info();
|
||||
|
||||
if (g_state.kind0_json && strcmp(g_state.kind0_json, "{}") == 0) {
|
||||
nt_log("No profile found on relays. Setting up new user...");
|
||||
menu_add_new_user();
|
||||
(void)state_load_user_info();
|
||||
if (state_load_user_info_async() != 0) {
|
||||
nt_log("Failed to start background user-info load.");
|
||||
} else {
|
||||
nt_log("Loading profile/relay data in background...");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
@@ -11,7 +11,7 @@ void menu_tweet(void) {
|
||||
TuiFrame frame = nt_frame("> Main Menu > Tweet");
|
||||
TuiStatus status = nt_status();
|
||||
char text[4096];
|
||||
int posted;
|
||||
int queued;
|
||||
|
||||
tuin_render_header(&frame);
|
||||
nt_print_reset();
|
||||
@@ -28,11 +28,12 @@ void menu_tweet(void) {
|
||||
return;
|
||||
}
|
||||
|
||||
posted = publish_signed_event_with_report("Tweet", 1, text, NULL, 8000);
|
||||
queued = publish_signed_event_with_report_async("Tweet", 1, text, NULL, 8000);
|
||||
|
||||
if (posted < 0) {
|
||||
tuin_notice("Tweet publish failed.");
|
||||
} else {
|
||||
nt_log("Tweet published.");
|
||||
if (queued != 0) {
|
||||
tuin_notice("Tweet publish failed to start.");
|
||||
return;
|
||||
}
|
||||
|
||||
nt_log("Tweet queued; publishing in background.");
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ void menu_write(void) {
|
||||
{NT_HK("T", "weet it"), 't'},
|
||||
{NT_HK("B", "log post (kind 30023)"), 'b'},
|
||||
{NT_HK("D", "iary entry (kind 30024)"), 'd'},
|
||||
{NT_HK("X", "Exit without saving"), 'x'},
|
||||
{"E" NT_HK("x", "it without saving"), 'x'},
|
||||
};
|
||||
TuiFrame frame = nt_frame("> Main Menu > Write");
|
||||
TuiStatus status = nt_status();
|
||||
|
||||
97
src/net.c
97
src/net.c
@@ -3,6 +3,7 @@
|
||||
#include "db.h"
|
||||
#include "state.h"
|
||||
#include "signer.h"
|
||||
#include "tui.h"
|
||||
|
||||
#include "../resources/nostr_core_lib/cjson/cJSON.h"
|
||||
#include "../resources/nostr_core_lib/nostr_core/nip042.h"
|
||||
@@ -396,12 +397,13 @@ static int nt_wait_publish_ok(nostr_ws_client_t *client,
|
||||
}
|
||||
|
||||
/* Publish one event to each relay: connect -> EVENT -> OK -> close. */
|
||||
int nt_publish_detailed(const char *event_json,
|
||||
const char **relay_urls,
|
||||
int relay_count,
|
||||
int timeout_ms,
|
||||
nt_publish_result_t **results_out,
|
||||
int *result_count_out) {
|
||||
static int nt_publish_detailed_impl(const char *event_json,
|
||||
const char **relay_urls,
|
||||
int relay_count,
|
||||
int timeout_ms,
|
||||
int with_db_log,
|
||||
nt_publish_result_t **results_out,
|
||||
int *result_count_out) {
|
||||
cJSON *event = NULL;
|
||||
char *event_id = NULL;
|
||||
nt_publish_result_t *results = NULL;
|
||||
@@ -456,7 +458,9 @@ int nt_publish_detailed(const char *event_json,
|
||||
client = nostr_ws_connect(relay);
|
||||
if (!client) {
|
||||
response = nt_strdup_local("connect_failed");
|
||||
db_log_relay_post(relay, event_id, 0, response ? response : "connect_failed");
|
||||
if (with_db_log) {
|
||||
db_log_relay_post(relay, event_id, 0, response ? response : "connect_failed");
|
||||
}
|
||||
slot->posted = 0;
|
||||
slot->response = response;
|
||||
continue;
|
||||
@@ -466,7 +470,9 @@ int nt_publish_detailed(const char *event_json,
|
||||
|
||||
if (nostr_relay_send_event(client, event) != NOSTR_WS_SUCCESS) {
|
||||
response = nt_strdup_local("send_failed");
|
||||
db_log_relay_post(relay, event_id, 0, response ? response : "send_failed");
|
||||
if (with_db_log) {
|
||||
db_log_relay_post(relay, event_id, 0, response ? response : "send_failed");
|
||||
}
|
||||
slot->posted = 0;
|
||||
slot->response = response;
|
||||
nostr_ws_close(client);
|
||||
@@ -482,7 +488,9 @@ int nt_publish_detailed(const char *event_json,
|
||||
accepted_count++;
|
||||
}
|
||||
|
||||
db_log_relay_post(relay, event_id, posted, response ? response : "no_response");
|
||||
if (with_db_log) {
|
||||
db_log_relay_post(relay, event_id, posted, response ? response : "no_response");
|
||||
}
|
||||
|
||||
slot->posted = posted;
|
||||
slot->response = response;
|
||||
@@ -498,6 +506,36 @@ int nt_publish_detailed(const char *event_json,
|
||||
return accepted_count;
|
||||
}
|
||||
|
||||
int nt_publish_detailed(const char *event_json,
|
||||
const char **relay_urls,
|
||||
int relay_count,
|
||||
int timeout_ms,
|
||||
nt_publish_result_t **results_out,
|
||||
int *result_count_out) {
|
||||
return nt_publish_detailed_impl(event_json,
|
||||
relay_urls,
|
||||
relay_count,
|
||||
timeout_ms,
|
||||
1,
|
||||
results_out,
|
||||
result_count_out);
|
||||
}
|
||||
|
||||
int nt_publish_detailed_nodb(const char *event_json,
|
||||
const char **relay_urls,
|
||||
int relay_count,
|
||||
int timeout_ms,
|
||||
nt_publish_result_t **results_out,
|
||||
int *result_count_out) {
|
||||
return nt_publish_detailed_impl(event_json,
|
||||
relay_urls,
|
||||
relay_count,
|
||||
timeout_ms,
|
||||
0,
|
||||
results_out,
|
||||
result_count_out);
|
||||
}
|
||||
|
||||
int nt_publish(const char *event_json,
|
||||
const char **relay_urls,
|
||||
int relay_count,
|
||||
@@ -569,11 +607,10 @@ static __attribute__((unused)) int nt_query_one_relay(const char *relay_url,
|
||||
cJSON_AddItemToArray(req_msg, cJSON_Duplicate(filter, 1));
|
||||
{
|
||||
char *req_json = cJSON_PrintUnformatted(req_msg);
|
||||
fprintf(stderr,
|
||||
"%s: request to %s: %s\n",
|
||||
phase_label ? phase_label : "query",
|
||||
relay_url ? relay_url : "(unknown relay)",
|
||||
req_json ? req_json : "(serialize_failed)");
|
||||
nt_log("%s: request to %s: %s",
|
||||
phase_label ? phase_label : "query",
|
||||
relay_url ? relay_url : "(unknown relay)",
|
||||
req_json ? req_json : "(serialize_failed)");
|
||||
free(req_json);
|
||||
}
|
||||
cJSON_Delete(req_msg);
|
||||
@@ -601,11 +638,10 @@ static __attribute__((unused)) int nt_query_one_relay(const char *relay_url,
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
fprintf(stderr,
|
||||
"%s: response from %s: %s\n",
|
||||
phase_label ? phase_label : "query",
|
||||
relay_url ? relay_url : "(unknown relay)",
|
||||
buffer);
|
||||
nt_log("%s: response from %s: %s",
|
||||
phase_label ? phase_label : "query",
|
||||
relay_url ? relay_url : "(unknown relay)",
|
||||
buffer);
|
||||
}
|
||||
|
||||
if (nostr_parse_relay_message(buffer, &message_type, &parsed) != NOSTR_WS_SUCCESS) {
|
||||
@@ -619,11 +655,10 @@ static __attribute__((unused)) int nt_query_one_relay(const char *relay_url,
|
||||
if (cJSON_IsObject(event_obj)) {
|
||||
if (verbose) {
|
||||
char *received_event = cJSON_PrintUnformatted(event_obj);
|
||||
fprintf(stderr,
|
||||
"%s: event from %s: %s\n",
|
||||
phase_label ? phase_label : "query",
|
||||
relay_url ? relay_url : "(unknown relay)",
|
||||
received_event ? received_event : "(serialize_failed)");
|
||||
nt_log("%s: event from %s: %s",
|
||||
phase_label ? phase_label : "query",
|
||||
relay_url ? relay_url : "(unknown relay)",
|
||||
received_event ? received_event : "(serialize_failed)");
|
||||
free(received_event);
|
||||
}
|
||||
|
||||
@@ -716,10 +751,9 @@ int nt_query_sync_verbose(const char *filter_json,
|
||||
}
|
||||
|
||||
if (verbose && phase_label) {
|
||||
fprintf(stderr,
|
||||
"%s: querying %d relays via nostr_core relay pool\n",
|
||||
phase_label,
|
||||
relay_count);
|
||||
nt_log("%s: querying %d relays via nostr_core relay pool",
|
||||
phase_label,
|
||||
relay_count);
|
||||
}
|
||||
|
||||
pool_events = nostr_relay_pool_query_sync(pool,
|
||||
@@ -757,10 +791,9 @@ int nt_query_sync_verbose(const char *filter_json,
|
||||
free(pool_events);
|
||||
|
||||
if (verbose && phase_label) {
|
||||
fprintf(stderr,
|
||||
"%s: relay pool collected %d unique events\n",
|
||||
phase_label,
|
||||
events_n);
|
||||
nt_log("%s: relay pool collected %d unique events",
|
||||
phase_label,
|
||||
events_n);
|
||||
}
|
||||
|
||||
nostr_relay_pool_destroy(pool);
|
||||
|
||||
@@ -337,6 +337,125 @@ static int nsigner_parse_url(const char *url, char *host_out, size_t host_out_sz
|
||||
return NT_NSIGNER_OK;
|
||||
}
|
||||
|
||||
static int nsigner_host_has_fips_suffix(const char *host) {
|
||||
size_t n;
|
||||
|
||||
if (!host) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
n = strlen(host);
|
||||
return (n > 5 && strcmp(host + (n - 5), ".fips") == 0);
|
||||
}
|
||||
|
||||
static int nsigner_extract_npub_from_fips_host(const char *host,
|
||||
char *npub_out,
|
||||
size_t npub_out_sz) {
|
||||
const char *dot;
|
||||
size_t n;
|
||||
|
||||
if (!host || !npub_out || npub_out_sz == 0 || !nsigner_host_has_fips_suffix(host)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
dot = strstr(host, ".fips");
|
||||
if (!dot || dot == host) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
n = (size_t)(dot - host);
|
||||
if (n == 0 || n >= npub_out_sz) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(npub_out, host, n);
|
||||
npub_out[n] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nsigner_lookup_identity_cache_ipv6(const char *npub,
|
||||
char *ipv6_out,
|
||||
size_t ipv6_out_sz) {
|
||||
FILE *fp;
|
||||
char chunk[512];
|
||||
char *json_buf = NULL;
|
||||
size_t used = 0;
|
||||
size_t cap = 0;
|
||||
cJSON *root = NULL;
|
||||
cJSON *entries;
|
||||
int rc = -1;
|
||||
|
||||
if (!npub || !ipv6_out || ipv6_out_sz == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fp = popen("fipsctl show identity-cache 2>/dev/null", "r");
|
||||
if (!fp) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (fgets(chunk, sizeof(chunk), fp)) {
|
||||
size_t need = strlen(chunk);
|
||||
if (used + need + 1 > cap) {
|
||||
size_t new_cap = (cap == 0) ? 2048 : (cap * 2);
|
||||
char *tmp;
|
||||
while (new_cap < used + need + 1) {
|
||||
new_cap *= 2;
|
||||
}
|
||||
tmp = (char *)realloc(json_buf, new_cap);
|
||||
if (!tmp) {
|
||||
free(json_buf);
|
||||
pclose(fp);
|
||||
return -1;
|
||||
}
|
||||
json_buf = tmp;
|
||||
cap = new_cap;
|
||||
}
|
||||
memcpy(json_buf + used, chunk, need);
|
||||
used += need;
|
||||
json_buf[used] = '\0';
|
||||
}
|
||||
|
||||
pclose(fp);
|
||||
if (!json_buf || used == 0) {
|
||||
free(json_buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
root = cJSON_Parse(json_buf);
|
||||
free(json_buf);
|
||||
if (!root) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
entries = cJSON_GetObjectItemCaseSensitive(root, "entries");
|
||||
if (cJSON_IsArray(entries)) {
|
||||
cJSON *entry;
|
||||
cJSON_ArrayForEach(entry, entries) {
|
||||
cJSON *npub_item = cJSON_GetObjectItemCaseSensitive(entry, "npub");
|
||||
cJSON *ipv6_item;
|
||||
if (!cJSON_IsString(npub_item) || !npub_item->valuestring) {
|
||||
continue;
|
||||
}
|
||||
if (strcmp(npub_item->valuestring, npub) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ipv6_item = cJSON_GetObjectItemCaseSensitive(entry, "ipv6_addr");
|
||||
if (cJSON_IsString(ipv6_item) && ipv6_item->valuestring &&
|
||||
ipv6_item->valuestring[0] != '\0' &&
|
||||
strlen(ipv6_item->valuestring) < ipv6_out_sz) {
|
||||
memcpy(ipv6_out, ipv6_item->valuestring, strlen(ipv6_item->valuestring) + 1);
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int nsigner_connect_url(const char *url) {
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *res = NULL;
|
||||
@@ -356,7 +475,22 @@ static int nsigner_connect_url(const char *url) {
|
||||
|
||||
snprintf(port_str, sizeof(port_str), "%d", port);
|
||||
if (getaddrinfo(host, port_str, &hints, &res) != 0) {
|
||||
return -1;
|
||||
char npub[128];
|
||||
char ipv6_addr[128];
|
||||
|
||||
if (nsigner_extract_npub_from_fips_host(host, npub, sizeof(npub)) == 0 &&
|
||||
nsigner_lookup_identity_cache_ipv6(npub, ipv6_addr, sizeof(ipv6_addr)) == 0) {
|
||||
struct addrinfo numeric_hints;
|
||||
memset(&numeric_hints, 0, sizeof(numeric_hints));
|
||||
numeric_hints.ai_family = AF_UNSPEC;
|
||||
numeric_hints.ai_socktype = SOCK_STREAM;
|
||||
numeric_hints.ai_flags = AI_NUMERICHOST;
|
||||
if (getaddrinfo(ipv6_addr, port_str, &numeric_hints, &res) != 0) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
for (ai = res; ai; ai = ai->ai_next) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "tui.h"
|
||||
|
||||
#include <ncurses.h>
|
||||
#include <pthread.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@@ -16,9 +17,11 @@ static int g_body_row = 0;
|
||||
static char g_app_name[128] = "NOSTR TERMINAL";
|
||||
static char g_app_version[64] = "";
|
||||
static char g_user[256] = "";
|
||||
static char g_status_line[512] = "";
|
||||
static NtLogEntry g_log_entries[NT_LOG_MAX];
|
||||
static int g_log_head = 0;
|
||||
static int g_log_count = 0;
|
||||
static pthread_mutex_t g_log_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static WINDOW *nt_body_window(void) {
|
||||
return (WINDOW *)tuin_body_window();
|
||||
@@ -86,17 +89,6 @@ int nt_run_external_editor(int (*spawn)(void *user), void *user) {
|
||||
return result;
|
||||
}
|
||||
|
||||
static const char *nt_log_latest(void) {
|
||||
int idx;
|
||||
|
||||
if (g_log_count <= 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
idx = (g_log_head - 1 + NT_LOG_MAX) % NT_LOG_MAX;
|
||||
return g_log_entries[idx].text;
|
||||
}
|
||||
|
||||
TuiFrame nt_frame(const char *breadcrumb) {
|
||||
static char composed_title[512];
|
||||
TuiFrame frame;
|
||||
@@ -115,7 +107,15 @@ TuiFrame nt_frame(const char *breadcrumb) {
|
||||
|
||||
TuiStatus nt_status(void) {
|
||||
TuiStatus status;
|
||||
status.text = nt_log_latest();
|
||||
|
||||
pthread_mutex_lock(&g_log_mutex);
|
||||
if (g_status_line[0] == '\0' && g_log_count > 0) {
|
||||
int idx = (g_log_head - 1 + NT_LOG_MAX) % NT_LOG_MAX;
|
||||
snprintf(g_status_line, sizeof(g_status_line), "%s", g_log_entries[idx].text);
|
||||
}
|
||||
status.text = g_status_line;
|
||||
pthread_mutex_unlock(&g_log_mutex);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -160,13 +160,18 @@ void nt_log(const char *fmt, ...) {
|
||||
snprintf(ts, sizeof(ts), "[--:--:--]");
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_log_mutex);
|
||||
|
||||
idx = g_log_head;
|
||||
snprintf(g_log_entries[idx].text, sizeof(g_log_entries[idx].text), "%s %s", ts, msg);
|
||||
snprintf(g_status_line, sizeof(g_status_line), "%s", g_log_entries[idx].text);
|
||||
|
||||
g_log_head = (g_log_head + 1) % NT_LOG_MAX;
|
||||
if (g_log_count < NT_LOG_MAX) {
|
||||
g_log_count++;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&g_log_mutex);
|
||||
}
|
||||
|
||||
void nt_log_show(void) {
|
||||
@@ -177,6 +182,7 @@ void nt_log_show(void) {
|
||||
int i;
|
||||
|
||||
log_text[0] = '\0';
|
||||
pthread_mutex_lock(&g_log_mutex);
|
||||
for (i = 0; i < g_log_count; ++i) {
|
||||
int idx = (g_log_head - g_log_count + i + NT_LOG_MAX) % NT_LOG_MAX;
|
||||
int n = snprintf(log_text + pos, sizeof(log_text) - (size_t)pos, "%s%s",
|
||||
@@ -191,13 +197,17 @@ void nt_log_show(void) {
|
||||
}
|
||||
pos += n;
|
||||
}
|
||||
pthread_mutex_unlock(&g_log_mutex);
|
||||
|
||||
tuin_pager_run(&frame, log_text, &status);
|
||||
tuin_pager_run_ex(&frame, log_text, &status, 1);
|
||||
}
|
||||
|
||||
void nt_log_clear(void) {
|
||||
pthread_mutex_lock(&g_log_mutex);
|
||||
g_log_head = 0;
|
||||
g_log_count = 0;
|
||||
g_status_line[0] = '\0';
|
||||
pthread_mutex_unlock(&g_log_mutex);
|
||||
}
|
||||
|
||||
void nt_pager_show_text(const char *breadcrumb, const char *text) {
|
||||
|
||||
223
src/publish.c
223
src/publish.c
@@ -5,15 +5,109 @@
|
||||
#include "tui.h"
|
||||
#include "signer.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
int publish_signed_event_with_report(const char *label,
|
||||
int kind,
|
||||
const char *content,
|
||||
cJSON *tags,
|
||||
int timeout_ms) {
|
||||
typedef struct {
|
||||
char *label;
|
||||
char *event_json;
|
||||
char **relay_urls;
|
||||
int relay_count;
|
||||
int timeout_ms;
|
||||
} NtPublishJob;
|
||||
|
||||
static int publish_signed_event_with_report_impl(const char *label,
|
||||
int kind,
|
||||
const char *content,
|
||||
cJSON *tags,
|
||||
int timeout_ms,
|
||||
int with_db_log);
|
||||
|
||||
static char *publish_strdup_local(const char *s) {
|
||||
size_t n;
|
||||
char *out;
|
||||
|
||||
if (!s) {
|
||||
return NULL;
|
||||
}
|
||||
n = strlen(s);
|
||||
out = (char *)malloc(n + 1U);
|
||||
if (!out) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy(out, s, n + 1U);
|
||||
return out;
|
||||
}
|
||||
|
||||
static void publish_job_free(NtPublishJob *job) {
|
||||
if (!job) {
|
||||
return;
|
||||
}
|
||||
free(job->label);
|
||||
free(job->event_json);
|
||||
if (job->relay_urls) {
|
||||
int i;
|
||||
for (i = 0; i < job->relay_count; i++) {
|
||||
free(job->relay_urls[i]);
|
||||
}
|
||||
free(job->relay_urls);
|
||||
}
|
||||
free(job);
|
||||
}
|
||||
|
||||
static void *publish_worker_main(void *arg) {
|
||||
NtPublishJob *job = (NtPublishJob *)arg;
|
||||
nt_publish_result_t *results = NULL;
|
||||
int result_count = 0;
|
||||
int accepted_count = 0;
|
||||
int i;
|
||||
|
||||
if (!job) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
nt_log("%s: posting to %d relay(s)...", job->label, job->relay_count);
|
||||
|
||||
accepted_count = nt_publish_detailed_nodb(job->event_json,
|
||||
(const char **)job->relay_urls,
|
||||
job->relay_count,
|
||||
job->timeout_ms,
|
||||
&results,
|
||||
&result_count);
|
||||
|
||||
if (result_count <= 0) {
|
||||
nt_log("%s: no relay attempts were made.", job->label);
|
||||
publish_job_free(job);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < result_count; i++) {
|
||||
const char *relay = results[i].relay_url ? results[i].relay_url : "(unknown relay)";
|
||||
const char *response = results[i].response ? results[i].response : "(no response)";
|
||||
|
||||
if (results[i].posted) {
|
||||
nt_log("[OK] %s", relay);
|
||||
} else {
|
||||
nt_log("[FAIL] %s", relay);
|
||||
nt_log(" %s", response);
|
||||
}
|
||||
}
|
||||
|
||||
nt_log("%s: accepted by %d/%d relay(s).", job->label, accepted_count, result_count);
|
||||
nt_publish_results_free(results, result_count);
|
||||
publish_job_free(job);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int publish_signed_event_with_report_impl(const char *label,
|
||||
int kind,
|
||||
const char *content,
|
||||
cJSON *tags,
|
||||
int timeout_ms,
|
||||
int with_db_log) {
|
||||
char *event_json = NULL;
|
||||
const char **write_relays;
|
||||
int relay_count = 0;
|
||||
@@ -38,12 +132,19 @@ int publish_signed_event_with_report(const char *label,
|
||||
nt_log("%s", event_json);
|
||||
nt_log("Posting to %d relay(s)...", relay_count);
|
||||
|
||||
accepted_count = nt_publish_detailed(event_json,
|
||||
write_relays,
|
||||
relay_count,
|
||||
timeout_ms,
|
||||
&results,
|
||||
&result_count);
|
||||
accepted_count = with_db_log
|
||||
? nt_publish_detailed(event_json,
|
||||
write_relays,
|
||||
relay_count,
|
||||
timeout_ms,
|
||||
&results,
|
||||
&result_count)
|
||||
: nt_publish_detailed_nodb(event_json,
|
||||
write_relays,
|
||||
relay_count,
|
||||
timeout_ms,
|
||||
&results,
|
||||
&result_count);
|
||||
|
||||
free(event_json);
|
||||
|
||||
@@ -68,3 +169,103 @@ int publish_signed_event_with_report(const char *label,
|
||||
nt_publish_results_free(results, result_count);
|
||||
return accepted_count;
|
||||
}
|
||||
|
||||
int publish_signed_event_with_report(const char *label,
|
||||
int kind,
|
||||
const char *content,
|
||||
cJSON *tags,
|
||||
int timeout_ms) {
|
||||
return publish_signed_event_with_report_impl(label,
|
||||
kind,
|
||||
content,
|
||||
tags,
|
||||
timeout_ms,
|
||||
1);
|
||||
}
|
||||
|
||||
int publish_signed_event_with_report_async(const char *label,
|
||||
int kind,
|
||||
const char *content,
|
||||
cJSON *tags,
|
||||
int timeout_ms) {
|
||||
NtPublishJob *job;
|
||||
pthread_t tid;
|
||||
char *event_json = NULL;
|
||||
const char **write_relays = NULL;
|
||||
int relay_count = 0;
|
||||
int i;
|
||||
|
||||
if (!label || label[0] == '\0') {
|
||||
label = "Event";
|
||||
}
|
||||
|
||||
/* Sign synchronously on UI thread; only network publish happens in background. */
|
||||
if (signer_create_and_sign(kind, content ? content : "", tags, time(NULL), &event_json) != 0 || !event_json) {
|
||||
nt_log("%s: failed to serialize event.", label);
|
||||
return -1;
|
||||
}
|
||||
|
||||
write_relays = state_get_write_relays(&relay_count);
|
||||
|
||||
job = (NtPublishJob *)calloc(1, sizeof(*job));
|
||||
if (!job) {
|
||||
free(event_json);
|
||||
return -1;
|
||||
}
|
||||
|
||||
job->label = publish_strdup_local(label);
|
||||
job->event_json = event_json;
|
||||
job->relay_count = relay_count;
|
||||
job->timeout_ms = timeout_ms;
|
||||
|
||||
if (relay_count > 0) {
|
||||
job->relay_urls = (char **)calloc((size_t)relay_count, sizeof(char *));
|
||||
if (!job->relay_urls) {
|
||||
publish_job_free(job);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < relay_count; i++) {
|
||||
const char *src = (write_relays && write_relays[i]) ? write_relays[i] : "";
|
||||
job->relay_urls[i] = publish_strdup_local(src);
|
||||
if (!job->relay_urls[i]) {
|
||||
publish_job_free(job);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!job->label || !job->event_json) {
|
||||
publish_job_free(job);
|
||||
return -1;
|
||||
}
|
||||
|
||||
{
|
||||
pthread_attr_t attr;
|
||||
int has_attr = 0;
|
||||
|
||||
if (pthread_attr_init(&attr) == 0) {
|
||||
has_attr = 1;
|
||||
(void)pthread_attr_setstacksize(&attr, 1024U * 1024U);
|
||||
}
|
||||
|
||||
if (pthread_create(&tid, has_attr ? &attr : NULL, publish_worker_main, job) != 0) {
|
||||
if (has_attr) {
|
||||
(void)pthread_attr_destroy(&attr);
|
||||
}
|
||||
publish_job_free(job);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (has_attr) {
|
||||
(void)pthread_attr_destroy(&attr);
|
||||
}
|
||||
}
|
||||
|
||||
if (pthread_detach(tid) != 0) {
|
||||
publish_job_free(job);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
73
src/state.c
73
src/state.c
@@ -10,13 +10,19 @@
|
||||
#include "../resources/nostr_core_lib/nostr_core/nip044.h"
|
||||
#include "../resources/nostr_core_lib/nostr_core/utils.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "tui.h"
|
||||
|
||||
nt_state_t g_state;
|
||||
|
||||
static pthread_mutex_t g_user_info_load_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static int g_user_info_loading = 0;
|
||||
|
||||
static char *state_strdup_local(const char *s) {
|
||||
size_t n;
|
||||
char *out;
|
||||
@@ -337,7 +343,7 @@ int state_load_user_info(void) {
|
||||
free(g_state.user_settings_json);
|
||||
g_state.user_settings_json = NULL;
|
||||
|
||||
fprintf(stderr, "Attempting to fetch kind 10002 relays from bootstrap relays\n");
|
||||
nt_log("Attempting to fetch kind 10002 relays from bootstrap relays");
|
||||
snprintf(filter,
|
||||
sizeof(filter),
|
||||
"{\"authors\":[\"%s\"],\"kinds\":[10002],\"limit\":1}",
|
||||
@@ -357,7 +363,7 @@ int state_load_user_info(void) {
|
||||
state_parse_relay_list();
|
||||
relays = state_get_read_relays(&relay_count);
|
||||
|
||||
fprintf(stderr, "Attempting to fetch kinds [0,3,10096,17375] from user relays\n");
|
||||
nt_log("Attempting to fetch kinds [0,3,10096,17375] from user relays");
|
||||
snprintf(filter,
|
||||
sizeof(filter),
|
||||
"{\"authors\":[\"%s\"],\"kinds\":[0,3,10096,17375],\"limit\":200}",
|
||||
@@ -424,7 +430,7 @@ int state_load_user_info(void) {
|
||||
free(kind10096_event);
|
||||
free(kind17375_event);
|
||||
|
||||
fprintf(stderr, "Attempting to fetch user-settings (kind 30078, d=user-settings)\n");
|
||||
nt_log("Attempting to fetch user-settings (kind 30078, d=user-settings)");
|
||||
snprintf(filter,
|
||||
sizeof(filter),
|
||||
"{\"authors\":[\"%s\"],\"kinds\":[30078],\"#d\":[\"user-settings\"],\"limit\":1}",
|
||||
@@ -453,14 +459,13 @@ int state_load_user_info(void) {
|
||||
free(settings_event_json);
|
||||
}
|
||||
|
||||
fprintf(stderr,
|
||||
"Load summary: 10002=%s 0=%s 3=%s 10096=%s 17375=%s user_settings=%s\n",
|
||||
g_state.kind10002_json ? "ok" : "missing",
|
||||
(g_state.kind0_json && strcmp(g_state.kind0_json, "{}") != 0) ? "ok" : "missing",
|
||||
(g_state.kind3_json && strstr(g_state.kind3_json, "\"tags\"") != NULL) ? "ok" : "missing",
|
||||
(g_state.kind10096_json && strcmp(g_state.kind10096_json, "{}") != 0) ? "ok" : "missing",
|
||||
(g_state.kind17375_json && strcmp(g_state.kind17375_json, "{}") != 0) ? "ok" : "missing",
|
||||
g_state.user_settings_json ? "ok" : "missing");
|
||||
nt_log("Load summary: 10002=%s 0=%s 3=%s 10096=%s 17375=%s user_settings=%s",
|
||||
g_state.kind10002_json ? "ok" : "missing",
|
||||
(g_state.kind0_json && strcmp(g_state.kind0_json, "{}") != 0) ? "ok" : "missing",
|
||||
(g_state.kind3_json && strstr(g_state.kind3_json, "\"tags\"") != NULL) ? "ok" : "missing",
|
||||
(g_state.kind10096_json && strcmp(g_state.kind10096_json, "{}") != 0) ? "ok" : "missing",
|
||||
(g_state.kind17375_json && strcmp(g_state.kind17375_json, "{}") != 0) ? "ok" : "missing",
|
||||
g_state.user_settings_json ? "ok" : "missing");
|
||||
|
||||
snprintf(g_state.user_name, sizeof(g_state.user_name), "%s", "not logged in");
|
||||
if (g_state.kind0_json) {
|
||||
@@ -481,6 +486,52 @@ int state_load_user_info(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *state_user_info_load_worker(void *arg) {
|
||||
(void)arg;
|
||||
(void)state_load_user_info();
|
||||
|
||||
pthread_mutex_lock(&g_user_info_load_mutex);
|
||||
g_user_info_loading = 0;
|
||||
pthread_mutex_unlock(&g_user_info_load_mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int state_load_user_info_async(void) {
|
||||
pthread_t tid;
|
||||
pthread_attr_t attr;
|
||||
int has_attr = 0;
|
||||
|
||||
pthread_mutex_lock(&g_user_info_load_mutex);
|
||||
if (g_user_info_loading) {
|
||||
pthread_mutex_unlock(&g_user_info_load_mutex);
|
||||
return 0;
|
||||
}
|
||||
g_user_info_loading = 1;
|
||||
pthread_mutex_unlock(&g_user_info_load_mutex);
|
||||
|
||||
if (pthread_attr_init(&attr) == 0) {
|
||||
has_attr = 1;
|
||||
(void)pthread_attr_setstacksize(&attr, 1024U * 1024U);
|
||||
}
|
||||
|
||||
if (pthread_create(&tid, has_attr ? &attr : NULL, state_user_info_load_worker, NULL) != 0) {
|
||||
if (has_attr) {
|
||||
(void)pthread_attr_destroy(&attr);
|
||||
}
|
||||
pthread_mutex_lock(&g_user_info_load_mutex);
|
||||
g_user_info_loading = 0;
|
||||
pthread_mutex_unlock(&g_user_info_load_mutex);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (has_attr) {
|
||||
(void)pthread_attr_destroy(&attr);
|
||||
}
|
||||
|
||||
(void)pthread_detach(tid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char **state_get_read_relays(int *count) {
|
||||
if (g_state.read_relay_count > 0 && g_state.read_relays) {
|
||||
if (count) {
|
||||
|
||||
Reference in New Issue
Block a user