commit 9f73103

uint  ·  2026-01-31 04:18:48 +0000 UTC
parent 088c85d
make endpoint handling more thread safe, add /rescan endpoint
5 files changed,  +147, -14
+118, -14
  1@@ -1,6 +1,7 @@
  2 #include <errno.h>
  3 #include <fcntl.h>
  4 #include <limits.h>
  5+#include <pthread.h>
  6 #include <stddef.h>
  7 #include <stdint.h>
  8 #include <stdio.h>
  9@@ -9,6 +10,7 @@
 10 #include <strings.h>
 11 #include <sys/stat.h>
 12 #include <sys/types.h>
 13+#include <time.h>
 14 #include <unistd.h>
 15 
 16 #include "config.h"
 17@@ -21,6 +23,7 @@
 18 
 19 static int cors_build(char* out, size_t outsz, const char* hdr, int preflight);
 20 static const struct item* find_item(uint64_t id);
 21+static int item_path_for_id(char out[4096], uint64_t id, const struct user* u);
 22 static const char* mime_from_path(const char* path);
 23 static int parse_hex64(const char* s, uint64_t* out);
 24 static int parse_range(const char* hdr, size_t total, size_t* start, size_t* end);
 25@@ -34,6 +37,7 @@ static int stat_item(const struct item* it, struct stat* st);
 26 static int stream_file(int c, const struct item* it, const char* hdr, int head_only);
 27 
 28 extern struct library lib;
 29+extern pthread_rwlock_t lib_lock;
 30 
 31 static int cors_origin_allowed(const char* origin)
 32 {
 33@@ -129,6 +133,40 @@ static const struct item* find_item(uint64_t id)
 34 	return NULL;
 35 }
 36 
 37+static int item_path_for_id(char out[4096], uint64_t id, const struct user* u)
 38+{
 39+	int ret = -1;
 40+
 41+	if (!out)
 42+		return -1;
 43+
 44+	out[0] = '\0';
 45+
 46+	pthread_rwlock_rdlock(&lib_lock);
 47+
 48+	const struct item* it = find_item(id);
 49+	if (!it) {
 50+		ret = 1; /* not found */
 51+		goto out;
 52+	}
 53+
 54+	if (u && !user_allows_path(u, it->path)) {
 55+		ret = 1; /* not found */
 56+		goto out;
 57+	}
 58+
 59+	if (snprintf(out, 4096, "%s", it->path) >= 4096) {
 60+		ret = -1;
 61+		goto out;
 62+	}
 63+
 64+	ret = 0;
 65+
 66+out:
 67+	pthread_rwlock_unlock(&lib_lock);
 68+	return ret; /* 0 ok, 1 not found, -1 error */
 69+}
 70+
 71 static const char* mime_from_path(const char* path)
 72 {
 73 	const char* dot = strrchr(path, '.');
 74@@ -615,6 +653,9 @@ int http_handle(int c)
 75 
 76 	if (strcmp(path, "/library") == 0) {
 77 		LOG(verbose_log, "HTTP", "Route              /library");
 78+
 79+		pthread_rwlock_rdlock(&lib_lock);
 80+
 81 		struct json j;
 82 		struct library view;
 83 		memset(&view, 0, sizeof(view));
 84@@ -626,6 +667,7 @@ int http_handle(int c)
 85 			if (lib.len > 0) {
 86 				view.items = calloc(lib.len, sizeof(*view.items));
 87 				if (!view.items) {
 88+					pthread_rwlock_unlock(&lib_lock);
 89 					reply_text(c, hdr, HTTP_500, "server error\n");
 90 					return -1;
 91 				}
 92@@ -643,10 +685,15 @@ int http_handle(int c)
 93 			if (u)
 94 				free(view.items);
 95 
 96+			pthread_rwlock_unlock(&lib_lock);
 97+
 98 			LOG(verbose_log, "JSON", "Encode     FAILED");
 99 			reply_text(c, hdr, HTTP_500, "json encode failed\n");
100 			return -1;
101 		}
102+
103+		pthread_rwlock_unlock(&lib_lock);
104+
105 		LOG(verbose_log, "JSON", "Encoded bytes      %zu bytes", j.len);
106 
107 		reply_json(c, hdr, HTTP_200, j.buf, j.len, !head_only);
108@@ -658,6 +705,52 @@ int http_handle(int c)
109 		return 0;
110 	}
111 
112+	if (strcmp(path, "/rescan") == 0) {
113+		LOG(verbose_log, "HTTP", "Route              /rescan");
114+
115+		if (users.len == 0) {
116+			LOG(true, "HTTP", "Rescan forbidden   Auth disabled");
117+			reply_text(c, hdr, HTTP_403, "Forbidden\n");
118+			return 0;
119+		}
120+
121+		LOG(true, "SCAN", "Rescan requested   %s", u->name);
122+
123+		struct timespec t0;
124+		struct timespec t1;
125+		clock_gettime(CLOCK_MONOTONIC, &t0);
126+
127+		size_t before = 0;
128+		size_t after  = 0;
129+
130+		pthread_rwlock_wrlock(&lib_lock);
131+
132+		before = lib.len;
133+		LOG(verbose_log, "SCAN", "Rescan begin       %s (%zu items)", media_dir, before);
134+
135+		int ok = scan_library_rescan(&lib, media_dir);
136+
137+		after = lib.len;
138+
139+		pthread_rwlock_unlock(&lib_lock);
140+
141+		clock_gettime(CLOCK_MONOTONIC, &t1);
142+
143+		long ms = (t1.tv_sec - t0.tv_sec) * 1000L +
144+			(t1.tv_nsec - t0.tv_nsec) / 1000000L;
145+
146+		if (ok < 0) {
147+			LOG(true, "SCAN", "Rescan FAILED      %s (%ld ms)", media_dir, ms);
148+			reply_text(c, hdr, HTTP_500, "rescan failed\n");
149+			return -1;
150+		}
151+
152+		LOG(true, "SCAN", "Rescan OK          %zu -> %zu (%ld ms)", before, after, ms);
153+
154+		reply_text(c, hdr, HTTP_200, "ok\n");
155+		return 0;
156+	}
157+
158 	if (strncmp(path, "/stream/", 8) == 0) {
159 		LOG(verbose_log, "HTTP", "Route              /stream");
160 
161@@ -667,17 +760,22 @@ int http_handle(int c)
162 			return 0;
163 		}
164 
165-		const struct item* it = find_item(id);
166-		if (!it) {
167+		char rel[4096];
168+		int fr = item_path_for_id(rel, id, u);
169+		if (fr == 1) {
170 			reply_text(c, hdr, HTTP_404, "not found\n");
171 			return 0;
172 		}
173-		if (u && !user_allows_path(u, it->path)) {
174-			reply_text(c, hdr, HTTP_404, "not found\n");
175-			return 0;
176+		if (fr < 0) {
177+			reply_text(c, hdr, HTTP_500, "server error\n");
178+			return -1;
179 		}
180 
181-		return stream_file(c, it, hdr, head_only);
182+		struct item tmp;
183+		tmp.id = id;
184+		tmp.path = rel;
185+
186+		return stream_file(c, &tmp, hdr, head_only);
187 	}
188 
189 	if (strncmp(path, "/meta/", 6) == 0) {
190@@ -689,26 +787,32 @@ int http_handle(int c)
191 			return 0;
192 		}
193 
194-		const struct item* it = find_item(id);
195-		if (!it) {
196+		char rel[4096];
197+		int fr = item_path_for_id(rel, id, u);
198+		if (fr == 1) {
199 			reply_text(c, hdr, HTTP_404, "not found\n");
200 			return 0;
201 		}
202-		if (u && !user_allows_path(u, it->path)) {
203-			reply_text(c, hdr, HTTP_404, "not found\n");
204-			return 0;
205+
206+		if (fr < 0) {
207+			reply_text(c, hdr, HTTP_500, "server error\n");
208+			return -1;
209 		}
210 
211+		struct item tmp;
212+		tmp.id = id;
213+		tmp.path = rel;
214+
215 		struct stat st;
216-		if (stat_item(it, &st) < 0) {
217+		if (stat_item(&tmp, &st) < 0) {
218 			reply_text(c, hdr, HTTP_404, "not found\n");
219 			return 0;
220 		}
221 
222-		const char* type = mime_from_path(it->path);
223+		const char* type = mime_from_path(rel);
224 
225 		struct json j;
226-		if (json_meta(&j, it, (size_t)st.st_size, type) < 0) {
227+		if (json_meta(&j, &tmp, (size_t)st.st_size, type) < 0) {
228 			reply_text(c, hdr, HTTP_500, "json failed\n");
229 			return -1;
230 		}
+1, -0
1@@ -5,6 +5,7 @@
2 #define HTTP_204    "HTTP/1.1 204 No Content\r\n"
3 #define HTTP_206    "HTTP/1.1 206 Partial Content\r\n"
4 #define HTTP_400    "HTTP/1.1 400 Bad Request\r\n"
5+#define HTTP_403    "HTTP/1.1 403 Forbidden\r\n"
6 #define HTTP_404    "HTTP/1.1 404 Not Found\r\n"
7 #define HTTP_405    "HTTP/1.1 405 Method Not Allowed\r\n"
8 #define HTTP_416    "HTTP/1.1 416 Range Not Satisfiable\r\n"
+1, -0
1@@ -17,6 +17,7 @@ struct library {
2 
3 void scan_library_free(struct library* l);
4 int scan_library(struct library* l, const char* root);
5+int scan_library_rescan(struct library* l, const char* root);
6 
7 #endif /* SCAN_H */
8 
+1, -0
1@@ -50,6 +50,7 @@ void setup(void);
2 static sem_t slots;
3 static int sock;
4 struct library lib;
5+pthread_rwlock_t lib_lock = PTHREAD_RWLOCK_INITIALIZER;
6 
7 static void apply_rlimits(void)
8 {
+26, -0
 1@@ -1,3 +1,4 @@
 2+#include <pthread.h>
 3 #include <stdbool.h>
 4 #include <stdint.h>
 5 #include <stdio.h>
 6@@ -142,3 +143,28 @@ void scan_library_free(struct library* l)
 7 	l->cap = 0;
 8 }
 9 
10+int scan_library_rescan(struct library* l, const char* root)
11+{
12+	struct library new;
13+	struct library old;
14+
15+	if (!l || !root)
16+		return -1;
17+
18+	memset(&new, 0, sizeof(new));
19+
20+	if (scan_library(&new, root) < 0) {
21+		scan_library_free(&new);
22+		return -1;
23+	}
24+
25+	/* swap */
26+	old = *l;
27+	*l = new;
28+
29+	/* free old */
30+	scan_library_free(&old);
31+
32+	return 0;
33+}
34+