#!/usr/bin/env php 6h) $now = time(); foreach (glob($tokDir.'/*.json') ?: [] as $f) { if (is_file($f) && ($now - @filemtime($f)) > 21600) @unlink($f); } foreach (glob($logDir.'/WORKER-*.log') ?: [] as $f) { if (is_file($f) && ($now - @filemtime($f)) > 21600) @unlink($f); } // Helpers to read/write the token file safely $job = json_decode((string)@file_get_contents($tokFile), true) ?: []; $save = function() use (&$job, $tokFile) { @file_put_contents($tokFile, json_encode($job, JSON_PRETTY_PRINT), LOCK_EX); @clearstatcache(true, $tokFile); }; $touchPhase = function(string $phase) use (&$job, $save) { $job['phase'] = $phase; $save(); }; // Init timing if (empty($job['startedAt'])) { $job['startedAt'] = time(); } $job['status'] = 'working'; $job['error'] = null; $save(); // Build the list of files to zip using the model (same validation FileRise uses) try { // Reuse FileModel’s validation by calling it but not keeping the zip; we’ll enumerate sizes here. $folder = (string)($job['folder'] ?? 'root'); $names = (array)($job['files'] ?? []); // Resolve folder path similarly to createZipArchive $baseDir = realpath(UPLOAD_DIR); if ($baseDir === false) { throw new RuntimeException('Uploads directory not configured correctly.'); } if (strtolower($folder) === 'root' || $folder === "") { $folderPathReal = $baseDir; } else { if (strpos($folder, '..') !== false) throw new RuntimeException('Invalid folder name.'); $parts = explode('/', trim($folder, "/\\ ")); foreach ($parts as $part) { if ($part === '' || !preg_match(REGEX_FOLDER_NAME, $part)) { throw new RuntimeException('Invalid folder name.'); } } $folderPath = rtrim(UPLOAD_DIR, '/\\') . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $parts); $folderPathReal = realpath($folderPath); if ($folderPathReal === false || strpos($folderPathReal, $baseDir) !== 0) { throw new RuntimeException('Folder not found.'); } } // Collect files (only regular files) $filesToZip = []; foreach ($names as $nm) { $bn = basename(trim((string)$nm)); if (!preg_match(REGEX_FILE_NAME, $bn)) continue; $fp = $folderPathReal . DIRECTORY_SEPARATOR . $bn; if (is_file($fp)) $filesToZip[] = $fp; } if (!$filesToZip) throw new RuntimeException('No valid files to zip.'); // Totals for progress $filesTotal = count($filesToZip); $bytesTotal = 0; foreach ($filesToZip as $fp) { $sz = @filesize($fp); if ($sz !== false) $bytesTotal += (int)$sz; } $job['filesTotal'] = $filesTotal; $job['bytesTotal'] = $bytesTotal; $job['filesDone'] = 0; $job['bytesDone'] = 0; $job['pct'] = 0; $job['current'] = null; $job['phase'] = 'zipping'; $save(); // Create final zip path in META_DIR/ziptmp $zipName = 'download-' . date('Ymd-His') . '-' . bin2hex(random_bytes(4)) . '.zip'; $zipPath = $root . DIRECTORY_SEPARATOR . $zipName; $zip = new ZipArchive(); if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { throw new RuntimeException('Could not create zip archive.'); } // Enumerate files; report up to 98% $bytesDone = 0; $filesDone = 0; foreach ($filesToZip as $fp) { $bn = basename($fp); $zip->addFile($fp, $bn); $filesDone++; $sz = @filesize($fp); if ($sz !== false) $bytesDone += (int)$sz; $job['filesDone'] = $filesDone; $job['bytesDone'] = $bytesDone; $job['current'] = $bn; $pct = ($bytesTotal > 0) ? (int) floor(($bytesDone / $bytesTotal) * 98) : 0; if ($pct < 0) $pct = 0; if ($pct > 98) $pct = 98; if ($pct > (int)($job['pct'] ?? 0)) $job['pct'] = $pct; $save(); } // Finalizing (this is where libzip writes & renames) $job['pct'] = max((int)($job['pct'] ?? 0), 99); $job['phase'] = 'finalizing'; $job['finalizeAt'] = time(); // Publish selected totals for a truthful UI during finalizing, // and clear incremental fields so the UI doesn't show "7/7 14 GB / 14 GB" prematurely. $job['selectedFiles'] = $filesTotal; $job['selectedBytes'] = $bytesTotal; $job['filesDone'] = null; $job['bytesDone'] = null; $job['current'] = null; $save(); // ---- finalize the zip on disk ---- $ok = $zip->close(); $statusStr = method_exists($zip, 'getStatusString') ? $zip->getStatusString() : ''; if (!$ok || !is_file($zipPath)) { $job['status'] = 'error'; $job['error'] = 'Failed to finalize ZIP' . ($statusStr ? " ($statusStr)" : ''); $save(); file_put_contents($logFile, "[".date('c')."] error: ".$job['error']."\n", FILE_APPEND); exit(0); } $job['status'] = 'done'; $job['zipPath'] = $zipPath; $job['pct'] = 100; $job['phase'] = 'finalized'; $save(); file_put_contents($logFile, "[".date('c')."] done zip={$zipPath}\n", FILE_APPEND); } catch (Throwable $e) { $job['status'] = 'error'; $job['error'] = 'Worker exception: '.$e->getMessage(); $save(); file_put_contents($logFile, "[".date('c')."] exception: ".$e->getMessage()."\n", FILE_APPEND); }