koel/app/Services/SongStorages/LocalStorage.php

120 lines
3.4 KiB
PHP
Raw Normal View History

2020-06-07 20:43:04 +00:00
<?php
namespace App\Services\SongStorages;
2020-06-07 20:43:04 +00:00
2024-04-18 17:20:14 +00:00
use App\Enums\SongStorageType;
2020-06-07 20:43:04 +00:00
use App\Exceptions\MediaPathNotSetException;
use App\Exceptions\SongUploadFailedException;
use App\Models\Setting;
use App\Models\Song;
2024-01-04 11:35:36 +00:00
use App\Models\User;
use App\Services\FileScanner;
use App\Values\ScanConfiguration;
use Exception;
2020-06-07 20:43:04 +00:00
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File;
2024-01-04 11:35:36 +00:00
use Throwable;
2020-06-07 20:43:04 +00:00
2020-12-22 20:11:22 +00:00
use function Functional\memoize;
final class LocalStorage extends SongStorage
2020-06-07 20:43:04 +00:00
{
2024-04-26 13:35:26 +00:00
public function __construct(private readonly FileScanner $scanner)
2020-06-07 20:43:04 +00:00
{
}
public function storeUploadedFile(UploadedFile $file, User $uploader): Song
2020-06-07 20:43:04 +00:00
{
2024-04-04 22:20:42 +00:00
self::assertSupported();
2024-01-04 11:35:36 +00:00
$uploadDirectory = $this->getUploadDirectory($uploader);
$targetFileName = $this->getTargetFileName($file, $uploader);
$file->move($uploadDirectory, $targetFileName);
$targetPathName = $uploadDirectory . $targetFileName;
2020-06-07 20:43:04 +00:00
2024-01-04 11:35:36 +00:00
try {
$result = $this->scanner->setFile($targetPathName)
->scan(ScanConfiguration::make(
owner: $uploader,
makePublic: $uploader->preferences->makeUploadsPublic
));
} catch (Throwable $e) {
File::delete($targetPathName);
throw new SongUploadFailedException($e->getMessage());
2024-01-04 11:35:36 +00:00
}
2020-06-07 20:43:04 +00:00
2022-07-29 11:08:24 +00:00
if ($result->isError()) {
File::delete($targetPathName);
2022-07-29 11:08:24 +00:00
throw new SongUploadFailedException($result->error);
2020-06-07 20:43:04 +00:00
}
return $this->scanner->getSong();
2020-06-07 20:43:04 +00:00
}
2024-01-04 11:35:36 +00:00
private function getUploadDirectory(User $uploader): string
2020-06-07 20:43:04 +00:00
{
2024-01-04 11:35:36 +00:00
return memoize(static function () use ($uploader): string {
2020-06-07 20:43:04 +00:00
$mediaPath = Setting::get('media_path');
2024-01-04 11:35:36 +00:00
throw_unless((bool) $mediaPath, MediaPathNotSetException::class);
$dir = sprintf(
'%s%s__KOEL_UPLOADS_$%s__%s',
$mediaPath,
DIRECTORY_SEPARATOR,
$uploader->id,
DIRECTORY_SEPARATOR
);
File::ensureDirectoryExists($dir);
2020-06-07 20:43:04 +00:00
2024-01-04 11:35:36 +00:00
return $dir;
});
2020-06-07 20:43:04 +00:00
}
2024-01-04 11:35:36 +00:00
private function getTargetFileName(UploadedFile $file, User $uploader): string
2020-06-07 20:43:04 +00:00
{
// If there's no existing file with the same name in the upload directory, use the original name.
// Otherwise, prefix the original name with a hash.
// The whole point is to keep a readable file name when we can.
2024-01-10 00:47:09 +00:00
if (!File::exists($this->getUploadDirectory($uploader) . $file->getClientOriginalName())) {
2020-06-07 20:43:04 +00:00
return $file->getClientOriginalName();
}
2020-12-22 20:11:22 +00:00
return $this->getUniqueHash() . '_' . $file->getClientOriginalName();
2020-06-07 20:43:04 +00:00
}
private function getUniqueHash(): string
{
return substr(sha1(uniqid()), 0, 6);
}
public function delete(Song $song, bool $backup = false): void
{
$path = $song->storage_metadata->getPath();
if ($backup) {
File::move($path, "$path.bak");
}
throw_unless(File::delete($path), new Exception("Failed to delete song file: $path"));
}
2024-04-26 13:35:26 +00:00
2024-09-08 11:21:06 +00:00
public function testSetup(): void
{
$mediaPath = Setting::get('media_path');
if (File::isReadable($mediaPath) && File::isWritable($mediaPath)) {
return;
}
throw new Exception("The media path $mediaPath is not readable or writable.");
}
public function getStorageType(): SongStorageType
2024-04-26 13:35:26 +00:00
{
return SongStorageType::LOCAL;
}
2020-06-07 20:43:04 +00:00
}