commit 3d7bcf8
uint
·
2026-01-25 22:16:45 +0000 UTC
parent 857194b
add streaming
3 files changed,
+190,
-43
+183,
-43
1@@ -1,7 +1,11 @@
2 #include <errno.h>
3+#include <fcntl.h>
4 #include <stddef.h>
5+#include <stdint.h>
6 #include <stdio.h>
7+#include <stdlib.h>
8 #include <string.h>
9+#include <sys/stat.h>
10 #include <unistd.h>
11
12 #include "config.h"
13@@ -10,64 +14,54 @@
14 #include "log.h"
15 #include "scan.h"
16
17+static const struct item* find_item(uint64_t id);
18+static int join_path(char* out, size_t outsz, const char* a, const char* b);
19+static int parse_hex64(const char* s, uint64_t* out);
20 static int read_reqline(int c, char* method, size_t msz, char* path, size_t psz);
21 static void reply_json(int c, const char* status, const char* body, size_t len);
22 static void reply_text(int c, const char* status, const char* body);
23+static int stream_file(int c, const struct item* it);
24 static int write_all(int fd, const void* buf, size_t n);
25
26-int http_handle(int c)
27-{
28- char method[16];
29- char path[1024];
30
31- if (read_reqline(c, method, sizeof(method), path, sizeof(path)) < 0) {
32- reply_text(c, HTTP_400, "bad request\n");
33- return -1;
34- }
35- LOG(verbose_log, "HTTP", "request: %s %s", method, path);
36+extern struct library lib;
37
38- if (strcmp(method, "GET") != 0) {
39- LOG(verbose_log, "HTTP", "method not allowed: %s", method);
40- reply_text(c, HTTP_405, "method not allowed\n");
41- return 0;
42- }
43+static const struct item* find_item(uint64_t id)
44+{
45+ for (size_t i = 0; i < lib.len; i++)
46+ if (lib.items[i].id == id)
47+ return &lib.items[i];
48
49- if (strcmp(path, "/ping") == 0) {
50- LOG(verbose_log, "HTTP", "route /ping");
51- reply_text(c, HTTP_200, "ok\n");
52- return 0;
53- }
54+ return NULL;
55+}
56
57- if (strcmp(path, "/library") == 0) {
58- LOG(verbose_log, "HTTP", "route /library");
59- struct library l;
60- struct json j;
61+static int join_path(char* out, size_t outsz, const char* a, const char* b)
62+{
63+ int n = snprintf(out, outsz, "%s/%s", a, b);
64+ if (n < 0)
65+ return -1;
66
67- if (scan_library(&l, media_dir) < 0) {
68- LOG(verbose_log, "SCAN", "scan failed");
69- reply_text(c, HTTP_500, "scan failed\n");
70- return -1;
71- }
72- LOG(verbose_log, "SCAN", "found %zu items", l.len);
73+ if ((size_t)n >= outsz)
74+ return -1;
75
76- if (json_library(&j, &l) < 0) {
77- LOG(verbose_log, "JSON", "encode failed");
78- scan_library_free(&l);
79- reply_text(c, HTTP_500, "json failed\n");
80- return -1;
81- }
82- LOG(verbose_log, "JSON", "encoded %zu bytes", j.len);
83+ return 0;
84+}
85
86- scan_library_free(&l);
87- reply_json(c, HTTP_200, j.buf, j.len);
88- json_free(&j);
89+static int parse_hex64(const char* s, uint64_t* out)
90+{
91+ char* end = NULL;
92
93- return 0;
94- }
95+ if (!s || *s == '\0')
96+ return -1;
97
98- LOG(verbose_log, "HTTP", "route not found: %s", path);
99- reply_text(c, HTTP_404, "not found\n");
100+ errno = 0;
101+ unsigned long long v = strtoull(s, &end, 16);
102+ if (errno != 0)
103+ return -1;
104+ if (!end || *end != '\0')
105+ return -1;
106
107+ *out = (uint64_t)v;
108 return 0;
109 }
110
111@@ -159,6 +153,88 @@ static void reply_text(int c, const char* status, const char* body)
112 (void)write_all(c, resp, (size_t)n);
113 }
114
115+static int stream_file(int c, const struct item* it)
116+{
117+ char full[4096];
118+
119+ /* build absolute path */
120+ if (join_path(full, sizeof(full), media_dir, it->path) < 0) {
121+ LOG(verbose_log, "HTTP", "path too long");
122+ reply_text(c, HTTP_500, "server error\n");
123+ return -1;
124+ }
125+
126+ /* open file */
127+ int fd = open(full, O_RDONLY);
128+ if (fd < 0) {
129+ LOG(verbose_log, "HTTP", "open failed: %s", it->path);
130+ reply_text(c, HTTP_404, "not found\n");
131+ return 0;
132+ }
133+
134+ /* stat file */
135+ struct stat st;
136+ if (fstat(fd, &st) < 0) {
137+ close(fd);
138+ reply_text(c, HTTP_500, "server error\n");
139+ return -1;
140+ }
141+
142+ /* reject non-regular files */
143+ if (!S_ISREG(st.st_mode)) {
144+ close(fd);
145+ reply_text(c, HTTP_404, "not found\n");
146+ return 0;
147+ }
148+
149+ size_t len = (size_t)st.st_size;
150+
151+ /* build response header */
152+ char resp[HTTP_RESP_MAX];
153+ int n = snprintf(
154+ resp, sizeof(resp),
155+ "%s"
156+ HTTP_BIN
157+ HTTP_LENGTH
158+ HTTP_CLOSE
159+ "\r\n",
160+ HTTP_200, len
161+ );
162+
163+ if (n < 0) {
164+ close(fd);
165+ return -1;
166+ }
167+
168+ if ((size_t)n >= sizeof(resp))
169+ n = (int)(sizeof(resp) - 1);
170+
171+ /* send headers */
172+ (void)write_all(c, resp, (size_t)n);
173+
174+ /* stream file */
175+ char buf[8192];
176+ for (;;) {
177+ ssize_t r = read(fd, buf, sizeof(buf));
178+ if (r < 0) {
179+ if (errno == EINTR)
180+ continue;
181+ break;
182+ }
183+
184+ if (r == 0)
185+ break;
186+
187+ if (write_all(c, buf, (size_t)r) < 0)
188+ break;
189+ }
190+
191+ close(fd);
192+ LOG(verbose_log, "HTTP", "streamed %s (%zu bytes)", it->path, len);
193+
194+ return 0;
195+}
196+
197 static int write_all(int fd, const void* buf, size_t n)
198 {
199 const char* p = buf;
200@@ -178,3 +254,67 @@ static int write_all(int fd, const void* buf, size_t n)
201 return 0;
202 }
203
204+int http_handle(int c)
205+{
206+ char method[16];
207+ char path[1024];
208+
209+ if (read_reqline(c, method, sizeof(method), path, sizeof(path)) < 0) {
210+ reply_text(c, HTTP_400, "bad request\n");
211+ return -1;
212+ }
213+ LOG(verbose_log, "HTTP", "request: %s %s", method, path);
214+
215+ if (strcmp(method, "GET") != 0) {
216+ LOG(verbose_log, "HTTP", "method not allowed: %s", method);
217+ reply_text(c, HTTP_405, "method not allowed\n");
218+ return 0;
219+ }
220+
221+ if (strcmp(path, "/ping") == 0) {
222+ LOG(verbose_log, "HTTP", "route /ping");
223+ reply_text(c, HTTP_200, "ok\n");
224+ return 0;
225+ }
226+
227+ if (strcmp(path, "/library") == 0) {
228+ LOG(verbose_log, "HTTP", "route /library");
229+ struct json j;
230+
231+ if (json_library(&j, &lib) < 0) {
232+ LOG(verbose_log, "JSON", "encode failed");
233+ reply_text(c, HTTP_500, "json failed\n");
234+ return -1;
235+ }
236+ LOG(verbose_log, "JSON", "encoded %zu bytes", j.len);
237+
238+ reply_json(c, HTTP_200, j.buf, j.len);
239+ json_free(&j);
240+
241+ return 0;
242+ }
243+
244+ if (strncmp(path, "/stream/", 8) == 0) {
245+ LOG(verbose_log, "HTTP", "route /stream");
246+
247+ uint64_t id;
248+ if (parse_hex64(path + 8, &id) < 0) {
249+ reply_text(c, HTTP_400, "bad request\n");
250+ return 0;
251+ }
252+
253+ const struct item* it = find_item(id);
254+ if (!it) {
255+ reply_text(c, HTTP_404, "not found\n");
256+ return 0;
257+ }
258+
259+ return stream_file(c, it);
260+ }
261+
262+ LOG(verbose_log, "HTTP", "route not found: %s", path);
263+ reply_text(c, HTTP_404, "not found\n");
264+
265+ return 0;
266+}
267+
+1,
-0
1@@ -9,6 +9,7 @@
2
3 #define HTTP_TEXT "Content-Type: text/plain\r\n"
4 #define HTTP_JSON "Content-Type: application/json\r\n"
5+#define HTTP_BIN "Content-Type: application/octet-stream\r\n"
6 #define HTTP_CLOSE "Connection: close\r\n"
7 #define HTTP_LENGTH "Content-Length: %zu\r\n"
8
+6,
-0
1@@ -22,6 +22,7 @@
2 #include "config.h"
3 #include "http.h"
4 #include "log.h"
5+#include "scan.h"
6
7 void die(const char* s, int e);
8 void run(void);
9@@ -29,6 +30,7 @@ void setup(void);
10 int write_all(int fd, const void* buf, size_t n);
11
12 int sock;
13+struct library lib;
14
15 void die(const char* s, int e)
16 {
17@@ -77,6 +79,10 @@ void setup(void)
18 if (ret < 0)
19 die("bind", EXIT_FAILURE);
20
21+ if (scan_library(&lib, media_dir) < 0)
22+ die("scan_library", EXIT_FAILURE);
23+ LOG(verbose_log, "SCAN", "cached %zu items", lib.len);
24+
25 ret = listen(sock, LISTEN_BACKLOG);
26 if (ret < 0)
27 die("listen", EXIT_FAILURE);