Mercurial
comparison mrjunejune/main.c @ 202:b9b184b3303c
[Notes] Images get processed and it is properly fetched. Thank you.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Sun, 15 Feb 2026 09:12:57 -0800 |
| parents | 6cdee35a7ba9 |
| children | e5aed6c36672 |
comparison
equal
deleted
inserted
replaced
| 201:6cdee35a7ba9 | 202:b9b184b3303c |
|---|---|
| 19 int64 media_id; | 19 int64 media_id; |
| 20 char s3_key_original[512]; | 20 char s3_key_original[512]; |
| 21 char s3_key_processed[512]; | 21 char s3_key_processed[512]; |
| 22 char content_type[128]; | 22 char content_type[128]; |
| 23 char access_token[256]; | 23 char access_token[256]; |
| 24 char db_path[256]; | |
| 24 S3_Config s3_config; | 25 S3_Config s3_config; |
| 25 } Media_Processing_Context; | 26 } Media_Processing_Context; |
| 26 | 27 |
| 27 // Server configuration (loaded from .config) | 28 // Server configuration (loaded from .config) |
| 28 static char g_upload_auth_token[256] = {0}; | 29 static char g_upload_auth_token[256] = {0}; |
| 666 Seobeo_Render_Html_FilePath(final_body, "/talk/index.html", arena); | 667 Seobeo_Render_Html_FilePath(final_body, "/talk/index.html", arena); |
| 667 Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); | 668 Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); |
| 668 return resp; | 669 return resp; |
| 669 } | 670 } |
| 670 | 671 |
| 671 Seobeo_Request_Entry *GetEditor(Seobeo_Request_Entry *req, Dowa_Arena *arena) | |
| 672 { | |
| 673 Seobeo_Request_Entry *resp = NULL; | |
| 674 char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024); | |
| 675 Seobeo_Render_Html_FilePath(final_body, "/editor/index.html", arena); | |
| 676 Dowa_HashMap_Push_Arena(resp, "body", final_body, arena); | |
| 677 return resp; | |
| 678 } | |
| 679 | |
| 680 Seobeo_Request_Entry *GetNotesLogin(Seobeo_Request_Entry *req, Dowa_Arena *arena) | 672 Seobeo_Request_Entry *GetNotesLogin(Seobeo_Request_Entry *req, Dowa_Arena *arena) |
| 681 { | 673 { |
| 682 Seobeo_Request_Entry *resp = NULL; | 674 Seobeo_Request_Entry *resp = NULL; |
| 683 char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024); | 675 char *final_body = Dowa_Arena_Allocate(arena, 50 * 1024); |
| 684 Seobeo_Render_Html_FilePath(final_body, "/notes/login.html", arena); | 676 Seobeo_Render_Html_FilePath(final_body, "/notes/login.html", arena); |
| 1266 // Background thread function for media processing | 1258 // Background thread function for media processing |
| 1267 void *Media_Process_Background(void *arg) | 1259 void *Media_Process_Background(void *arg) |
| 1268 { | 1260 { |
| 1269 Media_Processing_Context *ctx = (Media_Processing_Context *)arg; | 1261 Media_Processing_Context *ctx = (Media_Processing_Context *)arg; |
| 1270 | 1262 |
| 1263 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Background thread started for media_id=%lld\n", (long long)ctx->media_id); | |
| 1264 Seobeo_Log(SEOBEO_INFO, "[MEDIA] S3 key original: %s\n", ctx->s3_key_original); | |
| 1265 Seobeo_Log(SEOBEO_INFO, "[MEDIA] S3 key processed: %s\n", ctx->s3_key_processed); | |
| 1266 Seobeo_Log(SEOBEO_INFO, "[MEDIA] DB path: %s\n", ctx->db_path); | |
| 1267 | |
| 1271 // Open thread-local DB connection | 1268 // Open thread-local DB connection |
| 1272 Deita_Connection *db_conn = Deita_Connection_Create(DEITA_DATABASE_TYPE_SQLITE3, g_db_path); | 1269 Deita_Connection *db_conn = Deita_Connection_Create(DEITA_DATABASE_TYPE_SQLITE3, ctx->db_path); |
| 1273 if (!db_conn || !Deita_Connection_Is_Open(db_conn)) | 1270 if (!db_conn || !Deita_Connection_Is_Open(db_conn)) |
| 1274 { | 1271 { |
| 1275 printf("[MEDIA] Thread ERROR: Failed to open database for media_id=%lld\n", (long long)ctx->media_id); | 1272 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] Thread ERROR: Failed to open database for media_id=%lld\n", (long long)ctx->media_id); |
| 1276 free(ctx); | 1273 free(ctx); |
| 1277 return NULL; | 1274 return NULL; |
| 1278 } | 1275 } |
| 1279 | 1276 |
| 1280 // Update status to 'processing' | 1277 // Update status to 'processing' |
| 1281 const char *update_processing = | 1278 const char *update_processing = |
| 1282 "UPDATE media_uploads SET status='processing', updated_at=strftime('%s','now') WHERE id=?"; | 1279 "UPDATE media_uploads SET status='processing', updated_at=strftime('%s','now') WHERE id=?"; |
| 1283 char media_id_str[32]; | 1280 char media_id_str[32]; |
| 1284 snprintf(media_id_str, sizeof(media_id_str), "%lld", (long long)ctx->media_id); | 1281 snprintf(media_id_str, sizeof(media_id_str), "%lld", (long long)ctx->media_id); |
| 1285 const char *params[] = { media_id_str }; | 1282 const char *params[] = { media_id_str }; |
| 1286 Deita_Query_Execute_Update_Prepared(db_conn, update_processing, 1, params); | 1283 int32 update_result = Deita_Query_Execute_Update_Prepared(db_conn, update_processing, 1, params); |
| 1287 | 1284 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Updated status to 'processing' for media_id=%lld (result=%d)\n", (long long)ctx->media_id, update_result); |
| 1288 printf("[MEDIA] Processing media_id=%lld\n", (long long)ctx->media_id); | |
| 1289 | 1285 |
| 1290 // Generate presigned GET URL for download (10 min expiry) | 1286 // Generate presigned GET URL for download (10 min expiry) |
| 1287 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Generating presigned GET URL for media_id=%lld\n", (long long)ctx->media_id); | |
| 1291 S3_Presigned_URL download_url = S3_Presign_Get(&ctx->s3_config, ctx->s3_key_original, 600); | 1288 S3_Presigned_URL download_url = S3_Presign_Get(&ctx->s3_config, ctx->s3_key_original, 600); |
| 1292 if (!download_url.success) | 1289 if (!download_url.success) |
| 1293 { | 1290 { |
| 1291 const char *error_msg = download_url.error_message ? download_url.error_message : "Failed to generate download URL"; | |
| 1292 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: Failed to generate download URL for media_id=%lld: %s\n", | |
| 1293 (long long)ctx->media_id, error_msg); | |
| 1294 const char *update_error = | 1294 const char *update_error = |
| 1295 "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?"; | 1295 "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?"; |
| 1296 const char *error_params[] = { "Failed to generate download URL", media_id_str }; | 1296 const char *error_params[] = { error_msg, media_id_str }; |
| 1297 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); | 1297 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); |
| 1298 printf("[MEDIA] ERROR: Failed to generate download URL for media_id=%lld\n", (long long)ctx->media_id); | |
| 1299 S3_Presigned_URL_Destroy(&download_url); | 1298 S3_Presigned_URL_Destroy(&download_url); |
| 1300 Deita_Connection_Close(db_conn); | 1299 Deita_Connection_Close(db_conn); |
| 1301 free(ctx); | 1300 free(ctx); |
| 1302 return NULL; | 1301 return NULL; |
| 1303 } | 1302 } |
| 1303 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Generated presigned URL: %.100s...\n", download_url.url); | |
| 1304 | 1304 |
| 1305 // Generate temp file paths | 1305 // Generate temp file paths |
| 1306 char tmp_input[256]; | 1306 char tmp_input[256]; |
| 1307 char tmp_output[256]; | 1307 char tmp_output[256]; |
| 1308 char *uuid_input = malloc(UUID_LEN); | 1308 char *uuid_input = malloc(UUID_LEN); |
| 1315 snprintf(tmp_output, sizeof(tmp_output), "/tmp/%s.webp", uuid_output); | 1315 snprintf(tmp_output, sizeof(tmp_output), "/tmp/%s.webp", uuid_output); |
| 1316 free(uuid_input); | 1316 free(uuid_input); |
| 1317 free(uuid_output); | 1317 free(uuid_output); |
| 1318 | 1318 |
| 1319 // Download from S3 | 1319 // Download from S3 |
| 1320 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Downloading from S3 to %s for media_id=%lld\n", tmp_input, (long long)ctx->media_id); | |
| 1320 Seobeo_Client_Request *download_req = Seobeo_Client_Request_Create(download_url.url); | 1321 Seobeo_Client_Request *download_req = Seobeo_Client_Request_Create(download_url.url); |
| 1321 Seobeo_Client_Request_Set_Download_Path(download_req, tmp_input); | 1322 Seobeo_Client_Request_Set_Download_Path(download_req, tmp_input); |
| 1322 Seobeo_Client_Response *download_resp = Seobeo_Client_Request_Execute(download_req); | 1323 Seobeo_Client_Response *download_resp = Seobeo_Client_Request_Execute(download_req); |
| 1323 | 1324 |
| 1324 S3_Presigned_URL_Destroy(&download_url); | 1325 S3_Presigned_URL_Destroy(&download_url); |
| 1325 | 1326 |
| 1326 if (!download_resp || download_resp->status_code != 200) | 1327 if (!download_resp || download_resp->status_code != 200) |
| 1327 { | 1328 { |
| 1329 int status = download_resp ? download_resp->status_code : 0; | |
| 1330 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: Failed to download from S3 for media_id=%lld (status=%d)\n", | |
| 1331 (long long)ctx->media_id, status); | |
| 1328 const char *update_error = | 1332 const char *update_error = |
| 1329 "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?"; | 1333 "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?"; |
| 1330 const char *error_params[] = { "Failed to download from S3", media_id_str }; | 1334 const char *error_params[] = { "Failed to download from S3", media_id_str }; |
| 1331 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); | 1335 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); |
| 1332 printf("[MEDIA] ERROR: Failed to download from S3 for media_id=%lld\n", (long long)ctx->media_id); | |
| 1333 if (download_req) Seobeo_Client_Request_Destroy(download_req); | 1336 if (download_req) Seobeo_Client_Request_Destroy(download_req); |
| 1334 if (download_resp) Seobeo_Client_Response_Destroy(download_resp); | 1337 if (download_resp) Seobeo_Client_Response_Destroy(download_resp); |
| 1335 unlink(tmp_input); | 1338 unlink(tmp_input); |
| 1336 Deita_Connection_Close(db_conn); | 1339 Deita_Connection_Close(db_conn); |
| 1337 free(ctx); | 1340 free(ctx); |
| 1338 return NULL; | 1341 return NULL; |
| 1339 } | 1342 } |
| 1340 | 1343 |
| 1344 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully downloaded file to %s\n", tmp_input); | |
| 1341 Seobeo_Client_Request_Destroy(download_req); | 1345 Seobeo_Client_Request_Destroy(download_req); |
| 1342 Seobeo_Client_Response_Destroy(download_resp); | 1346 Seobeo_Client_Response_Destroy(download_resp); |
| 1343 | 1347 |
| 1344 // Convert to webp using FFmpeg | 1348 // Convert to webp using FFmpeg |
| 1345 char cmd[1024]; | 1349 char cmd[1024]; |
| 1346 char log_file[256]; | 1350 char log_file[256]; |
| 1347 snprintf(log_file, sizeof(log_file), "/tmp/ffmpeg_%lld.log", (long long)ctx->media_id); | 1351 snprintf(log_file, sizeof(log_file), "/tmp/ffmpeg_%lld.log", (long long)ctx->media_id); |
| 1348 snprintf(cmd, sizeof(cmd), "ffmpeg -y -i %s -quality 80 %s 2>%s", | 1352 snprintf(cmd, sizeof(cmd), "ffmpeg -y -i %s -quality 80 %s 2>%s", |
| 1349 tmp_input, tmp_output, log_file); | 1353 tmp_input, tmp_output, log_file); |
| 1350 | 1354 |
| 1355 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Running FFmpeg: %s\n", cmd); | |
| 1351 int ffmpeg_result = system(cmd); | 1356 int ffmpeg_result = system(cmd); |
| 1357 Seobeo_Log(SEOBEO_INFO, "[MEDIA] FFmpeg result: %d for media_id=%lld\n", ffmpeg_result, (long long)ctx->media_id); | |
| 1358 | |
| 1352 if (ffmpeg_result != 0) | 1359 if (ffmpeg_result != 0) |
| 1353 { | 1360 { |
| 1361 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: FFmpeg conversion failed for media_id=%lld (exit code %d). Check log: %s\n", | |
| 1362 (long long)ctx->media_id, ffmpeg_result, log_file); | |
| 1354 const char *update_error = | 1363 const char *update_error = |
| 1355 "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?"; | 1364 "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?"; |
| 1356 const char *error_params[] = { "Image conversion failed", media_id_str }; | 1365 const char *error_params[] = { "Image conversion failed", media_id_str }; |
| 1357 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); | 1366 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); |
| 1358 printf("[MEDIA] ERROR: FFmpeg conversion failed for media_id=%lld\n", (long long)ctx->media_id); | |
| 1359 unlink(tmp_input); | 1367 unlink(tmp_input); |
| 1360 unlink(tmp_output); | 1368 unlink(tmp_output); |
| 1361 Deita_Connection_Close(db_conn); | 1369 Deita_Connection_Close(db_conn); |
| 1362 free(ctx); | 1370 free(ctx); |
| 1363 return NULL; | 1371 return NULL; |
| 1364 } | 1372 } |
| 1373 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully converted to webp: %s\n", tmp_output); | |
| 1365 | 1374 |
| 1366 // Upload processed file to S3 | 1375 // Upload processed file to S3 |
| 1376 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Uploading processed file to S3: %s -> %s\n", tmp_output, ctx->s3_key_processed); | |
| 1367 S3_Result upload_result = S3_Upload_File_With_Content_Type( | 1377 S3_Result upload_result = S3_Upload_File_With_Content_Type( |
| 1368 &ctx->s3_config, tmp_output, ctx->s3_key_processed, "image/webp"); | 1378 &ctx->s3_config, tmp_output, ctx->s3_key_processed, "image/webp"); |
| 1369 | 1379 |
| 1370 if (!upload_result.success) | 1380 if (!upload_result.success) |
| 1371 { | 1381 { |
| 1382 const char *error_msg = upload_result.error_message ? upload_result.error_message : "Failed to upload processed file"; | |
| 1383 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: Failed to upload processed file for media_id=%lld: %s\n", | |
| 1384 (long long)ctx->media_id, error_msg); | |
| 1372 const char *update_error = | 1385 const char *update_error = |
| 1373 "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?"; | 1386 "UPDATE media_uploads SET status='error', error_message=?, updated_at=strftime('%s','now') WHERE id=?"; |
| 1374 const char *error_params[] = { "Failed to upload processed file", media_id_str }; | 1387 const char *error_params[] = { error_msg, media_id_str }; |
| 1375 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); | 1388 Deita_Query_Execute_Update_Prepared(db_conn, update_error, 2, error_params); |
| 1376 printf("[MEDIA] ERROR: Failed to upload processed file for media_id=%lld\n", (long long)ctx->media_id); | |
| 1377 unlink(tmp_input); | 1389 unlink(tmp_input); |
| 1378 unlink(tmp_output); | 1390 unlink(tmp_output); |
| 1379 Deita_Connection_Close(db_conn); | 1391 Deita_Connection_Close(db_conn); |
| 1380 free(ctx); | 1392 free(ctx); |
| 1381 return NULL; | 1393 return NULL; |
| 1382 } | 1394 } |
| 1383 | 1395 |
| 1396 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully uploaded processed file to S3\n"); | |
| 1397 | |
| 1384 // Update status to 'finished' | 1398 // Update status to 'finished' |
| 1385 const char *update_finished = | 1399 const char *update_finished = |
| 1386 "UPDATE media_uploads SET status='finished', updated_at=strftime('%s','now') WHERE id=?"; | 1400 "UPDATE media_uploads SET status='finished', updated_at=strftime('%s','now') WHERE id=?"; |
| 1387 Deita_Query_Execute_Update_Prepared(db_conn, update_finished, 1, params); | 1401 Deita_Query_Execute_Update_Prepared(db_conn, update_finished, 1, params); |
| 1388 | 1402 |
| 1389 printf("[MEDIA] Successfully processed media_id=%lld\n", (long long)ctx->media_id); | 1403 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully processed media_id=%lld - COMPLETE\n", (long long)ctx->media_id); |
| 1390 | 1404 |
| 1391 // Cleanup | 1405 // Cleanup |
| 1392 unlink(tmp_input); | 1406 unlink(tmp_input); |
| 1393 unlink(tmp_output); | 1407 unlink(tmp_output); |
| 1394 Deita_Connection_Close(db_conn); | 1408 Deita_Connection_Close(db_conn); |
| 1498 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | 1512 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); |
| 1499 Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Failed to update status\"}", arena); | 1513 Dowa_HashMap_Push_Arena(resp, "body", "{\"error\":\"Failed to update status\"}", arena); |
| 1500 return resp; | 1514 return resp; |
| 1501 } | 1515 } |
| 1502 | 1516 |
| 1517 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Content type for media_id=%lld: '%s'\n", (long long)media_id, content_type_copy); | |
| 1518 | |
| 1503 // If content_type starts with "image/", spawn background processing thread | 1519 // If content_type starts with "image/", spawn background processing thread |
| 1504 if (strncmp(content_type_copy, "image/", 6) == 0) | 1520 if (strncmp(content_type_copy, "image/", 6) == 0) |
| 1505 { | 1521 { |
| 1522 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Detected image type, preparing to spawn background thread for media_id=%lld\n", (long long)media_id); | |
| 1523 | |
| 1506 // Create context for background thread (heap allocated) | 1524 // Create context for background thread (heap allocated) |
| 1507 Media_Processing_Context *ctx = malloc(sizeof(Media_Processing_Context)); | 1525 Media_Processing_Context *ctx = malloc(sizeof(Media_Processing_Context)); |
| 1508 ctx->media_id = media_id; | 1526 ctx->media_id = media_id; |
| 1509 strncpy(ctx->s3_key_original, s3_key_original_copy, sizeof(ctx->s3_key_original) - 1); | 1527 strncpy(ctx->s3_key_original, s3_key_original_copy, sizeof(ctx->s3_key_original) - 1); |
| 1510 strncpy(ctx->s3_key_processed, s3_key_processed_copy, sizeof(ctx->s3_key_processed) - 1); | 1528 strncpy(ctx->s3_key_processed, s3_key_processed_copy, sizeof(ctx->s3_key_processed) - 1); |
| 1511 strncpy(ctx->content_type, content_type_copy, sizeof(ctx->content_type) - 1); | 1529 strncpy(ctx->content_type, content_type_copy, sizeof(ctx->content_type) - 1); |
| 1512 strncpy(ctx->access_token, token, sizeof(ctx->access_token) - 1); | 1530 strncpy(ctx->access_token, token, sizeof(ctx->access_token) - 1); |
| 1531 strncpy(ctx->db_path, g_db_path, sizeof(ctx->db_path) - 1); | |
| 1513 ctx->s3_key_original[sizeof(ctx->s3_key_original) - 1] = '\0'; | 1532 ctx->s3_key_original[sizeof(ctx->s3_key_original) - 1] = '\0'; |
| 1514 ctx->s3_key_processed[sizeof(ctx->s3_key_processed) - 1] = '\0'; | 1533 ctx->s3_key_processed[sizeof(ctx->s3_key_processed) - 1] = '\0'; |
| 1515 ctx->content_type[sizeof(ctx->content_type) - 1] = '\0'; | 1534 ctx->content_type[sizeof(ctx->content_type) - 1] = '\0'; |
| 1516 ctx->access_token[sizeof(ctx->access_token) - 1] = '\0'; | 1535 ctx->access_token[sizeof(ctx->access_token) - 1] = '\0'; |
| 1536 ctx->db_path[sizeof(ctx->db_path) - 1] = '\0'; | |
| 1517 ctx->s3_config = g_s3_config; | 1537 ctx->s3_config = g_s3_config; |
| 1538 | |
| 1539 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Creating pthread for media_id=%lld\n", (long long)media_id); | |
| 1518 | 1540 |
| 1519 // Spawn detached thread | 1541 // Spawn detached thread |
| 1520 pthread_t thread_id; | 1542 pthread_t thread_id; |
| 1521 int thread_result = pthread_create(&thread_id, NULL, Media_Process_Background, ctx); | 1543 int thread_result = pthread_create(&thread_id, NULL, Media_Process_Background, ctx); |
| 1522 | 1544 |
| 1523 if (thread_result != 0) | 1545 if (thread_result != 0) |
| 1524 { | 1546 { |
| 1525 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: Failed to spawn processing thread for media_id=%lld\n", (long long)media_id); | 1547 Seobeo_Log(SEOBEO_ERROR, "[MEDIA] ERROR: pthread_create failed with result=%d for media_id=%lld\n", thread_result, (long long)media_id); |
| 1526 free(ctx); | 1548 free(ctx); |
| 1527 } | 1549 } |
| 1528 else | 1550 else |
| 1529 { | 1551 { |
| 1530 // Detach thread so it cleans up automatically when done | 1552 // Detach thread so it cleans up automatically when done |
| 1531 pthread_detach(thread_id); | 1553 pthread_detach(thread_id); |
| 1532 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Spawned processing thread for media_id=%lld\n", (long long)media_id); | 1554 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Successfully spawned and detached thread for media_id=%lld\n", (long long)media_id); |
| 1533 } | 1555 } |
| 1556 } | |
| 1557 else | |
| 1558 { | |
| 1559 Seobeo_Log(SEOBEO_INFO, "[MEDIA] Non-image file, skipping background processing for media_id=%lld\n", (long long)media_id); | |
| 1534 } | 1560 } |
| 1535 | 1561 |
| 1536 Dowa_HashMap_Push_Arena(resp, "status", "200", arena); | 1562 Dowa_HashMap_Push_Arena(resp, "status", "200", arena); |
| 1537 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); | 1563 Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena); |
| 1538 Dowa_HashMap_Push_Arena(resp, "body", "{\"success\":true,\"status\":\"uploaded\"}", arena); | 1564 Dowa_HashMap_Push_Arena(resp, "body", "{\"success\":true,\"status\":\"uploaded\"}", arena); |
| 1598 | 1624 |
| 1599 const char *media_id_str = ((Seobeo_Request_Entry*)id_kv)->value; | 1625 const char *media_id_str = ((Seobeo_Request_Entry*)id_kv)->value; |
| 1600 | 1626 |
| 1601 // Query media status | 1627 // Query media status |
| 1602 const char *select_query = | 1628 const char *select_query = |
| 1603 "SELECT id, status, s3_key_processed, error_message FROM media_uploads WHERE id = ? AND access_token = ?"; | 1629 "SELECT id, status, s3_key_original, s3_key_processed, error_message FROM media_uploads WHERE id = ? AND access_token = ?"; |
| 1604 const char *select_params[] = { media_id_str, token }; | 1630 const char *select_params[] = { media_id_str, token }; |
| 1605 | 1631 |
| 1606 Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(g_db_connection, select_query, 2, select_params, arena); | 1632 Deita_Result_Set *p_result = Deita_Query_Execute_Prepared(g_db_connection, select_query, 2, select_params, arena); |
| 1607 | 1633 |
| 1608 if (!p_result || !Deita_Result_Set_Next(p_result)) | 1634 if (!p_result || !Deita_Result_Set_Next(p_result)) |
| 1614 return resp; | 1640 return resp; |
| 1615 } | 1641 } |
| 1616 | 1642 |
| 1617 int64 id = Deita_Result_Set_Get_Integer(p_result, 0); | 1643 int64 id = Deita_Result_Set_Get_Integer(p_result, 0); |
| 1618 const char *status = Deita_Result_Set_Get_Text(p_result, 1); | 1644 const char *status = Deita_Result_Set_Get_Text(p_result, 1); |
| 1619 const char *s3_key_processed = Deita_Result_Set_Get_Text(p_result, 2); | 1645 const char *s3_key_original = Deita_Result_Set_Get_Text(p_result, 2); |
| 1620 const char *error_message = Deita_Result_Set_Get_Text(p_result, 3); | 1646 const char *s3_key_processed = Deita_Result_Set_Get_Text(p_result, 3); |
| 1621 | 1647 const char *error_message = Deita_Result_Set_Get_Text(p_result, 4); |
| 1622 // Build CloudFront URL if status is 'finished' and s3_key_processed exists | 1648 |
| 1649 // Build CloudFront URL for processed file if status is 'finished' | |
| 1623 char processed_url[1024] = {0}; | 1650 char processed_url[1024] = {0}; |
| 1624 if (strcmp(status, "finished") == 0 && s3_key_processed && strlen(s3_key_processed) > 0) | 1651 if (strcmp(status, "finished") == 0 && s3_key_processed && strlen(s3_key_processed) > 0) |
| 1625 { | 1652 { |
| 1626 if (g_s3_cloudfront_url[0]) | 1653 if (g_s3_cloudfront_url[0]) |
| 1627 { | 1654 { |
| 1632 snprintf(processed_url, sizeof(processed_url), "https://%s.s3.%s.amazonaws.com/%s", | 1659 snprintf(processed_url, sizeof(processed_url), "https://%s.s3.%s.amazonaws.com/%s", |
| 1633 g_s3_bucket, g_s3_region, s3_key_processed); | 1660 g_s3_bucket, g_s3_region, s3_key_processed); |
| 1634 } | 1661 } |
| 1635 } | 1662 } |
| 1636 | 1663 |
| 1637 // Build JSON response | 1664 // Build CloudFront URL for original file (for non-images or before processing completes) |
| 1638 char *response_body = Dowa_Arena_Allocate(arena, 2048); | 1665 char original_url[1024] = {0}; |
| 1666 if (s3_key_original && strlen(s3_key_original) > 0) | |
| 1667 { | |
| 1668 if (g_s3_cloudfront_url[0]) | |
| 1669 { | |
| 1670 snprintf(original_url, sizeof(original_url), "%s/%s", g_s3_cloudfront_url, s3_key_original); | |
| 1671 } | |
| 1672 else | |
| 1673 { | |
| 1674 snprintf(original_url, sizeof(original_url), "https://%s.s3.%s.amazonaws.com/%s", | |
| 1675 g_s3_bucket, g_s3_region, s3_key_original); | |
| 1676 } | |
| 1677 } | |
| 1678 | |
| 1679 // Build JSON response with both processed_url and original_url | |
| 1680 char *response_body = Dowa_Arena_Allocate(arena, 3072); | |
| 1681 | |
| 1682 // Build the base response | |
| 1683 int offset = snprintf(response_body, 3072, | |
| 1684 "{\"id\":%lld,\"status\":\"%s\",", | |
| 1685 (long long)id, status); | |
| 1686 | |
| 1687 // Add processed_url | |
| 1639 if (strlen(processed_url) > 0) | 1688 if (strlen(processed_url) > 0) |
| 1640 { | 1689 { |
| 1641 snprintf(response_body, 2048, | 1690 offset += snprintf(response_body + offset, 3072 - offset, |
| 1642 "{\"id\":%lld,\"status\":\"%s\",\"processed_url\":\"%s\",\"error_message\":%s}", | 1691 "\"processed_url\":\"%s\",", processed_url); |
| 1643 (long long)id, status, processed_url, | |
| 1644 error_message ? "\"" : "null"); | |
| 1645 if (error_message) | |
| 1646 { | |
| 1647 // Append error message if exists | |
| 1648 size_t len = strlen(response_body); | |
| 1649 snprintf(response_body + len - 1, 2048 - len + 1, "%s\"}", error_message); | |
| 1650 } | |
| 1651 } | 1692 } |
| 1652 else | 1693 else |
| 1653 { | 1694 { |
| 1654 snprintf(response_body, 2048, | 1695 offset += snprintf(response_body + offset, 3072 - offset, |
| 1655 "{\"id\":%lld,\"status\":\"%s\",\"processed_url\":null,\"error_message\":%s}", | 1696 "\"processed_url\":null,"); |
| 1656 (long long)id, status, | 1697 } |
| 1657 error_message ? "\"" : "null"); | 1698 |
| 1658 if (error_message) | 1699 // Add original_url |
| 1659 { | 1700 if (strlen(original_url) > 0) |
| 1660 size_t len = strlen(response_body); | 1701 { |
| 1661 snprintf(response_body + len - 1, 2048 - len + 1, "%s\"}", error_message); | 1702 offset += snprintf(response_body + offset, 3072 - offset, |
| 1662 } | 1703 "\"original_url\":\"%s\",", original_url); |
| 1704 } | |
| 1705 else | |
| 1706 { | |
| 1707 offset += snprintf(response_body + offset, 3072 - offset, | |
| 1708 "\"original_url\":null,"); | |
| 1709 } | |
| 1710 | |
| 1711 // Add error_message | |
| 1712 if (error_message && strlen(error_message) > 0) | |
| 1713 { | |
| 1714 snprintf(response_body + offset, 3072 - offset, | |
| 1715 "\"error_message\":\"%s\"}", error_message); | |
| 1716 } | |
| 1717 else | |
| 1718 { | |
| 1719 snprintf(response_body + offset, 3072 - offset, | |
| 1720 "\"error_message\":null}"); | |
| 1663 } | 1721 } |
| 1664 | 1722 |
| 1665 Deita_Result_Set_Free(p_result); | 1723 Deita_Result_Set_Free(p_result); |
| 1666 | 1724 |
| 1667 Dowa_HashMap_Push_Arena(resp, "status", "200", arena); | 1725 Dowa_HashMap_Push_Arena(resp, "status", "200", arena); |
| 1713 g_s3_config.use_path_style = FALSE; | 1771 g_s3_config.use_path_style = FALSE; |
| 1714 | 1772 |
| 1715 printf("[S3] Configured: region=%s, bucket=%s, key=%s...\n", | 1773 printf("[S3] Configured: region=%s, bucket=%s, key=%s...\n", |
| 1716 g_s3_region, g_s3_bucket, s3_access_key[0] ? "***" : "(missing)"); | 1774 g_s3_region, g_s3_bucket, s3_access_key[0] ? "***" : "(missing)"); |
| 1717 | 1775 |
| 1776 // Show current working directory | |
| 1777 char cwd[1024]; | |
| 1778 if (getcwd(cwd, sizeof(cwd)) != NULL) | |
| 1779 { | |
| 1780 printf("[STARTUP] Current working directory: %s\n", cwd); | |
| 1781 printf("[STARTUP] Database path (relative): %s\n", g_db_path); | |
| 1782 } | |
| 1783 | |
| 1718 // Initialize database | 1784 // Initialize database |
| 1719 init_database(); | 1785 init_database(); |
| 1720 | 1786 |
| 1721 Seobeo_Router_Init(); | 1787 Seobeo_Router_Init(); |
| 1722 | 1788 |
| 1757 Seobeo_Router_Register("GET", "/blog/:blog_id", RenderBlog); | 1823 Seobeo_Router_Register("GET", "/blog/:blog_id", RenderBlog); |
| 1758 | 1824 |
| 1759 // -- Talk --/ | 1825 // -- Talk --/ |
| 1760 Seobeo_Router_Register("GET", "/talk", GetTalk); | 1826 Seobeo_Router_Register("GET", "/talk", GetTalk); |
| 1761 Seobeo_Router_Register("GET", "/talk/index.html", GetRedirectTalk); | 1827 Seobeo_Router_Register("GET", "/talk/index.html", GetRedirectTalk); |
| 1762 | |
| 1763 // -- Editor (legacy) --/ | |
| 1764 Seobeo_Router_Register("GET", "/editor", GetEditor); | |
| 1765 Seobeo_Router_Register("GET", "/editor/index.html", GetRedirectEditor); | |
| 1766 | 1828 |
| 1767 // -- Notes --/ | 1829 // -- Notes --/ |
| 1768 Seobeo_Router_Register("GET", "/notes", GetNotes); | 1830 Seobeo_Router_Register("GET", "/notes", GetNotes); |
| 1769 Seobeo_Router_Register("GET", "/notes/", GetNotes); | 1831 Seobeo_Router_Register("GET", "/notes/", GetNotes); |
| 1770 Seobeo_Router_Register("GET", "/notes/index.html", GetNotes); | 1832 Seobeo_Router_Register("GET", "/notes/index.html", GetNotes); |