mirror of
https://github.com/koel/koel
synced 2024-11-14 00:17:13 +00:00
chore: fix CS
This commit is contained in:
parent
a90d961440
commit
560d41bf1d
185 changed files with 801 additions and 642 deletions
28
.github/workflows/workflow.yml
vendored
28
.github/workflows/workflow.yml
vendored
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -64,7 +64,7 @@ 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.');
|
||||||
|
@ -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,7 +224,7 @@ 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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ 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');
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ class AlbumInformationFetched extends Event
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed[]
|
* @return array<mixed>
|
||||||
*/
|
*/
|
||||||
public function getInformation(): array
|
public function getInformation(): array
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,7 +24,7 @@ class ArtistInformationFetched
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return mixed[]
|
* @return array<mixed>
|
||||||
*/
|
*/
|
||||||
public function getInformation(): array
|
public function getInformation(): array
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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';
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
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
|
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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)], [
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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') {
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -7,6 +7,7 @@ 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 [
|
||||||
|
|
|
@ -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'];
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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 [];
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -17,7 +17,7 @@ class TidyLibrary
|
||||||
/**
|
/**
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$this->mediaSyncService->tidy();
|
$this->mediaSyncService->tidy();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,6 +10,9 @@ 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
|
||||||
|
@ -24,7 +25,8 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
* @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'];
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -18,7 +18,7 @@ use Illuminate\Support\Collection;
|
||||||
* @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
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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,7 +141,7 @@ 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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -245,9 +241,9 @@ class LastfmService extends AbstractApiClient implements ApiConsumerInterface
|
||||||
* @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);
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,7 +79,7 @@ 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
|
||||||
|
@ -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();
|
||||||
|
@ -253,15 +232,13 @@ class MediaSyncService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @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());
|
||||||
|
|
|
@ -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;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ 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) {
|
||||||
|
@ -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,6 +76,7 @@ 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 [
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
|
@ -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()
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,14 +6,10 @@ use App\Models\Song;
|
||||||
|
|
||||||
class Streamer
|
class Streamer
|
||||||
{
|
{
|
||||||
/**
|
/** @var Song|string */
|
||||||
* @var Song|string
|
|
||||||
*/
|
|
||||||
protected $song;
|
protected $song;
|
||||||
|
|
||||||
/**
|
/** @var string */
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $contentType;
|
protected $contentType;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
|
|
|
@ -8,5 +8,6 @@ interface StreamerInterface
|
||||||
{
|
{
|
||||||
public function setSong(Song $song): void;
|
public function setSong(Song $song): void;
|
||||||
|
|
||||||
|
/** @return mixed */
|
||||||
public function stream();
|
public function stream();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,10 @@ use App\Exceptions\MediaPathNotSetException;
|
||||||
use App\Exceptions\SongUploadFailedException;
|
use App\Exceptions\SongUploadFailedException;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
use App\Models\Song;
|
use App\Models\Song;
|
||||||
use function Functional\memoize;
|
|
||||||
use Illuminate\Http\UploadedFile;
|
use Illuminate\Http\UploadedFile;
|
||||||
|
|
||||||
|
use function Functional\memoize;
|
||||||
|
|
||||||
class UploadService
|
class UploadService
|
||||||
{
|
{
|
||||||
private const UPLOAD_DIRECTORY = '__KOEL_UPLOADS__';
|
private const UPLOAD_DIRECTORY = '__KOEL_UPLOADS__';
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue