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