Files
SGS/www/app/Http/Controllers/DBI/IndexController.php
Fabián Gonzalo Artur de la Villarmois bae79d68ab * Fix CI/CD php version
* Add Pint
* Format code
2026-03-29 17:49:51 +13:00

176 lines
5.2 KiB
PHP

<?php
namespace App\Http\Controllers\DBI;
use App\Enums\NintendoFileExtension;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Response;
class IndexController extends Controller
{
private const GAMES_DIRECTORY = 'games';
/**
* Handle DBI file browser requests.
* Serves either file downloads or Apache-style directory listings.
*/
public function __invoke(Request $request): Response|BinaryFileResponse
{
$path = $this->getRequestPath($request);
if ($this->isFileRequest($path)) {
return $this->serveFile($path);
}
return $this->serveDirectoryListing($path, $request);
}
/**
* Extract and decode the path from the request.
*/
private function getRequestPath(Request $request): ?string
{
$path = $request->route('path');
return $path ? rawurldecode($path) : null;
}
/**
* Determine if the request is for a file download.
*/
private function isFileRequest(?string $path): bool
{
return $path && NintendoFileExtension::isSupported($path);
}
/**
* Serve a file download response.
*/
private function serveFile(string $path): Response|BinaryFileResponse
{
$storagePath = self::GAMES_DIRECTORY.'/'.$path;
if (! Storage::exists($storagePath)) {
return response('File not found', 404);
}
return response()->file(Storage::path($storagePath), [
'Content-Type' => 'application/octet-stream',
'Accept-Ranges' => 'bytes',
]);
}
/**
* Serve an Apache-style directory listing.
*/
private function serveDirectoryListing(?string $path, Request $request): Response
{
$storagePath = $path
? self::GAMES_DIRECTORY.'/'.$path
: self::GAMES_DIRECTORY;
$directories = $this->getDirectories($storagePath);
$files = $this->getFiles($storagePath);
// Determine if this is a DBI client or regular browser
$isDBI = str_starts_with($request->userAgent() ?? '', 'DBI/');
// Determine base URL for links based on request path
$baseUrl = str_starts_with($request->path(), 'api/dbi') ? '/api/dbi/' : '/';
$html = $this->buildDirectoryListingHtml($directories, $files, $path ?? '', $baseUrl, $isDBI);
return response($html)->header('Content-Type', 'text/html');
}
/**
* Get directories that contain allowed file types.
*/
private function getDirectories(string $path): Collection
{
return collect(Storage::directories($path))
->filter(fn ($dir) => $this->containsInstallableFiles($dir))
->map(fn ($dir) => [
'name' => basename($dir),
'mtime' => Storage::lastModified($dir),
])
->sortBy('name', SORT_NATURAL | SORT_FLAG_CASE)
->values();
}
/**
* Get files with allowed extensions.
*/
private function getFiles(string $path): Collection
{
return collect(Storage::files($path))
->filter(fn ($file) => NintendoFileExtension::isSupported($file))
->map(fn ($file) => [
'name' => basename($file),
'mtime' => Storage::lastModified($file),
'size' => Storage::size($file),
])
->sortBy('name', SORT_NATURAL | SORT_FLAG_CASE)
->values();
}
/**
* Check if a directory contains any installable files.
*/
private function containsInstallableFiles(string $directory): bool
{
return collect(Storage::allFiles($directory))
->contains(fn ($file) => NintendoFileExtension::isSupported($file));
}
/**
* Build Apache-style HTML directory listing compatible with DBI.
*/
private function buildDirectoryListingHtml(Collection $directories, Collection $files, string $currentPath, string $baseUrl, bool $isDBI): string
{
$displayPath = $currentPath
? '/games/'.rawurldecode($currentPath).'/'
: '/games/';
return view('dbi.directory-listing', [
'displayPath' => $displayPath,
'currentPath' => $currentPath,
'baseUrl' => rtrim($baseUrl, '/'),
'isDBI' => $isDBI,
'parentUrl' => $this->getParentDirectoryUrl($currentPath, $baseUrl, $isDBI),
'directories' => $directories,
'files' => $files,
])->render();
}
/**
* Get the URL for the parent directory.
*/
private function getParentDirectoryUrl(string $currentPath, string $baseUrl, bool $isDBI): ?string
{
if (! $currentPath) {
return null;
}
// DBI needs relative paths
if ($isDBI) {
return '../';
}
// Browsers need absolute paths
$parentPath = dirname($currentPath);
if ($parentPath === '.') {
return $baseUrl;
}
return rtrim($baseUrl, '/').'/'.collect(explode('/', $parentPath))
->map(fn ($segment) => rawurlencode($segment))
->implode('/');
}
}