2022-06-10 10:47:46 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
2024-01-25 16:21:26 +00:00
|
|
|
use App\Facades\License;
|
2022-06-10 10:47:46 +00:00
|
|
|
use App\Models\Album;
|
|
|
|
use App\Models\Artist;
|
|
|
|
use App\Models\Song;
|
|
|
|
use App\Repositories\SongRepository;
|
2024-02-23 18:36:02 +00:00
|
|
|
use App\Services\SongStorages\SongStorage;
|
2022-06-10 10:47:46 +00:00
|
|
|
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;
|
2024-06-04 13:35:00 +00:00
|
|
|
use Illuminate\Support\Facades\Log;
|
2022-09-15 09:07:25 +00:00
|
|
|
use Throwable;
|
2022-06-10 10:47:46 +00:00
|
|
|
|
|
|
|
class SongService
|
|
|
|
{
|
2024-02-23 16:03:54 +00:00
|
|
|
public function __construct(
|
2024-04-18 14:36:28 +00:00
|
|
|
private readonly SongRepository $songRepository,
|
2024-06-04 13:35:00 +00:00
|
|
|
private readonly SongStorage $songStorage
|
2024-02-23 16:03:54 +00:00
|
|
|
) {
|
2022-06-10 10:47:46 +00:00
|
|
|
}
|
|
|
|
|
2024-04-18 11:27:07 +00:00
|
|
|
/** @return Collection<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 {
|
2024-06-04 13:35:00 +00:00
|
|
|
optional(
|
|
|
|
Song::query()->with('album.artist')->find($id),
|
|
|
|
fn (Song $song) => $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-25 16:21:26 +00:00
|
|
|
public function markSongsAsPublic(Collection $songs): void
|
2024-01-08 22:21:21 +00:00
|
|
|
{
|
|
|
|
Song::query()->whereIn('id', $songs->pluck('id'))->update(['is_public' => true]);
|
|
|
|
}
|
|
|
|
|
2024-01-25 16:21:26 +00:00
|
|
|
/** @return array<string> IDs of songs that are marked as private */
|
|
|
|
public function markSongsAsPrivate(Collection $songs): array
|
2024-01-08 22:21:21 +00:00
|
|
|
{
|
2024-01-25 16:21:26 +00:00
|
|
|
if (License::isPlus()) {
|
2024-06-04 13:35:00 +00:00
|
|
|
// Songs that are in collaborative playlists can't be marked as private.
|
2024-01-25 16:21:26 +00:00
|
|
|
/**
|
2024-04-18 11:27:07 +00:00
|
|
|
* @var Collection<array-key, Song> $collaborativeSongs
|
2024-01-25 16:21:26 +00:00
|
|
|
*/
|
|
|
|
$collaborativeSongs = Song::query()
|
|
|
|
->whereIn('songs.id', $songs->pluck('id'))
|
|
|
|
->join('playlist_song', 'songs.id', '=', 'playlist_song.song_id')
|
|
|
|
->join('playlist_collaborators', 'playlist_song.playlist_id', '=', 'playlist_collaborators.playlist_id')
|
|
|
|
->select('songs.id')
|
|
|
|
->distinct()
|
|
|
|
->pluck('songs.id')
|
|
|
|
->all();
|
|
|
|
|
|
|
|
$applicableSongIds = $songs->whereNotIn('id', $collaborativeSongs)->pluck('id')->all();
|
|
|
|
} else {
|
|
|
|
$applicableSongIds = $songs->pluck('id')->all();
|
|
|
|
}
|
|
|
|
|
|
|
|
Song::query()->whereIn('id', $applicableSongIds)->update(['is_public' => false]);
|
|
|
|
|
|
|
|
return $applicableSongIds;
|
2024-01-08 22:21:21 +00:00
|
|
|
}
|
|
|
|
|
2022-09-15 09:07:25 +00:00
|
|
|
/**
|
|
|
|
* @param array<string>|string $ids
|
|
|
|
*/
|
|
|
|
public function deleteSongs(array|string $ids): void
|
|
|
|
{
|
|
|
|
$ids = Arr::wrap($ids);
|
2024-06-04 13:35:00 +00:00
|
|
|
$shouldBackUp = config('koel.backup_on_delete');
|
2022-09-15 09:07:25 +00:00
|
|
|
|
2024-06-04 13:35:00 +00:00
|
|
|
DB::transaction(function () use ($ids, $shouldBackUp): void {
|
2022-09-15 09:07:25 +00:00
|
|
|
$songs = Song::query()->findMany($ids);
|
|
|
|
|
|
|
|
Song::destroy($ids);
|
|
|
|
|
|
|
|
$songs->each(function (Song $song) use ($shouldBackUp): void {
|
|
|
|
try {
|
2024-02-23 16:03:54 +00:00
|
|
|
$this->songStorage->delete($song, $shouldBackUp);
|
2022-09-15 09:07:25 +00:00
|
|
|
} catch (Throwable $e) {
|
2024-06-04 13:35:00 +00:00
|
|
|
Log::error('Failed to remove song file', [
|
2022-09-15 09:07:25 +00:00
|
|
|
'path' => $song->path,
|
|
|
|
'exception' => $e,
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2022-06-10 10:47:46 +00:00
|
|
|
}
|