commit 9aedfa4

uint  ·  2026-01-26 16:52:29 +0000 UTC
parent 769b10a
add /meta endpoint, add Content-Type for streams (update later)
4 files changed,  +162, -11
+115, -4
  1@@ -6,6 +6,7 @@
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5+#include <strings.h>
  6 #include <sys/stat.h>
  7 #include <sys/types.h>
  8 #include <unistd.h>
  9@@ -19,14 +20,17 @@
 10 static const char* cistrstr(const char* hay, const char* nee);
 11 static const struct item* find_item(uint64_t id);
 12 static int join_path(char* out, size_t outsz, const char* a, const char* b);
 13+static const char* mime_from_path(const char* path);
 14 static int parse_hex64(const char* s, uint64_t* out);
 15 static int parse_range(const char* hdr, size_t total, size_t* start, size_t* end);
 16 static int read_request(int c, char* method, size_t msz, char* path, size_t psz, char* hdr, size_t hsz);
 17 static void reply_json(int c, const char* status, const char* body, size_t len, int send_body);
 18 static void reply_text(int c, const char* status, const char* body);
 19+static int stat_item(const struct item* it, struct stat* st);
 20 static int stream_file(int c, const struct item* it, const char* hdr, int head_only);
 21 static int write_all(int fd, const void* buf, size_t n);
 22 
 23+
 24 extern struct library lib;
 25 
 26 static const char* cistrstr(const char* hay, const char* nee)
 27@@ -79,6 +83,59 @@ static int join_path(char* out, size_t outsz, const char* a, const char* b)
 28 	return 0;
 29 }
 30 
 31+static const char* mime_from_path(const char* path)
 32+{
 33+	const char* dot = strrchr(path, '.');
 34+	if (!dot || dot[1] == '\0')
 35+		return "application/octet-stream";
 36+
 37+	dot++;
 38+
 39+	/* audio */
 40+	if (strcasecmp(dot, "mp3") == 0)
 41+		return "audio/mpeg";
 42+	if (strcasecmp(dot, "m4a") == 0)
 43+		return "audio/mp4";
 44+	if (strcasecmp(dot, "aac") == 0)
 45+		return "audio/aac";
 46+	if (strcasecmp(dot, "flac") == 0)
 47+		return "audio/flac";
 48+	if (strcasecmp(dot, "wav") == 0)
 49+		return "audio/wav";
 50+	if (strcasecmp(dot, "ogg") == 0)
 51+		return "audio/ogg";
 52+	if (strcasecmp(dot, "opus") == 0)
 53+		return "audio/opus";
 54+
 55+	/* video */
 56+	if (strcasecmp(dot, "mp4") == 0)
 57+		return "video/mp4";
 58+	if (strcasecmp(dot, "mkv") == 0)
 59+		return "video/x-matroska";
 60+	if (strcasecmp(dot, "webm") == 0)
 61+		return "video/webm";
 62+	if (strcasecmp(dot, "mov") == 0)
 63+		return "video/quicktime";
 64+
 65+	/* images */
 66+	if (strcasecmp(dot, "jpg") == 0)
 67+		return "image/jpeg";
 68+	if (strcasecmp(dot, "jpeg") == 0)
 69+		return "image/jpeg";
 70+	if (strcasecmp(dot, "png") == 0)
 71+		return "image/png";
 72+	if (strcasecmp(dot, "gif") == 0)
 73+		return "image/gif";
 74+	if (strcasecmp(dot, "webp") == 0)
 75+		return "image/webp";
 76+
 77+	/* misc */
 78+	if (strcasecmp(dot, "pdf") == 0)
 79+		return "application/pdf";
 80+
 81+	return "application/octet-stream";
 82+}
 83+
 84 static int parse_hex64(const char* s, uint64_t* out)
 85 {
 86 	char* end = NULL;
 87@@ -275,6 +332,22 @@ static void reply_text(int c, const char* status, const char* body)
 88 	(void)write_all(c, resp, (size_t)n);
 89 }
 90 
 91+static int stat_item(const struct item* it, struct stat* st)
 92+{
 93+	char full[4096];
 94+
 95+	if (join_path(full, sizeof(full), media_dir, it->path) < 0)
 96+		return -1;
 97+
 98+	if (stat(full, st) < 0)
 99+		return -1;
100+
101+	if (!S_ISREG(st->st_mode))
102+		return -1;
103+
104+	return 0;
105+}
106+
107 static int stream_file(int c, const struct item* it, const char* hdr, int head_only)
108 {
109 	char full[4096];
110@@ -312,6 +385,7 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
111 	size_t total = (size_t)st.st_size;
112 	size_t start = 0;
113 	size_t end = total ? (total - 1) : 0;
114+	const char* type = mime_from_path(it->path);
115 
116 	int partial = parse_range(hdr, total, &start, &end);
117 	if (partial == RANGE_BAD) {
118@@ -341,28 +415,30 @@ static int stream_file(int c, const struct item* it, const char* hdr, int head_o
119 	int n;
120 
121 	if (partial == RANGE_OK) {
122+		/* partial */
123 		n = snprintf(
124 			resp, sizeof(resp),
125 			"%s"
126-			HTTP_BIN
127+			HTTP_CTYPE
128 			HTTP_RANGE
129 			HTTP_CRG
130 			HTTP_LENGTH
131 			HTTP_CLOSE
132 			"\r\n",
133-			HTTP_206, start, end, total, len
134+			HTTP_206, type, start, end, total, len
135 		);
136 	}
137 	else {
138+		/* non-partial */
139 		n = snprintf(
140 			resp, sizeof(resp),
141 			"%s"
142-			HTTP_BIN
143+			HTTP_CTYPE
144 			HTTP_RANGE
145 			HTTP_LENGTH
146 			HTTP_CLOSE
147 			"\r\n",
148-			HTTP_200, len
149+			HTTP_200, type, len
150 		);
151 	}
152 
153@@ -509,6 +585,41 @@ int http_handle(int c)
154 		return stream_file(c, it, hdr, head_only);
155 	}
156 
157+	if (strncmp(path, "/meta/", 6) == 0) {
158+		LOG(verbose_log, "HTTP", "route /meta");
159+
160+		uint64_t id;
161+		if (parse_hex64(path + 6, &id) < 0) {
162+			reply_text(c, HTTP_400, "bad request\n");
163+			return 0;
164+		}
165+
166+		const struct item* it = find_item(id);
167+		if (!it) {
168+			reply_text(c, HTTP_404, "not found\n");
169+			return 0;
170+		}
171+
172+		struct stat st;
173+		if (stat_item(it, &st) < 0) {
174+			reply_text(c, HTTP_404, "not found\n");
175+			return 0;
176+		}
177+
178+		const char* type = mime_from_path(it->path);
179+
180+		struct json j;
181+		if (json_meta(&j, it, (size_t)st.st_size, type) < 0) {
182+			reply_text(c, HTTP_500, "json failed\n");
183+			return -1;
184+		}
185+
186+		reply_json(c, HTTP_200, j.buf, j.len, !head_only);
187+		json_free(&j);
188+
189+		return 0;
190+	}
191+
192 	LOG(verbose_log, "HTTP", "route not found: %s", path);
193 	reply_text(c, HTTP_404, "not found\n");
194 
+1, -0
1@@ -12,6 +12,7 @@
2 #define HTTP_BIN    "Content-Type: application/octet-stream\r\n"
3 #define HTTP_CLOSE  "Connection: close\r\n"
4 #define HTTP_CRG    "Content-Range: bytes %zu-%zu/%zu\r\n"
5+#define HTTP_CTYPE  "Content-Type: %s\r\n"
6 #define HTTP_JSON   "Content-Type: application/json\r\n"
7 #define HTTP_LENGTH "Content-Length: %zu\r\n"
8 #define HTTP_RANGE  "Accept-Ranges: bytes\r\n"
+1, -0
1@@ -14,6 +14,7 @@ struct json {
2 
3 void json_free(struct json* j);
4 int json_library(struct json* j, const struct library* l);
5+int json_meta(struct json* j, const struct item* it, size_t size, const char* type);
6 
7 #endif /* JSON_H */
8 
+45, -7
 1@@ -128,6 +128,17 @@ static int json_string(struct json* j, const char* s)
 2 	return 0;
 3 }
 4 
 5+void json_free(struct json* j)
 6+{
 7+	if (!j)
 8+		return;
 9+
10+	free(j->buf);
11+	j->buf = NULL;
12+	j->len = 0;
13+	j->cap = 0;
14+}
15+
16 int json_library(struct json* j, const struct library* l)
17 {
18 	memset(j, 0, sizeof(*j));
19@@ -172,14 +183,41 @@ fail:
20 	return -1;
21 }
22 
23-void json_free(struct json* j)
24+int json_meta(struct json* j, const struct item* it, size_t size, const char* type)
25 {
26-	if (!j)
27-		return;
28+	memset(j, 0, sizeof(*j));
29 
30-	free(j->buf);
31-	j->buf = NULL;
32-	j->len = 0;
33-	j->cap = 0;
34+	if (json_puts(j, "{\"proto\":1,\"id\":") < 0)
35+		goto fail;
36+	if (json_hex64(j, it->id) < 0)
37+		goto fail;
38+
39+	if (json_puts(j, ",\"path\":") < 0)
40+		goto fail;
41+	if (json_string(j, it->path) < 0)
42+		goto fail;
43+
44+	if (json_puts(j, ",\"size\":") < 0)
45+		goto fail;
46+
47+	/* decimal size */
48+	char tmp[32];
49+	snprintf(tmp, sizeof(tmp), "%zu", size);
50+	if (json_puts(j, tmp) < 0)
51+		goto fail;
52+
53+	if (json_puts(j, ",\"type\":") < 0)
54+		goto fail;
55+	if (json_string(j, type) < 0)
56+		goto fail;
57+
58+	if (json_putc(j, '}') < 0)
59+		goto fail;
60+
61+	return 0;
62+
63+fail:
64+	json_free(j);
65+	return -1;
66 }
67