archive-api.php 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. <?php
  2. /**
  3. * Admin JSON API for building a gallery's ZIP archive on demand.
  4. *
  5. * The archive normally rebuilds itself in the background (worker.php), but the
  6. * photographer sometimes wants it *now* — before sending the link out — and
  7. * wants to watch it happen. This drives the same archive_run_slice() the worker
  8. * uses, one slice per request, with assets/archive.js looping until it reports
  9. * finished. Every request therefore stays a few seconds under the host's 60 s
  10. * cap no matter how large the gallery is.
  11. *
  12. * Fields: slug, action (start | step | cancel | delete).
  13. */
  14. require dirname(__DIR__) . '/app/bootstrap.php';
  15. if (!auth_check()) {
  16. json_response(['error' => 'Not authenticated'], 401);
  17. }
  18. if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
  19. json_response(['error' => 'POST only'], 405);
  20. }
  21. csrf_verify();
  22. // Release the session lock before the slow S3 leg, exactly as the uploader
  23. // does: PHP holds it for the whole request, and the admin would otherwise not
  24. // be able to load another page while a build is running.
  25. session_write_close();
  26. @set_time_limit(0); // honoured on some hosts; the slice budget is the real cap
  27. $slug = (string)($_POST['slug'] ?? '');
  28. $gallery = gallery_load($slug);
  29. if ($gallery === null) {
  30. json_response(['error' => 'Unknown gallery'], 404);
  31. }
  32. $startedAt = microtime(true);
  33. // One build at a time site-wide, shared with the background worker — otherwise
  34. // a manual rebuild and a background one would fight over the same state file.
  35. // Wait a little rather than failing instantly: the worker holds the lock through
  36. // short sleeps while a gallery settles, and an admin who asked for this should
  37. // not lose a race to one of them.
  38. $lock = archive_lock(15);
  39. if ($lock === null) {
  40. json_response(['error' => 'A background rebuild is running. It will finish on its own — or try again in a moment.'], 409);
  41. }
  42. // Whatever the wait above cost comes out of the slice, so the whole request
  43. // still fits inside one step_seconds and cannot drift towards the 60 s cap.
  44. $budget = max(5.0, (float)config('archive.step_seconds', 25) - (microtime(true) - $startedAt));
  45. switch ((string)($_POST['action'] ?? '')) {
  46. case 'start':
  47. archive_abort($slug); // discard any half-finished attempt
  48. json_response(archive_run_slice($slug, $budget));
  49. case 'step':
  50. json_response(archive_run_slice($slug, $budget));
  51. case 'cancel':
  52. archive_abort($slug);
  53. json_response(['ok' => true]);
  54. case 'delete':
  55. archive_delete($slug);
  56. json_response(['ok' => true]);
  57. default:
  58. json_response(['error' => 'Unknown action'], 400);
  59. }