Mercurial
comparison medi/main.c @ 217:7ef4c9d2a72d hg-web
[DO NOT PUSH] Random values.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sun, 25 Jan 2026 10:44:04 -0800 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 192:b818a4561a3c | 217:7ef4c9d2a72d |
|---|---|
| 1 #include "seobeo/seobeo.h" | |
| 2 #include "media_processor/media_processor.h" | |
| 3 | |
| 4 #include <stdio.h> | |
| 5 #include <stdlib.h> | |
| 6 #include <string.h> | |
| 7 #include <sys/stat.h> | |
| 8 #include <dirent.h> | |
| 9 #include <pthread.h> | |
| 10 #include <unistd.h> | |
| 11 | |
| 12 #define UPLOAD_DIR "./uploads" | |
| 13 #define PROCESSED_DIR "./media" | |
| 14 #define MAX_UPLOAD_SIZE (100 * 1024 * 1024) // 100MB | |
| 15 | |
| 16 // Background processing queue | |
| 17 typedef struct ProcessJob { | |
| 18 char* input_path; | |
| 19 char* id; | |
| 20 MediaType type; | |
| 21 struct ProcessJob* next; | |
| 22 } ProcessJob; | |
| 23 | |
| 24 static ProcessJob* job_queue_head = NULL; | |
| 25 static ProcessJob* job_queue_tail = NULL; | |
| 26 static pthread_mutex_t job_queue_mutex = PTHREAD_MUTEX_INITIALIZER; | |
| 27 static pthread_cond_t job_queue_cond = PTHREAD_COND_INITIALIZER; | |
| 28 | |
| 29 // ============================================================================ | |
| 30 // Helper Functions | |
| 31 // ============================================================================ | |
| 32 | |
| 33 static void ensure_directories(void) { | |
| 34 mkdir(UPLOAD_DIR, 0755); | |
| 35 mkdir(PROCESSED_DIR, 0755); | |
| 36 } | |
| 37 | |
| 38 static char* extract_filename_from_content_disposition(const char* header, Dowa_Arena* arena) { | |
| 39 const char* filename_start = strstr(header, "filename=\""); | |
| 40 if (!filename_start) return NULL; | |
| 41 | |
| 42 filename_start += 10; // Skip 'filename="' | |
| 43 const char* filename_end = strchr(filename_start, '"'); | |
| 44 if (!filename_end) return NULL; | |
| 45 | |
| 46 size_t len = filename_end - filename_start; | |
| 47 char* filename = Dowa_Arena_Allocate(arena, len + 1); | |
| 48 memcpy(filename, filename_start, len); | |
| 49 filename[len] = '\0'; | |
| 50 return filename; | |
| 51 } | |
| 52 | |
| 53 static char* find_multipart_boundary(const char* content_type, Dowa_Arena* arena) { | |
| 54 const char* boundary_start = strstr(content_type, "boundary="); | |
| 55 if (!boundary_start) return NULL; | |
| 56 | |
| 57 boundary_start += 9; // Skip 'boundary=' | |
| 58 | |
| 59 // Handle quoted boundary | |
| 60 if (*boundary_start == '"') { | |
| 61 boundary_start++; | |
| 62 const char* boundary_end = strchr(boundary_start, '"'); | |
| 63 if (!boundary_end) return NULL; | |
| 64 size_t len = boundary_end - boundary_start; | |
| 65 char* boundary = Dowa_Arena_Allocate(arena, len + 1); | |
| 66 memcpy(boundary, boundary_start, len); | |
| 67 boundary[len] = '\0'; | |
| 68 return boundary; | |
| 69 } | |
| 70 | |
| 71 // Unquoted boundary | |
| 72 size_t len = strlen(boundary_start); | |
| 73 char* boundary = Dowa_Arena_Allocate(arena, len + 1); | |
| 74 strcpy(boundary, boundary_start); | |
| 75 // Trim whitespace/newline | |
| 76 while (len > 0 && (boundary[len-1] == '\r' || boundary[len-1] == '\n' || boundary[len-1] == ' ')) { | |
| 77 boundary[--len] = '\0'; | |
| 78 } | |
| 79 return boundary; | |
| 80 } | |
| 81 | |
| 82 // ============================================================================ | |
| 83 // Background Processing Worker | |
| 84 // ============================================================================ | |
| 85 | |
| 86 static void enqueue_job(const char* input_path, const char* id, MediaType type) { | |
| 87 ProcessJob* job = malloc(sizeof(ProcessJob)); | |
| 88 job->input_path = strdup(input_path); | |
| 89 job->id = strdup(id); | |
| 90 job->type = type; | |
| 91 job->next = NULL; | |
| 92 | |
| 93 pthread_mutex_lock(&job_queue_mutex); | |
| 94 if (job_queue_tail) { | |
| 95 job_queue_tail->next = job; | |
| 96 job_queue_tail = job; | |
| 97 } else { | |
| 98 job_queue_head = job_queue_tail = job; | |
| 99 } | |
| 100 pthread_cond_signal(&job_queue_cond); | |
| 101 pthread_mutex_unlock(&job_queue_mutex); | |
| 102 } | |
| 103 | |
| 104 static void* processing_worker(void* arg) { | |
| 105 (void)arg; | |
| 106 | |
| 107 while (1) { | |
| 108 pthread_mutex_lock(&job_queue_mutex); | |
| 109 while (!job_queue_head) { | |
| 110 pthread_cond_wait(&job_queue_cond, &job_queue_mutex); | |
| 111 } | |
| 112 ProcessJob* job = job_queue_head; | |
| 113 job_queue_head = job->next; | |
| 114 if (!job_queue_head) job_queue_tail = NULL; | |
| 115 pthread_mutex_unlock(&job_queue_mutex); | |
| 116 | |
| 117 // Process the job | |
| 118 Seobeo_Log(SEOBEO_INFO, "Processing media: %s (id=%s)", job->input_path, job->id); | |
| 119 | |
| 120 MediaProcessorOpts opts = media_processor_opts_default(PROCESSED_DIR); | |
| 121 MediaResult* result = media_process_file(job->input_path, job->id, &opts); | |
| 122 | |
| 123 if (result->status == MEDIA_STATUS_COMPLETED) { | |
| 124 Seobeo_Log(SEOBEO_INFO, "Processing completed for %s", job->id); | |
| 125 } else { | |
| 126 Seobeo_Log(SEOBEO_ERROR, "Processing failed for %s: %s", job->id, result->error_message); | |
| 127 } | |
| 128 | |
| 129 media_result_free(result); | |
| 130 | |
| 131 // Clean up temp upload file | |
| 132 unlink(job->input_path); | |
| 133 | |
| 134 free(job->input_path); | |
| 135 free(job->id); | |
| 136 free(job); | |
| 137 } | |
| 138 | |
| 139 return NULL; | |
| 140 } | |
| 141 | |
| 142 void Seobeo_Render_Html( | |
| 143 char *final_body, | |
| 144 char *template, | |
| 145 Dowa_Arena *arena | |
| 146 ) | |
| 147 { | |
| 148 size_t current_offset = 0; | |
| 149 char *cursor = template; | |
| 150 | |
| 151 int32 token_len = 2; | |
| 152 | |
| 153 while (1) | |
| 154 { | |
| 155 char *start_tag = strstr(cursor, "{{"); | |
| 156 if (!start_tag) break; | |
| 157 | |
| 158 char *end_tag = strstr(start_tag, "}}"); | |
| 159 if (!end_tag) break; | |
| 160 | |
| 161 size_t leading_len = start_tag - cursor; | |
| 162 memcpy(final_body + current_offset, cursor, leading_len); | |
| 163 current_offset += leading_len; | |
| 164 | |
| 165 size_t name_len = end_tag - (start_tag + token_len); | |
| 166 char *include_name = Dowa_Arena_Allocate(arena, name_len + 1); | |
| 167 memcpy(include_name, start_tag + token_len, name_len); | |
| 168 include_name[name_len] = '\0'; | |
| 169 | |
| 170 size_t sub_file_size = 0; | |
| 171 char *sub_content = Seobeo_Web_LoadFile(include_name, &sub_file_size); | |
| 172 Seobeo_Log(SEOBEO_DEBUG, "[Curr] Sub content: %s\n", sub_content); | |
| 173 if (sub_content) | |
| 174 { | |
| 175 memcpy(final_body + current_offset, sub_content, sub_file_size); | |
| 176 current_offset += sub_file_size; | |
| 177 free(sub_content); | |
| 178 } | |
| 179 | |
| 180 cursor = end_tag + 2; | |
| 181 } | |
| 182 strcpy(final_body + current_offset, cursor); | |
| 183 } | |
| 184 | |
| 185 | |
| 186 void Seobeo_Render_Html_FilePath( | |
| 187 char *final_body, | |
| 188 char *path, | |
| 189 Dowa_Arena *arena | |
| 190 ) { | |
| 191 Seobeo_Log(SEOBEO_DEBUG, "[Curr] %s\n", path); | |
| 192 size_t html_size = 0; | |
| 193 char *template = Seobeo_Web_LoadFile(path, &html_size); | |
| 194 if (!template) return; | |
| 195 Seobeo_Render_Html(final_body, template, arena); | |
| 196 } | |
| 197 | |
| 198 // ============================================================================ | |
| 199 // API Route Handlers | |
| 200 // ============================================================================ | |
| 201 | |
| 202 // GET / - Serve the main HTML page | |
| 203 Seobeo_Request_Entry* handle_index(Seobeo_Request_Entry* req, Dowa_Arena* arena) { | |
| 204 Seobeo_Request_Entry *resp = NULL; | |
| 205 char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024); | |
| 206 Seobeo_Render_Html_FilePath(final_body, "/index.html", arena); | |
| 207 Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); | |
| 208 return resp; | |
| 209 } | |
| 210 | |
| 211 // POST /api/upload - Handle file upload | |
| 212 Seobeo_Request_Entry* handle_upload(Seobeo_Request_Entry* req, Dowa_Arena* arena) { | |
| 213 const char* content_type = Dowa_HashMap_Get(req, "content-type"); | |
| 214 const char* body = Dowa_HashMap_Get(req, "Body"); | |
| 215 const char* content_length_str = Dowa_HashMap_Get(req, "content-length"); | |
| 216 | |
| 217 Seobeo_Request_Entry* resp = NULL; | |
| 218 | |
| 219 if (!content_type || !body) { | |
| 220 Dowa_HashMap_Push_Arena(resp, "status", "400", arena); | |
| 221 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 222 Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Missing content\"}", arena); | |
| 223 return resp; | |
| 224 } | |
| 225 | |
| 226 size_t content_length = content_length_str ? atol(content_length_str) : strlen(body); | |
| 227 | |
| 228 if (content_length > MAX_UPLOAD_SIZE) { | |
| 229 Dowa_HashMap_Push_Arena(resp, "status", "413", arena); | |
| 230 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 231 Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"File too large\"}", arena); | |
| 232 return resp; | |
| 233 } | |
| 234 | |
| 235 // Parse multipart form data | |
| 236 char* boundary = find_multipart_boundary(content_type, arena); | |
| 237 if (!boundary) { | |
| 238 Dowa_HashMap_Push_Arena(resp, "status", "400", arena); | |
| 239 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 240 Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Invalid multipart data\"}", arena); | |
| 241 return resp; | |
| 242 } | |
| 243 | |
| 244 // Find file content in multipart data | |
| 245 char boundary_marker[256]; | |
| 246 snprintf(boundary_marker, sizeof(boundary_marker), "--%s", boundary); | |
| 247 | |
| 248 const char* part_start = strstr(body, boundary_marker); | |
| 249 if (!part_start) { | |
| 250 Dowa_HashMap_Push_Arena(resp, "status", "400", arena); | |
| 251 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 252 Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"No file found\"}", arena); | |
| 253 return resp; | |
| 254 } | |
| 255 | |
| 256 // Skip to headers | |
| 257 part_start = strstr(part_start, "\r\n"); | |
| 258 if (!part_start) { | |
| 259 Dowa_HashMap_Push_Arena(resp, "status", "400", arena); | |
| 260 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 261 Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Invalid format\"}", arena); | |
| 262 return resp; | |
| 263 } | |
| 264 part_start += 2; | |
| 265 | |
| 266 // Find filename from Content-Disposition | |
| 267 char* filename = NULL; | |
| 268 const char* header_end = strstr(part_start, "\r\n\r\n"); | |
| 269 if (header_end) { | |
| 270 char headers[1024] = {0}; | |
| 271 size_t headers_len = header_end - part_start; | |
| 272 if (headers_len < sizeof(headers)) { | |
| 273 memcpy(headers, part_start, headers_len); | |
| 274 filename = extract_filename_from_content_disposition(headers, arena); | |
| 275 } | |
| 276 } | |
| 277 | |
| 278 if (!filename) { | |
| 279 filename = "upload"; | |
| 280 } | |
| 281 | |
| 282 // Find file content | |
| 283 const char* file_start = header_end + 4; // Skip \r\n\r\n | |
| 284 | |
| 285 // Find end boundary | |
| 286 char end_boundary[256]; | |
| 287 snprintf(end_boundary, sizeof(end_boundary), "\r\n--%s", boundary); | |
| 288 const char* file_end = strstr(file_start, end_boundary); | |
| 289 | |
| 290 if (!file_end) { | |
| 291 Dowa_HashMap_Push_Arena(resp, "status", "400", arena); | |
| 292 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 293 Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Incomplete upload\"}", arena); | |
| 294 return resp; | |
| 295 } | |
| 296 | |
| 297 size_t file_size = file_end - file_start; | |
| 298 | |
| 299 // Generate ID and determine type | |
| 300 char* id = media_generate_id(); | |
| 301 MediaType type = media_detect_type(filename); | |
| 302 | |
| 303 if (type == MEDIA_TYPE_UNKNOWN) { | |
| 304 free(id); | |
| 305 Dowa_HashMap_Push_Arena(resp, "status", "400", arena); | |
| 306 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 307 Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Unsupported file type\"}", arena); | |
| 308 return resp; | |
| 309 } | |
| 310 | |
| 311 // Save to temp file | |
| 312 const char* ext = media_get_extension(filename); | |
| 313 char temp_path[512]; | |
| 314 snprintf(temp_path, sizeof(temp_path), "%s/%s%s", UPLOAD_DIR, id, ext ? ext : ""); | |
| 315 | |
| 316 FILE* f = fopen(temp_path, "wb"); | |
| 317 if (!f) { | |
| 318 free(id); | |
| 319 Dowa_HashMap_Push_Arena(resp, "status", "500", arena); | |
| 320 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 321 Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Failed to save file\"}", arena); | |
| 322 return resp; | |
| 323 } | |
| 324 | |
| 325 fwrite(file_start, 1, file_size, f); | |
| 326 fclose(f); | |
| 327 | |
| 328 // Enqueue for processing | |
| 329 enqueue_job(temp_path, id, type); | |
| 330 | |
| 331 // Return success with ID | |
| 332 char response_body[256]; | |
| 333 snprintf(response_body, sizeof(response_body), | |
| 334 "{\"id\":\"%s\",\"type\":\"%s\",\"status\":\"processing\"}", | |
| 335 id, type == MEDIA_TYPE_IMAGE ? "image" : "video"); | |
| 336 | |
| 337 char* body_copy = Dowa_Arena_Allocate(arena, strlen(response_body) + 1); | |
| 338 strcpy(body_copy, response_body); | |
| 339 | |
| 340 Dowa_HashMap_Push_Arena(resp, "status", "202", arena); | |
| 341 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 342 Dowa_HashMap_Push_Arena(resp, "Body", body_copy, arena); | |
| 343 | |
| 344 free(id); | |
| 345 return resp; | |
| 346 } | |
| 347 | |
| 348 // GET /api/media/:id - Get media info/status | |
| 349 Seobeo_Request_Entry* handle_media_info(Seobeo_Request_Entry* req, Dowa_Arena* arena) { | |
| 350 const char* path = Dowa_HashMap_Get(req, "path"); | |
| 351 | |
| 352 Seobeo_Request_Entry* resp = NULL; | |
| 353 | |
| 354 // Extract ID from path (e.g., /api/media/123456) | |
| 355 const char* id = path + strlen("/api/media/"); | |
| 356 if (!id || strlen(id) == 0) { | |
| 357 Dowa_HashMap_Push_Arena(resp, "status", "400", arena); | |
| 358 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 359 Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Missing media ID\"}", arena); | |
| 360 return resp; | |
| 361 } | |
| 362 | |
| 363 // Check if processed directory exists | |
| 364 char media_dir[512]; | |
| 365 snprintf(media_dir, sizeof(media_dir), "%s/%s", PROCESSED_DIR, id); | |
| 366 | |
| 367 struct stat st; | |
| 368 if (stat(media_dir, &st) != 0) { | |
| 369 // Check if still processing | |
| 370 char upload_pattern[512]; | |
| 371 snprintf(upload_pattern, sizeof(upload_pattern), "%s/%s", UPLOAD_DIR, id); | |
| 372 | |
| 373 DIR* dir = opendir(UPLOAD_DIR); | |
| 374 boolean found = FALSE; | |
| 375 if (dir) { | |
| 376 struct dirent* entry; | |
| 377 while ((entry = readdir(dir)) != NULL) { | |
| 378 if (strncmp(entry->d_name, id, strlen(id)) == 0) { | |
| 379 found = TRUE; | |
| 380 break; | |
| 381 } | |
| 382 } | |
| 383 closedir(dir); | |
| 384 } | |
| 385 | |
| 386 if (found) { | |
| 387 Dowa_HashMap_Push_Arena(resp, "status", "200", arena); | |
| 388 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 389 Dowa_HashMap_Push_Arena(resp, "Body", "{\"status\":\"processing\"}", arena); | |
| 390 } else { | |
| 391 Dowa_HashMap_Push_Arena(resp, "status", "404", arena); | |
| 392 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 393 Dowa_HashMap_Push_Arena(resp, "Body", "{\"error\":\"Media not found\"}", arena); | |
| 394 } | |
| 395 return resp; | |
| 396 } | |
| 397 | |
| 398 // Check what files exist | |
| 399 char webp_path[512], hls_path[512], thumb_path[512]; | |
| 400 snprintf(webp_path, sizeof(webp_path), "%s/image.webp", media_dir); | |
| 401 snprintf(hls_path, sizeof(hls_path), "%s/video.m3u8", media_dir); | |
| 402 snprintf(thumb_path, sizeof(thumb_path), "%s/video_thumb.jpg", media_dir); | |
| 403 | |
| 404 boolean is_image = (stat(webp_path, &st) == 0); | |
| 405 boolean is_video = (stat(hls_path, &st) == 0); | |
| 406 | |
| 407 char response[1024]; | |
| 408 if (is_image) { | |
| 409 snprintf(response, sizeof(response), | |
| 410 "{\"id\":\"%s\",\"type\":\"image\",\"status\":\"completed\"," | |
| 411 "\"webp\":\"/media/%s/image.webp\"," | |
| 412 "\"original\":\"/media/%s/original\"}", | |
| 413 id, id, id); | |
| 414 } else if (is_video) { | |
| 415 snprintf(response, sizeof(response), | |
| 416 "{\"id\":\"%s\",\"type\":\"video\",\"status\":\"completed\"," | |
| 417 "\"hls\":\"/media/%s/video.m3u8\"," | |
| 418 "\"thumbnail\":\"/media/%s/video_thumb.jpg\"," | |
| 419 "\"original\":\"/media/%s/original\"}", | |
| 420 id, id, id, id); | |
| 421 } else { | |
| 422 snprintf(response, sizeof(response), "{\"id\":\"%s\",\"status\":\"processing\"}", id); | |
| 423 } | |
| 424 | |
| 425 char* body = Dowa_Arena_Allocate(arena, strlen(response) + 1); | |
| 426 strcpy(body, response); | |
| 427 | |
| 428 Dowa_HashMap_Push_Arena(resp, "status", "200", arena); | |
| 429 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 430 Dowa_HashMap_Push_Arena(resp, "Body", body, arena); | |
| 431 | |
| 432 return resp; | |
| 433 } | |
| 434 | |
| 435 // GET /api/media - List all media | |
| 436 Seobeo_Request_Entry* handle_media_list(Seobeo_Request_Entry* req, Dowa_Arena* arena) { | |
| 437 (void)req; | |
| 438 | |
| 439 Seobeo_Request_Entry* resp = NULL; | |
| 440 | |
| 441 DIR* dir = opendir(PROCESSED_DIR); | |
| 442 if (!dir) { | |
| 443 Dowa_HashMap_Push_Arena(resp, "status", "200", arena); | |
| 444 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 445 Dowa_HashMap_Push_Arena(resp, "Body", "{\"items\":[]}", arena); | |
| 446 return resp; | |
| 447 } | |
| 448 | |
| 449 // Build JSON array | |
| 450 char json[16384] = "{\"items\":["; | |
| 451 size_t json_len = strlen(json); | |
| 452 boolean first = TRUE; | |
| 453 | |
| 454 struct dirent* entry; | |
| 455 while ((entry = readdir(dir)) != NULL) { | |
| 456 if (entry->d_name[0] == '.') continue; | |
| 457 | |
| 458 char media_dir[512]; | |
| 459 snprintf(media_dir, sizeof(media_dir), "%s/%s", PROCESSED_DIR, entry->d_name); | |
| 460 | |
| 461 struct stat st; | |
| 462 if (stat(media_dir, &st) != 0 || !S_ISDIR(st.st_mode)) continue; | |
| 463 | |
| 464 // Check type | |
| 465 char webp_path[512], hls_path[512]; | |
| 466 snprintf(webp_path, sizeof(webp_path), "%s/image.webp", media_dir); | |
| 467 snprintf(hls_path, sizeof(hls_path), "%s/video.m3u8", media_dir); | |
| 468 | |
| 469 const char* type = "unknown"; | |
| 470 if (stat(webp_path, &st) == 0) type = "image"; | |
| 471 else if (stat(hls_path, &st) == 0) type = "video"; | |
| 472 | |
| 473 char item[256]; | |
| 474 snprintf(item, sizeof(item), "%s{\"id\":\"%s\",\"type\":\"%s\"}", | |
| 475 first ? "" : ",", entry->d_name, type); | |
| 476 | |
| 477 size_t item_len = strlen(item); | |
| 478 if (json_len + item_len + 10 < sizeof(json)) { | |
| 479 strcpy(json + json_len, item); | |
| 480 json_len += item_len; | |
| 481 first = FALSE; | |
| 482 } | |
| 483 } | |
| 484 | |
| 485 closedir(dir); | |
| 486 | |
| 487 strcpy(json + json_len, "]}"); | |
| 488 | |
| 489 char* body = Dowa_Arena_Allocate(arena, strlen(json) + 1); | |
| 490 strcpy(body, json); | |
| 491 | |
| 492 Dowa_HashMap_Push_Arena(resp, "status", "200", arena); | |
| 493 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 494 Dowa_HashMap_Push_Arena(resp, "Body", body, arena); | |
| 495 | |
| 496 return resp; | |
| 497 } | |
| 498 | |
| 499 // ============================================================================ | |
| 500 // Main | |
| 501 // ============================================================================ | |
| 502 | |
| 503 int main(int argc, char** argv) { | |
| 504 const char* port = "8080"; | |
| 505 | |
| 506 printf("===========================================\n"); | |
| 507 printf(" Medi - Media Upload & Processing Server\n"); | |
| 508 printf("===========================================\n"); | |
| 509 printf("Starting on port %s...\n\n", port); | |
| 510 | |
| 511 // Create directories | |
| 512 ensure_directories(); | |
| 513 | |
| 514 // Start background processing worker | |
| 515 pthread_t worker_thread; | |
| 516 pthread_create(&worker_thread, NULL, processing_worker, NULL); | |
| 517 | |
| 518 // Initialize router | |
| 519 Seobeo_Router_Init(); | |
| 520 | |
| 521 // Register routes | |
| 522 Seobeo_Router_Register("GET", "/", handle_index); | |
| 523 Seobeo_Router_Register("POST", "/api/upload", handle_upload); | |
| 524 Seobeo_Router_Register("GET", "/api/media", handle_media_list); | |
| 525 Seobeo_Router_Register("GET", "/api/media/*", handle_media_info); | |
| 526 | |
| 527 // Start server (serves static files from ./media for /media/* paths) | |
| 528 Seobeo_Web_Server_Start("medi/src", port, SEOBEO_MODE_EDGE, 4); | |
| 529 | |
| 530 return 0; | |
| 531 } |