commit a148208

uint  ·  2026-01-25 21:02:47 +0000 UTC
parent 711f006
add directory scanning, add json response to scan
7 files changed,  +454, -4
+64, -4
  1@@ -6,10 +6,12 @@
  2 
  3 #include "config.h"
  4 #include "http.h"
  5-
  6-void logmsg(bool verbose, const char* tag, const char* fmt, ...);
  7+#include "json.h"
  8+#include "log.h"
  9+#include "scan.h"
 10 
 11 static int read_reqline(int c, char* method, size_t msz, char* path, size_t psz);
 12+static void reply_json(int c, const char* status, const char* body, size_t len);
 13 static void reply_text(int c, const char* status, const char* body);
 14 static int write_all(int fd, const void* buf, size_t n);
 15 
 16@@ -36,6 +38,33 @@ int http_handle(int c)
 17 		return 0;
 18 	}
 19 
 20+	if (strcmp(path, "/library") == 0) {
 21+		logmsg(verbose_log, "HTTP", "route /library");
 22+		struct library l;
 23+		struct json j;
 24+
 25+		if (scan_library(&l, media_dir) < 0) {
 26+			logmsg(verbose_log, "SCAN", "scan failed");
 27+			reply_text(c, HTTP_500, "scan failed\n");
 28+			return -1;
 29+		}
 30+		logmsg(verbose_log, "SCAN", "found %zu items", l.len);
 31+
 32+		if (json_library(&j, &l) < 0) {
 33+			logmsg(verbose_log, "JSON", "encode failed");
 34+			scan_library_free(&l);
 35+			reply_text(c, HTTP_500, "json failed\n");
 36+			return -1;
 37+		}
 38+		logmsg(verbose_log, "JSON", "encoded %zu bytes", j.len);
 39+
 40+		scan_library_free(&l);
 41+		reply_json(c, HTTP_200, j.buf, j.len);
 42+		json_free(&j);
 43+
 44+		return 0;
 45+	}
 46+
 47 	logmsg(verbose_log, "HTTP", "route not found: %s", path);
 48 	reply_text(c, HTTP_404, "not found\n");
 49 
 50@@ -52,7 +81,12 @@ static int read_reqline(int c, char* method, size_t msz, char* path, size_t psz)
 51 			return -1;
 52 
 53 		ssize_t r = read(c, buf + used, sizeof(buf) - 1 - used);
 54-		if (r <= 0)
 55+		if (r < 0) {
 56+			if (errno == EINTR)
 57+				continue;
 58+			return -1;
 59+		}
 60+		if (r == 0)
 61 			return -1;
 62 
 63 		used += (size_t)r;
 64@@ -74,6 +108,31 @@ static int read_reqline(int c, char* method, size_t msz, char* path, size_t psz)
 65 	return 0;
 66 }
 67 
 68+static void reply_json(int c, const char* status, const char* body, size_t len)
 69+{
 70+	char resp[HTTP_RESP_MAX];
 71+	int n = snprintf(
 72+		resp, sizeof(resp),
 73+
 74+		"%s"
 75+		HTTP_JSON
 76+		HTTP_LENGTH
 77+		HTTP_CLOSE
 78+		"\r\n",
 79+
 80+		status, len
 81+	);
 82+
 83+	if (n < 0)
 84+		return;
 85+
 86+	if ((size_t)n >= sizeof(resp))
 87+		n = (int)(sizeof(resp) - 1);
 88+
 89+	(void)write_all(c, resp, (size_t)n);
 90+	(void)write_all(c, body, len);
 91+}
 92+
 93 static void reply_text(int c, const char* status, const char* body)
 94 {
 95 	char resp[HTTP_RESP_MAX];
 96@@ -105,7 +164,7 @@ static int write_all(int fd, const void* buf, size_t n)
 97 	const char* p = buf;
 98 
 99 	while (n > 0) {
100-		size_t w = write(fd, p, n);
101+		ssize_t w = write(fd, p, n);
102 		if (w < 0) {
103 			if (errno == EINTR)
104 				continue;
105@@ -118,3 +177,4 @@ static int write_all(int fd, const void* buf, size_t n)
106 
107 	return 0;
108 }
109+
R server/config.h => server/include/config.def.h
+0, -0
R server/http.h => server/include/http.h
+2, -0
 1@@ -5,8 +5,10 @@
 2 #define HTTP_400    "HTTP/1.1 400 Bad Request\r\n"
 3 #define HTTP_404    "HTTP/1.1 404 Not Found\r\n"
 4 #define HTTP_405    "HTTP/1.1 405 Method Not Allowed\r\n"
 5+#define HTTP_500    "HTTP/1.1 500 Internal Server Error\r\n"
 6 
 7 #define HTTP_TEXT   "Content-Type: text/plain\r\n"
 8+#define HTTP_JSON   "Content-Type: application/json\r\n"
 9 #define HTTP_CLOSE  "Connection: close\r\n"
10 #define HTTP_LENGTH "Content-Length: %zu\r\n"
11 
+19, -0
 1@@ -0,0 +1,19 @@
 2+#ifndef JSON_H
 3+#define JSON_H
 4+
 5+#include <stddef.h>
 6+#include <stdint.h>
 7+
 8+#include "scan.h"
 9+
10+struct json {
11+	char*          buf;
12+	size_t         len;
13+	size_t         cap;
14+};
15+
16+void json_free(struct json* j);
17+int json_library(struct json* j, const struct library* l);
18+
19+#endif /* JSON_H */
20+
+22, -0
 1@@ -0,0 +1,22 @@
 2+#ifndef SCAN_H
 3+#define SCAN_H
 4+
 5+#include <stddef.h>
 6+#include <stdint.h>
 7+
 8+struct item {
 9+	uint64_t       id;
10+	char*          path;
11+};
12+
13+struct library {
14+	struct item*   items;
15+	size_t         len;
16+	size_t         cap;
17+};
18+
19+void scan_library_free(struct library* l);
20+int scan_library(struct library* l, const char* root);
21+
22+#endif /* SCAN_H */
23+
+185, -0
  1@@ -0,0 +1,185 @@
  2+#include <stdbool.h>
  3+#include <stdio.h>
  4+#include <stdlib.h>
  5+#include <string.h>
  6+
  7+#include "config.h"
  8+#include "json.h"
  9+#include "log.h"
 10+
 11+static int json_grow(struct json* j, size_t need);
 12+static int json_hex64(struct json* j, uint64_t v);
 13+static int json_putc(struct json* j, const char c);
 14+static int json_putn(struct json* j, const char* s, size_t n);
 15+static int json_puts(struct json* j, const char* s);
 16+static int json_string(struct json* j, const char* s);
 17+
 18+static int json_grow(struct json* j, size_t need)
 19+{
 20+	if (j->len + need <= j->cap)
 21+		return 0;
 22+
 23+	size_t ncap = j->cap ? j->cap : 1024; /* initial buffer */
 24+	while (ncap < j->len + need)
 25+		ncap *= 2;
 26+
 27+	char* nb = realloc(j->buf, ncap);
 28+	if (!nb) {
 29+		logmsg(verbose_log, "JSON", "out of memory");
 30+		return -1;
 31+	}
 32+
 33+	j->buf = nb;
 34+	j->cap = ncap;
 35+
 36+	return 0;
 37+}
 38+
 39+static int json_hex64(struct json* j, uint64_t v)
 40+{
 41+	char hex[17];
 42+	static const char* b16 = "0123456789abcdef";
 43+
 44+	for (int i = 15; i >= 0; i--) {
 45+		hex[i] = b16[v & 0xf];
 46+		v >>= 4;
 47+	}
 48+	hex[16] = '\0';
 49+
 50+	return json_string(j, hex);
 51+}
 52+
 53+static int json_putc(struct json* j, char c)
 54+{
 55+	return json_putn(j, &c, 1);
 56+}
 57+
 58+static int json_putn(struct json* j, const char* s, size_t n)
 59+{
 60+	if (json_grow(j, n + 1) < 0)
 61+		return -1;
 62+
 63+	memcpy(j->buf + j->len, s, n);
 64+	j->len += n;
 65+	j->buf[j->len] = '\0';
 66+
 67+	return 0;
 68+}
 69+
 70+static int json_puts(struct json* j, const char* s)
 71+{
 72+	return json_putn(j, s, strlen(s));
 73+}
 74+
 75+static int json_string(struct json* j, const char* s)
 76+{
 77+	/* opening quote */
 78+	if (json_putc(j, '"') < 0)
 79+		return -1;
 80+
 81+	/* emit string body */
 82+	for (; *s; s++) {
 83+		unsigned char c = (unsigned char)(*s);
 84+
 85+		/* escape quote and backslash */
 86+		if (c == '"' || c == '\\') {
 87+			if (json_putc(j, '\\') < 0)
 88+				return -1;
 89+			if (json_putc(j, (char)c) < 0)
 90+				return -1;
 91+			continue;
 92+		}
 93+
 94+		/* common escapes */
 95+		if (c == '\n') {
 96+			if (json_puts(j, "\\n") < 0)
 97+				return -1;
 98+			continue;
 99+		}
100+		if (c == '\r') {
101+			if (json_puts(j, "\\r") < 0)
102+				return -1;
103+			continue;
104+		}
105+		if (c == '\t') {
106+			if (json_puts(j, "\\t") < 0)
107+				return -1;
108+			continue;
109+		}
110+
111+		/* other control chars->\uxxxx */
112+		if (c < 0x20) {
113+			char esc[7];
114+			snprintf(esc, sizeof(esc), "\\u%04x", (unsigned)c);
115+			if (json_puts(j, esc) < 0)
116+				return -1;
117+			continue;
118+		}
119+
120+		/* literal byte */
121+		if (json_putc(j, (char)c) < 0)
122+			return -1;
123+	}
124+
125+	/* closing quote */
126+	if (json_putc(j, '"') < 0)
127+		return -1;
128+
129+	return 0;
130+}
131+
132+int json_library(struct json* j, const struct library* l)
133+{
134+	memset(j, 0, sizeof(*j));
135+
136+	/* header */
137+	if (json_puts(j, "{\"proto\":1,\"items\":[") < 0)
138+		goto fail;
139+
140+	/* items[] */
141+	for (size_t i = 0; i < l->len; i++) {
142+		/* item separator */
143+		if (i > 0) {
144+			if (json_putc(j, ',') < 0)
145+				goto fail;
146+		}
147+
148+		/* object open */
149+		if (json_puts(j, "{\"id\":") < 0)
150+			goto fail;
151+		if (json_hex64(j, l->items[i].id) < 0)
152+			goto fail;
153+
154+		/* path */
155+		if (json_puts(j, ",\"path\":") < 0)
156+			goto fail;
157+		if (json_string(j, l->items[i].path) < 0)
158+			goto fail;
159+
160+		/* object close */
161+		if (json_putc(j, '}') < 0)
162+			goto fail;
163+	}
164+
165+	/* footer */
166+	if (json_puts(j, "]}") < 0)
167+		goto fail;
168+
169+	return 0;
170+
171+fail:
172+	json_free(j);
173+	return -1;
174+}
175+
176+void json_free(struct json* j)
177+{
178+	if (!j)
179+		return;
180+
181+	free(j->buf);
182+	j->buf = NULL;
183+	j->len = 0;
184+	j->cap = 0;
185+}
186+
+162, -0
  1@@ -0,0 +1,162 @@
  2+#include <stdbool.h>
  3+#include <stdint.h>
  4+#include <stdio.h>
  5+#include <stdlib.h>
  6+#include <string.h>
  7+
  8+#include <dirent.h>
  9+#include <sys/stat.h>
 10+#include <unistd.h>
 11+
 12+#include "config.h"
 13+#include "scan.h"
 14+#include "log.h"
 15+
 16+static uint64_t encode_fnv1a64(const char* s);
 17+static int join_path(char* out, size_t outsz, const char* a, const char* b);
 18+static int library_push(struct library* l, const char* rel);
 19+static int scan_dir(struct library* l, const char* root, const char* rel);
 20+
 21+static uint64_t encode_fnv1a64(const char* s)
 22+{
 23+	/* standard fnv1a64 encoding */
 24+	uint64_t h = 1469598103934665603ULL;
 25+
 26+	for (; *s; s++) {
 27+		h ^= (unsigned char)(*s);
 28+		h *= 1099511628211ULL;
 29+	}
 30+
 31+	return h;
 32+}
 33+
 34+static int join_path(char* out, size_t outsz, const char* a, const char* b)
 35+{
 36+	size_t al = strlen(a);
 37+	size_t bl = strlen(b);
 38+
 39+	if (al + 1 + bl + 1 > outsz) {
 40+		logmsg(verbose_log, "SCAN", "path too long");
 41+		return -1;
 42+	}
 43+
 44+	memcpy(out, a, al);
 45+	out[al] = '/';
 46+	memcpy(out + al + 1, b, bl);
 47+	out[al + 1 + bl] = '\0';
 48+
 49+	return 0;
 50+}
 51+
 52+static int library_push(struct library* l, const char* rel)
 53+{
 54+	if (l->len == l->cap) {
 55+		size_t ncap = l->cap ? (l->cap * 2) : 64;
 56+		struct item* ni = realloc(l->items, ncap * sizeof(*ni));
 57+		if (!ni) {
 58+			logmsg(verbose_log, "SCAN", "out of memory");
 59+			return -1;
 60+		}
 61+		l->items = ni;
 62+		l->cap = ncap;
 63+	}
 64+
 65+	char* p = strdup(rel);
 66+	if (!p)
 67+		return -1;
 68+
 69+	l->items[l->len].id = encode_fnv1a64(rel);
 70+	l->items[l->len].path = p;
 71+	l->len++;
 72+
 73+	return 0;
 74+}
 75+
 76+static int scan_dir(struct library* l, const char* root, const char* rel)
 77+{
 78+	char full[4096];
 79+	DIR* d;
 80+	struct dirent* e;
 81+
 82+	if (rel[0] == '\0') {
 83+		if (snprintf(full, sizeof(full), "%s", root) >= (int)sizeof(full))
 84+			return -1;
 85+	}
 86+	else {
 87+		if (join_path(full, sizeof(full), root, rel) < 0)
 88+			return -1;
 89+	}
 90+
 91+	d = opendir(full);
 92+	if (!d) {
 93+		logmsg(verbose_log, "SCAN", "opendir failed: %s", full);
 94+		return -1;
 95+	}
 96+
 97+	while ((e = readdir(d)) != NULL) {
 98+		if (strcmp(e->d_name, ".") == 0)
 99+			continue;
100+		if (strcmp(e->d_name, "..") == 0)
101+			continue;
102+
103+		char rel2[4096];
104+		char full2[4096];
105+
106+		if (rel[0] == '\0') {
107+			if (snprintf(rel2, sizeof(rel2), "%s", e->d_name) >= (int)sizeof(rel2))
108+				goto fail;
109+		}
110+		else {
111+			if (join_path(rel2, sizeof(rel2), rel, e->d_name) < 0)
112+				goto fail;
113+		}
114+
115+		if (join_path(full2, sizeof(full2), root, rel2) < 0)
116+			goto fail;
117+
118+		struct stat st;
119+		if (lstat(full2, &st) < 0)
120+			continue;
121+
122+		if (S_ISDIR(st.st_mode)) {
123+			if (scan_dir(l, root, rel2) < 0)
124+				goto fail;
125+			continue;
126+		}
127+
128+		if (S_ISREG(st.st_mode)) {
129+			if (library_push(l, rel2) < 0)
130+				goto fail;
131+			continue;
132+		}
133+	}
134+
135+	closedir(d);
136+	return 0;
137+
138+fail:
139+	closedir(d);
140+	return -1;
141+}
142+
143+int scan_library(struct library* l, const char* root)
144+{
145+	memset(l, 0, sizeof(*l));
146+	return scan_dir(l, root, "");
147+}
148+
149+void scan_library_free(struct library* l)
150+{
151+	if (!l)
152+		return;
153+
154+	for (size_t i = 0; i < l->len; i++)
155+		free(l->items[i].path);
156+
157+	free(l->items);
158+
159+	l->items = NULL;
160+	l->len = 0;
161+	l->cap = 0;
162+}
163+