Code simplification, move to wizard style UI, JSON file cache (scans every 15 mins)
This commit is contained in:
parent
75a7c9a13b
commit
92362efd47
11 changed files with 1027 additions and 438 deletions
172
get_files.php
172
get_files.php
|
|
@ -5,124 +5,96 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/auth.php';
|
||||
require_once __DIR__ . '/media_cache_lib.php';
|
||||
require_auth(true);
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
/**
|
||||
* Emit JSON response with cache/timing observability headers.
|
||||
*/
|
||||
function fm_emit_response(array $payload, string $type, string $cacheStatus, float $startedAt, bool $forceRefresh, ?string $cacheGeneratedAt = null): void
|
||||
{
|
||||
$elapsedMs = (int)round((microtime(true) - $startedAt) * 1000);
|
||||
|
||||
header('X-Media-Type: ' . $type);
|
||||
header('X-Media-Cache: ' . $cacheStatus);
|
||||
header('X-Media-Time-Ms: ' . (string)$elapsedMs);
|
||||
header('X-Media-Force-Refresh: ' . ($forceRefresh ? '1' : '0'));
|
||||
|
||||
if (is_string($cacheGeneratedAt) && $cacheGeneratedAt !== '') {
|
||||
header('X-Media-Cache-Generated-At: ' . $cacheGeneratedAt);
|
||||
}
|
||||
|
||||
echo json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$startedAt = microtime(true);
|
||||
|
||||
// Media type from query parameter (default: videos)
|
||||
$type = $_GET['type'] ?? 'videos';
|
||||
|
||||
// Resolve base directory via MEDIA_ROOT
|
||||
$mediaRoot = getenv('MEDIA_ROOT');
|
||||
if ($mediaRoot === false || trim($mediaRoot) === '') {
|
||||
echo json_encode(['error' => 'MEDIA_ROOT is not set']);
|
||||
exit;
|
||||
if (!is_string($type)) {
|
||||
$type = 'unknown';
|
||||
}
|
||||
|
||||
$baseDir = realpath(trim($mediaRoot));
|
||||
if ($baseDir === false || !is_dir($baseDir)) {
|
||||
echo json_encode(['error' => 'MEDIA_ROOT base directory not found']);
|
||||
exit;
|
||||
$forceRefresh = isset($_GET['refresh']) && (string)$_GET['refresh'] === '1';
|
||||
|
||||
if (!is_string($type) || !fm_is_valid_type($type)) {
|
||||
fm_emit_response(['error' => 'Invalid media type'], $type, 'invalid_type', $startedAt, $forceRefresh);
|
||||
}
|
||||
|
||||
$directories = [
|
||||
'videos' => $baseDir . DIRECTORY_SEPARATOR . 'Videos',
|
||||
'music' => $baseDir . DIRECTORY_SEPARATOR . 'Music',
|
||||
];
|
||||
|
||||
// Extensions per tab (don’t mix)
|
||||
$extensionsByType = [
|
||||
'videos' => ['mp4', 'mkv', 'avi', 'mov', 'wmv', 'flv', 'webm', 'm4v'],
|
||||
'music' => ['mp3', 'wav', 'ogg', 'flac', 'm4a', 'aac', 'wma', 'opus'],
|
||||
];
|
||||
|
||||
if (!isset($directories[$type])) {
|
||||
echo json_encode(['error' => 'Invalid media type']);
|
||||
exit;
|
||||
$resolveError = null;
|
||||
$baseDir = fm_resolve_media_root($resolveError);
|
||||
if ($baseDir === null) {
|
||||
fm_emit_response(['error' => $resolveError ?? 'MEDIA_ROOT base directory not found'], $type, 'error', $startedAt, $forceRefresh);
|
||||
}
|
||||
|
||||
$targetDir = $directories[$type];
|
||||
$validExtensions = $extensionsByType[$type];
|
||||
|
||||
// Fail fast if dir missing
|
||||
if (!is_dir($targetDir)) {
|
||||
echo json_encode(['error' => 'Directory not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively scan a directory and build a folder->(folders/files) structure.
|
||||
* Files are returned as paths relative to $baseDir.
|
||||
*/
|
||||
function scanDirectory(string $dir, string $baseDir, array $validExtensions): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
// Resolve base once (prevents per-file realpath overhead and handles normalization)
|
||||
$realBase = realpath($baseDir);
|
||||
if ($realBase === false) {
|
||||
return [];
|
||||
if (!$forceRefresh) {
|
||||
$cacheError = null;
|
||||
$cached = fm_read_cache_payload($type, $baseDir, $cacheError);
|
||||
if (is_array($cached)) {
|
||||
$tree = $cached['tree'];
|
||||
fm_emit_response(
|
||||
empty($tree) ? ['message' => 'No media files found'] : $tree,
|
||||
$type,
|
||||
'hit',
|
||||
$startedAt,
|
||||
$forceRefresh,
|
||||
isset($cached['generated_at']) && is_string($cached['generated_at']) ? $cached['generated_at'] : null
|
||||
);
|
||||
}
|
||||
$realBase = rtrim($realBase, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
// scandir() can return false on permissions
|
||||
$items = @scandir($dir);
|
||||
if ($items === false) return [];
|
||||
$rebuildError = null;
|
||||
$tree = fm_rebuild_cache($type, $baseDir, false, $rebuildError);
|
||||
|
||||
foreach ($items as $item) {
|
||||
// Skip dot/hidden entries
|
||||
if ($item === '.' || $item === '..' || ($item !== '' && $item[0] === '.')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fullPath = $dir . DIRECTORY_SEPARATOR . $item;
|
||||
|
||||
if (is_dir($fullPath)) {
|
||||
$sub = scanDirectory($fullPath, $baseDir, $validExtensions);
|
||||
if (!empty($sub)) {
|
||||
$result[$item] = $sub;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$ext = strtolower(pathinfo($item, PATHINFO_EXTENSION));
|
||||
if (!in_array($ext, $validExtensions, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Normalize and enforce: file must live under baseDir
|
||||
$realFile = realpath($fullPath);
|
||||
if ($realFile === false) {
|
||||
continue;
|
||||
}
|
||||
if (strpos($realFile, $realBase) !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Return relative path from baseDir
|
||||
$relativePath = substr($realFile, strlen($realBase));
|
||||
$result[$item] = $relativePath;
|
||||
if (!is_array($tree)) {
|
||||
$scanError = null;
|
||||
$tree = fm_scan_media_tree($type, $baseDir, $scanError);
|
||||
if (!is_array($tree)) {
|
||||
fm_emit_response(
|
||||
['error' => $scanError ?? $rebuildError ?? 'Failed to scan media directory'],
|
||||
$type,
|
||||
'error',
|
||||
$startedAt,
|
||||
$forceRefresh
|
||||
);
|
||||
}
|
||||
|
||||
// Sort: folders first, then files, alphabetically
|
||||
uksort($result, function ($a, $b) use ($result) {
|
||||
$aIsDir = is_array($result[$a]);
|
||||
$bIsDir = is_array($result[$b]);
|
||||
|
||||
if ($aIsDir && !$bIsDir) return -1;
|
||||
if (!$aIsDir && $bIsDir) return 1;
|
||||
|
||||
// IMPORTANT: keys can be ints if the filename is numeric (e.g. "01", "2026")
|
||||
return strcasecmp((string)$a, (string)$b);
|
||||
});
|
||||
|
||||
return $result;
|
||||
fm_emit_response(
|
||||
empty($tree) ? ['message' => 'No media files found'] : $tree,
|
||||
$type,
|
||||
'fallback_scan',
|
||||
$startedAt,
|
||||
$forceRefresh
|
||||
);
|
||||
}
|
||||
|
||||
$tree = scanDirectory($targetDir, $baseDir, $validExtensions);
|
||||
|
||||
echo json_encode(
|
||||
fm_emit_response(
|
||||
empty($tree) ? ['message' => 'No media files found'] : $tree,
|
||||
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
|
||||
$type,
|
||||
$forceRefresh ? 'rebuild_forced' : 'rebuild',
|
||||
$startedAt,
|
||||
$forceRefresh
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue