mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
chore: fix CS
This commit is contained in:
parent
a90d961440
commit
560d41bf1d
185 changed files with 801 additions and 642 deletions
30
.github/workflows/workflow.yml
vendored
30
.github/workflows/workflow.yml
vendored
|
@ -1,15 +1,33 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- actions
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
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:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Composer
|
||||
|
|
|
@ -23,7 +23,7 @@ class Application extends IlluminateApplication
|
|||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function rev(string $file, string $manifestFile = null): string
|
||||
public function rev(string $file, ?string $manifestFile = null): string
|
||||
{
|
||||
static $manifest = null;
|
||||
|
||||
|
@ -53,6 +53,6 @@ class Application extends IlluminateApplication
|
|||
{
|
||||
$cdnUrl = trim(config('koel.cdn.url'), '/ ');
|
||||
|
||||
return $cdnUrl ? $cdnUrl.'/'.trim(ltrim($name, '/')) : trim(asset($name));
|
||||
return $cdnUrl ? $cdnUrl . '/' . trim(ltrim($name, '/')) : trim(asset($name));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ class ChangePasswordCommand extends Command
|
|||
public function __construct(Hash $hash)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->hash = $hash;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,12 +8,12 @@ use App\Models\Setting;
|
|||
use App\Models\User;
|
||||
use App\Repositories\SettingRepository;
|
||||
use App\Services\MediaCacheService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
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 Jackiedo\DotenvEditor\DotenvEditor;
|
||||
use Throwable;
|
||||
|
||||
class InitCommand extends Command
|
||||
{
|
||||
|
@ -51,7 +51,7 @@ class InitCommand extends Command
|
|||
{
|
||||
$this->comment('Attempting to install or upgrade Koel.');
|
||||
$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()) {
|
||||
$this->info('Running in no-interaction mode');
|
||||
|
@ -64,25 +64,25 @@ class InitCommand extends Command
|
|||
$this->maybeSeedDatabase();
|
||||
$this->maybeSetMediaPath();
|
||||
$this->maybeCompileFrontEndAssets();
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
$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.');
|
||||
|
||||
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')) {
|
||||
$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(
|
||||
"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! 🤘');
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ class InitCommand extends Command
|
|||
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) {
|
||||
$path = $this->ask('Media path', config('koel.media_path'));
|
||||
|
@ -224,9 +224,9 @@ class InitCommand extends Command
|
|||
$this->db->reconnect()->getPdo();
|
||||
|
||||
break;
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
$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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,14 +25,13 @@ class SyncCommand extends Command
|
|||
private $mediaSyncService;
|
||||
private $settingRepository;
|
||||
|
||||
/**
|
||||
* @var ProgressBar
|
||||
*/
|
||||
/** @var ProgressBar */
|
||||
private $progressBar;
|
||||
|
||||
public function __construct(MediaSyncService $mediaSyncService, SettingRepository $settingRepository)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->mediaSyncService = $mediaSyncService;
|
||||
$this->settingRepository = $settingRepository;
|
||||
}
|
||||
|
@ -43,8 +42,9 @@ class SyncCommand extends Command
|
|||
public function handle(): void
|
||||
{
|
||||
$this->ensureMediaPath();
|
||||
$record = $this->argument('record');
|
||||
|
||||
if (!$record = $this->argument('record')) {
|
||||
if (!$record) {
|
||||
$this->syncAll();
|
||||
|
||||
return;
|
||||
|
@ -60,7 +60,7 @@ class SyncCommand extends Command
|
|||
*/
|
||||
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.
|
||||
// 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->output->writeln(
|
||||
PHP_EOL.PHP_EOL
|
||||
."<info>Completed! {$this->synced} new or updated song(s)</info>, "
|
||||
."{$this->ignored} unchanged song(s), "
|
||||
."and <comment>{$this->invalid} invalid file(s)</comment>."
|
||||
PHP_EOL . PHP_EOL
|
||||
. "<info>Completed! {$this->synced} new or updated song(s)</info>, "
|
||||
. "{$this->ignored} unchanged song(s), "
|
||||
. "and <comment>{$this->invalid} invalid file(s)</comment>."
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,7 @@ class SyncCommand extends Command
|
|||
++$this->ignored;
|
||||
} elseif ($result === FileSynchronizer::SYNC_RESULT_BAD_FILE) {
|
||||
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;
|
||||
|
|
|
@ -16,6 +16,7 @@ class TidyLibraryCommand extends Command
|
|||
public function __construct(MediaSyncService $mediaSyncService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->mediaSyncService = $mediaSyncService;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
|||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
protected function commands()
|
||||
protected function commands(): void
|
||||
{
|
||||
$this->load(__DIR__.'/Commands');
|
||||
$this->load(__DIR__ . '/Commands');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ class AlbumInformationFetched extends Event
|
|||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function getInformation(): array
|
||||
{
|
||||
|
|
|
@ -24,7 +24,7 @@ class ArtistInformationFetched
|
|||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function getInformation(): array
|
||||
{
|
||||
|
|
|
@ -7,9 +7,9 @@ abstract class Event
|
|||
/**
|
||||
* Get the channels the event should be broadcast on.
|
||||
*
|
||||
* @return array
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function broadcastOn()
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ class SongLikeToggled extends Event
|
|||
public $interaction;
|
||||
public $user;
|
||||
|
||||
public function __construct(Interaction $interaction, User $user = null)
|
||||
public function __construct(Interaction $interaction, ?User $user = null)
|
||||
{
|
||||
$this->interaction = $interaction;
|
||||
$this->user = $user ?: auth()->user();
|
||||
|
|
|
@ -10,7 +10,7 @@ use Illuminate\Support\Facades\Facade;
|
|||
*/
|
||||
class Download extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor()
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return 'Download';
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ namespace App\Facades;
|
|||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class iTunes extends Facade
|
||||
class ITunes extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor()
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return 'iTunes';
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use Illuminate\Support\Facades\Facade;
|
|||
*/
|
||||
class Util extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor()
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return 'Util';
|
||||
}
|
||||
|
|
13
app/Facades/YouTube.php
Normal file
13
app/Facades/YouTube.php
Normal 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';
|
||||
}
|
||||
}
|
|
@ -10,11 +10,11 @@ use Throwable;
|
|||
class SmartPlaylistRuleParameterFactory
|
||||
{
|
||||
/**
|
||||
* @param mixed[] $value
|
||||
* @param array<mixed> $value
|
||||
*
|
||||
* @throws Throwable
|
||||
*
|
||||
* @return string[]
|
||||
* @return array<string>
|
||||
*/
|
||||
public function createParameters(string $model, string $operator, array $value): array
|
||||
{
|
||||
|
|
|
@ -4,12 +4,12 @@ namespace App\Helpers;
|
|||
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
return app()->staticUrl(config('koel.artist_image_dir').$fileName);
|
||||
return app()->staticUrl(config('koel.artist_image_dir') . $fileName);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use App\Repositories\PlaylistRepository;
|
|||
use App\Repositories\SettingRepository;
|
||||
use App\Repositories\UserRepository;
|
||||
use App\Services\ApplicationInformationService;
|
||||
use App\Services\iTunesService;
|
||||
use App\Services\ITunesService;
|
||||
use App\Services\LastfmService;
|
||||
use App\Services\MediaCacheService;
|
||||
use App\Services\YouTubeService;
|
||||
|
@ -35,7 +35,7 @@ class DataController extends Controller
|
|||
public function __construct(
|
||||
LastfmService $lastfmService,
|
||||
YouTubeService $youTubeService,
|
||||
iTunesService $iTunesService,
|
||||
ITunesService $iTunesService,
|
||||
MediaCacheService $mediaCacheService,
|
||||
SettingRepository $settingRepository,
|
||||
PlaylistRepository $playlistRepository,
|
||||
|
|
|
@ -16,6 +16,7 @@ class RecentlyPlayedController extends Controller
|
|||
?Authenticatable $currentUser
|
||||
) {
|
||||
parent::__construct($interactionService, $currentUser);
|
||||
|
||||
$this->interactionRepository = $interactionRepository;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ class SongController extends Controller
|
|||
public function __construct(MediaInformationService $mediaInformationService, YouTubeService $youTubeService)
|
||||
{
|
||||
parent::__construct($mediaInformationService);
|
||||
|
||||
$this->youTubeService = $youTubeService;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,9 +38,14 @@ class SongController extends Controller
|
|||
|
||||
$compilation = (bool) trim(array_get($tags, 'albumartist'));
|
||||
$album = Album::getOrCreate($artist, array_get($tags, 'album'), $compilation);
|
||||
$cover = array_get($tags, 'cover');
|
||||
|
||||
if ($cover = array_get($tags, 'cover')) {
|
||||
$this->mediaMetadataService->writeAlbumCover($album, base64_decode($cover['data']), $cover['extension']);
|
||||
if ($cover) {
|
||||
$this->mediaMetadataService->writeAlbumCover(
|
||||
$album,
|
||||
base64_decode($cover['data'], true),
|
||||
$cover['extension']
|
||||
);
|
||||
}
|
||||
|
||||
$song = Song::updateOrCreate(['id' => $this->helperService->getFileHash($path)], [
|
||||
|
|
|
@ -13,6 +13,7 @@ class FavoritesController extends Controller
|
|||
public function __construct(DownloadService $downloadService, InteractionRepository $interactionRepository)
|
||||
{
|
||||
parent::__construct($downloadService);
|
||||
|
||||
$this->interactionRepository = $interactionRepository;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ class SongController extends Controller
|
|||
public function __construct(DownloadService $downloadService, SongRepository $songRepository)
|
||||
{
|
||||
parent::__construct($downloadService);
|
||||
|
||||
$this->songRepository = $songRepository;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,16 +4,16 @@ namespace App\Http\Controllers;
|
|||
|
||||
use App\Http\Requests\API\ViewSongOnITunesRequest;
|
||||
use App\Models\Album;
|
||||
use App\Services\iTunesService;
|
||||
use App\Services\ITunesService;
|
||||
use App\Services\TokenManager;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class iTunesController extends Controller
|
||||
class ITunesController extends Controller
|
||||
{
|
||||
private $iTunesService;
|
||||
private $tokenManager;
|
||||
|
||||
public function __construct(iTunesService $iTunesService, TokenManager $tokenManager)
|
||||
public function __construct(ITunesService $iTunesService, TokenManager $tokenManager)
|
||||
{
|
||||
$this->iTunesService = $iTunesService;
|
||||
$this->tokenManager = $tokenManager;
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Http\Middleware;
|
|||
use Closure;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class Authenticate
|
||||
{
|
||||
|
@ -15,7 +16,7 @@ class Authenticate
|
|||
$this->auth = $auth;
|
||||
}
|
||||
|
||||
public function handle(Request $request, Closure $next)
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if ($this->auth->guest()) {
|
||||
if ($request->ajax() || $request->route()->getName() === 'play') {
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Http\Middleware;
|
|||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\UrlGenerator;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ForceHttps
|
||||
{
|
||||
|
@ -15,7 +16,7 @@ class ForceHttps
|
|||
$this->url = $url;
|
||||
}
|
||||
|
||||
public function handle(Request $request, Closure $next)
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (config('koel.force_https')) {
|
||||
$this->url->forceScheme('https');
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Http\Middleware;
|
|||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Authenticate requests from Object Storage services (like S3).
|
||||
|
@ -11,7 +12,7 @@ use Illuminate\Http\Request;
|
|||
*/
|
||||
class ObjectStorageAuthenticate
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if ($request->appKey !== config('app.key')) {
|
||||
return response('Unauthorized.', 401);
|
||||
|
|
|
@ -11,6 +11,7 @@ abstract class AbstractMediaImageUpdateRequest extends Request
|
|||
return auth()->user()->is_admin;
|
||||
}
|
||||
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
@ -20,15 +21,15 @@ abstract class AbstractMediaImageUpdateRequest extends Request
|
|||
|
||||
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
|
||||
{
|
||||
[$type, $data] = explode(';', $this->{$this->getImageFieldName()});
|
||||
[$_, $extension] = explode('/', $type);
|
||||
[$type,] = explode(';', $this->{$this->getImageFieldName()});
|
||||
[, $extension] = explode('/', $type);
|
||||
|
||||
return $extension;
|
||||
}
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
namespace App\Http\Requests\API;
|
||||
|
||||
/**
|
||||
* @property string[] $songs
|
||||
* @property array<string> $songs
|
||||
*/
|
||||
class BatchInteractionRequest extends Request
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -9,6 +9,7 @@ use App\Http\Requests\API\Request;
|
|||
*/
|
||||
class StorePlayCountRequest extends Request
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace App\Http\Requests\API;
|
|||
*/
|
||||
class LastfmCallbackRequest extends Request
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace App\Http\Requests\API;
|
|||
*/
|
||||
class LastfmSetSessionKeyRequest extends Request
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -6,6 +6,7 @@ use App\Http\Requests\API\Request as BaseRequest;
|
|||
|
||||
class Request extends BaseRequest
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -6,11 +6,12 @@ use App\Http\Requests\API\ObjectStorage\S3\Request as BaseRequest;
|
|||
|
||||
/**
|
||||
* @property string $bucket
|
||||
* @property string[] $tags
|
||||
* @property array<string> $tags
|
||||
* @property string $key
|
||||
*/
|
||||
class PutSongRequest extends BaseRequest
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -10,6 +10,7 @@ use App\Http\Requests\API\ObjectStorage\S3\Request as BaseRequest;
|
|||
*/
|
||||
class RemoveSongRequest extends BaseRequest
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
namespace App\Http\Requests\API;
|
||||
|
||||
/**
|
||||
* @property string[] $songs
|
||||
* @property array<string> $songs
|
||||
* @property string $name
|
||||
* @property array $rules
|
||||
*/
|
||||
class PlaylistStoreRequest extends Request
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
namespace App\Http\Requests\API;
|
||||
|
||||
/**
|
||||
* @property string[] $songs
|
||||
* @property array<string> $songs
|
||||
*/
|
||||
class PlaylistSyncRequest extends Request
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -7,11 +7,12 @@ namespace App\Http\Requests\API;
|
|||
*/
|
||||
class ProfileUpdateRequest extends Request
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required',
|
||||
'email' => 'required|email|unique:users,email,'.auth()->user()->id,
|
||||
'email' => 'required|email|unique:users,email,' . auth()->user()->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace App\Http\Requests\API;
|
|||
*/
|
||||
class ScrobbleStoreRequest extends Request
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return ['timestamp' => 'required|numeric'];
|
||||
|
|
|
@ -12,6 +12,7 @@ class SettingRequest extends Request
|
|||
return auth()->user()->is_admin;
|
||||
}
|
||||
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
namespace App\Http\Requests\API;
|
||||
|
||||
/**
|
||||
* @property string[] $songs
|
||||
* @property mixed[] $data
|
||||
* @property array<string> $songs
|
||||
* @property array<mixed> $data
|
||||
*/
|
||||
class SongUpdateRequest extends Request
|
||||
{
|
||||
|
@ -13,6 +13,7 @@ class SongUpdateRequest extends Request
|
|||
return $this->user()->is_admin;
|
||||
}
|
||||
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -13,6 +13,7 @@ class UploadRequest extends AbstractRequest
|
|||
return auth()->user()->is_admin;
|
||||
}
|
||||
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace App\Http\Requests\API;
|
|||
*/
|
||||
class UserLoginRequest extends Request
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -15,6 +15,7 @@ class UserStoreRequest extends Request
|
|||
return auth()->user()->is_admin;
|
||||
}
|
||||
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -17,6 +17,7 @@ class UserUpdateRequest extends Request
|
|||
return auth()->user()->is_admin;
|
||||
}
|
||||
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
/** @var User $user */
|
||||
|
@ -24,7 +25,7 @@ class UserUpdateRequest extends Request
|
|||
|
||||
return [
|
||||
'name' => 'required',
|
||||
'email' => 'required|email|unique:users,email,'.$user->id,
|
||||
'email' => 'required|email|unique:users,email,' . $user->id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace App\Http\Requests\API;
|
|||
*/
|
||||
class ViewSongOnITunesRequest extends Request
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -11,6 +11,7 @@ abstract class AbstractRequest extends FormRequest
|
|||
return true;
|
||||
}
|
||||
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [];
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace App\Http\Requests\Download;
|
|||
*/
|
||||
class SongRequest extends Request
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -16,6 +16,5 @@ abstract class Job
|
|||
| provides access to the "onQueue" and "delay" queue helper methods.
|
||||
|
|
||||
*/
|
||||
|
||||
use Queueable;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ class InotifyWatchRecord extends WatchRecord implements WatchRecordInterface
|
|||
public function __construct(string $input)
|
||||
{
|
||||
parent::__construct($input);
|
||||
|
||||
$this->parse($input);
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ abstract class WatchRecord implements WatchRecordInterface
|
|||
return $this->path;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->input;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace App\Libraries\WatchRecord;
|
|||
|
||||
interface WatchRecordInterface
|
||||
{
|
||||
public function parse(string $string);
|
||||
public function parse(string $string): void;
|
||||
|
||||
public function getPath(): string;
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace App\Listeners;
|
|||
|
||||
use App\Events\AlbumInformationFetched;
|
||||
use App\Services\MediaMetadataService;
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class DownloadAlbumCover
|
||||
{
|
||||
|
@ -26,7 +26,7 @@ class DownloadAlbumCover
|
|||
if (!$album->has_cover && $image && ini_get('allow_url_fopen')) {
|
||||
try {
|
||||
$this->mediaMetadataService->downloadAlbumCover($album, $image);
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace App\Listeners;
|
|||
|
||||
use App\Events\ArtistInformationFetched;
|
||||
use App\Services\MediaMetadataService;
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class DownloadArtistImage
|
||||
{
|
||||
|
@ -26,7 +26,7 @@ class DownloadArtistImage
|
|||
if (!$artist->has_image && $image && ini_get('allow_url_fopen')) {
|
||||
try {
|
||||
$this->mediaMetadataService->downloadArtistImage($artist, $image);
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,6 @@ class JWTEventListener
|
|||
|
||||
public function subscribe(Dispatcher $events): void
|
||||
{
|
||||
$events->listen('tymon.jwt.valid', self::class.'@onValidUser');
|
||||
$events->listen('tymon.jwt.valid', self::class . '@onValidUser');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,9 @@ class LoveTrackOnLastfm
|
|||
|
||||
public function handle(SongLikeToggled $event): void
|
||||
{
|
||||
if (!$this->lastfm->enabled() ||
|
||||
!($sessionKey = $event->user->lastfm_session_key) ||
|
||||
if (
|
||||
!$this->lastfm->enabled() ||
|
||||
!$event->user->lastfm_session_key ||
|
||||
$event->interaction->song->artist->is_unknown
|
||||
) {
|
||||
return;
|
||||
|
|
|
@ -17,7 +17,7 @@ class TidyLibrary
|
|||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): void
|
||||
{
|
||||
$this->mediaSyncService->tidy();
|
||||
}
|
||||
|
|
|
@ -17,10 +17,7 @@ class UpdateLastfmNowPlaying
|
|||
|
||||
public function handle(SongStartedPlaying $event): void
|
||||
{
|
||||
if (!$this->lastfm->enabled() ||
|
||||
!($sessionKey = $event->user->lastfm_session_key) ||
|
||||
$event->song->artist->is_unknown
|
||||
) {
|
||||
if (!$this->lastfm->enabled() || !$event->user->lastfm_session_key || $event->song->artist->is_unknown) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace App\Models;
|
||||
|
||||
use function App\Helpers\album_cover_path;
|
||||
use function App\Helpers\album_cover_url;
|
||||
use App\Traits\SupportsDeleteWhereIDsNotIn;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
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\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 bool $has_cover If the album has a non-default cover image
|
||||
* @property int $id
|
||||
* @property string $name Name of the album
|
||||
* @property bool $is_compilation If the album is a compilation from multiple artists
|
||||
* @property Artist $artist The album's artist
|
||||
* @property int $artist_id
|
||||
* @property Collection $songs
|
||||
* @property bool $is_unknown If the album is the Unknown Album
|
||||
* @property bool $has_cover If the album has a non-default cover image
|
||||
* @property int $id
|
||||
* @property string $name Name of the album
|
||||
* @property bool $is_compilation If the album is a compilation from multiple artists
|
||||
* @property Artist $artist The album's artist
|
||||
* @property int $artist_id
|
||||
* @property Collection $songs
|
||||
* @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_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
|
||||
*
|
||||
* @method static self firstOrCreate(array $where, array $params = [])
|
||||
|
@ -38,9 +40,9 @@ class Album extends Model
|
|||
use HasFactory;
|
||||
use SupportsDeleteWhereIDsNotIn;
|
||||
|
||||
const UNKNOWN_ID = 1;
|
||||
const UNKNOWN_NAME = 'Unknown Album';
|
||||
const UNKNOWN_COVER = 'unknown-album.png';
|
||||
public const UNKNOWN_ID = 1;
|
||||
public const UNKNOWN_NAME = 'Unknown Album';
|
||||
public const UNKNOWN_COVER = 'unknown-album.png';
|
||||
|
||||
protected $guarded = ['id'];
|
||||
protected $hidden = ['updated_at'];
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Facades\Util;
|
||||
use function App\Helpers\artist_image_path;
|
||||
use function App\Helpers\artist_image_url;
|
||||
use App\Traits\SupportsDeleteWhereIDsNotIn;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
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\HasManyThrough;
|
||||
|
||||
use function App\Helpers\artist_image_path;
|
||||
use function App\Helpers\artist_image_url;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
|
@ -34,10 +35,10 @@ class Artist extends Model
|
|||
use HasFactory;
|
||||
use SupportsDeleteWhereIDsNotIn;
|
||||
|
||||
const UNKNOWN_ID = 1;
|
||||
const UNKNOWN_NAME = 'Unknown Artist';
|
||||
const VARIOUS_ID = 2;
|
||||
const VARIOUS_NAME = 'Various Artists';
|
||||
public const UNKNOWN_ID = 1;
|
||||
public const UNKNOWN_NAME = 'Unknown Artist';
|
||||
public const VARIOUS_ID = 2;
|
||||
public const VARIOUS_NAME = 'Various Artists';
|
||||
|
||||
protected $guarded = ['id'];
|
||||
protected $hidden = ['created_at', 'updated_at'];
|
||||
|
@ -87,7 +88,9 @@ class Artist extends Model
|
|||
public static function getOrCreate(?string $name = null): self
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,13 @@ class Rule
|
|||
private function validateOperator(string $operator): void
|
||||
{
|
||||
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)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,15 +23,13 @@ class Setting extends Model
|
|||
/**
|
||||
* Get a setting value.
|
||||
*
|
||||
* @return mixed|string
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get(string $key)
|
||||
{
|
||||
if ($record = self::find($key)) {
|
||||
return $record->value;
|
||||
}
|
||||
$record = self::find($key);
|
||||
|
||||
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,
|
||||
* in which case $value will be discarded
|
||||
* @param mixed $value
|
||||
*/
|
||||
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.
|
||||
* This makes settings more flexible.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setValueAttribute($value): void
|
||||
{
|
||||
|
@ -68,8 +63,6 @@ class Setting extends Model
|
|||
/**
|
||||
* Get the unserialized setting value.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValueAttribute($value)
|
||||
|
|
|
@ -14,19 +14,19 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
|||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* @property string $path
|
||||
* @property string $title
|
||||
* @property Album $album
|
||||
* @property Artist $artist
|
||||
* @property string[] $s3_params
|
||||
* @property float $length
|
||||
* @property string $lyrics
|
||||
* @property int $track
|
||||
* @property int $disc
|
||||
* @property int $album_id
|
||||
* @property string $id
|
||||
* @property int $artist_id
|
||||
* @property int $mtime
|
||||
* @property string $path
|
||||
* @property string $title
|
||||
* @property Album $album
|
||||
* @property Artist $artist
|
||||
* @property array<string> $s3_params
|
||||
* @property float $length
|
||||
* @property string $lyrics
|
||||
* @property int $track
|
||||
* @property int $disc
|
||||
* @property int $album_id
|
||||
* @property string $id
|
||||
* @property int $artist_id
|
||||
* @property int $mtime
|
||||
*
|
||||
* @method static self updateOrCreate(array $where, array $params)
|
||||
* @method static Builder select(string $string)
|
||||
|
@ -48,14 +48,9 @@ class Song extends Model
|
|||
* Attributes to be hidden from JSON outputs.
|
||||
* Here we specify to hide lyrics as well to save some bandwidth (actually, lots of it).
|
||||
* Lyrics can then be queried on demand.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = ['lyrics', 'updated_at', 'path', 'mtime'];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'length' => 'float',
|
||||
'mtime' => 'int',
|
||||
|
@ -64,12 +59,6 @@ class Song extends Model
|
|||
];
|
||||
|
||||
protected $keyType = 'string';
|
||||
|
||||
/**
|
||||
* Indicates if the IDs are auto-incrementing.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $incrementing = false;
|
||||
|
||||
public function artist(): BelongsTo
|
||||
|
@ -95,14 +84,16 @@ class Song extends Model
|
|||
/**
|
||||
* Update song info.
|
||||
*
|
||||
* @param string[] $ids
|
||||
* @param string[] $data the data array, with these supported fields:
|
||||
* - title
|
||||
* - artistName
|
||||
* - albumName
|
||||
* - lyrics
|
||||
* All of these are optional, in which case the info will not be changed
|
||||
* (except for lyrics, which will be emptied)
|
||||
* @param array<string> $ids
|
||||
* @param array<string> $data the data array, with these supported fields:
|
||||
* - title
|
||||
* - artistName
|
||||
* - albumName
|
||||
* - lyrics
|
||||
* All of these are optional, in which case the info will not be changed
|
||||
* (except for lyrics, which will be emptied)
|
||||
*
|
||||
* @return Collection|array<Song>
|
||||
*/
|
||||
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
|
||||
$isCompilation = true;
|
||||
break;
|
||||
|
||||
case 2: // Keep current compilation status
|
||||
$isCompilation = $this->album->artist_id === Artist::VARIOUS_ID;
|
||||
break;
|
||||
|
||||
default:
|
||||
$isCompilation = false;
|
||||
break;
|
||||
|
@ -197,7 +190,7 @@ class Song extends Model
|
|||
public function scopeInDirectory(Builder $query, string $path): Builder
|
||||
{
|
||||
// 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%");
|
||||
}
|
||||
|
@ -234,7 +227,7 @@ class Song extends Model
|
|||
/**
|
||||
* Get the bucket and key name of an S3 object.
|
||||
*
|
||||
* @return string[]|null
|
||||
* @return array<string>|null
|
||||
*/
|
||||
public function getS3ParamsAttribute(): ?array
|
||||
{
|
||||
|
@ -247,10 +240,7 @@ class Song extends Model
|
|||
return compact('bucket', 'key');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ID of the song when it's converted to string.
|
||||
*/
|
||||
public function __toString()
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
|
|
@ -3,17 +3,15 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Facades\Download;
|
||||
use Exception;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
use ZipArchive;
|
||||
|
||||
class SongZipArchive
|
||||
{
|
||||
/**
|
||||
* @var ZipArchive
|
||||
*/
|
||||
/** @var ZipArchive */
|
||||
private $archive;
|
||||
|
||||
/**
|
||||
|
@ -60,7 +58,7 @@ class SongZipArchive
|
|||
try {
|
||||
$path = Download::fromSong($song);
|
||||
$this->archive->addFile($path, $this->generateZipContentFileNameFromPath($path));
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
Log::error($e);
|
||||
}
|
||||
|
||||
|
@ -95,7 +93,7 @@ class SongZipArchive
|
|||
++$this->fileNames[$name];
|
||||
$parts = explode('.', $name);
|
||||
$ext = $parts[count($parts) - 1];
|
||||
$parts[count($parts) - 1] = $this->fileNames[$name].".$ext";
|
||||
$parts[count($parts) - 1] = $this->fileNames[$name] . ".$ext";
|
||||
$name = implode('.', $parts);
|
||||
} else {
|
||||
$this->fileNames[$name] = 1;
|
||||
|
|
|
@ -30,7 +30,6 @@ class User extends Authenticatable
|
|||
/**
|
||||
* The preferences that we don't want to show to the client.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private const HIDDEN_PREFERENCES = ['lastfm_session_key'];
|
||||
|
||||
|
@ -114,7 +113,7 @@ class User extends Authenticatable
|
|||
/**
|
||||
* User preferences are stored as a serialized associative array.
|
||||
*
|
||||
* @param mixed[] $value
|
||||
* @param array<mixed> $value
|
||||
*/
|
||||
public function setPreferencesAttribute(array $value): void
|
||||
{
|
||||
|
@ -124,7 +123,7 @@ class User extends Authenticatable
|
|||
/**
|
||||
* Unserialize the user preferences back to an array before returning.
|
||||
*
|
||||
* @return mixed[]
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function getPreferencesAttribute(?string $value): array
|
||||
{
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
namespace App\Observers;
|
||||
|
||||
use App\Models\Album;
|
||||
use Exception;
|
||||
use Illuminate\Log\Logger;
|
||||
use Throwable;
|
||||
|
||||
class AlbumObserver
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ class AlbumObserver
|
|||
|
||||
try {
|
||||
unlink($album->cover_path);
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->error($e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use Illuminate\Database\Schema\Builder;
|
|||
use Illuminate\Database\SQLiteConnection;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Validation\Factory as Validator;
|
||||
use Laravel\Tinker\TinkerServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -35,7 +36,7 @@ class AppServiceProvider extends ServiceProvider
|
|||
public function register(): void
|
||||
{
|
||||
if ($this->app->environment() !== 'production') {
|
||||
$this->app->register(\Laravel\Tinker\TinkerServiceProvider::class);
|
||||
$this->app->register(TinkerServiceProvider::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ class AuthServiceProvider extends ServiceProvider
|
|||
/**
|
||||
* Register any application authentication / authorization services.
|
||||
*/
|
||||
public function boot()
|
||||
public function boot(): void
|
||||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
|
|
|
@ -10,9 +10,8 @@ class BroadcastServiceProvider extends ServiceProvider
|
|||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
public function boot(): void
|
||||
{
|
||||
Broadcast::routes();
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ class EventServiceProvider extends ServiceProvider
|
|||
/**
|
||||
* Register any other events for your application.
|
||||
*/
|
||||
public function boot()
|
||||
public function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
|
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\iTunesService;
|
||||
use App\Services\ITunesService;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class iTunesServiceProvider extends ServiceProvider
|
||||
class ITunesServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
app()->singleton('iTunes', static function (): iTunesService {
|
||||
return app(iTunesService::class);
|
||||
app()->singleton('iTunes', static function (): ITunesService {
|
||||
return app(ITunesService::class);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,9 +19,8 @@ class RouteServiceProvider extends ServiceProvider
|
|||
/**
|
||||
* Define your route model bindings, pattern filters, etc.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
public function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
}
|
||||
|
@ -29,9 +28,8 @@ class RouteServiceProvider extends ServiceProvider
|
|||
/**
|
||||
* Define the routes for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function map()
|
||||
public function map(): void
|
||||
{
|
||||
$this->mapApiRoutes();
|
||||
$this->mapWebRoutes();
|
||||
|
@ -42,9 +40,8 @@ class RouteServiceProvider extends ServiceProvider
|
|||
*
|
||||
* These routes all receive session state, CSRF protection, etc.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function mapWebRoutes()
|
||||
protected function mapWebRoutes(): void
|
||||
{
|
||||
Route::middleware('web')
|
||||
->namespace($this->namespace)
|
||||
|
@ -56,9 +53,8 @@ class RouteServiceProvider extends ServiceProvider
|
|||
*
|
||||
* These routes are typically stateless.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function mapApiRoutes()
|
||||
protected function mapApiRoutes(): void
|
||||
{
|
||||
Route::prefix('api')
|
||||
->middleware('api')
|
||||
|
|
|
@ -20,8 +20,10 @@ class StreamerServiceProvider extends ServiceProvider
|
|||
switch (config('koel.streaming.method')) {
|
||||
case 'x-sendfile':
|
||||
return new XSendFileStreamer();
|
||||
|
||||
case 'x-accel-redirect':
|
||||
return new XAccelRedirectStreamer();
|
||||
|
||||
default:
|
||||
return new PHPStreamer();
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
namespace App\Repositories;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Throwable;
|
||||
|
||||
abstract class AbstractRepository implements RepositoryInterface
|
||||
{
|
||||
|
@ -25,7 +25,7 @@ abstract class AbstractRepository implements RepositoryInterface
|
|||
// rendering the whole installation failing.
|
||||
try {
|
||||
$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 Collection|array<Model> */
|
||||
public function getByIds(array $ids): Collection
|
||||
{
|
||||
return $this->model->whereIn($this->model->getKeyName(), $ids)->get();
|
||||
}
|
||||
|
||||
/** @return Collection|array<Model> */
|
||||
public function getAll(): Collection
|
||||
{
|
||||
return $this->model->all();
|
||||
|
|
|
@ -12,14 +12,13 @@ class AlbumRepository extends AbstractRepository
|
|||
return Album::class;
|
||||
}
|
||||
|
||||
/** @return array<int> */
|
||||
public function getNonEmptyAlbumIds(): array
|
||||
{
|
||||
$ids = Song::select('album_id')
|
||||
return Song::select('album_id')
|
||||
->groupBy('album_id')
|
||||
->get()
|
||||
->pluck('album_id')
|
||||
->toArray();
|
||||
|
||||
return $ids;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ class ArtistRepository extends AbstractRepository
|
|||
return Artist::class;
|
||||
}
|
||||
|
||||
/** @return array<int> */
|
||||
public function getNonEmptyArtistIds(): array
|
||||
{
|
||||
return Song::select('artist_id')
|
||||
|
|
|
@ -17,9 +17,7 @@ class InteractionRepository extends AbstractRepository
|
|||
return Interaction::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all songs favorited by a user.
|
||||
*/
|
||||
/** @return Collection|array<Interaction> */
|
||||
public function getUserFavorites(User $user): Collection
|
||||
{
|
||||
return $this->model->where([
|
||||
|
@ -31,9 +29,7 @@ class InteractionRepository extends AbstractRepository
|
|||
->pluck('song');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Interaction[]
|
||||
*/
|
||||
/** @return array<Interaction> */
|
||||
public function getRecentlyPlayed(User $user, ?int $count = null): array
|
||||
{
|
||||
/** @var Builder $query */
|
||||
|
|
|
@ -15,6 +15,7 @@ class PlaylistRepository extends AbstractRepository
|
|||
return Playlist::class;
|
||||
}
|
||||
|
||||
/** @return Collection|array<Playlist> */
|
||||
public function getAllByCurrentUser(): Collection
|
||||
{
|
||||
return $this->byCurrentUser()->orderBy('name')->get();
|
||||
|
|
|
@ -9,15 +9,11 @@ interface RepositoryInterface
|
|||
{
|
||||
public function getModelClass(): string;
|
||||
|
||||
/**
|
||||
* @param int|string $id
|
||||
*/
|
||||
public function getOneById($id): ?Model;
|
||||
|
||||
/**
|
||||
* @param int[]|string[] $ids
|
||||
*/
|
||||
/** @return Collection|array<Model> */
|
||||
public function getByIds(array $ids): Collection;
|
||||
|
||||
/** @return Collection|array<Model> */
|
||||
public function getAll(): Collection;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ class SettingRepository extends AbstractRepository
|
|||
return Setting::class;
|
||||
}
|
||||
|
||||
/** @return array<mixed> */
|
||||
public function getAllAsKeyValueArray(): array
|
||||
{
|
||||
return $this->model->pluck('value', 'key')->all();
|
||||
|
|
|
@ -12,6 +12,7 @@ class SongRepository extends AbstractRepository
|
|||
public function __construct(HelperService $helperService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->helperService = $helperService;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Repositories\Traits;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
trait ByCurrentUser
|
||||
|
@ -12,6 +13,7 @@ trait ByCurrentUser
|
|||
return $this->model->whereUserId($this->auth->id());
|
||||
}
|
||||
|
||||
/** @return Collection|array<Model> */
|
||||
public function getAllByCurrentUser(): Collection
|
||||
{
|
||||
return $this->byCurrentUser()->get();
|
||||
|
|
|
@ -10,7 +10,7 @@ class ImageData implements Rule
|
|||
public function passes($attribute, $value): bool
|
||||
{
|
||||
try {
|
||||
[$header, $_] = explode(';', $value);
|
||||
[$header,] = explode(';', $value);
|
||||
|
||||
return (bool) preg_match('/data:image\/(jpe?g|png|gif)/i', $header);
|
||||
} catch (Throwable $exception) {
|
||||
|
|
|
@ -48,7 +48,7 @@ abstract class AbstractApiClient
|
|||
* @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
|
||||
* 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
|
||||
*/
|
||||
|
@ -77,7 +77,7 @@ abstract class AbstractApiClient
|
|||
* Make an HTTP call to the external resource.
|
||||
*
|
||||
* @param string $method The HTTP method
|
||||
* @param mixed[] $args An array of parameters
|
||||
* @param array<mixed> $args An array of parameters
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
|
@ -90,8 +90,8 @@ abstract class AbstractApiClient
|
|||
}
|
||||
|
||||
$uri = $args[0];
|
||||
$opts = isset($args[1]) ? $args[1] : [];
|
||||
$appendKey = isset($args[2]) ? $args[2] : true;
|
||||
$opts = $args[1] ?? [];
|
||||
$appendKey = $args[2] ?? true;
|
||||
|
||||
return $this->request($method, $uri, $appendKey, $opts);
|
||||
}
|
||||
|
@ -108,14 +108,14 @@ abstract class AbstractApiClient
|
|||
$uri = "/$uri";
|
||||
}
|
||||
|
||||
$uri = $this->getEndpoint().$uri;
|
||||
$uri = $this->getEndpoint() . $uri;
|
||||
}
|
||||
|
||||
if ($appendKey) {
|
||||
if (parse_url($uri, PHP_URL_QUERY)) {
|
||||
$uri .= "&{$this->keyParam}=".$this->getKey();
|
||||
$uri .= "&{$this->keyParam}=" . $this->getKey();
|
||||
} else {
|
||||
$uri .= "?{$this->keyParam}=".$this->getKey();
|
||||
$uri .= "?{$this->keyParam}=" . $this->getKey();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
namespace App\Services;
|
||||
|
||||
use App\Application;
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
use Illuminate\Log\Logger;
|
||||
use Throwable;
|
||||
|
||||
class ApplicationInformationService
|
||||
{
|
||||
|
@ -33,7 +33,7 @@ class ApplicationInformationService
|
|||
return json_decode(
|
||||
$this->client->get('https://api.github.com/repos/phanan/koel/tags')->getBody()
|
||||
)[0]->name;
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->error($e);
|
||||
|
||||
return Application::KOEL_VERSION;
|
||||
|
|
|
@ -33,12 +33,16 @@ class DownloadService
|
|||
switch (get_class($mixed)) {
|
||||
case Song::class:
|
||||
return $this->fromSong($mixed);
|
||||
|
||||
case Collection::class:
|
||||
return $this->fromMultipleSongs($mixed);
|
||||
|
||||
case Album::class:
|
||||
return $this->fromAlbum($mixed);
|
||||
|
||||
case Artist::class:
|
||||
return $this->fromArtist($mixed);
|
||||
|
||||
case Playlist::class:
|
||||
return $this->fromPlaylist($mixed);
|
||||
}
|
||||
|
@ -48,13 +52,13 @@ class DownloadService
|
|||
|
||||
public function fromSong(Song $song): string
|
||||
{
|
||||
if ($s3Params = $song->s3_params) {
|
||||
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($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.
|
||||
// We're just assuming that to be the case here.
|
||||
|
|
|
@ -6,13 +6,13 @@ use App\Models\Album;
|
|||
use App\Models\Artist;
|
||||
use App\Models\Song;
|
||||
use App\Repositories\SongRepository;
|
||||
use Exception;
|
||||
use getID3;
|
||||
use getid3_lib;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
use InvalidArgumentException;
|
||||
use SplFileInfo;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Throwable;
|
||||
|
||||
class FileSynchronizer
|
||||
{
|
||||
|
@ -27,19 +27,13 @@ class FileSynchronizer
|
|||
private $cache;
|
||||
private $finder;
|
||||
|
||||
/**
|
||||
* @var SplFileInfo
|
||||
*/
|
||||
/** @var SplFileInfo */
|
||||
private $splFileInfo;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
/** @var int */
|
||||
private $fileModifiedTime;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
/** @var string */
|
||||
private $filePath;
|
||||
|
||||
/**
|
||||
|
@ -57,9 +51,7 @@ class FileSynchronizer
|
|||
*/
|
||||
private $song;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
/** @var string|null */
|
||||
private $syncError;
|
||||
|
||||
public function __construct(
|
||||
|
@ -78,9 +70,7 @@ class FileSynchronizer
|
|||
$this->finder = $finder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|SplFileInfo $path
|
||||
*/
|
||||
/** @param string|SplFileInfo $path */
|
||||
public function setFile($path): self
|
||||
{
|
||||
$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.
|
||||
try {
|
||||
$this->fileModifiedTime = $this->splFileInfo->getMTime();
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
// Not worth logging the error. Just use current stamp for mtime.
|
||||
$this->fileModifiedTime = time();
|
||||
}
|
||||
|
@ -103,6 +93,8 @@ class FileSynchronizer
|
|||
|
||||
/**
|
||||
* Get all applicable info from the file.
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function getFileInfo(): array
|
||||
{
|
||||
|
@ -124,7 +116,7 @@ class FileSynchronizer
|
|||
'album' => '',
|
||||
'albumartist' => '',
|
||||
'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'],
|
||||
'track' => $this->getTrackNumberFromInfo($info),
|
||||
'disc' => (int) array_get($info, 'comments.part_of_a_set.0', 1),
|
||||
|
@ -134,7 +126,9 @@ class FileSynchronizer
|
|||
'mtime' => $this->fileModifiedTime,
|
||||
];
|
||||
|
||||
if (!$comments = array_get($info, 'comments_html')) {
|
||||
$comments = array_get($info, 'comments_html');
|
||||
|
||||
if (!$comments) {
|
||||
return $props;
|
||||
}
|
||||
|
||||
|
@ -147,8 +141,8 @@ class FileSynchronizer
|
|||
/**
|
||||
* Sync the song with all available media info against the database.
|
||||
*
|
||||
* @param 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 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
|
||||
*/
|
||||
public function sync(array $tags, bool $force = false): int
|
||||
{
|
||||
|
@ -156,7 +150,9 @@ class FileSynchronizer
|
|||
return self::SYNC_RESULT_UNMODIFIED;
|
||||
}
|
||||
|
||||
if (!$info = $this->getFileInfo()) {
|
||||
$info = $this->getFileInfo();
|
||||
|
||||
if (!$info) {
|
||||
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.
|
||||
*
|
||||
* @param mixed[]|null $coverData
|
||||
* @param array<mixed>|null $coverData
|
||||
*/
|
||||
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 ($coverData) {
|
||||
$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);
|
||||
|
||||
|
@ -233,7 +229,9 @@ class FileSynchronizer
|
|||
}
|
||||
|
||||
// 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);
|
||||
$this->mediaMetadataService->writeAlbumCover($album, file_get_contents($cover), $extension);
|
||||
}
|
||||
|
@ -249,16 +247,16 @@ class FileSynchronizer
|
|||
private function getCoverFileUnderSameDirectory(): ?string
|
||||
{
|
||||
// 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(
|
||||
iterator_to_array(
|
||||
$this->finder->create()
|
||||
->depth(0)
|
||||
->ignoreUnreadableDirs()
|
||||
->files()
|
||||
->followLinks()
|
||||
->name('/(cov|fold)er\.(jpe?g|png)$/i')
|
||||
->in(dirname($this->filePath))
|
||||
->depth(0)
|
||||
->ignoreUnreadableDirs()
|
||||
->files()
|
||||
->followLinks()
|
||||
->name('/(cov|fold)er\.(jpe?g|png)$/i')
|
||||
->in(dirname($this->filePath))
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -272,7 +270,7 @@ class FileSynchronizer
|
|||
{
|
||||
try {
|
||||
return (bool) exif_imagetype($path);
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,6 @@ class HelperService
|
|||
*/
|
||||
public function getFileHash(string $path): string
|
||||
{
|
||||
return md5(config('app.key').$path);
|
||||
return md5(config('app.key') . $path);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
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.
|
||||
|
@ -29,7 +29,7 @@ class iTunesService extends AbstractApiClient implements ApiConsumerInterface
|
|||
24 * 60 * 7,
|
||||
function () use ($term, $album, $artist): ?string {
|
||||
$params = [
|
||||
'term' => $term.($album ? " $album" : '').($artist ? " $artist" : ''),
|
||||
'term' => $term . ($album ? " $album" : '') . ($artist ? " $artist" : ''),
|
||||
'media' => 'music',
|
||||
'entity' => 'song',
|
||||
'limit' => 1,
|
||||
|
@ -45,12 +45,10 @@ class iTunesService extends AbstractApiClient implements ApiConsumerInterface
|
|||
|
||||
$trackUrl = $response->results[0]->trackViewUrl;
|
||||
$connector = parse_url($trackUrl, PHP_URL_QUERY) ? '&' : '?';
|
||||
$trackUrl .= "{$connector}at=".config('koel.itunes.affiliate_id');
|
||||
|
||||
return $trackUrl;
|
||||
return $trackUrl . "{$connector}at=" . config('koel.itunes.affiliate_id');
|
||||
}
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->error($e);
|
||||
|
||||
return null;
|
||||
|
|
|
@ -23,7 +23,8 @@ class ImageWriter
|
|||
->make($data)
|
||||
->resize(
|
||||
$config['max_width'] ?? self::DEFAULT_MAX_WIDTH,
|
||||
null, static function (Constraint $constraint): void {
|
||||
null,
|
||||
static function (Constraint $constraint): void {
|
||||
$constraint->upsize();
|
||||
$constraint->aspectRatio();
|
||||
}
|
||||
|
|
|
@ -56,9 +56,9 @@ class InteractionService
|
|||
/**
|
||||
* 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
|
||||
{
|
||||
|
@ -82,7 +82,7 @@ class InteractionService
|
|||
/**
|
||||
* Unlike several songs at once.
|
||||
*
|
||||
* @param string[] $songIds
|
||||
* @param array<string> $songIds
|
||||
*/
|
||||
public function batchUnlike(array $songIds, User $user): void
|
||||
{
|
||||
|
|
|
@ -2,14 +2,12 @@
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
|
||||
class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
||||
{
|
||||
/**
|
||||
* Override the key param, since, again, Lastfm wants to be different.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $keyParam = 'api_key';
|
||||
|
||||
|
@ -32,9 +30,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
|||
/**
|
||||
* Get information about an artist.
|
||||
*
|
||||
* @param string $name Name of the artist
|
||||
*
|
||||
* @return mixed[]|null
|
||||
* @return array<mixed>|null
|
||||
*/
|
||||
public function getArtistInformation(string $name): ?array
|
||||
{
|
||||
|
@ -54,7 +50,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
|||
|
||||
return $this->buildArtistInformation($response->artist);
|
||||
});
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->error($e);
|
||||
|
||||
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.
|
||||
*
|
||||
* @param object $data
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return mixed[]
|
||||
* @return array<mixed>
|
||||
*/
|
||||
private function buildArtistInformation($data): array
|
||||
{
|
||||
|
@ -83,7 +79,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
|||
/**
|
||||
* Get information about an album.
|
||||
*
|
||||
* @return mixed[]|null
|
||||
* @return array<mixed>|null
|
||||
*/
|
||||
public function getAlbumInformation(string $albumName, string $artistName): ?array
|
||||
{
|
||||
|
@ -107,7 +103,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
|||
|
||||
return $this->buildAlbumInformation($response->album);
|
||||
});
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->error($e);
|
||||
|
||||
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.
|
||||
*
|
||||
* @param object $data
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return mixed[]
|
||||
* @return array<mixed>
|
||||
*/
|
||||
private function buildAlbumInformation($data): array
|
||||
{
|
||||
|
@ -156,7 +152,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
|||
|
||||
try {
|
||||
return $this->get("/?$query&format=json", [], false)->session->key;
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->error($e);
|
||||
|
||||
return null;
|
||||
|
@ -166,11 +162,11 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
|||
/**
|
||||
* Scrobble a song.
|
||||
*
|
||||
* @param string $artist The artist name
|
||||
* @param string $track The track name
|
||||
* @param string $artist The artist name
|
||||
* @param string $track The track name
|
||||
* @param string|int $timestamp The UNIX timestamp
|
||||
* @param string $album The album name
|
||||
* @param string $sk The session key
|
||||
* @param string $album The album name
|
||||
* @param string $sk The session key
|
||||
*/
|
||||
public function scrobble(string $artist, string $track, $timestamp, string $album, string $sk): void
|
||||
{
|
||||
|
@ -184,7 +180,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
|||
|
||||
try {
|
||||
$this->post('/', $this->buildAuthCallParams($params), false);
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->error($e);
|
||||
}
|
||||
}
|
||||
|
@ -192,10 +188,10 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
|||
/**
|
||||
* 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 $sk The session key
|
||||
* @param bool $love Whether to love or unlove. Such cheesy terms... urrgggh
|
||||
* @param string $sk The session key
|
||||
* @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
|
||||
{
|
||||
|
@ -204,7 +200,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
|||
|
||||
try {
|
||||
$this->post('/', $this->buildAuthCallParams($params), false);
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->error($e);
|
||||
}
|
||||
}
|
||||
|
@ -212,11 +208,11 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
|||
/**
|
||||
* Update a track's "now playing" on Last.fm.
|
||||
*
|
||||
* @param string $artist Name of the artist
|
||||
* @param string $track Name of the track
|
||||
* @param string $album Name of the album
|
||||
* @param string $artist Name of the artist
|
||||
* @param string $track Name of the track
|
||||
* @param string $album Name of the album
|
||||
* @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
|
||||
{
|
||||
|
@ -229,7 +225,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
|||
|
||||
try {
|
||||
$this->post('/', $this->buildAuthCallParams($params), false);
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->error($e);
|
||||
}
|
||||
}
|
||||
|
@ -242,12 +238,12 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
|||
*
|
||||
* @see http://www.last.fm/api/webauth#5
|
||||
*
|
||||
* @param array $params the array of parameters
|
||||
* @param bool $toString Whether to turn the array into a query string
|
||||
* @param array $params the array of parameters
|
||||
* @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();
|
||||
ksort($params);
|
||||
|
@ -257,7 +253,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
|||
$str = '';
|
||||
|
||||
foreach ($params as $name => $value) {
|
||||
$str .= $name.$value;
|
||||
$str .= $name . $value;
|
||||
}
|
||||
|
||||
$str .= $this->getSecret();
|
||||
|
@ -268,6 +264,7 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
|||
}
|
||||
|
||||
$query = '';
|
||||
|
||||
foreach ($params as $key => $value) {
|
||||
$query .= "$key=$value&";
|
||||
}
|
||||
|
@ -277,8 +274,6 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
|||
|
||||
/**
|
||||
* Correctly format a value returned by Last.fm.
|
||||
*
|
||||
* @param string|array $value
|
||||
*/
|
||||
protected function formatText(?string $value): string
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ class MediaCacheService
|
|||
* Get media data.
|
||||
* If caching is enabled, the data will be retrieved from the cache.
|
||||
*
|
||||
* @return mixed[]
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function get(): array
|
||||
{
|
||||
|
@ -38,7 +38,7 @@ class MediaCacheService
|
|||
/**
|
||||
* Query fresh data from the database.
|
||||
*
|
||||
* @return mixed[]
|
||||
* @return array<mixed>
|
||||
*/
|
||||
private function query(): array
|
||||
{
|
||||
|
|
|
@ -19,7 +19,7 @@ class MediaInformationService
|
|||
/**
|
||||
* 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
|
||||
{
|
||||
|
@ -43,7 +43,7 @@ class MediaInformationService
|
|||
/**
|
||||
* 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
|
||||
{
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use function App\Helpers\album_cover_path;
|
||||
use function App\Helpers\artist_image_path;
|
||||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Throwable;
|
||||
|
||||
use function App\Helpers\album_cover_path;
|
||||
use function App\Helpers\artist_image_path;
|
||||
|
||||
class MediaMetadataService
|
||||
{
|
||||
|
@ -52,7 +53,7 @@ class MediaMetadataService
|
|||
|
||||
$album->update(['cover' => basename($destination)]);
|
||||
$this->createThumbnailForAlbum($album);
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->error($e);
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +89,7 @@ class MediaMetadataService
|
|||
}
|
||||
|
||||
$artist->update(['image' => basename($destination)]);
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->error($e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,8 +23,6 @@ class MediaSyncService
|
|||
/**
|
||||
* All applicable tags in a media file that we cater for.
|
||||
* Note that each isn't necessarily a valid ID3 tag name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public const APPLICABLE_TAGS = [
|
||||
'artist',
|
||||
|
@ -81,10 +79,10 @@ class MediaSyncService
|
|||
/**
|
||||
* Sync the media. Oh sync the media.
|
||||
*
|
||||
* @param string[] $tags The tags to sync.
|
||||
* Only taken into account for existing records.
|
||||
* New records will have all tags synced in regardless.
|
||||
* @param bool $force Whether to force syncing even unchanged files
|
||||
* @param array<string> $tags The tags to sync.
|
||||
* Only taken into account for existing records.
|
||||
* New records will have all tags synced in regardless.
|
||||
* @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
|
||||
*
|
||||
* @throws Exception
|
||||
|
@ -117,9 +115,11 @@ class MediaSyncService
|
|||
case FileSynchronizer::SYNC_RESULT_SUCCESS:
|
||||
$results['success'][] = $path;
|
||||
break;
|
||||
|
||||
case FileSynchronizer::SYNC_RESULT_UNMODIFIED:
|
||||
$results['unmodified'][] = $path;
|
||||
break;
|
||||
|
||||
default:
|
||||
$results['bad_files'][] = $path;
|
||||
break;
|
||||
|
@ -147,7 +147,7 @@ class MediaSyncService
|
|||
*
|
||||
* @param string $path The directory's full path
|
||||
*
|
||||
* @return SplFileInfo[]
|
||||
* @return array<SplFileInfo>
|
||||
*/
|
||||
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
|
||||
{
|
||||
$this->logger->info("New watch record received: '{$record->getPath()}'");
|
||||
$record->isFile() ? $this->syncFileRecord($record) : $this->syncDirectoryRecord($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync a file's watch record.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function syncFileRecord(WatchRecordInterface $record): void
|
||||
{
|
||||
$path = $record->getPath();
|
||||
$this->logger->info("'$path' is a file.");
|
||||
|
||||
// If the file has been deleted...
|
||||
if ($record->isDeleted()) {
|
||||
$this->handleDeletedFileRecord($path);
|
||||
}
|
||||
// Otherwise, it's a new or changed file. Try to sync it in.
|
||||
elseif ($record->isNewOrModified()) {
|
||||
} elseif ($record->isNewOrModified()) {
|
||||
$this->handleNewOrModifiedFileRecord($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync a directory's watch record.
|
||||
*/
|
||||
private function syncDirectoryRecord(WatchRecordInterface $record): void
|
||||
{
|
||||
$path = $record->getPath();
|
||||
|
@ -213,7 +197,7 @@ class MediaSyncService
|
|||
* If the input array is empty or contains only invalid items, we use all tags.
|
||||
* Otherwise, we only use the valid items in it.
|
||||
*
|
||||
* @param string[] $tags
|
||||
* @param array<string> $tags
|
||||
*/
|
||||
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
|
||||
{
|
||||
$inUseAlbums = $this->albumRepository->getNonEmptyAlbumIds();
|
||||
|
@ -249,19 +228,17 @@ class MediaSyncService
|
|||
}
|
||||
|
||||
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
|
||||
{
|
||||
if ($song = $this->songRepository->getOneByPath($path)) {
|
||||
$song = $this->songRepository->getOneByPath($path);
|
||||
|
||||
if ($song) {
|
||||
$song->delete();
|
||||
$this->logger->info("$path deleted.");
|
||||
|
||||
event(new LibraryChanged());
|
||||
} else {
|
||||
$this->logger->info("$path doesn't exist in our database--skipping.");
|
||||
|
@ -283,7 +260,9 @@ class MediaSyncService
|
|||
|
||||
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");
|
||||
|
||||
event(new LibraryChanged());
|
||||
|
|
|
@ -28,9 +28,7 @@ class S3Service implements ObjectStorageInterface
|
|||
// Here we specify that the request is valid for 1 hour.
|
||||
// We'll also cache the public URL for future reuse.
|
||||
$request = $this->s3Client->createPresignedRequest($cmd, '+1 hour');
|
||||
$url = (string) $request->getUri();
|
||||
|
||||
return $url;
|
||||
return (string) $request->getUri();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,10 +22,11 @@ class SmartPlaylistService
|
|||
$this->songRepository = $songRepository;
|
||||
}
|
||||
|
||||
/** @return Collection|array<Song> */
|
||||
public function getSongs(Playlist $playlist): Collection
|
||||
{
|
||||
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);
|
||||
|
@ -53,7 +54,7 @@ class SmartPlaylistService
|
|||
* (basically everything related to interactions).
|
||||
* For those, we create an additional "user_id" rule.
|
||||
*
|
||||
* @param array[] $rules
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function addRequiresUserRules(array $rules, User $user): array
|
||||
{
|
||||
|
@ -75,10 +76,11 @@ class SmartPlaylistService
|
|||
return $rules;
|
||||
}
|
||||
|
||||
/** @return array<mixed> */
|
||||
private function createRequireUserRule(User $user, string $modelPrefix): array
|
||||
{
|
||||
return [
|
||||
'model' => $modelPrefix.'user_id',
|
||||
'model' => $modelPrefix . 'user_id',
|
||||
'operator' => 'is',
|
||||
'value' => [$user->id],
|
||||
];
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
namespace App\Services\Streamers;
|
||||
|
||||
use DaveRandom\Resume\FileResource;
|
||||
use function DaveRandom\Resume\get_request_header;
|
||||
use DaveRandom\Resume\InvalidRangeHeaderException;
|
||||
use DaveRandom\Resume\NonExistentFileException;
|
||||
use DaveRandom\Resume\RangeSet;
|
||||
|
@ -13,9 +12,11 @@ use DaveRandom\Resume\UnreadableFileException;
|
|||
use DaveRandom\Resume\UnsatisfiableRangeException;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
use function DaveRandom\Resume\get_request_header;
|
||||
|
||||
class PHPStreamer extends Streamer implements DirectStreamerInterface
|
||||
{
|
||||
public function stream()
|
||||
public function stream(): void
|
||||
{
|
||||
try {
|
||||
$rangeSet = RangeSet::createFromHeader(get_request_header('Range'));
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
namespace App\Services\Streamers;
|
||||
|
||||
use App\Services\S3Service;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Routing\Redirector;
|
||||
|
||||
class S3Streamer extends Streamer implements ObjectStorageStreamerInterface
|
||||
{
|
||||
|
@ -11,12 +13,15 @@ class S3Streamer extends Streamer implements ObjectStorageStreamerInterface
|
|||
public function __construct(S3Service $s3Service)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->s3Service = $s3Service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream the current song through S3.
|
||||
* Actually, we just redirect the request to the S3 object's location.
|
||||
*
|
||||
* @return Redirector|RedirectResponse
|
||||
*/
|
||||
public function stream()
|
||||
{
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue