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+