mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-08-10 07:11:18 +02:00
Two critical fixes: 1. Relink flow no longer destroys/recreates FilePond instances: The relink (XamxamRelinkFile) and PeerTube relink (XamxamRelinkPeerTube) previously refreshed the entire fichiers fragment via HTMX after pond.addFile(). This triggered destroyFilePondsIn on ALL pools, which could fire server.remove callbacks and move existing files to corbeille. Now just closes the modal — the file is already added to the pool in-place, and syncOrderInput creates the hidden form input. 2. Cleanup page « Corbeille (restaurable) » now actually shows files: _cleanup-stats-data.php previously classified trash files by checking if the thesis_files DB row still existed. But both deleteThesisFileToTrash and FilepondHandler::handleRemove DELETE the DB row. So ALL trash files appeared as `stale` (not restorable). Now uses the JSON sidecar file presence as the classification criterion — if the sidecar exists and is recent, the file is restorable regardless of DB row state. Also removed unused DB query from _cleanup-stats-data.php.
212 lines
7.3 KiB
PHP
212 lines
7.3 KiB
PHP
<?php
|
|
/**
|
|
* Shared cleanup stats data-fetching logic.
|
|
*
|
|
* Used by both cleanup-stats.php (JSON endpoint) and
|
|
* cleanup-stats-fragment.php (HTML fragment).
|
|
*/
|
|
|
|
function dirSizeRecursive(string $dir): int
|
|
{
|
|
$size = 0;
|
|
if (!is_dir($dir)) {
|
|
return 0;
|
|
}
|
|
$items = @scandir($dir);
|
|
if ($items === false) {
|
|
return 0;
|
|
}
|
|
foreach ($items as $item) {
|
|
if ($item === '.' || $item === '..') {
|
|
continue;
|
|
}
|
|
$path = $dir . '/' . $item;
|
|
if (is_dir($path)) {
|
|
$size += dirSizeRecursive($path);
|
|
} else {
|
|
$size += filesize($path);
|
|
}
|
|
}
|
|
return $size;
|
|
}
|
|
|
|
function humanBytes(int $bytes): string
|
|
{
|
|
if ($bytes > 1073741824) {
|
|
return number_format($bytes / 1073741824, 1) . ' GB';
|
|
}
|
|
if ($bytes > 1048576) {
|
|
return number_format($bytes / 1048576, 1) . ' MB';
|
|
}
|
|
return number_format($bytes / 1024, 1) . ' KB';
|
|
}
|
|
|
|
function getCleanupStats(): array
|
|
{
|
|
$storageRoot = STORAGE_ROOT;
|
|
$filepondDir = $storageRoot . '/tmp/filepond';
|
|
$trashDir = $storageRoot . '/tmp/_trash';
|
|
$maxAgeFilepond = 7200; // 2h
|
|
$maxAgeTrash = 2592000; // 30d
|
|
|
|
$sessionSavePath = session_save_path();
|
|
if (!$sessionSavePath || $sessionSavePath === '') {
|
|
$sessionSavePath = sys_get_temp_dir();
|
|
}
|
|
|
|
$now = time();
|
|
|
|
// ── FilePond stats ───────────────────────────────────────────────────
|
|
$fpStaleCount = 0;
|
|
$fpStaleSize = 0;
|
|
$fpStaleFiles = [];
|
|
$fpActiveCount = 0;
|
|
$fpActiveSize = 0;
|
|
|
|
if (is_dir($filepondDir)) {
|
|
$items = @scandir($filepondDir);
|
|
if ($items !== false) {
|
|
foreach ($items as $item) {
|
|
if ($item === '.' || $item === '..' || $item === '.gitkeep') {
|
|
continue;
|
|
}
|
|
$dirPath = $filepondDir . '/' . $item;
|
|
if (!is_dir($dirPath)) {
|
|
continue;
|
|
}
|
|
|
|
$size = dirSizeRecursive($dirPath);
|
|
$mtime = filemtime($dirPath);
|
|
$ageMinutes = (int)(($now - $mtime) / 60);
|
|
|
|
$stale = false;
|
|
|
|
// Session-based detection
|
|
$manifestPath = $dirPath . '/manifest.json';
|
|
if (file_exists($manifestPath)) {
|
|
$manifest = json_decode(file_get_contents($manifestPath), true);
|
|
if (is_array($manifest) && !empty($manifest['session_id'])) {
|
|
$sessionFile = $sessionSavePath . '/sess_' . $manifest['session_id'];
|
|
if (!file_exists($sessionFile)) {
|
|
$stale = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Time-based fallback
|
|
if (!$stale && $ageMinutes > ($maxAgeFilepond / 60)) {
|
|
$stale = true;
|
|
}
|
|
|
|
if ($stale) {
|
|
$fpStaleCount++;
|
|
$fpStaleSize += $size;
|
|
$fpStaleFiles[] = [
|
|
'name' => $item,
|
|
'size' => $size,
|
|
'human' => humanBytes($size),
|
|
'age_minutes' => $ageMinutes,
|
|
];
|
|
} else {
|
|
$fpActiveCount++;
|
|
$fpActiveSize += $size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Trash stats ──────────────────────────────────────────────────────
|
|
require_once __DIR__ . '/../../../src/Database.php';
|
|
$db = new Database();
|
|
$pdo = $db->getPDO();
|
|
|
|
$trStaleCount = 0;
|
|
$trStaleSize = 0;
|
|
$trStaleFiles = [];
|
|
$trActiveCount = 0;
|
|
$trActiveSize = 0;
|
|
$trActiveFiles = [];
|
|
|
|
if (is_dir($trashDir)) {
|
|
$items = @scandir($trashDir);
|
|
if ($items !== false) {
|
|
foreach ($items as $item) {
|
|
if ($item === '.' || $item === '..') {
|
|
continue;
|
|
}
|
|
// Skip JSON sidecar files — they're listed alongside their parent.
|
|
if (str_ends_with($item, '.json')) {
|
|
continue;
|
|
}
|
|
$filePath = $trashDir . '/' . $item;
|
|
if (!is_file($filePath)) {
|
|
continue;
|
|
}
|
|
|
|
$size = filesize($filePath);
|
|
$mtime = filemtime($filePath);
|
|
$ageDays = (int)(($now - $mtime) / 86400);
|
|
|
|
// Check for sidecar metadata — files with sidecars are restorable
|
|
// regardless of whether the thesis_files DB row still exists.
|
|
$sidecarPath = $filePath . '.json';
|
|
$hasSidecar = file_exists($sidecarPath);
|
|
$sidecarData = null;
|
|
if ($hasSidecar) {
|
|
$sidecarData = json_decode(file_get_contents($sidecarPath), true);
|
|
if (!is_array($sidecarData)) {
|
|
$hasSidecar = false;
|
|
$sidecarData = null;
|
|
}
|
|
}
|
|
|
|
// Restorable = has a valid sidecar AND is younger than max age
|
|
$restorable = $hasSidecar && $ageDays <= ($maxAgeTrash / 86400);
|
|
|
|
if ($restorable) {
|
|
$trActiveCount++;
|
|
$trActiveSize += $size;
|
|
$trActiveFiles[] = [
|
|
'name' => $item,
|
|
'size' => $size,
|
|
'human' => humanBytes($size),
|
|
'age_days' => $ageDays,
|
|
'has_sidecar' => true,
|
|
'thesis_id' => $sidecarData['thesis_id'] ?? null,
|
|
'file_type' => $sidecarData['file_type'] ?? '?',
|
|
'original_name' => $sidecarData['file_name'] ?? '',
|
|
'original_path' => $sidecarData['file_path'] ?? '',
|
|
];
|
|
} else {
|
|
$trStaleCount++;
|
|
$trStaleSize += $size;
|
|
$trStaleFiles[] = [
|
|
'name' => $item,
|
|
'size' => $size,
|
|
'human' => humanBytes($size),
|
|
'age_days' => $ageDays,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return [
|
|
'filepond_stale_count' => $fpStaleCount,
|
|
'filepond_stale_size' => $fpStaleSize,
|
|
'filepond_stale_human' => humanBytes($fpStaleSize),
|
|
'filepond_stale_files' => $fpStaleFiles,
|
|
'filepond_active_count' => $fpActiveCount,
|
|
'filepond_active_size' => $fpActiveSize,
|
|
'filepond_active_human' => humanBytes($fpActiveSize),
|
|
'trash_stale_count' => $trStaleCount,
|
|
'trash_stale_size' => $trStaleSize,
|
|
'trash_stale_human' => humanBytes($trStaleSize),
|
|
'trash_stale_files' => $trStaleFiles,
|
|
'trash_active_count' => $trActiveCount,
|
|
'trash_active_size' => $trActiveSize,
|
|
'trash_active_human' => humanBytes($trActiveSize),
|
|
'trash_active_files' => $trActiveFiles,
|
|
];
|
|
}
|