2022-06-10 10:47:46 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
2022-09-15 09:07:25 +00:00
|
|
|
use App\Events\LibraryChanged;
|
2022-06-10 10:47:46 +00:00
|
|
|
use App\Models\Album;
|
|
|
|
use App\Models\Artist;
|
|
|
|
use App\Models\Song;
|
|
|
|
use App\Repositories\SongRepository;
|
|
|
|
use App\Values\SongUpdateData;
|
2022-09-15 09:07:25 +00:00
|
|
|
use Illuminate\Support\Arr;
|
2022-06-10 10:47:46 +00:00
|
|
|
use Illuminate\Support\Collection;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
2022-09-15 09:07:25 +00:00
|
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
use Throwable;
|
2022-06-10 10:47:46 +00:00
|
|
|
|
|
|
|
class SongService
|
|
|
|
{
|
2022-09-15 09:07:25 +00:00
|
|
|
public function __construct(private SongRepository $songRepository, private LoggerInterface $logger)
|
2022-06-10 10:47:46 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @return Collection|array<array-key, Song> */
|
2022-10-12 09:27:35 +00:00
|
|
|
public function updateSongs(array $ids, SongUpdateData $data): Collection
|
2022-06-10 10:47:46 +00:00
|
|
|
{
|
2022-11-29 12:16:43 +00:00
|
|
|
if (count($ids) === 1) {
|
|
|
|
// If we're only updating one song, an empty non-required should be converted to the default values.
|
|
|
|
// This allows the user to clear those fields.
|
|
|
|
$data->disc = $data->disc ?: 1;
|
|
|
|
$data->track = $data->track ?: 0;
|
|
|
|
$data->lyrics = $data->lyrics ?: '';
|
|
|
|
$data->year = $data->year ?: null;
|
|
|
|
$data->genre = $data->genre ?: '';
|
|
|
|
$data->albumArtistName = $data->albumArtistName ?: $data->artistName;
|
|
|
|
}
|
|
|
|
|
2022-10-12 09:27:35 +00:00
|
|
|
return DB::transaction(function () use ($ids, $data): Collection {
|
|
|
|
return collect($ids)->reduce(function (Collection $updated, string $id) use ($data): Collection {
|
2022-06-10 10:47:46 +00:00
|
|
|
/** @var Song|null $song */
|
|
|
|
$song = Song::with('album', 'album.artist', 'artist')->find($id);
|
|
|
|
|
2022-07-29 06:47:10 +00:00
|
|
|
if ($song) {
|
2022-11-29 12:16:43 +00:00
|
|
|
$updated->push($this->updateSong($song, clone $data));
|
2022-06-10 10:47:46 +00:00
|
|
|
}
|
|
|
|
|
2022-10-12 09:27:35 +00:00
|
|
|
return $updated;
|
|
|
|
}, collect());
|
|
|
|
});
|
2022-06-10 10:47:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private function updateSong(Song $song, SongUpdateData $data): Song
|
|
|
|
{
|
2022-11-29 12:16:43 +00:00
|
|
|
// for non-nullable fields, if the provided data is empty, use the existing value
|
2022-10-12 09:27:35 +00:00
|
|
|
$data->albumName = $data->albumName ?: $song->album->name;
|
|
|
|
$data->artistName = $data->artistName ?: $song->artist->name;
|
|
|
|
$data->title = $data->title ?: $song->title;
|
2022-11-29 12:16:43 +00:00
|
|
|
|
|
|
|
// For nullable fields, use the existing value only if the provided data is explicitly null.
|
|
|
|
// This allows us to clear those fields.
|
|
|
|
$data->albumArtistName ??= $song->album_artist->name;
|
|
|
|
$data->lyrics ??= $song->lyrics;
|
|
|
|
$data->track ??= $song->track;
|
|
|
|
$data->disc ??= $song->disc;
|
|
|
|
$data->genre ??= $song->genre;
|
|
|
|
$data->year ??= $song->year;
|
2022-10-12 09:27:35 +00:00
|
|
|
|
|
|
|
$albumArtist = Artist::getOrCreate($data->albumArtistName);
|
|
|
|
$artist = Artist::getOrCreate($data->artistName);
|
|
|
|
$album = Album::getOrCreate($albumArtist, $data->albumName);
|
|
|
|
|
|
|
|
$song->album_id = $album->id;
|
|
|
|
$song->artist_id = $artist->id;
|
|
|
|
$song->title = $data->title;
|
|
|
|
$song->lyrics = $data->lyrics;
|
|
|
|
$song->track = $data->track;
|
|
|
|
$song->disc = $data->disc;
|
|
|
|
$song->genre = $data->genre;
|
|
|
|
$song->year = $data->year;
|
2022-06-10 10:47:46 +00:00
|
|
|
|
2022-07-06 13:08:40 +00:00
|
|
|
$song->push();
|
2022-06-10 10:47:46 +00:00
|
|
|
|
|
|
|
return $this->songRepository->getOne($song->id);
|
|
|
|
}
|
2022-09-15 09:07:25 +00:00
|
|
|
|
2024-01-16 21:14:14 +00:00
|
|
|
public function publicizeSongs(Collection $songs): void
|
2024-01-08 22:21:21 +00:00
|
|
|
{
|
|
|
|
Song::query()->whereIn('id', $songs->pluck('id'))->update(['is_public' => true]);
|
|
|
|
}
|
|
|
|
|
2024-01-16 21:14:14 +00:00
|
|
|
public function privatizeSongs(Collection $songs): void
|
2024-01-08 22:21:21 +00:00
|
|
|
{
|
|
|
|
Song::query()->whereIn('id', $songs->pluck('id'))->update(['is_public' => false]);
|
|
|
|
}
|
|
|
|
|
2022-09-15 09:07:25 +00:00
|
|
|
/**
|
|
|
|
* @param array<string>|string $ids
|
|
|
|
*/
|
|
|
|
public function deleteSongs(array|string $ids): void
|
|
|
|
{
|
|
|
|
$ids = Arr::wrap($ids);
|
|
|
|
|
|
|
|
DB::transaction(function () use ($ids): void {
|
|
|
|
$shouldBackUp = config('koel.backup_on_delete');
|
|
|
|
|
|
|
|
/** @var Collection|array<array-key, Song> $songs */
|
|
|
|
$songs = Song::query()->findMany($ids);
|
|
|
|
|
|
|
|
Song::destroy($ids);
|
|
|
|
|
|
|
|
$songs->each(function (Song $song) use ($shouldBackUp): void {
|
|
|
|
try {
|
|
|
|
if ($shouldBackUp) {
|
|
|
|
rename($song->path, $song->path . '.bak');
|
|
|
|
} else {
|
|
|
|
unlink($song->path);
|
|
|
|
}
|
|
|
|
} catch (Throwable $e) {
|
|
|
|
$this->logger->error('Failed to remove song file', [
|
|
|
|
'path' => $song->path,
|
|
|
|
'exception' => $e,
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
event(new LibraryChanged());
|
|
|
|
});
|
|
|
|
}
|
2022-06-10 10:47:46 +00:00
|
|
|
}
|