mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
feat: adapt downloading to Plus
This commit is contained in:
parent
f04f940ffa
commit
473f1c11a1
26 changed files with 175 additions and 173 deletions
|
@ -25,7 +25,7 @@ class LicenseInstanceCast implements CastsAttributes
|
|||
}
|
||||
}
|
||||
|
||||
/** @param LicenseInstance $value */
|
||||
/** @param ?LicenseInstance $value */
|
||||
public function set($model, string $key, $value, array $attributes): ?string
|
||||
{
|
||||
try {
|
||||
|
|
|
@ -25,7 +25,7 @@ class LicenseMetaCast implements CastsAttributes
|
|||
}
|
||||
}
|
||||
|
||||
/** @param LicenseMeta $value */
|
||||
/** @param ?LicenseMeta $value */
|
||||
public function set($model, string $key, $value, array $attributes): ?string
|
||||
{
|
||||
try {
|
||||
|
|
|
@ -39,7 +39,7 @@ class ActivateLicenseCommand extends Command
|
|||
|
||||
$this->components->twoColumnDetail('Expires On', 'Never ever');
|
||||
|
||||
$this->line('');
|
||||
$this->newLine();
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ class CheckLicenseStatusCommand extends Command
|
|||
);
|
||||
|
||||
$this->components->twoColumnDetail('Expires On', 'Never ever');
|
||||
$this->line('');
|
||||
$this->newLine();
|
||||
break;
|
||||
|
||||
case LicenseStatus::STATUS_NO_LICENSE:
|
||||
|
|
|
@ -27,7 +27,6 @@ class ScanCommand extends Command
|
|||
protected $description = 'Scan for songs in the configured directory.';
|
||||
|
||||
private ?string $mediaPath;
|
||||
private ?User $owner;
|
||||
private ProgressBar $progressBar;
|
||||
|
||||
public function __construct(private MediaScanner $mediaScanner)
|
||||
|
@ -50,15 +49,22 @@ class ScanCommand extends Command
|
|||
|
||||
public function handle(): int
|
||||
{
|
||||
$this->owner = $this->getOwner();
|
||||
$this->mediaPath = $this->getMediaPath();
|
||||
|
||||
$config = ScanConfiguration::make(
|
||||
owner: $this->getOwner(),
|
||||
// When scanning via CLI, the songs should be public by default, unless explicitly specified otherwise.
|
||||
makePublic: !$this->option('private'),
|
||||
ignores: collect($this->option('ignore'))->sort()->values()->all(),
|
||||
force: $this->option('force')
|
||||
);
|
||||
|
||||
$record = $this->argument('record');
|
||||
|
||||
if ($record) {
|
||||
$this->scanSingleRecord($record);
|
||||
$this->scanSingleRecord($record, $config);
|
||||
} else {
|
||||
$this->scanMediaPath();
|
||||
$this->scanMediaPath($config);
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
|
@ -67,27 +73,14 @@ class ScanCommand extends Command
|
|||
/**
|
||||
* Scan all files in the configured media path.
|
||||
*/
|
||||
private function scanMediaPath(): void
|
||||
private function scanMediaPath(ScanConfiguration $config): void
|
||||
{
|
||||
$this->components->info('Scanning ' . $this->mediaPath);
|
||||
|
||||
// The tags to ignore from scanning.
|
||||
// Notice that this is only meaningful for existing records.
|
||||
// New records will have every applicable field scanned.
|
||||
$ignores = collect($this->option('ignore'))->sort()->values()->all();
|
||||
|
||||
if ($ignores) {
|
||||
$this->components->info('Ignoring tag(s): ' . implode(', ', $ignores));
|
||||
if ($config->ignores) {
|
||||
$this->components->info('Ignoring tag(s): ' . implode(', ', $config->ignores));
|
||||
}
|
||||
|
||||
$config = ScanConfiguration::make(
|
||||
owner: $this->owner,
|
||||
// When scanning via CLI, the songs should be public by default, unless explicitly specified otherwise.
|
||||
makePublic: !$this->option('private'),
|
||||
ignores: $ignores,
|
||||
force: $this->option('force')
|
||||
);
|
||||
|
||||
$results = $this->mediaScanner->scan($config);
|
||||
|
||||
$this->newLine(2);
|
||||
|
@ -110,9 +103,9 @@ class ScanCommand extends Command
|
|||
*
|
||||
* @see http://man7.org/linux/man-pages/man1/inotifywait.1.html
|
||||
*/
|
||||
private function scanSingleRecord(string $record): void
|
||||
private function scanSingleRecord(string $record, ScanConfiguration $config): void
|
||||
{
|
||||
$this->mediaScanner->scanWatchRecord(new InotifyWatchRecord($record));
|
||||
$this->mediaScanner->scanWatchRecord(new InotifyWatchRecord($record), $config);
|
||||
}
|
||||
|
||||
public function onScanProgress(ScanResult $result): void
|
||||
|
|
|
@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Facade;
|
|||
|
||||
/**
|
||||
* @method static string fromSong(Song $song)
|
||||
* @see \App\Services\DownloadService
|
||||
*/
|
||||
class Download extends Facade
|
||||
{
|
||||
|
|
|
@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Facade;
|
|||
/**
|
||||
* @method static bool isPlus()
|
||||
* @method static bool isCommunity()
|
||||
* @see \App\Services\LicenseService
|
||||
*/
|
||||
class License extends Facade
|
||||
{
|
||||
|
|
|
@ -4,12 +4,16 @@ namespace App\Http\Controllers\Download;
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Album;
|
||||
use App\Models\User;
|
||||
use App\Repositories\SongRepository;
|
||||
use App\Services\DownloadService;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
|
||||
class DownloadAlbumController extends Controller
|
||||
{
|
||||
public function __invoke(Album $album, DownloadService $download)
|
||||
/** @param User $user */
|
||||
public function __invoke(Album $album, SongRepository $repository, DownloadService $download, Authenticatable $user)
|
||||
{
|
||||
return response()->download($download->from($album));
|
||||
return response()->download($download->from($repository->getByAlbum($album, $user)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,20 @@ namespace App\Http\Controllers\Download;
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Artist;
|
||||
use App\Models\User;
|
||||
use App\Repositories\SongRepository;
|
||||
use App\Services\DownloadService;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
|
||||
class DownloadArtistController extends Controller
|
||||
{
|
||||
public function __invoke(Artist $artist, DownloadService $download)
|
||||
{
|
||||
return response()->download($download->from($artist));
|
||||
/** @param User $user */
|
||||
public function __invoke(
|
||||
Artist $artist,
|
||||
SongRepository $repository,
|
||||
DownloadService $download,
|
||||
Authenticatable $user
|
||||
) {
|
||||
return response()->download($download->from($repository->getByArtist($artist, $user)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,15 +4,15 @@ namespace App\Http\Controllers\Download;
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Repositories\InteractionRepository;
|
||||
use App\Repositories\SongRepository;
|
||||
use App\Services\DownloadService;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
|
||||
class DownloadFavoritesController extends Controller
|
||||
{
|
||||
/** @param User $user */
|
||||
public function __invoke(DownloadService $download, InteractionRepository $repository, Authenticatable $user)
|
||||
public function __invoke(DownloadService $download, SongRepository $repository, Authenticatable $user)
|
||||
{
|
||||
return response()->download($download->from($repository->getUserFavorites($user)));
|
||||
return response()->download($download->from($repository->getFavorites($user)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,14 +4,30 @@ namespace App\Http\Controllers\Download;
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Playlist;
|
||||
use App\Models\User;
|
||||
use App\Repositories\SongRepository;
|
||||
use App\Services\DownloadService;
|
||||
use App\Services\SmartPlaylistService;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
|
||||
class DownloadPlaylistController extends Controller
|
||||
{
|
||||
public function __invoke(Playlist $playlist, DownloadService $download)
|
||||
{
|
||||
$this->authorize('own', $playlist);
|
||||
/** @param User $user */
|
||||
public function __invoke(
|
||||
Playlist $playlist,
|
||||
SongRepository $repository,
|
||||
SmartPlaylistService $smartPlaylistService,
|
||||
DownloadService $download,
|
||||
Authenticatable $user
|
||||
) {
|
||||
$this->authorize('download', $playlist);
|
||||
|
||||
return response()->download($download->from($playlist));
|
||||
return response()->download(
|
||||
$download->from(
|
||||
$playlist->is_smart
|
||||
? $smartPlaylistService->getSongs($playlist, $user)
|
||||
: $repository->getByStandardPlaylist($playlist, $user)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,9 @@ class DownloadSongsController extends Controller
|
|||
{
|
||||
public function __invoke(DownloadSongsRequest $request, DownloadService $download, SongRepository $repository)
|
||||
{
|
||||
$songs = $repository->getMany($request->songs);
|
||||
$songs->each(fn ($song) => $this->authorize('download', $song));
|
||||
|
||||
return response()->download($download->from($repository->getMany($request->songs)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ class SongZipArchive
|
|||
*/
|
||||
private array $fileNames = [];
|
||||
|
||||
public function __construct(string $path = '')
|
||||
public function __construct(?string $path = null)
|
||||
{
|
||||
$this->path = $path ?: self::generateRandomArchivePath();
|
||||
|
||||
|
|
|
@ -11,4 +11,9 @@ class PlaylistPolicy
|
|||
{
|
||||
return $playlist->user->is($user);
|
||||
}
|
||||
|
||||
public function download(User $user, Playlist $playlist): bool
|
||||
{
|
||||
return $this->own($user, $playlist);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,4 +27,13 @@ class SongPolicy
|
|||
{
|
||||
return (License::isCommunity() && $user->is_admin) || $song->owner_id === $user->id;
|
||||
}
|
||||
|
||||
public function download(User $user, Song $song): bool
|
||||
{
|
||||
if (!config('koel.download.allow')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return License::isCommunity() || $song->is_public || $song->owner_id === $user->id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,12 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Testing\TestResponse;
|
||||
|
||||
class MacroProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -22,5 +25,18 @@ class MacroProvider extends ServiceProvider
|
|||
|
||||
return $this;
|
||||
});
|
||||
|
||||
if (app()->runningUnitTests()) {
|
||||
UploadedFile::macro('fromFile', static function (string $path, ?string $name = null): UploadedFile {
|
||||
return UploadedFile::fake()->createWithContent($name ?? basename($path), File::get($path));
|
||||
});
|
||||
|
||||
TestResponse::macro('log', function (string $file = 'test-response.json'): TestResponse {
|
||||
/** @var TestResponse $this */
|
||||
File::put(storage_path('logs/' . $file), $this->getContent());
|
||||
|
||||
return $this;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ class AlbumRepository extends Repository
|
|||
/** @return Collection|array<array-key, Album> */
|
||||
public function getMostPlayed(int $count = 6, ?User $user = null): Collection
|
||||
{
|
||||
/** @var User $user */
|
||||
$user ??= $this->auth->user();
|
||||
|
||||
$query = Album::query()
|
||||
|
|
|
@ -14,7 +14,7 @@ class ArtistRepository extends Repository
|
|||
/** @return Collection|array<array-key, Artist> */
|
||||
public function getMostPlayed(int $count = 6, ?User $user = null): Collection
|
||||
{
|
||||
/** @var User $user */
|
||||
/** @var ?User $user */
|
||||
$user ??= auth()->user();
|
||||
|
||||
$query = Artist::query()
|
||||
|
|
|
@ -29,7 +29,7 @@ class SongRepository extends Repository
|
|||
/** @return Collection|array<array-key, Song> */
|
||||
public function getRecentlyAdded(int $count = 10, ?User $scopedUser = null): Collection
|
||||
{
|
||||
/** @var User $scopedUser */
|
||||
/** @var ?User $scopedUser */
|
||||
$scopedUser ??= $this->auth->user();
|
||||
|
||||
return Song::query()
|
||||
|
@ -43,7 +43,7 @@ class SongRepository extends Repository
|
|||
/** @return Collection|array<array-key, Song> */
|
||||
public function getMostPlayed(int $count = 7, ?User $scopedUser = null): Collection
|
||||
{
|
||||
/** @var User $scopedUser */
|
||||
/** @var ?User $scopedUser */
|
||||
$scopedUser ??= $this->auth->user();
|
||||
|
||||
return Song::query()
|
||||
|
@ -58,7 +58,7 @@ class SongRepository extends Repository
|
|||
/** @return Collection|array<array-key, Song> */
|
||||
public function getRecentlyPlayed(int $count = 7, ?User $scopedUser = null): Collection
|
||||
{
|
||||
/** @var User $scopedUser */
|
||||
/** @var ?User $scopedUser */
|
||||
$scopedUser ??= $this->auth->user();
|
||||
|
||||
return Song::query()
|
||||
|
@ -75,7 +75,7 @@ class SongRepository extends Repository
|
|||
?User $scopedUser = null,
|
||||
int $perPage = 50
|
||||
): Paginator {
|
||||
/** @var User $scopedUser */
|
||||
/** @var ?User $scopedUser */
|
||||
$scopedUser ??= $this->auth->user();
|
||||
|
||||
return Song::query()
|
||||
|
@ -92,7 +92,7 @@ class SongRepository extends Repository
|
|||
?User $scopedUser = null,
|
||||
int $perPage = 50
|
||||
): Paginator {
|
||||
/** @var User $scopedUser */
|
||||
/** @var ?User $scopedUser */
|
||||
$scopedUser ??= $this->auth->user();
|
||||
|
||||
return Song::query()
|
||||
|
@ -110,7 +110,7 @@ class SongRepository extends Repository
|
|||
int $limit = self::DEFAULT_QUEUE_LIMIT,
|
||||
?User $scopedUser = null,
|
||||
): Collection {
|
||||
/** @var User $scopedUser */
|
||||
/** @var ?User $scopedUser */
|
||||
$scopedUser ??= $this->auth->user();
|
||||
|
||||
return Song::query()
|
||||
|
@ -124,7 +124,7 @@ class SongRepository extends Repository
|
|||
/** @return Collection|array<array-key, Song> */
|
||||
public function getFavorites(?User $scopedUser = null): Collection
|
||||
{
|
||||
/** @var User $scopedUser */
|
||||
/** @var ?User $scopedUser */
|
||||
$scopedUser ??= $this->auth->user();
|
||||
|
||||
return Song::query()
|
||||
|
@ -137,7 +137,7 @@ class SongRepository extends Repository
|
|||
/** @return Collection|array<array-key, Song> */
|
||||
public function getByAlbum(Album $album, ?User $scopedUser = null): Collection
|
||||
{
|
||||
/** @var User $scopedUser */
|
||||
/** @var ?User $scopedUser */
|
||||
$scopedUser ??= $this->auth->user();
|
||||
|
||||
return Song::query()
|
||||
|
@ -153,7 +153,7 @@ class SongRepository extends Repository
|
|||
/** @return Collection|array<array-key, Song> */
|
||||
public function getByArtist(Artist $artist, ?User $scopedUser = null): Collection
|
||||
{
|
||||
/** @var User $scopedUser */
|
||||
/** @var ?User $scopedUser */
|
||||
$scopedUser ??= $this->auth->user();
|
||||
|
||||
return Song::query()
|
||||
|
@ -171,7 +171,7 @@ class SongRepository extends Repository
|
|||
/** @return Collection|array<array-key, Song> */
|
||||
public function getByStandardPlaylist(Playlist $playlist, ?User $scopedUser = null): Collection
|
||||
{
|
||||
/** @var User $scopedUser */
|
||||
/** @var ?User $scopedUser */
|
||||
$scopedUser ??= $this->auth->user();
|
||||
|
||||
return Song::query()
|
||||
|
@ -187,7 +187,7 @@ class SongRepository extends Repository
|
|||
/** @return Collection|array<array-key, Song> */
|
||||
public function getRandom(int $limit, ?User $scopedUser = null): Collection
|
||||
{
|
||||
/** @var User $scopedUser */
|
||||
/** @var ?User $scopedUser */
|
||||
$scopedUser ??= $this->auth->user();
|
||||
|
||||
return Song::query()
|
||||
|
@ -201,7 +201,7 @@ class SongRepository extends Repository
|
|||
/** @return Collection|array<array-key, Song> */
|
||||
public function getMany(array $ids, bool $inThatOrder = false, ?User $scopedUser = null): Collection
|
||||
{
|
||||
/** @var User $scopedUser */
|
||||
/** @var ?User $scopedUser */
|
||||
$scopedUser ??= $this->auth->user();
|
||||
|
||||
$songs = Song::query()
|
||||
|
@ -215,7 +215,7 @@ class SongRepository extends Repository
|
|||
|
||||
public function getOne($id, ?User $scopedUser = null): Song
|
||||
{
|
||||
/** @var User $scopedUser */
|
||||
/** @var ?User $scopedUser */
|
||||
$scopedUser ??= $this->auth->user();
|
||||
|
||||
return Song::query()
|
||||
|
@ -226,7 +226,7 @@ class SongRepository extends Repository
|
|||
|
||||
public function findOne($id, ?User $scopedUser = null): ?Song
|
||||
{
|
||||
/** @var User $scopedUser */
|
||||
/** @var ?User $scopedUser */
|
||||
$scopedUser ??= $this->auth->user();
|
||||
|
||||
return Song::query()
|
||||
|
@ -248,7 +248,7 @@ class SongRepository extends Repository
|
|||
/** @return Collection|array<array-key, Song> */
|
||||
public function getRandomByGenre(string $genre, int $limit, ?User $scopedUser = null): Collection
|
||||
{
|
||||
/** @var User $scopedUser */
|
||||
/** @var ?User $scopedUser */
|
||||
$scopedUser ??= $this->auth->user();
|
||||
|
||||
return Song::query()
|
||||
|
|
|
@ -2,14 +2,11 @@
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
||||
use App\Models\Playlist;
|
||||
use App\Models\Song;
|
||||
use App\Models\SongZipArchive;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Collection;
|
||||
use InvalidArgumentException;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class DownloadService
|
||||
{
|
||||
|
@ -17,57 +14,7 @@ class DownloadService
|
|||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic method to generate a download archive from various source types.
|
||||
*
|
||||
* @return string Full path to the generated archive
|
||||
*/
|
||||
public function from(Playlist|Song|Album|Artist|Collection $downloadable): string
|
||||
{
|
||||
switch (get_class($downloadable)) {
|
||||
case Song::class:
|
||||
return $this->fromSong($downloadable);
|
||||
|
||||
case Collection::class:
|
||||
case EloquentCollection::class:
|
||||
return $this->fromMultipleSongs($downloadable);
|
||||
|
||||
case Album::class:
|
||||
return $this->fromAlbum($downloadable);
|
||||
|
||||
case Artist::class:
|
||||
return $this->fromArtist($downloadable);
|
||||
|
||||
case Playlist::class:
|
||||
return $this->fromPlaylist($downloadable);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unsupported download type.');
|
||||
}
|
||||
|
||||
public function fromSong(Song $song): string
|
||||
{
|
||||
if ($song->s3_params) {
|
||||
// The song is hosted on Amazon S3.
|
||||
// We download it back to our local server first.
|
||||
$url = $this->s3Service->getSongPublicUrl($song);
|
||||
abort_unless((bool) $url, 404);
|
||||
|
||||
$localPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . basename($song->s3_params['key']);
|
||||
|
||||
// The following function requires allow_url_fopen to be ON.
|
||||
// We're just assuming that to be the case here.
|
||||
copy($url, $localPath);
|
||||
} else {
|
||||
// The song is hosted locally. Make sure the file exists.
|
||||
$localPath = $song->path;
|
||||
abort_unless(file_exists($localPath), 404);
|
||||
}
|
||||
|
||||
return $localPath;
|
||||
}
|
||||
|
||||
private function fromMultipleSongs(Collection $songs): string
|
||||
public function from(Collection $songs): string
|
||||
{
|
||||
if ($songs->count() === 1) {
|
||||
return $this->fromSong($songs->first());
|
||||
|
@ -79,24 +26,25 @@ class DownloadService
|
|||
->getPath();
|
||||
}
|
||||
|
||||
private function fromPlaylist(Playlist $playlist): string
|
||||
public function fromSong(Song $song): string
|
||||
{
|
||||
return $this->fromMultipleSongs($playlist->songs);
|
||||
}
|
||||
if ($song->s3_params) {
|
||||
// The song is hosted on Amazon S3.
|
||||
// We download it back to our local server first.
|
||||
$url = $this->s3Service->getSongPublicUrl($song);
|
||||
abort_unless((bool) $url, Response::HTTP_NOT_FOUND);
|
||||
|
||||
private function fromAlbum(Album $album): string
|
||||
{
|
||||
return $this->fromMultipleSongs($album->songs);
|
||||
}
|
||||
$localPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . basename($song->s3_params['key']);
|
||||
|
||||
public function fromArtist(Artist $artist): string
|
||||
{
|
||||
// We cater to the case where the artist is an "album artist," which means she has songs through albums as well.
|
||||
$songs = $artist->albums->reduce(
|
||||
static fn (Collection $songs, Album $album) => $songs->merge($album->songs),
|
||||
$artist->songs
|
||||
)->unique('id');
|
||||
// The following function requires allow_url_fopen to be ON.
|
||||
// We're just assuming that to be the case here.
|
||||
copy($url, $localPath);
|
||||
} else {
|
||||
// The song is hosted locally. Make sure the file exists.
|
||||
$localPath = $song->path;
|
||||
abort_unless(File::exists($localPath), Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
return $this->fromMultipleSongs($songs);
|
||||
return $localPath;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ class LicenseService
|
|||
return Cache::get('license_status');
|
||||
}
|
||||
|
||||
/** @var License $license */
|
||||
/** @var ?License $license */
|
||||
$license = License::query()->latest()->first();
|
||||
|
||||
if (!$license) {
|
||||
|
|
|
@ -30,12 +30,14 @@ Route::middleware('web')->group(static function (): void {
|
|||
Route::middleware('audio.auth')->group(static function (): void {
|
||||
Route::get('play/{song}/{transcode?}/{bitrate?}', PlayController::class)->name('song.play');
|
||||
|
||||
Route::prefix('download')->group(static function (): void {
|
||||
Route::get('songs', DownloadSongsController::class);
|
||||
Route::get('album/{album}', DownloadAlbumController::class);
|
||||
Route::get('artist/{artist}', DownloadArtistController::class);
|
||||
Route::get('playlist/{playlist}', DownloadPlaylistController::class);
|
||||
Route::get('favorites', DownloadFavoritesController::class);
|
||||
});
|
||||
if (config('koel.download.allow')) {
|
||||
Route::prefix('download')->group(static function (): void {
|
||||
Route::get('songs', DownloadSongsController::class);
|
||||
Route::get('album/{album}', DownloadAlbumController::class);
|
||||
Route::get('artist/{artist}', DownloadArtistController::class);
|
||||
Route::get('playlist/{playlist}', DownloadPlaylistController::class);
|
||||
Route::get('favorites', DownloadFavoritesController::class);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,7 +19,7 @@ class UploadTest extends TestCase
|
|||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->file = UploadedFile::fromFile(test_path('songs/full.mp3'), 'song.mp3');
|
||||
$this->file = UploadedFile::fromFile(test_path('songs/full.mp3'), 'song.mp3'); //@phpstan-ignore-line
|
||||
}
|
||||
|
||||
public function testUnauthorizedPost(): void
|
||||
|
|
|
@ -37,11 +37,10 @@ class MediaScannerTest extends TestCase
|
|||
|
||||
public function testScan(): void
|
||||
{
|
||||
/** @var User $owner */
|
||||
$owner = User::factory()->admin()->create();
|
||||
|
||||
$this->expectsEvents(MediaScanCompleted::class);
|
||||
|
||||
/** @var User $owner */
|
||||
$owner = User::factory()->admin()->create();
|
||||
$this->scanner->scan(ScanConfiguration::make(owner: $owner));
|
||||
|
||||
// Standard mp3 files under root path should be recognized
|
||||
|
@ -94,10 +93,11 @@ class MediaScannerTest extends TestCase
|
|||
|
||||
public function testModifiedFileIsRescanned(): void
|
||||
{
|
||||
$config = ScanConfiguration::make(owner: User::factory()->admin()->create());
|
||||
|
||||
$this->expectsEvents(MediaScanCompleted::class);
|
||||
|
||||
/** @var User $owner */
|
||||
$owner = User::factory()->admin()->create();
|
||||
$config = ScanConfiguration::make(owner: $owner);
|
||||
$this->scanner->scan($config);
|
||||
|
||||
/** @var Song $song */
|
||||
|
@ -111,10 +111,12 @@ class MediaScannerTest extends TestCase
|
|||
|
||||
public function testRescanWithoutForceDoesNotResetData(): void
|
||||
{
|
||||
$config = ScanConfiguration::make(owner: User::factory()->admin()->create());
|
||||
|
||||
$this->expectsEvents(MediaScanCompleted::class);
|
||||
|
||||
/** @var User $owner */
|
||||
$owner = User::factory()->admin()->create();
|
||||
$config = ScanConfiguration::make(owner: $owner);
|
||||
|
||||
$this->scanner->scan($config);
|
||||
|
||||
/** @var Song $song */
|
||||
|
@ -134,11 +136,10 @@ class MediaScannerTest extends TestCase
|
|||
|
||||
public function testForceScanResetsData(): void
|
||||
{
|
||||
/** @var User $owner */
|
||||
$owner = User::factory()->admin()->create();
|
||||
|
||||
$this->expectsEvents(MediaScanCompleted::class);
|
||||
|
||||
/** @var User $owner */
|
||||
$owner = User::factory()->admin()->create();
|
||||
$this->scanner->scan(ScanConfiguration::make(owner: $owner));
|
||||
|
||||
/** @var Song $song */
|
||||
|
@ -149,7 +150,9 @@ class MediaScannerTest extends TestCase
|
|||
'lyrics' => 'Booom Wroooom',
|
||||
]);
|
||||
|
||||
$this->scanner->scan(ScanConfiguration::make(owner: User::factory()->admin()->create(), force: true));
|
||||
/** @var User $anotherOwner */
|
||||
$anotherOwner = User::factory()->admin()->create();
|
||||
$this->scanner->scan(ScanConfiguration::make(owner: $anotherOwner, force: true));
|
||||
|
||||
$song->refresh();
|
||||
|
||||
|
@ -161,11 +164,10 @@ class MediaScannerTest extends TestCase
|
|||
|
||||
public function testScanWithIgnoredTags(): void
|
||||
{
|
||||
/** @var User $owner */
|
||||
$owner = User::factory()->admin()->create();
|
||||
|
||||
$this->expectsEvents(MediaScanCompleted::class);
|
||||
|
||||
/** @var User $owner */
|
||||
$owner = User::factory()->admin()->create();
|
||||
$this->scanner->scan(ScanConfiguration::make(owner: $owner));
|
||||
|
||||
/** @var Song $song */
|
||||
|
@ -186,10 +188,10 @@ class MediaScannerTest extends TestCase
|
|||
|
||||
public function testScanAllTagsForNewFilesRegardlessOfIgnoredOption(): void
|
||||
{
|
||||
$this->expectsEvents(MediaScanCompleted::class);
|
||||
|
||||
/** @var User $owner */
|
||||
$owner = User::factory()->admin()->create();
|
||||
|
||||
$this->expectsEvents(MediaScanCompleted::class);
|
||||
$this->scanner->scan(ScanConfiguration::make(owner: $owner));
|
||||
|
||||
/** @var Song $song */
|
||||
|
@ -214,11 +216,14 @@ class MediaScannerTest extends TestCase
|
|||
{
|
||||
$this->expectsEvents(LibraryChanged::class);
|
||||
|
||||
/** @var User $owner */
|
||||
$owner = User::factory()->admin()->create();
|
||||
|
||||
$path = $this->path('/blank.mp3');
|
||||
|
||||
$this->scanner->scanWatchRecord(
|
||||
new InotifyWatchRecord("CLOSE_WRITE,CLOSE $path"),
|
||||
ScanConfiguration::make(owner: User::factory()->admin()->create())
|
||||
ScanConfiguration::make(owner: $owner)
|
||||
);
|
||||
|
||||
self::assertDatabaseHas(Song::class, ['path' => $path]);
|
||||
|
@ -228,6 +233,9 @@ class MediaScannerTest extends TestCase
|
|||
{
|
||||
$this->expectsEvents(LibraryChanged::class);
|
||||
|
||||
/** @var User $owner */
|
||||
$owner = User::factory()->admin()->create();
|
||||
|
||||
static::createSampleMediaSet();
|
||||
|
||||
/** @var Song $song */
|
||||
|
@ -235,7 +243,7 @@ class MediaScannerTest extends TestCase
|
|||
|
||||
$this->scanner->scanWatchRecord(
|
||||
new InotifyWatchRecord("DELETE $song->path"),
|
||||
ScanConfiguration::make(owner: User::factory()->admin()->create())
|
||||
ScanConfiguration::make(owner: $owner)
|
||||
);
|
||||
|
||||
self::assertModelMissing($song);
|
||||
|
@ -243,10 +251,12 @@ class MediaScannerTest extends TestCase
|
|||
|
||||
public function testScanDeletedDirectoryViaWatch(): void
|
||||
{
|
||||
$config = ScanConfiguration::make(owner: User::factory()->admin()->create());
|
||||
|
||||
$this->expectsEvents(LibraryChanged::class, MediaScanCompleted::class);
|
||||
|
||||
/** @var User $owner */
|
||||
$owner = User::factory()->admin()->create();
|
||||
$config = ScanConfiguration::make(owner: $owner);
|
||||
|
||||
$this->scanner->scan($config);
|
||||
$this->scanner->scanWatchRecord(new InotifyWatchRecord("MOVED_FROM,ISDIR $this->mediaPath/subdir"), $config);
|
||||
|
||||
|
@ -290,7 +300,9 @@ class MediaScannerTest extends TestCase
|
|||
|
||||
public function testOptionallyIgnoreHiddenFiles(): void
|
||||
{
|
||||
$config = ScanConfiguration::make(owner: User::factory()->admin()->create());
|
||||
/** @var User $owner */
|
||||
$owner = User::factory()->admin()->create();
|
||||
$config = ScanConfiguration::make(owner: $owner);
|
||||
|
||||
config(['koel.ignore_dot_files' => false]);
|
||||
$this->scanner->scan($config);
|
||||
|
|
|
@ -51,7 +51,7 @@ class UploadServiceTest extends TestCase
|
|||
/** @var User $user */
|
||||
$user = User::factory()->create();
|
||||
|
||||
$song = $this->service->handleUploadedFile(UploadedFile::fromFile(test_path('songs/full.mp3')), $user);
|
||||
$song = $this->service->handleUploadedFile(UploadedFile::fromFile(test_path('songs/full.mp3')), $user); //@phpstan-ignore-line
|
||||
|
||||
self::assertSame($song->owner_id, $user->id);
|
||||
self::assertSame(public_path("sandbox/media/__KOEL_UPLOADS_\${$user->id}__/full.mp3"), $song->path);
|
||||
|
|
|
@ -10,9 +10,6 @@ use App\Services\CommunityLicenseService;
|
|||
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Testing\TestResponse;
|
||||
use ReflectionClass;
|
||||
use Tests\Traits\CreatesApplication;
|
||||
use Tests\Traits\SandboxesTests;
|
||||
|
@ -29,18 +26,6 @@ abstract class TestCase extends BaseTestCase
|
|||
parent::setUp();
|
||||
|
||||
License::swap($this->app->make(CommunityLicenseService::class));
|
||||
|
||||
TestResponse::macro('log', function (string $file = 'test-response.json'): TestResponse {
|
||||
/** @var TestResponse $this */
|
||||
File::put(storage_path('logs/' . $file), $this->getContent());
|
||||
|
||||
return $this;
|
||||
});
|
||||
|
||||
UploadedFile::macro('fromFile', static function (string $path, ?string $name = null): UploadedFile {
|
||||
return UploadedFile::fake()->createWithContent($name ?? basename($path), File::get($path));
|
||||
});
|
||||
|
||||
self::createSandbox();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue