Mercurial
comparison mrjunejune/main.c @ 250:745fd127b2a1
[seobeo] Add bounded worker interface
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 04 Aug 2026 06:23:37 -0700 |
| parents | b8b6e726964a |
| children | 667156fcd3e3 |
comparison
equal
deleted
inserted
replaced
| 249:c5129452493e | 250:745fd127b2a1 |
|---|---|
| 14 #define UUID_LEN 37 | 14 #define UUID_LEN 37 |
| 15 | 15 |
| 16 volatile sig_atomic_t stop_server = 0; | 16 volatile sig_atomic_t stop_server = 0; |
| 17 static _Atomic uint32 counter = 0; | 17 static _Atomic uint32 counter = 0; |
| 18 static _Atomic boolean g_latex_rendering = FALSE; | 18 static _Atomic boolean g_latex_rendering = FALSE; |
| 19 | 19 static Seobeo_Worker_Pool *g_media_worker_pool = NULL; |
| 20 // Media Processing Context for background threads | 20 |
| 21 // Media processing context owned by a background worker. | |
| 21 typedef struct { | 22 typedef struct { |
| 22 int64 media_id; | 23 int64 media_id; |
| 23 char s3_key_original[512]; | 24 char s3_key_original[512]; |
| 24 char s3_key_processed[512]; | 25 char s3_key_processed[512]; |
| 25 char content_type[128]; | 26 char content_type[128]; |
| 29 } Media_Processing_Context; | 30 } Media_Processing_Context; |
| 30 | 31 |
| 31 typedef struct { | 32 typedef struct { |
| 32 char *input_path; | 33 char *input_path; |
| 33 char *output_path; | 34 char *output_path; |
| 35 int result; | |
| 34 } File_Converter_Config; | 36 } File_Converter_Config; |
| 35 | 37 |
| 36 // Server configuration (loaded from .config) | 38 // Server configuration (loaded from .config) |
| 37 static char g_upload_auth_token[256] = {0}; | 39 static char g_upload_auth_token[256] = {0}; |
| 38 static char g_s3_region[64] = "us-west-2"; | 40 static char g_s3_region[64] = "us-west-2"; |
| 405 Dowa_HashMap_Push_Arena(resp, "x-content-type-options", "nosniff", arena); | 407 Dowa_HashMap_Push_Arena(resp, "x-content-type-options", "nosniff", arena); |
| 406 Dowa_HashMap_Push_Arena(resp, "body", pdf, arena); | 408 Dowa_HashMap_Push_Arena(resp, "body", pdf, arena); |
| 407 return resp; | 409 return resp; |
| 408 } | 410 } |
| 409 | 411 |
| 410 // Background thread function for media processing | 412 // Joinable worker function for local image conversion. |
| 411 void *Simple_WebpConverter_Background(void *arg) | 413 void Simple_WebpConverter_Background(void *arg) |
| 412 { | 414 { |
| 413 File_Converter_Config *configuration = (File_Converter_Config *)arg; | 415 File_Converter_Config *configuration = (File_Converter_Config *)arg; |
| 414 | 416 |
| 415 char cmd[1024]; | 417 char cmd[1024]; |
| 416 snprintf(cmd, sizeof(cmd), "ffmpeg -y -i %s -quality 80 %s 2>/tmp/error_log", | 418 snprintf(cmd, sizeof(cmd), "ffmpeg -y -i %s -quality 80 %s 2>/tmp/error_log", |
| 417 configuration->input_path, configuration->output_path); | 419 configuration->input_path, configuration->output_path); |
| 418 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Running FFmpeg: %s\n", cmd); | 420 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Running FFmpeg: %s\n", cmd); |
| 419 int ffmpeg_result = system(cmd); | 421 configuration->result = system(cmd); |
| 420 | 422 |
| 421 Seobeo_Log(SEOBEO_INFO, "[MEDIA] FFmpeg result: %d\n", ffmpeg_result); | 423 Seobeo_Log( |
| 422 if (ffmpeg_result != 0) | 424 SEOBEO_INFO, |
| 425 "[MEDIA] FFmpeg result: %d\n", | |
| 426 configuration->result); | |
| 427 if (configuration->result != 0) | |
| 423 { | 428 { |
| 424 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: FFmpeg conversion failed\n"); | 429 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: FFmpeg conversion failed\n"); |
| 425 return NULL; | 430 return; |
| 426 } | 431 } |
| 427 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully converted to webp: %s\n"); | 432 Seobeo_Log( |
| 428 return NULL; | 433 SEOBEO_INFO, |
| 434 "[MEDIA] Successfully converted to webp: %s\n", | |
| 435 configuration->output_path); | |
| 429 } | 436 } |
| 430 | 437 |
| 431 Seobeo_Request_Entry *ConvertImageToWebP(Seobeo_Request_Entry *req, Dowa_Arena *arena) | 438 Seobeo_Request_Entry *ConvertImageToWebP(Seobeo_Request_Entry *req, Dowa_Arena *arena) |
| 432 { | 439 { |
| 433 Seobeo_Request_Entry *resp = NULL; | 440 Seobeo_Request_Entry *resp = NULL; |
| 479 Seobeo_Log(SEOBEO_DEBUG, "Converting image, file_size=%zu bytes\n", file_size); | 486 Seobeo_Log(SEOBEO_DEBUG, "Converting image, file_size=%zu bytes\n", file_size); |
| 480 | 487 |
| 481 int open_flags = O_RDWR | O_CREAT | O_EXCL; | 488 int open_flags = O_RDWR | O_CREAT | O_EXCL; |
| 482 | 489 |
| 483 char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); | 490 char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); |
| 484 uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; | 491 uint32 seed = |
| 492 (uint32)time(NULL) ^ | |
| 493 (uint32)Seobeo_Thread_Current_Id() ^ | |
| 494 counter++; | |
| 485 Dowa_String_UUID(seed, uuid4); | 495 Dowa_String_UUID(seed, uuid4); |
| 486 char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; | 496 char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; |
| 487 snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4); | 497 snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4); |
| 488 int input_fd = open(input_path, open_flags, 0600); | 498 int input_fd = open(input_path, open_flags, 0600); |
| 489 if (input_fd == -1) | 499 if (input_fd == -1) |
| 497 write(input_fd, file_data, file_size); | 507 write(input_fd, file_data, file_size); |
| 498 close(input_fd); | 508 close(input_fd); |
| 499 | 509 |
| 500 | 510 |
| 501 uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); | 511 uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); |
| 502 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; | 512 seed = |
| 513 (uint32)time(NULL) ^ | |
| 514 (uint32)Seobeo_Thread_Current_Id() ^ | |
| 515 counter++; | |
| 503 Dowa_String_UUID(seed, uuid4); | 516 Dowa_String_UUID(seed, uuid4); |
| 504 char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; | 517 char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; |
| 505 snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.webp", uuid4); | 518 snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.webp", uuid4); |
| 506 Seobeo_Log(SEOBEO_DEBUG, "output_path %s\n", output_path); | 519 Seobeo_Log(SEOBEO_DEBUG, "output_path %s\n", output_path); |
| 507 Seobeo_Log(SEOBEO_DEBUG, "open_flags: 0x%x\n", open_flags); | 520 Seobeo_Log(SEOBEO_DEBUG, "open_flags: 0x%x\n", open_flags); |
| 521 close(output_fd); | 534 close(output_fd); |
| 522 | 535 |
| 523 File_Converter_Config *configuration = Dowa_Arena_Allocate(arena, sizeof(File_Converter_Config)); | 536 File_Converter_Config *configuration = Dowa_Arena_Allocate(arena, sizeof(File_Converter_Config)); |
| 524 configuration->input_path = input_path; | 537 configuration->input_path = input_path; |
| 525 configuration->output_path = output_path; | 538 configuration->output_path = output_path; |
| 526 | 539 configuration->result = -1; |
| 527 pthread_t thread_id; | 540 |
| 528 int thread_result = pthread_create(&thread_id, NULL, Simple_WebpConverter_Background, (void *)configuration); | 541 Seobeo_Thread *p_worker = Seobeo_Thread_Start( |
| 529 | 542 Simple_WebpConverter_Background, |
| 530 if (thread_result != 0) | 543 configuration, |
| 544 NULL); | |
| 545 if (!p_worker || | |
| 546 Seobeo_Thread_Join(p_worker) != SEOBEO_WORKER_OK || | |
| 547 configuration->result != 0) | |
| 531 { | 548 { |
| 532 unlink(input_path); | 549 unlink(input_path); |
| 533 unlink(output_path); | 550 unlink(output_path); |
| 534 char *error_msg = "FFmpeg conversion failed"; | 551 char *error_msg = "FFmpeg conversion failed"; |
| 535 Dowa_HashMap_Push_Arena(resp, "status", "500", arena); | 552 Dowa_HashMap_Push_Arena(resp, "status", "500", arena); |
| 536 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); | 553 Dowa_HashMap_Push_Arena(resp, "content-type", "text/plain", arena); |
| 537 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); | 554 Dowa_HashMap_Push_Arena(resp, "body", error_msg, arena); |
| 538 return resp; | 555 return resp; |
| 539 } | 556 } |
| 540 else | 557 |
| 541 { | |
| 542 // Detach thread so it cleans up automatically when done | |
| 543 pthread_detach(thread_id); | |
| 544 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully spawned and detached thread\n"); | |
| 545 } | |
| 546 | |
| 547 size_t converted_size = 0; | |
| 548 FILE *out_file = fopen(output_path, "rb"); | 558 FILE *out_file = fopen(output_path, "rb"); |
| 549 if (!out_file) | 559 if (!out_file) |
| 550 { | 560 { |
| 551 unlink(input_path); | 561 unlink(input_path); |
| 552 unlink(output_path); | 562 unlink(output_path); |
| 605 printf("DEBUG: Converting video, file_size=%zu bytes\n", file_size); | 615 printf("DEBUG: Converting video, file_size=%zu bytes\n", file_size); |
| 606 | 616 |
| 607 int open_flags = O_RDWR | O_CREAT | O_EXCL; | 617 int open_flags = O_RDWR | O_CREAT | O_EXCL; |
| 608 | 618 |
| 609 char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); | 619 char *uuid4 = (char *)Dowa_Arena_Allocate(arena, UUID_LEN); |
| 610 uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; | 620 uint32 seed = |
| 621 (uint32)time(NULL) ^ | |
| 622 (uint32)Seobeo_Thread_Current_Id() ^ | |
| 623 counter++; | |
| 611 Dowa_String_UUID(seed, uuid4); | 624 Dowa_String_UUID(seed, uuid4); |
| 612 char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH); | 625 char *input_path = Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH); |
| 613 snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4); | 626 snprintf(input_path, TMP_FILE_LENGTH, "/tmp/%s", uuid4); |
| 614 Seobeo_Log(SEOBEO_DEBUG, "Input path: %s\n", input_path); | 627 Seobeo_Log(SEOBEO_DEBUG, "Input path: %s\n", input_path); |
| 615 | 628 |
| 625 } | 638 } |
| 626 | 639 |
| 627 write(input_fd, file_data, file_size); | 640 write(input_fd, file_data, file_size); |
| 628 close(input_fd); | 641 close(input_fd); |
| 629 | 642 |
| 630 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; | 643 seed = |
| 644 (uint32)time(NULL) ^ | |
| 645 (uint32)Seobeo_Thread_Current_Id() ^ | |
| 646 counter++; | |
| 631 Dowa_String_UUID(seed, uuid4); | 647 Dowa_String_UUID(seed, uuid4); |
| 632 char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; | 648 char *output_path = (char *)Dowa_Arena_Allocate(arena, TMP_FILE_LENGTH);; |
| 633 snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.mp4", uuid4); | 649 snprintf(output_path, TMP_FILE_LENGTH, "/tmp/%s.mp4", uuid4); |
| 634 int output_fd = open(output_path, open_flags, 0600); | 650 int output_fd = open(output_path, open_flags, 0600); |
| 635 if (output_fd == -1) | 651 if (output_fd == -1) |
| 951 } | 967 } |
| 952 | 968 |
| 953 // Generate unique S3 key with timestamp | 969 // Generate unique S3 key with timestamp |
| 954 char s3_key[512]; | 970 char s3_key[512]; |
| 955 char *uuid = Dowa_Arena_Allocate(arena, UUID_LEN); | 971 char *uuid = Dowa_Arena_Allocate(arena, UUID_LEN); |
| 956 uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; | 972 uint32 seed = |
| 973 (uint32)time(NULL) ^ | |
| 974 (uint32)Seobeo_Thread_Current_Id() ^ | |
| 975 counter++; | |
| 957 Dowa_String_UUID(seed, uuid); | 976 Dowa_String_UUID(seed, uuid); |
| 958 snprintf(s3_key, sizeof(s3_key), "uploads/%s/%s", uuid, filename); | 977 snprintf(s3_key, sizeof(s3_key), "uploads/%s/%s", uuid, filename); |
| 959 | 978 |
| 960 // Generate presigned URL | 979 // Generate presigned URL |
| 961 S3_Presigned_URL presigned = S3_Presign_Put(&g_s3_config, s3_key, content_type, g_s3_url_expires); | 980 S3_Presigned_URL presigned = S3_Presign_Put(&g_s3_config, s3_key, content_type, g_s3_url_expires); |
| 1334 return resp; | 1353 return resp; |
| 1335 } | 1354 } |
| 1336 | 1355 |
| 1337 // Generate UUID for this upload | 1356 // Generate UUID for this upload |
| 1338 char *uuid = Dowa_Arena_Allocate(arena, UUID_LEN); | 1357 char *uuid = Dowa_Arena_Allocate(arena, UUID_LEN); |
| 1339 uint32 seed = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; | 1358 uint32 seed = |
| 1359 (uint32)time(NULL) ^ | |
| 1360 (uint32)Seobeo_Thread_Current_Id() ^ | |
| 1361 counter++; | |
| 1340 Dowa_String_UUID(seed, uuid); | 1362 Dowa_String_UUID(seed, uuid); |
| 1341 | 1363 |
| 1342 // Generate S3 keys | 1364 // Generate S3 keys |
| 1343 char s3_key_original[512]; | 1365 char s3_key_original[512]; |
| 1344 char s3_key_processed[512]; | 1366 char s3_key_processed[512]; |
| 1421 printf("[MEDIA] Created media_id=%lld, file=%s\n", (long long)media_id, filename); | 1443 printf("[MEDIA] Created media_id=%lld, file=%s\n", (long long)media_id, filename); |
| 1422 | 1444 |
| 1423 return resp; | 1445 return resp; |
| 1424 } | 1446 } |
| 1425 | 1447 |
| 1426 // Background thread function for media processing | 1448 // Worker-pool task for S3 media processing. |
| 1427 void *Media_Process_Background(void *arg) | 1449 void Media_Process_Background(void *arg) |
| 1428 { | 1450 { |
| 1429 Media_Processing_Context *ctx = (Media_Processing_Context *)arg; | 1451 Media_Processing_Context *ctx = (Media_Processing_Context *)arg; |
| 1430 | 1452 |
| 1431 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Background thread started for media_id=%lld\n", (long long)ctx->media_id); | 1453 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Background worker started for media_id=%lld\n", (long long)ctx->media_id); |
| 1432 Seobeo_Log(SEOBEO_INFO, "[MEDIA] S3 key original: %s\n", ctx->s3_key_original); | 1454 Seobeo_Log(SEOBEO_INFO, "[MEDIA] S3 key original: %s\n", ctx->s3_key_original); |
| 1433 Seobeo_Log(SEOBEO_INFO, "[MEDIA] S3 key processed: %s\n", ctx->s3_key_processed); | 1455 Seobeo_Log(SEOBEO_INFO, "[MEDIA] S3 key processed: %s\n", ctx->s3_key_processed); |
| 1434 Seobeo_Log(SEOBEO_INFO, "[MEDIA] DB path: %s\n", ctx->db_path); | 1456 Seobeo_Log(SEOBEO_INFO, "[MEDIA] DB path: %s\n", ctx->db_path); |
| 1435 | 1457 |
| 1436 // Open thread-local DB connection | 1458 // Open a worker-local DB connection. |
| 1437 Deita_Connection *db_conn = Deita_Connection_Create(DEITA_DATABASE_TYPE_SQLITE3, ctx->db_path); | 1459 Deita_Connection *db_conn = Deita_Connection_Create(DEITA_DATABASE_TYPE_SQLITE3, ctx->db_path); |
| 1438 if (!db_conn || !Deita_Connection_Is_Open(db_conn)) | 1460 if (!db_conn || !Deita_Connection_Is_Open(db_conn)) |
| 1439 { | 1461 { |
| 1440 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] Thread ERROR: Failed to open database for media_id=%lld\n", (long long)ctx->media_id); | 1462 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] Worker ERROR: Failed to open database for media_id=%lld\n", (long long)ctx->media_id); |
| 1441 free(ctx); | 1463 return; |
| 1442 return NULL; | |
| 1443 } | 1464 } |
| 1444 | 1465 |
| 1445 // Update status to 'processing' | 1466 // Update status to 'processing' |
| 1446 const char *update_processing = | 1467 const char *update_processing = |
| 1447 "UPDATE media_uploads SET status='processing', updated_at=strftime('%s','now') WHERE id=?"; | 1468 "UPDATE media_uploads SET status='processing', updated_at=strftime('%s','now') WHERE id=?"; |
| 1463 "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?"; | 1484 "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?"; |
| 1464 const char *error_params[] = { error_msg, media_id_str }; | 1485 const char *error_params[] = { error_msg, media_id_str }; |
| 1465 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); | 1486 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); |
| 1466 S3_Presigned_URL_Destroy(&download_url); | 1487 S3_Presigned_URL_Destroy(&download_url); |
| 1467 Deita_Connection_Close(db_conn); | 1488 Deita_Connection_Close(db_conn); |
| 1468 free(ctx); | 1489 return; |
| 1469 return NULL; | |
| 1470 } | 1490 } |
| 1471 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Generated presigned URL: %.100s...\n", download_url.url); | 1491 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Generated presigned URL: %.100s...\n", download_url.url); |
| 1472 | 1492 |
| 1473 // Generate temp file paths | 1493 // Generate temp file paths |
| 1474 char tmp_input[256]; | 1494 char tmp_input[256]; |
| 1475 char tmp_output[256]; | 1495 char tmp_output[256]; |
| 1476 char *uuid_input = malloc(UUID_LEN); | 1496 char *uuid_input = malloc(UUID_LEN); |
| 1477 char *uuid_output = malloc(UUID_LEN); | 1497 char *uuid_output = malloc(UUID_LEN); |
| 1478 uint32 seed1 = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; | 1498 uint32 seed1 = |
| 1479 uint32 seed2 = (uint32)time(NULL) ^ (uint32)pthread_self() ^ counter++; | 1499 (uint32)time(NULL) ^ |
| 1500 (uint32)Seobeo_Thread_Current_Id() ^ | |
| 1501 counter++; | |
| 1502 uint32 seed2 = | |
| 1503 (uint32)time(NULL) ^ | |
| 1504 (uint32)Seobeo_Thread_Current_Id() ^ | |
| 1505 counter++; | |
| 1480 Dowa_String_UUID(seed1, uuid_input); | 1506 Dowa_String_UUID(seed1, uuid_input); |
| 1481 Dowa_String_UUID(seed2, uuid_output); | 1507 Dowa_String_UUID(seed2, uuid_output); |
| 1482 snprintf(tmp_input, sizeof(tmp_input), "/tmp/%s", uuid_input); | 1508 snprintf(tmp_input, sizeof(tmp_input), "/tmp/%s", uuid_input); |
| 1483 snprintf(tmp_output, sizeof(tmp_output), "/tmp/%s.webp", uuid_output); | 1509 snprintf(tmp_output, sizeof(tmp_output), "/tmp/%s.webp", uuid_output); |
| 1484 free(uuid_input); | 1510 free(uuid_input); |
| 1503 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); | 1529 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); |
| 1504 if (download_req) Seobeo_Client_Request_Destroy(download_req); | 1530 if (download_req) Seobeo_Client_Request_Destroy(download_req); |
| 1505 if (download_resp) Seobeo_Client_Response_Destroy(download_resp); | 1531 if (download_resp) Seobeo_Client_Response_Destroy(download_resp); |
| 1506 unlink(tmp_input); | 1532 unlink(tmp_input); |
| 1507 Deita_Connection_Close(db_conn); | 1533 Deita_Connection_Close(db_conn); |
| 1508 free(ctx); | 1534 return; |
| 1509 return NULL; | |
| 1510 } | 1535 } |
| 1511 | 1536 |
| 1512 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully downloaded file to %s\n", tmp_input); | 1537 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully downloaded file to %s\n", tmp_input); |
| 1513 Seobeo_Client_Request_Destroy(download_req); | 1538 Seobeo_Client_Request_Destroy(download_req); |
| 1514 Seobeo_Client_Response_Destroy(download_resp); | 1539 Seobeo_Client_Response_Destroy(download_resp); |
| 1533 const char *error_params[] = { "Image conversion failed", media_id_str }; | 1558 const char *error_params[] = { "Image conversion failed", media_id_str }; |
| 1534 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); | 1559 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); |
| 1535 unlink(tmp_input); | 1560 unlink(tmp_input); |
| 1536 unlink(tmp_output); | 1561 unlink(tmp_output); |
| 1537 Deita_Connection_Close(db_conn); | 1562 Deita_Connection_Close(db_conn); |
| 1538 free(ctx); | 1563 return; |
| 1539 return NULL; | |
| 1540 } | 1564 } |
| 1541 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully converted to webp: %s\n", tmp_output); | 1565 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully converted to webp: %s\n", tmp_output); |
| 1542 | 1566 |
| 1543 // Upload processed file to S3 | 1567 // Upload processed file to S3 |
| 1544 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Uploading processed file to S3: %s -> %s\n", tmp_output, ctx->s3_key_processed); | 1568 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Uploading processed file to S3: %s -> %s\n", tmp_output, ctx->s3_key_processed); |
| 1555 const char *error_params[] = { error_msg, media_id_str }; | 1579 const char *error_params[] = { error_msg, media_id_str }; |
| 1556 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); | 1580 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); |
| 1557 unlink(tmp_input); | 1581 unlink(tmp_input); |
| 1558 unlink(tmp_output); | 1582 unlink(tmp_output); |
| 1559 Deita_Connection_Close(db_conn); | 1583 Deita_Connection_Close(db_conn); |
| 1560 free(ctx); | 1584 return; |
| 1561 return NULL; | |
| 1562 } | 1585 } |
| 1563 | 1586 |
| 1564 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully uploaded processed file to S3\n"); | 1587 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully uploaded processed file to S3\n"); |
| 1565 | 1588 |
| 1566 // Update status to 'finished' | 1589 // Update status to 'finished' |
| 1572 | 1595 |
| 1573 // Cleanup | 1596 // Cleanup |
| 1574 unlink(tmp_input); | 1597 unlink(tmp_input); |
| 1575 unlink(tmp_output); | 1598 unlink(tmp_output); |
| 1576 Deita_Connection_Close(db_conn); | 1599 Deita_Connection_Close(db_conn); |
| 1577 free(ctx); | |
| 1578 | |
| 1579 return NULL; | |
| 1580 } | 1600 } |
| 1581 | 1601 |
| 1582 // Media Upload API - Mark uploaded | 1602 // Media Upload API - Mark uploaded |
| 1583 // POST /api/media/:id/uploaded | 1603 // POST /api/media/:id/uploaded |
| 1584 // Headers: Authorization: Bearer <token> | 1604 // Headers: Authorization: Bearer <token> |
| 1682 return resp; | 1702 return resp; |
| 1683 } | 1703 } |
| 1684 | 1704 |
| 1685 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Content type for media_id=%lld: '%s'\n", (long long)media_id, content_type_copy); | 1705 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Content type for media_id=%lld: '%s'\n", (long long)media_id, content_type_copy); |
| 1686 | 1706 |
| 1687 // If content_type starts with "image/", spawn background processing thread | 1707 // Images are processed asynchronously by the bounded media pool. |
| 1688 if (strncmp(content_type_copy, "image/", 6) == 0) | 1708 if (strncmp(content_type_copy, "image/", 6) == 0) |
| 1689 { | 1709 { |
| 1690 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Detected image type, preparing to spawn background thread for media_id=%lld\n", (long long)media_id); | 1710 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Queueing image processing for media_id=%lld\n", (long long)media_id); |
| 1691 | 1711 |
| 1692 // Create context for background thread (heap allocated) | 1712 // The pool owns this context after a successful submission. |
| 1693 Media_Processing_Context *ctx = malloc(sizeof(Media_Processing_Context)); | 1713 Media_Processing_Context *ctx = malloc(sizeof(Media_Processing_Context)); |
| 1714 if (!ctx) | |
| 1715 { | |
| 1716 Dowa_HashMap_Push_Arena(resp, "status", "500", arena); | |
| 1717 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 1718 Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Unable to allocate media work\"}", arena); | |
| 1719 return resp; | |
| 1720 } | |
| 1694 ctx->media_id = media_id; | 1721 ctx->media_id = media_id; |
| 1695 strncpy(ctx->s3_key_original, s3_key_original_copy, sizeof(ctx->s3_key_original) - 1); | 1722 strncpy(ctx->s3_key_original, s3_key_original_copy, sizeof(ctx->s3_key_original) - 1); |
| 1696 strncpy(ctx->s3_key_processed, s3_key_processed_copy, sizeof(ctx->s3_key_processed) - 1); | 1723 strncpy(ctx->s3_key_processed, s3_key_processed_copy, sizeof(ctx->s3_key_processed) - 1); |
| 1697 strncpy(ctx->content_type, content_type_copy, sizeof(ctx->content_type) - 1); | 1724 strncpy(ctx->content_type, content_type_copy, sizeof(ctx->content_type) - 1); |
| 1698 strncpy(ctx->access_token, token, sizeof(ctx->access_token) - 1); | 1725 strncpy(ctx->access_token, token, sizeof(ctx->access_token) - 1); |
| 1702 ctx->content_type[sizeof(ctx->content_type) - 1] = '\0'; | 1729 ctx->content_type[sizeof(ctx->content_type) - 1] = '\0'; |
| 1703 ctx->access_token[sizeof(ctx->access_token) - 1] = '\0'; | 1730 ctx->access_token[sizeof(ctx->access_token) - 1] = '\0'; |
| 1704 ctx->db_path[sizeof(ctx->db_path) - 1] = '\0'; | 1731 ctx->db_path[sizeof(ctx->db_path) - 1] = '\0'; |
| 1705 ctx->s3_config = g_s3_config; | 1732 ctx->s3_config = g_s3_config; |
| 1706 | 1733 |
| 1707 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Creating pthread for media_id=%lld\n", (long long)media_id); | 1734 Seobeo_Worker_Result worker_result = |
| 1708 | 1735 g_media_worker_pool |
| 1709 // Spawn detached thread | 1736 ? Seobeo_Worker_Pool_Submit( |
| 1710 pthread_t thread_id; | 1737 g_media_worker_pool, |
| 1711 int thread_result = pthread_create(&thread_id, NULL, Media_Process_Background, ctx); | 1738 Media_Process_Background, |
| 1712 | 1739 ctx, |
| 1713 if (thread_result != 0) | 1740 free) |
| 1741 : SEOBEO_WORKER_STOPPED; | |
| 1742 if (worker_result != SEOBEO_WORKER_OK) | |
| 1714 { | 1743 { |
| 1715 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: pthread_create failed with result=%d for media_id=%lld\n", thread_result, (long long)media_id); | 1744 Seobeo_Log( |
| 1745 SEOBEO_ERROR, | |
| 1746 "[MEDIA] Worker submission failed with result=%d for media_id=%lld\n", | |
| 1747 worker_result, | |
| 1748 (long long)media_id); | |
| 1716 free(ctx); | 1749 free(ctx); |
| 1750 const char *update_error = | |
| 1751 "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?"; | |
| 1752 const char *error_params[] = { | |
| 1753 "Media worker queue is unavailable", | |
| 1754 media_id_str, | |
| 1755 }; | |
| 1756 Deita_Query_Execute_Update_Prepared( | |
| 1757 g_db_connection, | |
| 1758 update_error, | |
| 1759 2, | |
| 1760 error_params); | |
| 1761 Dowa_HashMap_Push_Arena(resp, "status", "503", arena); | |
| 1762 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | |
| 1763 Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Media worker queue is unavailable\"}", arena); | |
| 1764 return resp; | |
| 1717 } | 1765 } |
| 1718 else | 1766 Seobeo_Log( |
| 1719 { | 1767 SEOBEO_INFO, |
| 1720 // Detach thread so it cleans up automatically when done | 1768 "[MEDIA] Submitted media_id=%lld to the worker pool\n", |
| 1721 pthread_detach(thread_id); | 1769 (long long)media_id); |
| 1722 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully spawned and detached thread for media_id=%lld\n", (long long)media_id); | |
| 1723 } | |
| 1724 } | 1770 } |
| 1725 else | 1771 else |
| 1726 { | 1772 { |
| 1727 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Non-image file, skipping background processing for media_id=%lld\n", (long long)media_id); | 1773 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Non-image file, skipping background processing for media_id=%lld\n", (long long)media_id); |
| 1728 } | 1774 } |
| 1950 } | 1996 } |
| 1951 | 1997 |
| 1952 // Initialize database | 1998 // Initialize database |
| 1953 init_database(); | 1999 init_database(); |
| 1954 | 2000 |
| 2001 g_media_worker_pool = Seobeo_Worker_Pool_Create(2, 16); | |
| 2002 if (!g_media_worker_pool) | |
| 2003 { | |
| 2004 Seobeo_Log( | |
| 2005 SEOBEO_ERROR, | |
| 2006 "[MEDIA] Unable to initialize the media worker pool\n"); | |
| 2007 } | |
| 2008 | |
| 1955 Seobeo_Router_Init(); | 2009 Seobeo_Router_Init(); |
| 1956 | 2010 |
| 1957 Seobeo_Router_Register("GET", "/", GetHomePage); | 2011 Seobeo_Router_Register("GET", "/", GetHomePage); |
| 1958 Seobeo_Router_Register("GET", "/index.html", GetRedirectHomePage); | 2012 Seobeo_Router_Register("GET", "/index.html", GetRedirectHomePage); |
| 1959 | 2013 |
| 2013 Seobeo_Log(SEOBEO_INFO, "WTF is going on\n"); | 2067 Seobeo_Log(SEOBEO_INFO, "WTF is going on\n"); |
| 2014 const char *server_port = getenv("MRJUNEJUNE_PORT"); | 2068 const char *server_port = getenv("MRJUNEJUNE_PORT"); |
| 2015 if (!server_port || server_port[0] == '\0') | 2069 if (!server_port || server_port[0] == '\0') |
| 2016 server_port = "6969"; | 2070 server_port = "6969"; |
| 2017 Seobeo_Web_Server_Start("mrjunejune/src", server_port, SEOBEO_MODE_EDGE, 4); | 2071 Seobeo_Web_Server_Start("mrjunejune/src", server_port, SEOBEO_MODE_EDGE, 4); |
| 2018 } | 2072 Seobeo_Worker_Pool_Destroy(g_media_worker_pool); |
| 2073 g_media_worker_pool = NULL; | |
| 2074 } |