chore: fix CS

This commit is contained in:
Phan An 2020-12-22 21:11:22 +01:00
parent a90d961440
commit 560d41bf1d
185 changed files with 801 additions and 642 deletions

View file

@ -1,15 +1,33 @@
name: CI name: CI
on: on: [push]
push:
branches:
- master
- actions
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
php-version: [ 7.4, 8.0 ]
fail-fast: false
steps:
- uses: actions/checkout@v1
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
tools: composer:v2
coverage: xdebug
- name: Copy .env file
run: cp .env.ci .env
- name: Install dependencies
run: composer install --prefer-dist --no-interaction --no-scripts --no-progress
- name: Generate app key
run: php artisan key:generate --quiet
- name: Run code style checker
run: composer cs
- name: Run static analysis
run: composer analyze -- --no-progress
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Composer - name: Composer

View file

@ -23,7 +23,7 @@ class Application extends IlluminateApplication
* *
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
public function rev(string $file, string $manifestFile = null): string public function rev(string $file, ?string $manifestFile = null): string
{ {
static $manifest = null; static $manifest = null;
@ -53,6 +53,6 @@ class Application extends IlluminateApplication
{ {
$cdnUrl = trim(config('koel.cdn.url'), '/ '); $cdnUrl = trim(config('koel.cdn.url'), '/ ');
return $cdnUrl ? $cdnUrl.'/'.trim(ltrim($name, '/')) : trim(asset($name)); return $cdnUrl ? $cdnUrl . '/' . trim(ltrim($name, '/')) : trim(asset($name));
} }
} }

View file

@ -20,6 +20,7 @@ class ChangePasswordCommand extends Command
public function __construct(Hash $hash) public function __construct(Hash $hash)
{ {
parent::__construct(); parent::__construct();
$this->hash = $hash; $this->hash = $hash;
} }

View file

@ -8,12 +8,12 @@ use App\Models\Setting;
use App\Models\User; use App\Models\User;
use App\Repositories\SettingRepository; use App\Repositories\SettingRepository;
use App\Services\MediaCacheService; use App\Services\MediaCacheService;
use Exception;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Kernel as Artisan; use Illuminate\Contracts\Console\Kernel as Artisan;
use Illuminate\Contracts\Hashing\Hasher as Hash; use Illuminate\Contracts\Hashing\Hasher as Hash;
use Illuminate\Database\DatabaseManager as DB; use Illuminate\Database\DatabaseManager as DB;
use Jackiedo\DotenvEditor\DotenvEditor; use Jackiedo\DotenvEditor\DotenvEditor;
use Throwable;
class InitCommand extends Command class InitCommand extends Command
{ {
@ -51,7 +51,7 @@ class InitCommand extends Command
{ {
$this->comment('Attempting to install or upgrade Koel.'); $this->comment('Attempting to install or upgrade Koel.');
$this->comment('Remember, you can always install/upgrade manually following the guide here:'); $this->comment('Remember, you can always install/upgrade manually following the guide here:');
$this->info('📙 '.config('koel.misc.docs_url').PHP_EOL); $this->info('📙 ' . config('koel.misc.docs_url') . PHP_EOL);
if ($this->inNoInteractionMode()) { if ($this->inNoInteractionMode()) {
$this->info('Running in no-interaction mode'); $this->info('Running in no-interaction mode');
@ -64,25 +64,25 @@ class InitCommand extends Command
$this->maybeSeedDatabase(); $this->maybeSeedDatabase();
$this->maybeSetMediaPath(); $this->maybeSetMediaPath();
$this->maybeCompileFrontEndAssets(); $this->maybeCompileFrontEndAssets();
} catch (Exception $e) { } catch (Throwable $e) {
$this->error("Oops! Koel installation or upgrade didn't finish successfully."); $this->error("Oops! Koel installation or upgrade didn't finish successfully.");
$this->error('Please try again, or visit '.config('koel.misc.docs_url').' for manual installation.'); $this->error('Please try again, or visit ' . config('koel.misc.docs_url') . ' for manual installation.');
$this->error('😥 Sorry for this. You deserve better.'); $this->error('😥 Sorry for this. You deserve better.');
return; return;
} }
$this->comment(PHP_EOL.'🎆 Success! Koel can now be run from localhost with `php artisan serve`.'); $this->comment(PHP_EOL . '🎆 Success! Koel can now be run from localhost with `php artisan serve`.');
if (Setting::get('media_path')) { if (Setting::get('media_path')) {
$this->comment('You can also scan for media with `php artisan koel:sync`.'); $this->comment('You can also scan for media with `php artisan koel:sync`.');
} }
$this->comment('Again, visit 📙 '.config('koel.misc.docs_url').' for the official documentation.'); $this->comment('Again, visit 📙 ' . config('koel.misc.docs_url') . ' for the official documentation.');
$this->comment( $this->comment(
"Feeling generous and want to support Koel's development? Check out " "Feeling generous and want to support Koel's development? Check out "
.config('koel.misc.sponsor_github_url') . config('koel.misc.sponsor_github_url')
.' 🤗' . ' 🤗'
); );
$this->comment('Thanks for using Koel. You rock! 🤘'); $this->comment('Thanks for using Koel. You rock! 🤘');
} }
@ -175,7 +175,7 @@ class InitCommand extends Command
return; return;
} }
$this->info('The absolute path to your media directory. If this is skipped (left blank) now, you can set it later via the web interface.'); $this->info('The absolute path to your media directory. If this is skipped (left blank) now, you can set it later via the web interface.'); // @phpcs-ignore-line
while (true) { while (true) {
$path = $this->ask('Media path', config('koel.media_path')); $path = $this->ask('Media path', config('koel.media_path'));
@ -224,9 +224,9 @@ class InitCommand extends Command
$this->db->reconnect()->getPdo(); $this->db->reconnect()->getPdo();
break; break;
} catch (Exception $e) { } catch (Throwable $e) {
$this->error($e->getMessage()); $this->error($e->getMessage());
$this->warn(PHP_EOL."Koel cannot connect to the database. Let's set it up."); $this->warn(PHP_EOL . "Koel cannot connect to the database. Let's set it up.");
$this->setUpDatabase(); $this->setUpDatabase();
} }
} }

View file

@ -25,14 +25,13 @@ class SyncCommand extends Command
private $mediaSyncService; private $mediaSyncService;
private $settingRepository; private $settingRepository;
/** /** @var ProgressBar */
* @var ProgressBar
*/
private $progressBar; private $progressBar;
public function __construct(MediaSyncService $mediaSyncService, SettingRepository $settingRepository) public function __construct(MediaSyncService $mediaSyncService, SettingRepository $settingRepository)
{ {
parent::__construct(); parent::__construct();
$this->mediaSyncService = $mediaSyncService; $this->mediaSyncService = $mediaSyncService;
$this->settingRepository = $settingRepository; $this->settingRepository = $settingRepository;
} }
@ -43,8 +42,9 @@ class SyncCommand extends Command
public function handle(): void public function handle(): void
{ {
$this->ensureMediaPath(); $this->ensureMediaPath();
$record = $this->argument('record');
if (!$record = $this->argument('record')) { if (!$record) {
$this->syncAll(); $this->syncAll();
return; return;
@ -60,7 +60,7 @@ class SyncCommand extends Command
*/ */
protected function syncAll(): void protected function syncAll(): void
{ {
$this->info('Syncing media from '.Setting::get('media_path').PHP_EOL); $this->info('Syncing media from ' . Setting::get('media_path') . PHP_EOL);
// Get the tags to sync. // Get the tags to sync.
// Notice that this is only meaningful for existing records. // Notice that this is only meaningful for existing records.
@ -70,10 +70,10 @@ class SyncCommand extends Command
$this->mediaSyncService->sync(null, $tags, $this->option('force'), $this); $this->mediaSyncService->sync(null, $tags, $this->option('force'), $this);
$this->output->writeln( $this->output->writeln(
PHP_EOL.PHP_EOL PHP_EOL . PHP_EOL
."<info>Completed! {$this->synced} new or updated song(s)</info>, " . "<info>Completed! {$this->synced} new or updated song(s)</info>, "
."{$this->ignored} unchanged song(s), " . "{$this->ignored} unchanged song(s), "
."and <comment>{$this->invalid} invalid file(s)</comment>." . "and <comment>{$this->invalid} invalid file(s)</comment>."
); );
} }
@ -107,7 +107,7 @@ class SyncCommand extends Command
++$this->ignored; ++$this->ignored;
} elseif ($result === FileSynchronizer::SYNC_RESULT_BAD_FILE) { } elseif ($result === FileSynchronizer::SYNC_RESULT_BAD_FILE) {
if ($this->option('verbose')) { if ($this->option('verbose')) {
$this->error(PHP_EOL."'$name' is not a valid media file: ".$reason); $this->error(PHP_EOL . "'$name' is not a valid media file: " . $reason);
} }
++$this->invalid; ++$this->invalid;

View file

@ -16,6 +16,7 @@ class TidyLibraryCommand extends Command
public function __construct(MediaSyncService $mediaSyncService) public function __construct(MediaSyncService $mediaSyncService)
{ {
parent::__construct(); parent::__construct();
$this->mediaSyncService = $mediaSyncService; $this->mediaSyncService = $mediaSyncService;
} }

View file

@ -6,8 +6,8 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel class Kernel extends ConsoleKernel
{ {
protected function commands() protected function commands(): void
{ {
$this->load(__DIR__.'/Commands'); $this->load(__DIR__ . '/Commands');
} }
} }

View file

@ -24,7 +24,7 @@ class AlbumInformationFetched extends Event
} }
/** /**
* @return mixed[] * @return array<mixed>
*/ */
public function getInformation(): array public function getInformation(): array
{ {

View file

@ -24,7 +24,7 @@ class ArtistInformationFetched
} }
/** /**
* @return mixed[] * @return array<mixed>
*/ */
public function getInformation(): array public function getInformation(): array
{ {

View file

@ -7,9 +7,9 @@ abstract class Event
/** /**
* Get the channels the event should be broadcast on. * Get the channels the event should be broadcast on.
* *
* @return array * @return array<mixed>
*/ */
public function broadcastOn() public function broadcastOn(): array
{ {
return []; return [];
} }

View file

@ -13,7 +13,7 @@ class SongLikeToggled extends Event
public $interaction; public $interaction;
public $user; public $user;
public function __construct(Interaction $interaction, User $user = null) public function __construct(Interaction $interaction, ?User $user = null)
{ {
$this->interaction = $interaction; $this->interaction = $interaction;
$this->user = $user ?: auth()->user(); $this->user = $user ?: auth()->user();

View file

@ -10,7 +10,7 @@ use Illuminate\Support\Facades\Facade;
*/ */
class Download extends Facade class Download extends Facade
{ {
protected static function getFacadeAccessor() protected static function getFacadeAccessor(): string
{ {
return 'Download'; return 'Download';
} }

View file

@ -4,9 +4,9 @@ namespace App\Facades;
use Illuminate\Support\Facades\Facade; use Illuminate\Support\Facades\Facade;
class iTunes extends Facade class ITunes extends Facade
{ {
protected static function getFacadeAccessor() protected static function getFacadeAccessor(): string
{ {
return 'iTunes'; return 'iTunes';
} }

View file

@ -9,7 +9,7 @@ use Illuminate\Support\Facades\Facade;
*/ */
class Util extends Facade class Util extends Facade
{ {
protected static function getFacadeAccessor() protected static function getFacadeAccessor(): string
{ {
return 'Util'; return 'Util';
} }

13
app/Facades/YouTube.php Normal file
View file

@ -0,0 +1,13 @@
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class YouTube extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'YouTube';
}
}

View file

@ -10,11 +10,11 @@ use Throwable;
class SmartPlaylistRuleParameterFactory class SmartPlaylistRuleParameterFactory
{ {
/** /**
* @param mixed[] $value * @param array<mixed> $value
* *
* @throws Throwable * @throws Throwable
* *
* @return string[] * @return array<string>
*/ */
public function createParameters(string $model, string $operator, array $value): array public function createParameters(string $model, string $operator, array $value): array
{ {

View file

@ -4,12 +4,12 @@ namespace App\Helpers;
function album_cover_path(string $fileName): string function album_cover_path(string $fileName): string
{ {
return public_path(config('koel.album_cover_dir').$fileName); return public_path(config('koel.album_cover_dir') . $fileName);
} }
function album_cover_url(string $fileName): string function album_cover_url(string $fileName): string
{ {
return app()->staticUrl(config('koel.album_cover_dir').$fileName); return app()->staticUrl(config('koel.album_cover_dir') . $fileName);
} }
/** /**
@ -22,10 +22,10 @@ function album_thumbnail_url(string $fileName): string
function artist_image_path(string $fileName): string function artist_image_path(string $fileName): string
{ {
return public_path(config('koel.artist_image_dir').$fileName); return public_path(config('koel.artist_image_dir') . $fileName);
} }
function artist_image_url(string $fileName): string function artist_image_url(string $fileName): string
{ {
return app()->staticUrl(config('koel.artist_image_dir').$fileName); return app()->staticUrl(config('koel.artist_image_dir') . $fileName);
} }

View file

@ -9,7 +9,7 @@ use App\Repositories\PlaylistRepository;
use App\Repositories\SettingRepository; use App\Repositories\SettingRepository;
use App\Repositories\UserRepository; use App\Repositories\UserRepository;
use App\Services\ApplicationInformationService; use App\Services\ApplicationInformationService;
use App\Services\iTunesService; use App\Services\ITunesService;
use App\Services\LastfmService; use App\Services\LastfmService;
use App\Services\MediaCacheService; use App\Services\MediaCacheService;
use App\Services\YouTubeService; use App\Services\YouTubeService;
@ -35,7 +35,7 @@ class DataController extends Controller
public function __construct( public function __construct(
LastfmService $lastfmService, LastfmService $lastfmService,
YouTubeService $youTubeService, YouTubeService $youTubeService,
iTunesService $iTunesService, ITunesService $iTunesService,
MediaCacheService $mediaCacheService, MediaCacheService $mediaCacheService,
SettingRepository $settingRepository, SettingRepository $settingRepository,
PlaylistRepository $playlistRepository, PlaylistRepository $playlistRepository,

View file

@ -16,6 +16,7 @@ class RecentlyPlayedController extends Controller
?Authenticatable $currentUser ?Authenticatable $currentUser
) { ) {
parent::__construct($interactionService, $currentUser); parent::__construct($interactionService, $currentUser);
$this->interactionRepository = $interactionRepository; $this->interactionRepository = $interactionRepository;
} }

View file

@ -13,6 +13,7 @@ class SongController extends Controller
public function __construct(MediaInformationService $mediaInformationService, YouTubeService $youTubeService) public function __construct(MediaInformationService $mediaInformationService, YouTubeService $youTubeService)
{ {
parent::__construct($mediaInformationService); parent::__construct($mediaInformationService);
$this->youTubeService = $youTubeService; $this->youTubeService = $youTubeService;
} }

View file

@ -38,9 +38,14 @@ class SongController extends Controller
$compilation = (bool) trim(array_get($tags, 'albumartist')); $compilation = (bool) trim(array_get($tags, 'albumartist'));
$album = Album::getOrCreate($artist, array_get($tags, 'album'), $compilation); $album = Album::getOrCreate($artist, array_get($tags, 'album'), $compilation);
$cover = array_get($tags, 'cover');
if ($cover = array_get($tags, 'cover')) { if ($cover) {
$this->mediaMetadataService->writeAlbumCover($album, base64_decode($cover['data']), $cover['extension']); $this->mediaMetadataService->writeAlbumCover(
$album,
base64_decode($cover['data'], true),
$cover['extension']
);
} }
$song = Song::updateOrCreate(['id' => $this->helperService->getFileHash($path)], [ $song = Song::updateOrCreate(['id' => $this->helperService->getFileHash($path)], [

View file

@ -13,6 +13,7 @@ class FavoritesController extends Controller
public function __construct(DownloadService $downloadService, InteractionRepository $interactionRepository) public function __construct(DownloadService $downloadService, InteractionRepository $interactionRepository)
{ {
parent::__construct($downloadService); parent::__construct($downloadService);
$this->interactionRepository = $interactionRepository; $this->interactionRepository = $interactionRepository;
} }

View file

@ -13,6 +13,7 @@ class SongController extends Controller
public function __construct(DownloadService $downloadService, SongRepository $songRepository) public function __construct(DownloadService $downloadService, SongRepository $songRepository)
{ {
parent::__construct($downloadService); parent::__construct($downloadService);
$this->songRepository = $songRepository; $this->songRepository = $songRepository;
} }

View file

@ -4,16 +4,16 @@ namespace App\Http\Controllers;
use App\Http\Requests\API\ViewSongOnITunesRequest; use App\Http\Requests\API\ViewSongOnITunesRequest;
use App\Models\Album; use App\Models\Album;
use App\Services\iTunesService; use App\Services\ITunesService;
use App\Services\TokenManager; use App\Services\TokenManager;
use Illuminate\Http\Response; use Illuminate\Http\Response;
class iTunesController extends Controller class ITunesController extends Controller
{ {
private $iTunesService; private $iTunesService;
private $tokenManager; private $tokenManager;
public function __construct(iTunesService $iTunesService, TokenManager $tokenManager) public function __construct(ITunesService $iTunesService, TokenManager $tokenManager)
{ {
$this->iTunesService = $iTunesService; $this->iTunesService = $iTunesService;
$this->tokenManager = $tokenManager; $this->tokenManager = $tokenManager;

View file

@ -5,6 +5,7 @@ namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Contracts\Auth\Guard; use Illuminate\Contracts\Auth\Guard;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class Authenticate class Authenticate
{ {
@ -15,7 +16,7 @@ class Authenticate
$this->auth = $auth; $this->auth = $auth;
} }
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next): Response
{ {
if ($this->auth->guest()) { if ($this->auth->guest()) {
if ($request->ajax() || $request->route()->getName() === 'play') { if ($request->ajax() || $request->route()->getName() === 'play') {

View file

@ -5,6 +5,7 @@ namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Routing\UrlGenerator; use Illuminate\Routing\UrlGenerator;
use Symfony\Component\HttpFoundation\Response;
class ForceHttps class ForceHttps
{ {
@ -15,7 +16,7 @@ class ForceHttps
$this->url = $url; $this->url = $url;
} }
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next): Response
{ {
if (config('koel.force_https')) { if (config('koel.force_https')) {
$this->url->forceScheme('https'); $this->url->forceScheme('https');

View file

@ -4,6 +4,7 @@ namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
/** /**
* Authenticate requests from Object Storage services (like S3). * Authenticate requests from Object Storage services (like S3).
@ -11,7 +12,7 @@ use Illuminate\Http\Request;
*/ */
class ObjectStorageAuthenticate class ObjectStorageAuthenticate
{ {
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next): Response
{ {
if ($request->appKey !== config('app.key')) { if ($request->appKey !== config('app.key')) {
return response('Unauthorized.', 401); return response('Unauthorized.', 401);

View file

@ -11,6 +11,7 @@ abstract class AbstractMediaImageUpdateRequest extends Request
return auth()->user()->is_admin; return auth()->user()->is_admin;
} }
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [
@ -20,15 +21,15 @@ abstract class AbstractMediaImageUpdateRequest extends Request
public function getFileContentAsBinaryString(): string public function getFileContentAsBinaryString(): string
{ {
[$_, $data] = explode(',', $this->{$this->getImageFieldName()}); [, $data] = explode(',', $this->{$this->getImageFieldName()});
return base64_decode($data); return base64_decode($data, true);
} }
public function getFileExtension(): string public function getFileExtension(): string
{ {
[$type, $data] = explode(';', $this->{$this->getImageFieldName()}); [$type,] = explode(';', $this->{$this->getImageFieldName()});
[$_, $extension] = explode('/', $type); [, $extension] = explode('/', $type);
return $extension; return $extension;
} }

View file

@ -3,10 +3,11 @@
namespace App\Http\Requests\API; namespace App\Http\Requests\API;
/** /**
* @property string[] $songs * @property array<string> $songs
*/ */
class BatchInteractionRequest extends Request class BatchInteractionRequest extends Request
{ {
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -9,6 +9,7 @@ use App\Http\Requests\API\Request;
*/ */
class StorePlayCountRequest extends Request class StorePlayCountRequest extends Request
{ {
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -8,6 +8,7 @@ namespace App\Http\Requests\API;
*/ */
class LastfmCallbackRequest extends Request class LastfmCallbackRequest extends Request
{ {
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -7,6 +7,7 @@ namespace App\Http\Requests\API;
*/ */
class LastfmSetSessionKeyRequest extends Request class LastfmSetSessionKeyRequest extends Request
{ {
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -6,6 +6,7 @@ use App\Http\Requests\API\Request as BaseRequest;
class Request extends BaseRequest class Request extends BaseRequest
{ {
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -6,11 +6,12 @@ use App\Http\Requests\API\ObjectStorage\S3\Request as BaseRequest;
/** /**
* @property string $bucket * @property string $bucket
* @property string[] $tags * @property array<string> $tags
* @property string $key * @property string $key
*/ */
class PutSongRequest extends BaseRequest class PutSongRequest extends BaseRequest
{ {
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -10,6 +10,7 @@ use App\Http\Requests\API\ObjectStorage\S3\Request as BaseRequest;
*/ */
class RemoveSongRequest extends BaseRequest class RemoveSongRequest extends BaseRequest
{ {
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -3,12 +3,13 @@
namespace App\Http\Requests\API; namespace App\Http\Requests\API;
/** /**
* @property string[] $songs * @property array<string> $songs
* @property string $name * @property string $name
* @property array $rules * @property array $rules
*/ */
class PlaylistStoreRequest extends Request class PlaylistStoreRequest extends Request
{ {
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -3,10 +3,11 @@
namespace App\Http\Requests\API; namespace App\Http\Requests\API;
/** /**
* @property string[] $songs * @property array<string> $songs
*/ */
class PlaylistSyncRequest extends Request class PlaylistSyncRequest extends Request
{ {
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -7,11 +7,12 @@ namespace App\Http\Requests\API;
*/ */
class ProfileUpdateRequest extends Request class ProfileUpdateRequest extends Request
{ {
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [
'name' => 'required', 'name' => 'required',
'email' => 'required|email|unique:users,email,'.auth()->user()->id, 'email' => 'required|email|unique:users,email,' . auth()->user()->id,
]; ];
} }
} }

View file

@ -7,6 +7,7 @@ namespace App\Http\Requests\API;
*/ */
class ScrobbleStoreRequest extends Request class ScrobbleStoreRequest extends Request
{ {
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return ['timestamp' => 'required|numeric']; return ['timestamp' => 'required|numeric'];

View file

@ -12,6 +12,7 @@ class SettingRequest extends Request
return auth()->user()->is_admin; return auth()->user()->is_admin;
} }
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -3,8 +3,8 @@
namespace App\Http\Requests\API; namespace App\Http\Requests\API;
/** /**
* @property string[] $songs * @property array<string> $songs
* @property mixed[] $data * @property array<mixed> $data
*/ */
class SongUpdateRequest extends Request class SongUpdateRequest extends Request
{ {
@ -13,6 +13,7 @@ class SongUpdateRequest extends Request
return $this->user()->is_admin; return $this->user()->is_admin;
} }
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -13,6 +13,7 @@ class UploadRequest extends AbstractRequest
return auth()->user()->is_admin; return auth()->user()->is_admin;
} }
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -8,6 +8,7 @@ namespace App\Http\Requests\API;
*/ */
class UserLoginRequest extends Request class UserLoginRequest extends Request
{ {
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -15,6 +15,7 @@ class UserStoreRequest extends Request
return auth()->user()->is_admin; return auth()->user()->is_admin;
} }
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -17,6 +17,7 @@ class UserUpdateRequest extends Request
return auth()->user()->is_admin; return auth()->user()->is_admin;
} }
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
/** @var User $user */ /** @var User $user */
@ -24,7 +25,7 @@ class UserUpdateRequest extends Request
return [ return [
'name' => 'required', 'name' => 'required',
'email' => 'required|email|unique:users,email,'.$user->id, 'email' => 'required|email|unique:users,email,' . $user->id,
]; ];
} }
} }

View file

@ -8,6 +8,7 @@ namespace App\Http\Requests\API;
*/ */
class ViewSongOnITunesRequest extends Request class ViewSongOnITunesRequest extends Request
{ {
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -11,6 +11,7 @@ abstract class AbstractRequest extends FormRequest
return true; return true;
} }
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return []; return [];

View file

@ -7,6 +7,7 @@ namespace App\Http\Requests\Download;
*/ */
class SongRequest extends Request class SongRequest extends Request
{ {
/** @return array<mixed> */
public function rules(): array public function rules(): array
{ {
return [ return [

View file

@ -16,6 +16,5 @@ abstract class Job
| provides access to the "onQueue" and "delay" queue helper methods. | provides access to the "onQueue" and "delay" queue helper methods.
| |
*/ */
use Queueable; use Queueable;
} }

View file

@ -10,6 +10,7 @@ class InotifyWatchRecord extends WatchRecord implements WatchRecordInterface
public function __construct(string $input) public function __construct(string $input)
{ {
parent::__construct($input); parent::__construct($input);
$this->parse($input); $this->parse($input);
} }

View file

@ -53,7 +53,7 @@ abstract class WatchRecord implements WatchRecordInterface
return $this->path; return $this->path;
} }
public function __toString() public function __toString(): string
{ {
return $this->input; return $this->input;
} }

View file

@ -4,7 +4,7 @@ namespace App\Libraries\WatchRecord;
interface WatchRecordInterface interface WatchRecordInterface
{ {
public function parse(string $string); public function parse(string $string): void;
public function getPath(): string; public function getPath(): string;

View file

@ -4,7 +4,7 @@ namespace App\Listeners;
use App\Events\AlbumInformationFetched; use App\Events\AlbumInformationFetched;
use App\Services\MediaMetadataService; use App\Services\MediaMetadataService;
use Exception; use Throwable;
class DownloadAlbumCover class DownloadAlbumCover
{ {
@ -26,7 +26,7 @@ class DownloadAlbumCover
if (!$album->has_cover && $image && ini_get('allow_url_fopen')) { if (!$album->has_cover && $image && ini_get('allow_url_fopen')) {
try { try {
$this->mediaMetadataService->downloadAlbumCover($album, $image); $this->mediaMetadataService->downloadAlbumCover($album, $image);
} catch (Exception $e) { } catch (Throwable $e) {
} }
} }
} }

View file

@ -4,7 +4,7 @@ namespace App\Listeners;
use App\Events\ArtistInformationFetched; use App\Events\ArtistInformationFetched;
use App\Services\MediaMetadataService; use App\Services\MediaMetadataService;
use Exception; use Throwable;
class DownloadArtistImage class DownloadArtistImage
{ {
@ -26,7 +26,7 @@ class DownloadArtistImage
if (!$artist->has_image && $image && ini_get('allow_url_fopen')) { if (!$artist->has_image && $image && ini_get('allow_url_fopen')) {
try { try {
$this->mediaMetadataService->downloadArtistImage($artist, $image); $this->mediaMetadataService->downloadArtistImage($artist, $image);
} catch (Exception $e) { } catch (Throwable $e) {
} }
} }
} }

View file

@ -13,6 +13,6 @@ class JWTEventListener
public function subscribe(Dispatcher $events): void public function subscribe(Dispatcher $events): void
{ {
$events->listen('tymon.jwt.valid', self::class.'@onValidUser'); $events->listen('tymon.jwt.valid', self::class . '@onValidUser');
} }
} }

View file

@ -17,8 +17,9 @@ class LoveTrackOnLastfm
public function handle(SongLikeToggled $event): void public function handle(SongLikeToggled $event): void
{ {
if (!$this->lastfm->enabled() || if (
!($sessionKey = $event->user->lastfm_session_key) || !$this->lastfm->enabled() ||
!$event->user->lastfm_session_key ||
$event->interaction->song->artist->is_unknown $event->interaction->song->artist->is_unknown
) { ) {
return; return;

View file

@ -17,7 +17,7 @@ class TidyLibrary
/** /**
* @throws Exception * @throws Exception
*/ */
public function handle() public function handle(): void
{ {
$this->mediaSyncService->tidy(); $this->mediaSyncService->tidy();
} }

View file

@ -17,10 +17,7 @@ class UpdateLastfmNowPlaying
public function handle(SongStartedPlaying $event): void public function handle(SongStartedPlaying $event): void
{ {
if (!$this->lastfm->enabled() || if (!$this->lastfm->enabled() || !$event->user->lastfm_session_key || $event->song->artist->is_unknown) {
!($sessionKey = $event->user->lastfm_session_key) ||
$event->song->artist->is_unknown
) {
return; return;
} }

View file

@ -2,8 +2,6 @@
namespace App\Models; namespace App\Models;
use function App\Helpers\album_cover_path;
use function App\Helpers\album_cover_url;
use App\Traits\SupportsDeleteWhereIDsNotIn; use App\Traits\SupportsDeleteWhereIDsNotIn;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
@ -12,19 +10,23 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use function App\Helpers\album_cover_path;
use function App\Helpers\album_cover_url;
/** /**
* @property string $cover The album cover's file name * @property string $cover The album cover's file name
* @property string|null $cover_path The absolute path to the cover file * @property string|null $cover_path The absolute path to the cover file
* @property bool $has_cover If the album has a non-default cover image * @property bool $has_cover If the album has a non-default cover image
* @property int $id * @property int $id
* @property string $name Name of the album * @property string $name Name of the album
* @property bool $is_compilation If the album is a compilation from multiple artists * @property bool $is_compilation If the album is a compilation from multiple artists
* @property Artist $artist The album's artist * @property Artist $artist The album's artist
* @property int $artist_id * @property int $artist_id
* @property Collection $songs * @property Collection $songs
* @property bool $is_unknown If the album is the Unknown Album * @property bool $is_unknown If the album is the Unknown Album
* @property string|null $thumbnail_name The file name of the album's thumbnail * @property string|null $thumbnail_name The file name of the album's thumbnail
* @property string|null $thumbnail_path The full path to the thumbnail. Notice that this doesn't guarantee the thumbnail exists. * @property string|null $thumbnail_path The full path to the thumbnail.
* Notice that this doesn't guarantee the thumbnail exists.
* @property string|null $thumbnail The public URL to the album's thumbnail * @property string|null $thumbnail The public URL to the album's thumbnail
* *
* @method static self firstOrCreate(array $where, array $params = []) * @method static self firstOrCreate(array $where, array $params = [])
@ -38,9 +40,9 @@ class Album extends Model
use HasFactory; use HasFactory;
use SupportsDeleteWhereIDsNotIn; use SupportsDeleteWhereIDsNotIn;
const UNKNOWN_ID = 1; public const UNKNOWN_ID = 1;
const UNKNOWN_NAME = 'Unknown Album'; public const UNKNOWN_NAME = 'Unknown Album';
const UNKNOWN_COVER = 'unknown-album.png'; public const UNKNOWN_COVER = 'unknown-album.png';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $hidden = ['updated_at']; protected $hidden = ['updated_at'];

View file

@ -3,8 +3,6 @@
namespace App\Models; namespace App\Models;
use App\Facades\Util; use App\Facades\Util;
use function App\Helpers\artist_image_path;
use function App\Helpers\artist_image_url;
use App\Traits\SupportsDeleteWhereIDsNotIn; use App\Traits\SupportsDeleteWhereIDsNotIn;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
@ -13,6 +11,9 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use function App\Helpers\artist_image_path;
use function App\Helpers\artist_image_url;
/** /**
* @property int $id * @property int $id
* @property string $name * @property string $name
@ -34,10 +35,10 @@ class Artist extends Model
use HasFactory; use HasFactory;
use SupportsDeleteWhereIDsNotIn; use SupportsDeleteWhereIDsNotIn;
const UNKNOWN_ID = 1; public const UNKNOWN_ID = 1;
const UNKNOWN_NAME = 'Unknown Artist'; public const UNKNOWN_NAME = 'Unknown Artist';
const VARIOUS_ID = 2; public const VARIOUS_ID = 2;
const VARIOUS_NAME = 'Various Artists'; public const VARIOUS_NAME = 'Various Artists';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $hidden = ['created_at', 'updated_at']; protected $hidden = ['created_at', 'updated_at'];
@ -87,7 +88,9 @@ class Artist extends Model
public static function getOrCreate(?string $name = null): self public static function getOrCreate(?string $name = null): self
{ {
// Remove the BOM from UTF-8/16/32, as it will mess up the database constraints. // Remove the BOM from UTF-8/16/32, as it will mess up the database constraints.
if ($encoding = Util::detectUTFEncoding($name)) { $encoding = Util::detectUTFEncoding($name);
if ($encoding) {
$name = mb_convert_encoding($name, 'UTF-8', $encoding); $name = mb_convert_encoding($name, 'UTF-8', $encoding);
} }

View file

@ -89,7 +89,13 @@ class Rule
private function validateOperator(string $operator): void private function validateOperator(string $operator): void
{ {
if (!in_array($operator, self::VALID_OPERATORS, true)) { if (!in_array($operator, self::VALID_OPERATORS, true)) {
throw new InvalidArgumentException(sprintf('%s is not a valid value for operators. Valid values are: %s', $operator, implode(', ', self::VALID_OPERATORS))); throw new InvalidArgumentException(
sprintf(
'%s is not a valid value for operators. Valid values are: %s',
$operator,
implode(', ', self::VALID_OPERATORS)
)
);
} }
} }
} }

View file

@ -23,15 +23,13 @@ class Setting extends Model
/** /**
* Get a setting value. * Get a setting value.
* *
* @return mixed|string * @return mixed
*/ */
public static function get(string $key) public static function get(string $key)
{ {
if ($record = self::find($key)) { $record = self::find($key);
return $record->value;
}
return null; return $record ? $record->value : null;
} }
/** /**
@ -39,7 +37,6 @@ class Setting extends Model
* *
* @param string|array $key the key of the setting, or an associative array of settings, * @param string|array $key the key of the setting, or an associative array of settings,
* in which case $value will be discarded * in which case $value will be discarded
* @param mixed $value
*/ */
public static function set($key, $value = null): void public static function set($key, $value = null): void
{ {
@ -57,8 +54,6 @@ class Setting extends Model
/** /**
* Serialize the setting value before saving into the database. * Serialize the setting value before saving into the database.
* This makes settings more flexible. * This makes settings more flexible.
*
* @param mixed $value
*/ */
public function setValueAttribute($value): void public function setValueAttribute($value): void
{ {
@ -68,8 +63,6 @@ class Setting extends Model
/** /**
* Get the unserialized setting value. * Get the unserialized setting value.
* *
* @param mixed $value
*
* @return mixed * @return mixed
*/ */
public function getValueAttribute($value) public function getValueAttribute($value)

View file

@ -14,19 +14,19 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
/** /**
* @property string $path * @property string $path
* @property string $title * @property string $title
* @property Album $album * @property Album $album
* @property Artist $artist * @property Artist $artist
* @property string[] $s3_params * @property array<string> $s3_params
* @property float $length * @property float $length
* @property string $lyrics * @property string $lyrics
* @property int $track * @property int $track
* @property int $disc * @property int $disc
* @property int $album_id * @property int $album_id
* @property string $id * @property string $id
* @property int $artist_id * @property int $artist_id
* @property int $mtime * @property int $mtime
* *
* @method static self updateOrCreate(array $where, array $params) * @method static self updateOrCreate(array $where, array $params)
* @method static Builder select(string $string) * @method static Builder select(string $string)
@ -48,14 +48,9 @@ class Song extends Model
* Attributes to be hidden from JSON outputs. * Attributes to be hidden from JSON outputs.
* Here we specify to hide lyrics as well to save some bandwidth (actually, lots of it). * Here we specify to hide lyrics as well to save some bandwidth (actually, lots of it).
* Lyrics can then be queried on demand. * Lyrics can then be queried on demand.
*
* @var array
*/ */
protected $hidden = ['lyrics', 'updated_at', 'path', 'mtime']; protected $hidden = ['lyrics', 'updated_at', 'path', 'mtime'];
/**
* @var array
*/
protected $casts = [ protected $casts = [
'length' => 'float', 'length' => 'float',
'mtime' => 'int', 'mtime' => 'int',
@ -64,12 +59,6 @@ class Song extends Model
]; ];
protected $keyType = 'string'; protected $keyType = 'string';
/**
* Indicates if the IDs are auto-incrementing.
*
* @var bool
*/
public $incrementing = false; public $incrementing = false;
public function artist(): BelongsTo public function artist(): BelongsTo
@ -95,14 +84,16 @@ class Song extends Model
/** /**
* Update song info. * Update song info.
* *
* @param string[] $ids * @param array<string> $ids
* @param string[] $data the data array, with these supported fields: * @param array<string> $data the data array, with these supported fields:
* - title * - title
* - artistName * - artistName
* - albumName * - albumName
* - lyrics * - lyrics
* All of these are optional, in which case the info will not be changed * All of these are optional, in which case the info will not be changed
* (except for lyrics, which will be emptied) * (except for lyrics, which will be emptied)
*
* @return Collection|array<Song>
*/ */
public static function updateInfo(array $ids, array $data): Collection public static function updateInfo(array $ids, array $data): Collection
{ {
@ -164,9 +155,11 @@ class Song extends Model
case 1: // ALL, or forcing compilation status to be Yes case 1: // ALL, or forcing compilation status to be Yes
$isCompilation = true; $isCompilation = true;
break; break;
case 2: // Keep current compilation status case 2: // Keep current compilation status
$isCompilation = $this->album->artist_id === Artist::VARIOUS_ID; $isCompilation = $this->album->artist_id === Artist::VARIOUS_ID;
break; break;
default: default:
$isCompilation = false; $isCompilation = false;
break; break;
@ -197,7 +190,7 @@ class Song extends Model
public function scopeInDirectory(Builder $query, string $path): Builder public function scopeInDirectory(Builder $query, string $path): Builder
{ {
// Make sure the path ends with a directory separator. // Make sure the path ends with a directory separator.
$path = rtrim(trim($path), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; $path = rtrim(trim($path), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
return $query->where('path', 'LIKE', "$path%"); return $query->where('path', 'LIKE', "$path%");
} }
@ -234,7 +227,7 @@ class Song extends Model
/** /**
* Get the bucket and key name of an S3 object. * Get the bucket and key name of an S3 object.
* *
* @return string[]|null * @return array<string>|null
*/ */
public function getS3ParamsAttribute(): ?array public function getS3ParamsAttribute(): ?array
{ {
@ -247,10 +240,7 @@ class Song extends Model
return compact('bucket', 'key'); return compact('bucket', 'key');
} }
/** public function __toString(): string
* Return the ID of the song when it's converted to string.
*/
public function __toString()
{ {
return $this->id; return $this->id;
} }

View file

@ -3,17 +3,15 @@
namespace App\Models; namespace App\Models;
use App\Facades\Download; use App\Facades\Download;
use Exception;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use RuntimeException; use RuntimeException;
use Throwable;
use ZipArchive; use ZipArchive;
class SongZipArchive class SongZipArchive
{ {
/** /** @var ZipArchive */
* @var ZipArchive
*/
private $archive; private $archive;
/** /**
@ -60,7 +58,7 @@ class SongZipArchive
try { try {
$path = Download::fromSong($song); $path = Download::fromSong($song);
$this->archive->addFile($path, $this->generateZipContentFileNameFromPath($path)); $this->archive->addFile($path, $this->generateZipContentFileNameFromPath($path));
} catch (Exception $e) { } catch (Throwable $e) {
Log::error($e); Log::error($e);
} }
@ -95,7 +93,7 @@ class SongZipArchive
++$this->fileNames[$name]; ++$this->fileNames[$name];
$parts = explode('.', $name); $parts = explode('.', $name);
$ext = $parts[count($parts) - 1]; $ext = $parts[count($parts) - 1];
$parts[count($parts) - 1] = $this->fileNames[$name].".$ext"; $parts[count($parts) - 1] = $this->fileNames[$name] . ".$ext";
$name = implode('.', $parts); $name = implode('.', $parts);
} else { } else {
$this->fileNames[$name] = 1; $this->fileNames[$name] = 1;

View file

@ -30,7 +30,6 @@ class User extends Authenticatable
/** /**
* The preferences that we don't want to show to the client. * The preferences that we don't want to show to the client.
* *
* @var array
*/ */
private const HIDDEN_PREFERENCES = ['lastfm_session_key']; private const HIDDEN_PREFERENCES = ['lastfm_session_key'];
@ -114,7 +113,7 @@ class User extends Authenticatable
/** /**
* User preferences are stored as a serialized associative array. * User preferences are stored as a serialized associative array.
* *
* @param mixed[] $value * @param array<mixed> $value
*/ */
public function setPreferencesAttribute(array $value): void public function setPreferencesAttribute(array $value): void
{ {
@ -124,7 +123,7 @@ class User extends Authenticatable
/** /**
* Unserialize the user preferences back to an array before returning. * Unserialize the user preferences back to an array before returning.
* *
* @return mixed[] * @return array<mixed>
*/ */
public function getPreferencesAttribute(?string $value): array public function getPreferencesAttribute(?string $value): array
{ {

View file

@ -3,8 +3,8 @@
namespace App\Observers; namespace App\Observers;
use App\Models\Album; use App\Models\Album;
use Exception;
use Illuminate\Log\Logger; use Illuminate\Log\Logger;
use Throwable;
class AlbumObserver class AlbumObserver
{ {
@ -28,7 +28,7 @@ class AlbumObserver
try { try {
unlink($album->cover_path); unlink($album->cover_path);
} catch (Exception $e) { } catch (Throwable $e) {
$this->logger->error($e); $this->logger->error($e);
} }
} }

View file

@ -7,6 +7,7 @@ use Illuminate\Database\Schema\Builder;
use Illuminate\Database\SQLiteConnection; use Illuminate\Database\SQLiteConnection;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Factory as Validator; use Illuminate\Validation\Factory as Validator;
use Laravel\Tinker\TinkerServiceProvider;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
@ -35,7 +36,7 @@ class AppServiceProvider extends ServiceProvider
public function register(): void public function register(): void
{ {
if ($this->app->environment() !== 'production') { if ($this->app->environment() !== 'production') {
$this->app->register(\Laravel\Tinker\TinkerServiceProvider::class); $this->app->register(TinkerServiceProvider::class);
} }
} }
} }

View file

@ -26,7 +26,7 @@ class AuthServiceProvider extends ServiceProvider
/** /**
* Register any application authentication / authorization services. * Register any application authentication / authorization services.
*/ */
public function boot() public function boot(): void
{ {
$this->registerPolicies(); $this->registerPolicies();

View file

@ -10,9 +10,8 @@ class BroadcastServiceProvider extends ServiceProvider
/** /**
* Bootstrap any application services. * Bootstrap any application services.
* *
* @return void
*/ */
public function boot() public function boot(): void
{ {
Broadcast::routes(); Broadcast::routes();

View file

@ -57,7 +57,7 @@ class EventServiceProvider extends ServiceProvider
/** /**
* Register any other events for your application. * Register any other events for your application.
*/ */
public function boot() public function boot(): void
{ {
parent::boot(); parent::boot();

View file

@ -2,15 +2,15 @@
namespace App\Providers; namespace App\Providers;
use App\Services\iTunesService; use App\Services\ITunesService;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
class iTunesServiceProvider extends ServiceProvider class ITunesServiceProvider extends ServiceProvider
{ {
public function register(): void public function register(): void
{ {
app()->singleton('iTunes', static function (): iTunesService { app()->singleton('iTunes', static function (): ITunesService {
return app(iTunesService::class); return app(ITunesService::class);
}); });
} }
} }

View file

@ -19,9 +19,8 @@ class RouteServiceProvider extends ServiceProvider
/** /**
* Define your route model bindings, pattern filters, etc. * Define your route model bindings, pattern filters, etc.
* *
* @return void
*/ */
public function boot() public function boot(): void
{ {
parent::boot(); parent::boot();
} }
@ -29,9 +28,8 @@ class RouteServiceProvider extends ServiceProvider
/** /**
* Define the routes for the application. * Define the routes for the application.
* *
* @return void
*/ */
public function map() public function map(): void
{ {
$this->mapApiRoutes(); $this->mapApiRoutes();
$this->mapWebRoutes(); $this->mapWebRoutes();
@ -42,9 +40,8 @@ class RouteServiceProvider extends ServiceProvider
* *
* These routes all receive session state, CSRF protection, etc. * These routes all receive session state, CSRF protection, etc.
* *
* @return void
*/ */
protected function mapWebRoutes() protected function mapWebRoutes(): void
{ {
Route::middleware('web') Route::middleware('web')
->namespace($this->namespace) ->namespace($this->namespace)
@ -56,9 +53,8 @@ class RouteServiceProvider extends ServiceProvider
* *
* These routes are typically stateless. * These routes are typically stateless.
* *
* @return void
*/ */
protected function mapApiRoutes() protected function mapApiRoutes(): void
{ {
Route::prefix('api') Route::prefix('api')
->middleware('api') ->middleware('api')

View file

@ -20,8 +20,10 @@ class StreamerServiceProvider extends ServiceProvider
switch (config('koel.streaming.method')) { switch (config('koel.streaming.method')) {
case 'x-sendfile': case 'x-sendfile':
return new XSendFileStreamer(); return new XSendFileStreamer();
case 'x-accel-redirect': case 'x-accel-redirect':
return new XAccelRedirectStreamer(); return new XAccelRedirectStreamer();
default: default:
return new PHPStreamer(); return new PHPStreamer();
} }

View file

@ -2,10 +2,10 @@
namespace App\Repositories; namespace App\Repositories;
use Exception;
use Illuminate\Contracts\Auth\Guard; use Illuminate\Contracts\Auth\Guard;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Throwable;
abstract class AbstractRepository implements RepositoryInterface abstract class AbstractRepository implements RepositoryInterface
{ {
@ -25,7 +25,7 @@ abstract class AbstractRepository implements RepositoryInterface
// rendering the whole installation failing. // rendering the whole installation failing.
try { try {
$this->auth = app(Guard::class); $this->auth = app(Guard::class);
} catch (Exception $e) { } catch (Throwable $e) {
} }
} }
@ -34,11 +34,13 @@ abstract class AbstractRepository implements RepositoryInterface
return $this->model->find($id); return $this->model->find($id);
} }
/** @return Collection|array<Model> */
public function getByIds(array $ids): Collection public function getByIds(array $ids): Collection
{ {
return $this->model->whereIn($this->model->getKeyName(), $ids)->get(); return $this->model->whereIn($this->model->getKeyName(), $ids)->get();
} }
/** @return Collection|array<Model> */
public function getAll(): Collection public function getAll(): Collection
{ {
return $this->model->all(); return $this->model->all();

View file

@ -12,14 +12,13 @@ class AlbumRepository extends AbstractRepository
return Album::class; return Album::class;
} }
/** @return array<int> */
public function getNonEmptyAlbumIds(): array public function getNonEmptyAlbumIds(): array
{ {
$ids = Song::select('album_id') return Song::select('album_id')
->groupBy('album_id') ->groupBy('album_id')
->get() ->get()
->pluck('album_id') ->pluck('album_id')
->toArray(); ->toArray();
return $ids;
} }
} }

View file

@ -12,6 +12,7 @@ class ArtistRepository extends AbstractRepository
return Artist::class; return Artist::class;
} }
/** @return array<int> */
public function getNonEmptyArtistIds(): array public function getNonEmptyArtistIds(): array
{ {
return Song::select('artist_id') return Song::select('artist_id')

View file

@ -17,9 +17,7 @@ class InteractionRepository extends AbstractRepository
return Interaction::class; return Interaction::class;
} }
/** /** @return Collection|array<Interaction> */
* Get all songs favorited by a user.
*/
public function getUserFavorites(User $user): Collection public function getUserFavorites(User $user): Collection
{ {
return $this->model->where([ return $this->model->where([
@ -31,9 +29,7 @@ class InteractionRepository extends AbstractRepository
->pluck('song'); ->pluck('song');
} }
/** /** @return array<Interaction> */
* @return Interaction[]
*/
public function getRecentlyPlayed(User $user, ?int $count = null): array public function getRecentlyPlayed(User $user, ?int $count = null): array
{ {
/** @var Builder $query */ /** @var Builder $query */

View file

@ -15,6 +15,7 @@ class PlaylistRepository extends AbstractRepository
return Playlist::class; return Playlist::class;
} }
/** @return Collection|array<Playlist> */
public function getAllByCurrentUser(): Collection public function getAllByCurrentUser(): Collection
{ {
return $this->byCurrentUser()->orderBy('name')->get(); return $this->byCurrentUser()->orderBy('name')->get();

View file

@ -9,15 +9,11 @@ interface RepositoryInterface
{ {
public function getModelClass(): string; public function getModelClass(): string;
/**
* @param int|string $id
*/
public function getOneById($id): ?Model; public function getOneById($id): ?Model;
/** /** @return Collection|array<Model> */
* @param int[]|string[] $ids
*/
public function getByIds(array $ids): Collection; public function getByIds(array $ids): Collection;
/** @return Collection|array<Model> */
public function getAll(): Collection; public function getAll(): Collection;
} }

View file

@ -11,6 +11,7 @@ class SettingRepository extends AbstractRepository
return Setting::class; return Setting::class;
} }
/** @return array<mixed> */
public function getAllAsKeyValueArray(): array public function getAllAsKeyValueArray(): array
{ {
return $this->model->pluck('value', 'key')->all(); return $this->model->pluck('value', 'key')->all();

View file

@ -12,6 +12,7 @@ class SongRepository extends AbstractRepository
public function __construct(HelperService $helperService) public function __construct(HelperService $helperService)
{ {
parent::__construct(); parent::__construct();
$this->helperService = $helperService; $this->helperService = $helperService;
} }

View file

@ -3,6 +3,7 @@
namespace App\Repositories\Traits; namespace App\Repositories\Traits;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
trait ByCurrentUser trait ByCurrentUser
@ -12,6 +13,7 @@ trait ByCurrentUser
return $this->model->whereUserId($this->auth->id()); return $this->model->whereUserId($this->auth->id());
} }
/** @return Collection|array<Model> */
public function getAllByCurrentUser(): Collection public function getAllByCurrentUser(): Collection
{ {
return $this->byCurrentUser()->get(); return $this->byCurrentUser()->get();

View file

@ -10,7 +10,7 @@ class ImageData implements Rule
public function passes($attribute, $value): bool public function passes($attribute, $value): bool
{ {
try { try {
[$header, $_] = explode(';', $value); [$header,] = explode(';', $value);
return (bool) preg_match('/data:image\/(jpe?g|png|gif)/i', $header); return (bool) preg_match('/data:image\/(jpe?g|png|gif)/i', $header);
} catch (Throwable $exception) { } catch (Throwable $exception) {

View file

@ -48,7 +48,7 @@ abstract class AbstractApiClient
* @param bool $appendKey Whether to automatically append the API key into the URI. * @param bool $appendKey Whether to automatically append the API key into the URI.
* While it's usually the case, some services (like Last.fm) requires * While it's usually the case, some services (like Last.fm) requires
* an "API signature" of the request. Appending an API key will break the request. * an "API signature" of the request. Appending an API key will break the request.
* @param mixed[] $params An array of parameters * @param array<mixed> $params An array of parameters
* *
* @return mixed|SimpleXMLElement|null * @return mixed|SimpleXMLElement|null
*/ */
@ -77,7 +77,7 @@ abstract class AbstractApiClient
* Make an HTTP call to the external resource. * Make an HTTP call to the external resource.
* *
* @param string $method The HTTP method * @param string $method The HTTP method
* @param mixed[] $args An array of parameters * @param array<mixed> $args An array of parameters
* *
* @throws InvalidArgumentException * @throws InvalidArgumentException
* *
@ -90,8 +90,8 @@ abstract class AbstractApiClient
} }
$uri = $args[0]; $uri = $args[0];
$opts = isset($args[1]) ? $args[1] : []; $opts = $args[1] ?? [];
$appendKey = isset($args[2]) ? $args[2] : true; $appendKey = $args[2] ?? true;
return $this->request($method, $uri, $appendKey, $opts); return $this->request($method, $uri, $appendKey, $opts);
} }
@ -108,14 +108,14 @@ abstract class AbstractApiClient
$uri = "/$uri"; $uri = "/$uri";
} }
$uri = $this->getEndpoint().$uri; $uri = $this->getEndpoint() . $uri;
} }
if ($appendKey) { if ($appendKey) {
if (parse_url($uri, PHP_URL_QUERY)) { if (parse_url($uri, PHP_URL_QUERY)) {
$uri .= "&{$this->keyParam}=".$this->getKey(); $uri .= "&{$this->keyParam}=" . $this->getKey();
} else { } else {
$uri .= "?{$this->keyParam}=".$this->getKey(); $uri .= "?{$this->keyParam}=" . $this->getKey();
} }
} }

View file

@ -3,10 +3,10 @@
namespace App\Services; namespace App\Services;
use App\Application; use App\Application;
use Exception;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Cache\Repository as Cache;
use Illuminate\Log\Logger; use Illuminate\Log\Logger;
use Throwable;
class ApplicationInformationService class ApplicationInformationService
{ {
@ -33,7 +33,7 @@ class ApplicationInformationService
return json_decode( return json_decode(
$this->client->get('https://api.github.com/repos/phanan/koel/tags')->getBody() $this->client->get('https://api.github.com/repos/phanan/koel/tags')->getBody()
)[0]->name; )[0]->name;
} catch (Exception $e) { } catch (Throwable $e) {
$this->logger->error($e); $this->logger->error($e);
return Application::KOEL_VERSION; return Application::KOEL_VERSION;

View file

@ -33,12 +33,16 @@ class DownloadService
switch (get_class($mixed)) { switch (get_class($mixed)) {
case Song::class: case Song::class:
return $this->fromSong($mixed); return $this->fromSong($mixed);
case Collection::class: case Collection::class:
return $this->fromMultipleSongs($mixed); return $this->fromMultipleSongs($mixed);
case Album::class: case Album::class:
return $this->fromAlbum($mixed); return $this->fromAlbum($mixed);
case Artist::class: case Artist::class:
return $this->fromArtist($mixed); return $this->fromArtist($mixed);
case Playlist::class: case Playlist::class:
return $this->fromPlaylist($mixed); return $this->fromPlaylist($mixed);
} }
@ -48,13 +52,13 @@ class DownloadService
public function fromSong(Song $song): string public function fromSong(Song $song): string
{ {
if ($s3Params = $song->s3_params) { if ($song->s3_params) {
// The song is hosted on Amazon S3. // The song is hosted on Amazon S3.
// We download it back to our local server first. // We download it back to our local server first.
$url = $this->s3Service->getSongPublicUrl($song); $url = $this->s3Service->getSongPublicUrl($song);
abort_unless($url, 404); abort_unless($url, 404);
$localPath = sys_get_temp_dir().DIRECTORY_SEPARATOR.basename($s3Params['key']); $localPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . basename($song->s3_params['key']);
// The following function requires allow_url_fopen to be ON. // The following function requires allow_url_fopen to be ON.
// We're just assuming that to be the case here. // We're just assuming that to be the case here.

View file

@ -6,13 +6,13 @@ use App\Models\Album;
use App\Models\Artist; use App\Models\Artist;
use App\Models\Song; use App\Models\Song;
use App\Repositories\SongRepository; use App\Repositories\SongRepository;
use Exception;
use getID3; use getID3;
use getid3_lib; use getid3_lib;
use Illuminate\Contracts\Cache\Repository as Cache; use Illuminate\Contracts\Cache\Repository as Cache;
use InvalidArgumentException; use InvalidArgumentException;
use SplFileInfo; use SplFileInfo;
use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\Finder;
use Throwable;
class FileSynchronizer class FileSynchronizer
{ {
@ -27,19 +27,13 @@ class FileSynchronizer
private $cache; private $cache;
private $finder; private $finder;
/** /** @var SplFileInfo */
* @var SplFileInfo
*/
private $splFileInfo; private $splFileInfo;
/** /** @var int */
* @var int
*/
private $fileModifiedTime; private $fileModifiedTime;
/** /** @var string */
* @var string
*/
private $filePath; private $filePath;
/** /**
@ -57,9 +51,7 @@ class FileSynchronizer
*/ */
private $song; private $song;
/** /** @var string|null */
* @var string|null
*/
private $syncError; private $syncError;
public function __construct( public function __construct(
@ -78,9 +70,7 @@ class FileSynchronizer
$this->finder = $finder; $this->finder = $finder;
} }
/** /** @param string|SplFileInfo $path */
* @param string|SplFileInfo $path
*/
public function setFile($path): self public function setFile($path): self
{ {
$this->splFileInfo = $path instanceof SplFileInfo ? $path : new SplFileInfo($path); $this->splFileInfo = $path instanceof SplFileInfo ? $path : new SplFileInfo($path);
@ -88,7 +78,7 @@ class FileSynchronizer
// Workaround for #344, where getMTime() fails for certain files with Unicode names on Windows. // Workaround for #344, where getMTime() fails for certain files with Unicode names on Windows.
try { try {
$this->fileModifiedTime = $this->splFileInfo->getMTime(); $this->fileModifiedTime = $this->splFileInfo->getMTime();
} catch (Exception $e) { } catch (Throwable $e) {
// Not worth logging the error. Just use current stamp for mtime. // Not worth logging the error. Just use current stamp for mtime.
$this->fileModifiedTime = time(); $this->fileModifiedTime = time();
} }
@ -103,6 +93,8 @@ class FileSynchronizer
/** /**
* Get all applicable info from the file. * Get all applicable info from the file.
*
* @return array<mixed>
*/ */
public function getFileInfo(): array public function getFileInfo(): array
{ {
@ -124,7 +116,7 @@ class FileSynchronizer
'album' => '', 'album' => '',
'albumartist' => '', 'albumartist' => '',
'compilation' => false, 'compilation' => false,
'title' => basename($this->filePath, '.'.pathinfo($this->filePath, PATHINFO_EXTENSION)), // default to be file name 'title' => basename($this->filePath, '.' . pathinfo($this->filePath, PATHINFO_EXTENSION)),
'length' => $info['playtime_seconds'], 'length' => $info['playtime_seconds'],
'track' => $this->getTrackNumberFromInfo($info), 'track' => $this->getTrackNumberFromInfo($info),
'disc' => (int) array_get($info, 'comments.part_of_a_set.0', 1), 'disc' => (int) array_get($info, 'comments.part_of_a_set.0', 1),
@ -134,7 +126,9 @@ class FileSynchronizer
'mtime' => $this->fileModifiedTime, 'mtime' => $this->fileModifiedTime,
]; ];
if (!$comments = array_get($info, 'comments_html')) { $comments = array_get($info, 'comments_html');
if (!$comments) {
return $props; return $props;
} }
@ -147,8 +141,8 @@ class FileSynchronizer
/** /**
* Sync the song with all available media info against the database. * Sync the song with all available media info against the database.
* *
* @param string[] $tags The (selective) tags to sync (if the song exists) * @param array<string> $tags The (selective) tags to sync (if the song exists)
* @param bool $force Whether to force syncing, even if the file is unchanged * @param bool $force Whether to force syncing, even if the file is unchanged
*/ */
public function sync(array $tags, bool $force = false): int public function sync(array $tags, bool $force = false): int
{ {
@ -156,7 +150,9 @@ class FileSynchronizer
return self::SYNC_RESULT_UNMODIFIED; return self::SYNC_RESULT_UNMODIFIED;
} }
if (!$info = $this->getFileInfo()) { $info = $this->getFileInfo();
if (!$info) {
return self::SYNC_RESULT_BAD_FILE; return self::SYNC_RESULT_BAD_FILE;
} }
@ -218,14 +214,14 @@ class FileSynchronizer
/** /**
* Try to generate a cover for an album based on extracted data, or use the cover file under the directory. * Try to generate a cover for an album based on extracted data, or use the cover file under the directory.
* *
* @param mixed[]|null $coverData * @param array<mixed>|null $coverData
*/ */
private function generateAlbumCover(Album $album, ?array $coverData): void private function generateAlbumCover(Album $album, ?array $coverData): void
{ {
// If the album has no cover, we try to get the cover image from existing tag data // If the album has no cover, we try to get the cover image from existing tag data
if ($coverData) { if ($coverData) {
$extension = explode('/', $coverData['image_mime']); $extension = explode('/', $coverData['image_mime']);
$extension = empty($extension[1]) ? 'png' : $extension[1]; $extension = $extension[1] ? 'png' : $extension[1];
$this->mediaMetadataService->writeAlbumCover($album, $coverData['data'], $extension); $this->mediaMetadataService->writeAlbumCover($album, $coverData['data'], $extension);
@ -233,7 +229,9 @@ class FileSynchronizer
} }
// Or, if there's a cover image under the same directory, use it. // Or, if there's a cover image under the same directory, use it.
if ($cover = $this->getCoverFileUnderSameDirectory()) { $cover = $this->getCoverFileUnderSameDirectory();
if ($cover) {
$extension = pathinfo($cover, PATHINFO_EXTENSION); $extension = pathinfo($cover, PATHINFO_EXTENSION);
$this->mediaMetadataService->writeAlbumCover($album, file_get_contents($cover), $extension); $this->mediaMetadataService->writeAlbumCover($album, file_get_contents($cover), $extension);
} }
@ -249,16 +247,16 @@ class FileSynchronizer
private function getCoverFileUnderSameDirectory(): ?string private function getCoverFileUnderSameDirectory(): ?string
{ {
// As directory scanning can be expensive, we cache and reuse the result. // As directory scanning can be expensive, we cache and reuse the result.
return $this->cache->remember(md5($this->filePath.'_cover'), 24 * 60, function (): ?string { return $this->cache->remember(md5($this->filePath . '_cover'), 24 * 60, function (): ?string {
$matches = array_keys( $matches = array_keys(
iterator_to_array( iterator_to_array(
$this->finder->create() $this->finder->create()
->depth(0) ->depth(0)
->ignoreUnreadableDirs() ->ignoreUnreadableDirs()
->files() ->files()
->followLinks() ->followLinks()
->name('/(cov|fold)er\.(jpe?g|png)$/i') ->name('/(cov|fold)er\.(jpe?g|png)$/i')
->in(dirname($this->filePath)) ->in(dirname($this->filePath))
) )
); );
@ -272,7 +270,7 @@ class FileSynchronizer
{ {
try { try {
return (bool) exif_imagetype($path); return (bool) exif_imagetype($path);
} catch (Exception $e) { } catch (Throwable $e) {
return false; return false;
} }
} }

View file

@ -10,6 +10,6 @@ class HelperService
*/ */
public function getFileHash(string $path): string public function getFileHash(string $path): string
{ {
return md5(config('app.key').$path); return md5(config('app.key') . $path);
} }
} }

View file

@ -2,9 +2,9 @@
namespace App\Services; namespace App\Services;
use Exception; use Throwable;
class iTunesService extends AbstractApiClient implements ApiConsumerInterface class ITunesService extends AbstractApiClient implements ApiConsumerInterface
{ {
/** /**
* Determines whether to use iTunes services. * Determines whether to use iTunes services.
@ -29,7 +29,7 @@ class iTunesService extends AbstractApiClient implements ApiConsumerInterface
24 * 60 * 7, 24 * 60 * 7,
function () use ($term, $album, $artist): ?string { function () use ($term, $album, $artist): ?string {
$params = [ $params = [
'term' => $term.($album ? " $album" : '').($artist ? " $artist" : ''), 'term' => $term . ($album ? " $album" : '') . ($artist ? " $artist" : ''),
'media' => 'music', 'media' => 'music',
'entity' => 'song', 'entity' => 'song',
'limit' => 1, 'limit' => 1,
@ -45,12 +45,10 @@ class iTunesService extends AbstractApiClient implements ApiConsumerInterface
$trackUrl = $response->results[0]->trackViewUrl; $trackUrl = $response->results[0]->trackViewUrl;
$connector = parse_url($trackUrl, PHP_URL_QUERY) ? '&' : '?'; $connector = parse_url($trackUrl, PHP_URL_QUERY) ? '&' : '?';
$trackUrl .= "{$connector}at=".config('koel.itunes.affiliate_id'); return $trackUrl . "{$connector}at=" . config('koel.itunes.affiliate_id');
return $trackUrl;
} }
); );
} catch (Exception $e) { } catch (Throwable $e) {
$this->logger->error($e); $this->logger->error($e);
return null; return null;

View file

@ -23,7 +23,8 @@ class ImageWriter
->make($data) ->make($data)
->resize( ->resize(
$config['max_width'] ?? self::DEFAULT_MAX_WIDTH, $config['max_width'] ?? self::DEFAULT_MAX_WIDTH,
null, static function (Constraint $constraint): void { null,
static function (Constraint $constraint): void {
$constraint->upsize(); $constraint->upsize();
$constraint->aspectRatio(); $constraint->aspectRatio();
} }

View file

@ -56,9 +56,9 @@ class InteractionService
/** /**
* Like several songs at once as a user. * Like several songs at once as a user.
* *
* @param string[] $songIds * @param array<string> $songIds
* *
* @return Interaction[] the array of Interaction objects * @return array<Interaction> the array of Interaction objects
*/ */
public function batchLike(array $songIds, User $user): array public function batchLike(array $songIds, User $user): array
{ {
@ -82,7 +82,7 @@ class InteractionService
/** /**
* Unlike several songs at once. * Unlike several songs at once.
* *
* @param string[] $songIds * @param array<string> $songIds
*/ */
public function batchUnlike(array $songIds, User $user): void public function batchUnlike(array $songIds, User $user): void
{ {

View file

@ -2,14 +2,12 @@
namespace App\Services; namespace App\Services;
use Exception; use Throwable;
class LastfmService extends AbstractApiClient implements ApiConsumerInterface class LastfmService extends AbstractApiClient implements ApiConsumerInterface
{ {
/** /**
* Override the key param, since, again, Lastfm wants to be different. * Override the key param, since, again, Lastfm wants to be different.
*
* @var string
*/ */
protected $keyParam = 'api_key'; protected $keyParam = 'api_key';
@ -32,9 +30,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
/** /**
* Get information about an artist. * Get information about an artist.
* *
* @param string $name Name of the artist * @return array<mixed>|null
*
* @return mixed[]|null
*/ */
public function getArtistInformation(string $name): ?array public function getArtistInformation(string $name): ?array
{ {
@ -54,7 +50,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
return $this->buildArtistInformation($response->artist); return $this->buildArtistInformation($response->artist);
}); });
} catch (Exception $e) { } catch (Throwable $e) {
$this->logger->error($e); $this->logger->error($e);
return null; return null;
@ -64,9 +60,9 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
/** /**
* Build a Koel-usable array of artist information using the data from Last.fm. * Build a Koel-usable array of artist information using the data from Last.fm.
* *
* @param object $data * @param mixed $data
* *
* @return mixed[] * @return array<mixed>
*/ */
private function buildArtistInformation($data): array private function buildArtistInformation($data): array
{ {
@ -83,7 +79,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
/** /**
* Get information about an album. * Get information about an album.
* *
* @return mixed[]|null * @return array<mixed>|null
*/ */
public function getAlbumInformation(string $albumName, string $artistName): ?array public function getAlbumInformation(string $albumName, string $artistName): ?array
{ {
@ -107,7 +103,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
return $this->buildAlbumInformation($response->album); return $this->buildAlbumInformation($response->album);
}); });
} catch (Exception $e) { } catch (Throwable $e) {
$this->logger->error($e); $this->logger->error($e);
return null; return null;
@ -117,9 +113,9 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
/** /**
* Build a Koel-usable array of album information using the data from Last.fm. * Build a Koel-usable array of album information using the data from Last.fm.
* *
* @param object $data * @param mixed $data
* *
* @return mixed[] * @return array<mixed>
*/ */
private function buildAlbumInformation($data): array private function buildAlbumInformation($data): array
{ {
@ -156,7 +152,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
try { try {
return $this->get("/?$query&format=json", [], false)->session->key; return $this->get("/?$query&format=json", [], false)->session->key;
} catch (Exception $e) { } catch (Throwable $e) {
$this->logger->error($e); $this->logger->error($e);
return null; return null;
@ -166,11 +162,11 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
/** /**
* Scrobble a song. * Scrobble a song.
* *
* @param string $artist The artist name * @param string $artist The artist name
* @param string $track The track name * @param string $track The track name
* @param string|int $timestamp The UNIX timestamp * @param string|int $timestamp The UNIX timestamp
* @param string $album The album name * @param string $album The album name
* @param string $sk The session key * @param string $sk The session key
*/ */
public function scrobble(string $artist, string $track, $timestamp, string $album, string $sk): void public function scrobble(string $artist, string $track, $timestamp, string $album, string $sk): void
{ {
@ -184,7 +180,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
try { try {
$this->post('/', $this->buildAuthCallParams($params), false); $this->post('/', $this->buildAuthCallParams($params), false);
} catch (Exception $e) { } catch (Throwable $e) {
$this->logger->error($e); $this->logger->error($e);
} }
} }
@ -192,10 +188,10 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
/** /**
* Love or unlove a track on Last.fm. * Love or unlove a track on Last.fm.
* *
* @param string $track The track name * @param string $track The track name
* @param string $artist The artist's name * @param string $artist The artist's name
* @param string $sk The session key * @param string $sk The session key
* @param bool $love Whether to love or unlove. Such cheesy terms... urrgggh * @param bool $love Whether to love or unlove. Such cheesy terms... urrgggh
*/ */
public function toggleLoveTrack(string $track, string $artist, string $sk, ?bool $love = true): void public function toggleLoveTrack(string $track, string $artist, string $sk, ?bool $love = true): void
{ {
@ -204,7 +200,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
try { try {
$this->post('/', $this->buildAuthCallParams($params), false); $this->post('/', $this->buildAuthCallParams($params), false);
} catch (Exception $e) { } catch (Throwable $e) {
$this->logger->error($e); $this->logger->error($e);
} }
} }
@ -212,11 +208,11 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
/** /**
* Update a track's "now playing" on Last.fm. * Update a track's "now playing" on Last.fm.
* *
* @param string $artist Name of the artist * @param string $artist Name of the artist
* @param string $track Name of the track * @param string $track Name of the track
* @param string $album Name of the album * @param string $album Name of the album
* @param int|float $duration Duration of the track, in seconds * @param int|float $duration Duration of the track, in seconds
* @param string $sk The session key * @param string $sk The session key
*/ */
public function updateNowPlaying(string $artist, string $track, string $album, $duration, string $sk): void public function updateNowPlaying(string $artist, string $track, string $album, $duration, string $sk): void
{ {
@ -229,7 +225,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
try { try {
$this->post('/', $this->buildAuthCallParams($params), false); $this->post('/', $this->buildAuthCallParams($params), false);
} catch (Exception $e) { } catch (Throwable $e) {
$this->logger->error($e); $this->logger->error($e);
} }
} }
@ -242,12 +238,12 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
* *
* @see http://www.last.fm/api/webauth#5 * @see http://www.last.fm/api/webauth#5
* *
* @param array $params the array of parameters * @param array $params the array of parameters
* @param bool $toString Whether to turn the array into a query string * @param bool $toString Whether to turn the array into a query string
* *
* @return array|string * @return array<mixed>|string
*/ */
public function buildAuthCallParams(array $params, bool $toString = false) public function buildAuthCallParams(array $params, bool $toString = false): string
{ {
$params['api_key'] = $this->getKey(); $params['api_key'] = $this->getKey();
ksort($params); ksort($params);
@ -257,7 +253,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
$str = ''; $str = '';
foreach ($params as $name => $value) { foreach ($params as $name => $value) {
$str .= $name.$value; $str .= $name . $value;
} }
$str .= $this->getSecret(); $str .= $this->getSecret();
@ -268,6 +264,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
} }
$query = ''; $query = '';
foreach ($params as $key => $value) { foreach ($params as $key => $value) {
$query .= "$key=$value&"; $query .= "$key=$value&";
} }
@ -277,8 +274,6 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
/** /**
* Correctly format a value returned by Last.fm. * Correctly format a value returned by Last.fm.
*
* @param string|array $value
*/ */
protected function formatText(?string $value): string protected function formatText(?string $value): string
{ {

View file

@ -22,7 +22,7 @@ class MediaCacheService
* Get media data. * Get media data.
* If caching is enabled, the data will be retrieved from the cache. * If caching is enabled, the data will be retrieved from the cache.
* *
* @return mixed[] * @return array<mixed>
*/ */
public function get(): array public function get(): array
{ {
@ -38,7 +38,7 @@ class MediaCacheService
/** /**
* Query fresh data from the database. * Query fresh data from the database.
* *
* @return mixed[] * @return array<mixed>
*/ */
private function query(): array private function query(): array
{ {

View file

@ -19,7 +19,7 @@ class MediaInformationService
/** /**
* Get extra information about an album from Last.fm. * Get extra information about an album from Last.fm.
* *
* @return array|null the album info in an array format, or null on failure * @return array<mixed>|null the album info in an array format, or null on failure
*/ */
public function getAlbumInformation(Album $album): ?array public function getAlbumInformation(Album $album): ?array
{ {
@ -43,7 +43,7 @@ class MediaInformationService
/** /**
* Get extra information about an artist from Last.fm. * Get extra information about an artist from Last.fm.
* *
* @return array|null the artist info in an array format, or null on failure * @return array<mixed>|null the artist info in an array format, or null on failure
*/ */
public function getArtistInformation(Artist $artist): ?array public function getArtistInformation(Artist $artist): ?array
{ {

View file

@ -2,12 +2,13 @@
namespace App\Services; namespace App\Services;
use function App\Helpers\album_cover_path;
use function App\Helpers\artist_image_path;
use App\Models\Album; use App\Models\Album;
use App\Models\Artist; use App\Models\Artist;
use Exception;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Throwable;
use function App\Helpers\album_cover_path;
use function App\Helpers\artist_image_path;
class MediaMetadataService class MediaMetadataService
{ {
@ -52,7 +53,7 @@ class MediaMetadataService
$album->update(['cover' => basename($destination)]); $album->update(['cover' => basename($destination)]);
$this->createThumbnailForAlbum($album); $this->createThumbnailForAlbum($album);
} catch (Exception $e) { } catch (Throwable $e) {
$this->logger->error($e); $this->logger->error($e);
} }
} }
@ -88,7 +89,7 @@ class MediaMetadataService
} }
$artist->update(['image' => basename($destination)]); $artist->update(['image' => basename($destination)]);
} catch (Exception $e) { } catch (Throwable $e) {
$this->logger->error($e); $this->logger->error($e);
} }
} }

View file

@ -23,8 +23,6 @@ class MediaSyncService
/** /**
* All applicable tags in a media file that we cater for. * All applicable tags in a media file that we cater for.
* Note that each isn't necessarily a valid ID3 tag name. * Note that each isn't necessarily a valid ID3 tag name.
*
* @var array
*/ */
public const APPLICABLE_TAGS = [ public const APPLICABLE_TAGS = [
'artist', 'artist',
@ -81,10 +79,10 @@ class MediaSyncService
/** /**
* Sync the media. Oh sync the media. * Sync the media. Oh sync the media.
* *
* @param string[] $tags The tags to sync. * @param array<string> $tags The tags to sync.
* Only taken into account for existing records. * Only taken into account for existing records.
* New records will have all tags synced in regardless. * New records will have all tags synced in regardless.
* @param bool $force Whether to force syncing even unchanged files * @param bool $force Whether to force syncing even unchanged files
* @param SyncCommand $syncCommand the SyncMedia command object, to log to console if executed by artisan * @param SyncCommand $syncCommand the SyncMedia command object, to log to console if executed by artisan
* *
* @throws Exception * @throws Exception
@ -117,9 +115,11 @@ class MediaSyncService
case FileSynchronizer::SYNC_RESULT_SUCCESS: case FileSynchronizer::SYNC_RESULT_SUCCESS:
$results['success'][] = $path; $results['success'][] = $path;
break; break;
case FileSynchronizer::SYNC_RESULT_UNMODIFIED: case FileSynchronizer::SYNC_RESULT_UNMODIFIED:
$results['unmodified'][] = $path; $results['unmodified'][] = $path;
break; break;
default: default:
$results['bad_files'][] = $path; $results['bad_files'][] = $path;
break; break;
@ -147,7 +147,7 @@ class MediaSyncService
* *
* @param string $path The directory's full path * @param string $path The directory's full path
* *
* @return SplFileInfo[] * @return array<SplFileInfo>
*/ */
public function gatherFiles(string $path): array public function gatherFiles(string $path): array
{ {
@ -162,40 +162,24 @@ class MediaSyncService
); );
} }
/**
* Sync media using a watch record.
*
* @throws Exception
*/
public function syncByWatchRecord(WatchRecordInterface $record): void public function syncByWatchRecord(WatchRecordInterface $record): void
{ {
$this->logger->info("New watch record received: '{$record->getPath()}'"); $this->logger->info("New watch record received: '{$record->getPath()}'");
$record->isFile() ? $this->syncFileRecord($record) : $this->syncDirectoryRecord($record); $record->isFile() ? $this->syncFileRecord($record) : $this->syncDirectoryRecord($record);
} }
/**
* Sync a file's watch record.
*
* @throws Exception
*/
private function syncFileRecord(WatchRecordInterface $record): void private function syncFileRecord(WatchRecordInterface $record): void
{ {
$path = $record->getPath(); $path = $record->getPath();
$this->logger->info("'$path' is a file."); $this->logger->info("'$path' is a file.");
// If the file has been deleted...
if ($record->isDeleted()) { if ($record->isDeleted()) {
$this->handleDeletedFileRecord($path); $this->handleDeletedFileRecord($path);
} } elseif ($record->isNewOrModified()) {
// Otherwise, it's a new or changed file. Try to sync it in.
elseif ($record->isNewOrModified()) {
$this->handleNewOrModifiedFileRecord($path); $this->handleNewOrModifiedFileRecord($path);
} }
} }
/**
* Sync a directory's watch record.
*/
private function syncDirectoryRecord(WatchRecordInterface $record): void private function syncDirectoryRecord(WatchRecordInterface $record): void
{ {
$path = $record->getPath(); $path = $record->getPath();
@ -213,7 +197,7 @@ class MediaSyncService
* If the input array is empty or contains only invalid items, we use all tags. * If the input array is empty or contains only invalid items, we use all tags.
* Otherwise, we only use the valid items in it. * Otherwise, we only use the valid items in it.
* *
* @param string[] $tags * @param array<string> $tags
*/ */
public function setTags(array $tags = []): void public function setTags(array $tags = []): void
{ {
@ -225,11 +209,6 @@ class MediaSyncService
} }
} }
/**
* Tidy up the library by deleting empty albums and artists.
*
* @throws Exception
*/
public function tidy(): void public function tidy(): void
{ {
$inUseAlbums = $this->albumRepository->getNonEmptyAlbumIds(); $inUseAlbums = $this->albumRepository->getNonEmptyAlbumIds();
@ -249,19 +228,17 @@ class MediaSyncService
} }
if (config('koel.memory_limit')) { if (config('koel.memory_limit')) {
ini_set('memory_limit', config('koel.memory_limit').'M'); ini_set('memory_limit', config('koel.memory_limit') . 'M');
} }
} }
/**
* @throws Exception
*/
private function handleDeletedFileRecord(string $path): void private function handleDeletedFileRecord(string $path): void
{ {
if ($song = $this->songRepository->getOneByPath($path)) { $song = $this->songRepository->getOneByPath($path);
if ($song) {
$song->delete(); $song->delete();
$this->logger->info("$path deleted."); $this->logger->info("$path deleted.");
event(new LibraryChanged()); event(new LibraryChanged());
} else { } else {
$this->logger->info("$path doesn't exist in our database--skipping."); $this->logger->info("$path doesn't exist in our database--skipping.");
@ -283,7 +260,9 @@ class MediaSyncService
private function handleDeletedDirectoryRecord(string $path): void private function handleDeletedDirectoryRecord(string $path): void
{ {
if ($count = Song::inDirectory($path)->delete()) { $count = Song::inDirectory($path)->delete();
if ($count) {
$this->logger->info("Deleted $count song(s) under $path"); $this->logger->info("Deleted $count song(s) under $path");
event(new LibraryChanged()); event(new LibraryChanged());

View file

@ -28,9 +28,7 @@ class S3Service implements ObjectStorageInterface
// Here we specify that the request is valid for 1 hour. // Here we specify that the request is valid for 1 hour.
// We'll also cache the public URL for future reuse. // We'll also cache the public URL for future reuse.
$request = $this->s3Client->createPresignedRequest($cmd, '+1 hour'); $request = $this->s3Client->createPresignedRequest($cmd, '+1 hour');
$url = (string) $request->getUri(); return (string) $request->getUri();
return $url;
}); });
} }
} }

View file

@ -22,10 +22,11 @@ class SmartPlaylistService
$this->songRepository = $songRepository; $this->songRepository = $songRepository;
} }
/** @return Collection|array<Song> */
public function getSongs(Playlist $playlist): Collection public function getSongs(Playlist $playlist): Collection
{ {
if (!$playlist->is_smart) { if (!$playlist->is_smart) {
throw new RuntimeException($playlist->name.' is not a smart playlist.'); throw new RuntimeException($playlist->name . ' is not a smart playlist.');
} }
$rules = $this->addRequiresUserRules($playlist->rules, $playlist->user); $rules = $this->addRequiresUserRules($playlist->rules, $playlist->user);
@ -53,7 +54,7 @@ class SmartPlaylistService
* (basically everything related to interactions). * (basically everything related to interactions).
* For those, we create an additional "user_id" rule. * For those, we create an additional "user_id" rule.
* *
* @param array[] $rules * @return array<mixed>
*/ */
public function addRequiresUserRules(array $rules, User $user): array public function addRequiresUserRules(array $rules, User $user): array
{ {
@ -75,10 +76,11 @@ class SmartPlaylistService
return $rules; return $rules;
} }
/** @return array<mixed> */
private function createRequireUserRule(User $user, string $modelPrefix): array private function createRequireUserRule(User $user, string $modelPrefix): array
{ {
return [ return [
'model' => $modelPrefix.'user_id', 'model' => $modelPrefix . 'user_id',
'operator' => 'is', 'operator' => 'is',
'value' => [$user->id], 'value' => [$user->id],
]; ];

View file

@ -3,7 +3,6 @@
namespace App\Services\Streamers; namespace App\Services\Streamers;
use DaveRandom\Resume\FileResource; use DaveRandom\Resume\FileResource;
use function DaveRandom\Resume\get_request_header;
use DaveRandom\Resume\InvalidRangeHeaderException; use DaveRandom\Resume\InvalidRangeHeaderException;
use DaveRandom\Resume\NonExistentFileException; use DaveRandom\Resume\NonExistentFileException;
use DaveRandom\Resume\RangeSet; use DaveRandom\Resume\RangeSet;
@ -13,9 +12,11 @@ use DaveRandom\Resume\UnreadableFileException;
use DaveRandom\Resume\UnsatisfiableRangeException; use DaveRandom\Resume\UnsatisfiableRangeException;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use function DaveRandom\Resume\get_request_header;
class PHPStreamer extends Streamer implements DirectStreamerInterface class PHPStreamer extends Streamer implements DirectStreamerInterface
{ {
public function stream() public function stream(): void
{ {
try { try {
$rangeSet = RangeSet::createFromHeader(get_request_header('Range')); $rangeSet = RangeSet::createFromHeader(get_request_header('Range'));

View file

@ -3,6 +3,8 @@
namespace App\Services\Streamers; namespace App\Services\Streamers;
use App\Services\S3Service; use App\Services\S3Service;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Redirector;
class S3Streamer extends Streamer implements ObjectStorageStreamerInterface class S3Streamer extends Streamer implements ObjectStorageStreamerInterface
{ {
@ -11,12 +13,15 @@ class S3Streamer extends Streamer implements ObjectStorageStreamerInterface
public function __construct(S3Service $s3Service) public function __construct(S3Service $s3Service)
{ {
parent::__construct(); parent::__construct();
$this->s3Service = $s3Service; $this->s3Service = $s3Service;
} }
/** /**
* Stream the current song through S3. * Stream the current song through S3.
* Actually, we just redirect the request to the S3 object's location. * Actually, we just redirect the request to the S3 object's location.
*
* @return Redirector|RedirectResponse
*/ */
public function stream() public function stream()
{ {

Some files were not shown because too many files have changed in this diff Show more