mirror of
https://github.com/koel/koel
synced 2024-11-28 15:00:42 +00:00
Merge branch 'compilation'
This commit is contained in:
commit
899602bc7a
51 changed files with 1107 additions and 966 deletions
|
@ -28,8 +28,7 @@ class DataController extends Controller
|
|||
$playlist['songs'] = array_pluck($playlist['songs'], 'id');
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'artists' => MediaCache::get(),
|
||||
return response()->json(MediaCache::get() + [
|
||||
'settings' => auth()->user()->is_admin ? Setting::pluck('value', 'key')->all() : [],
|
||||
'playlists' => $playlists,
|
||||
'interactions' => Interaction::byCurrentUser()->get(),
|
||||
|
|
|
@ -18,7 +18,8 @@ class InteractionController extends Controller
|
|||
*/
|
||||
public function play(Request $request)
|
||||
{
|
||||
if ($interaction = Interaction::increasePlayCount($request->input('song'), $request->user())) {
|
||||
$interaction = Interaction::increasePlayCount($request->song, $request->user());
|
||||
if ($interaction) {
|
||||
event(new SongStartedPlaying($interaction->song, $interaction->user));
|
||||
}
|
||||
|
||||
|
@ -34,7 +35,7 @@ class InteractionController extends Controller
|
|||
*/
|
||||
public function like(Request $request)
|
||||
{
|
||||
return response()->json(Interaction::toggleLike($request->input('song'), $request->user()));
|
||||
return response()->json(Interaction::toggleLike($request->song, $request->user()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,7 +47,7 @@ class InteractionController extends Controller
|
|||
*/
|
||||
public function batchLike(BatchInteractionRequest $request)
|
||||
{
|
||||
return response()->json(Interaction::batchLike((array) $request->input('songs'), $request->user()));
|
||||
return response()->json(Interaction::batchLike((array) $request->songs, $request->user()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,6 +59,6 @@ class InteractionController extends Controller
|
|||
*/
|
||||
public function batchUnlike(BatchInteractionRequest $request)
|
||||
{
|
||||
return response()->json(Interaction::batchUnlike((array) $request->input('songs'), $request->user()));
|
||||
return response()->json(Interaction::batchUnlike((array) $request->songs, $request->user()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ class LastfmController extends Controller
|
|||
*/
|
||||
public function callback(Request $request, Lastfm $lastfm)
|
||||
{
|
||||
abort_unless($token = $request->input('token'), 500, 'Something wrong happened.');
|
||||
abort_unless($token = $request->token, 500, 'Something wrong happened.');
|
||||
|
||||
// Get the session key using the obtained token.
|
||||
abort_unless($sessionKey = $lastfm->getSessionKey($token), 500, 'Invalid token key.');
|
||||
|
@ -83,7 +83,7 @@ class LastfmController extends Controller
|
|||
*/
|
||||
public function setSessionKey(Request $request)
|
||||
{
|
||||
$this->auth->user()->savePreference('lastfm_session_key', trim($request->input('key')));
|
||||
$this->auth->user()->savePreference('lastfm_session_key', trim($request->key));
|
||||
|
||||
return response()->json();
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ class SongController extends Controller
|
|||
$song = Song::updateOrCreate(['id' => Media::getHash($path)], [
|
||||
'path' => $path,
|
||||
'album_id' => $album->id,
|
||||
'contributing_artist_id' => $compilation ? $artist->id : null,
|
||||
'artist_id' => $artist->id,
|
||||
'title' => trim(array_get($tags, 'title', '')),
|
||||
'length' => array_get($tags, 'duration', 0),
|
||||
'track' => (int) array_get($tags, 'track'),
|
||||
|
|
|
@ -28,7 +28,7 @@ class PlaylistController extends Controller
|
|||
public function store(PlaylistStoreRequest $request)
|
||||
{
|
||||
$playlist = auth()->user()->playlists()->create($request->only('name'));
|
||||
$playlist->songs()->sync($request->input('songs', []));
|
||||
$playlist->songs()->sync((array) $request->songs);
|
||||
|
||||
$playlist->songs = $playlist->songs->pluck('id');
|
||||
|
||||
|
|
|
@ -21,8 +21,8 @@ class ProfileController extends Controller
|
|||
{
|
||||
$data = $request->only('name', 'email');
|
||||
|
||||
if ($password = $request->input('password')) {
|
||||
$data['password'] = Hash::make($password);
|
||||
if ($request->password) {
|
||||
$data['password'] = Hash::make($request->password);
|
||||
}
|
||||
|
||||
return response()->json(auth()->user()->update($data));
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Http\Controllers\API;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Song;
|
||||
|
||||
class ScrobbleController extends Controller
|
||||
|
@ -9,13 +10,14 @@ class ScrobbleController extends Controller
|
|||
/**
|
||||
* Create a Last.fm scrobble entry for a song.
|
||||
*
|
||||
* @param Song $song
|
||||
* @param string $timestamp The UNIX timestamp when the song started playing.
|
||||
* @param Request $request
|
||||
* @param Song $song
|
||||
* @param string $timestamp The UNIX timestamp when the song started playing.
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function store(Song $song, $timestamp)
|
||||
public function store(Request $request, Song $song, $timestamp)
|
||||
{
|
||||
return response()->json($song->scrobble($timestamp));
|
||||
return response()->json($song->scrobble($request->user(), $timestamp));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ class SettingController extends Controller
|
|||
public function store(SettingRequest $request)
|
||||
{
|
||||
// For right now there's only one setting to be saved
|
||||
Setting::set('media_path', rtrim(trim($request->input('media_path')), '/'));
|
||||
Setting::set('media_path', rtrim(trim($request->media_path), '/'));
|
||||
|
||||
// In a next version we should opt for a "MediaPathChanged" event,
|
||||
// but let's just do this async now.
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Http\Requests\API\SongUpdateRequest;
|
||||
use App\Models\Song;
|
||||
use App\Services\Streamers\PHPStreamer;
|
||||
use App\Services\Streamers\S3Streamer;
|
||||
use App\Services\Streamers\TranscodingStreamer;
|
||||
use App\Services\Streamers\XAccelRedirectStreamer;
|
||||
use App\Services\Streamers\XSendFileStreamer;
|
||||
use App\Models\Song;
|
||||
use Illuminate\Http\Request;
|
||||
use YouTube;
|
||||
|
||||
class SongController extends Controller
|
||||
|
@ -18,6 +19,7 @@ class SongController extends Controller
|
|||
*
|
||||
* @link https://github.com/phanan/koel/wiki#streaming-music
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Song $song The song to stream.
|
||||
* @param null|bool $transcode Whether to force transcoding the song.
|
||||
* If this is omitted, by default Koel will transcode FLAC.
|
||||
|
@ -26,7 +28,7 @@ class SongController extends Controller
|
|||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function play(Song $song, $transcode = null, $bitRate = null)
|
||||
public function play(Request $request, Song $song, $transcode = null, $bitRate = null)
|
||||
{
|
||||
if ($song->s3_params) {
|
||||
return (new S3Streamer($song))->stream();
|
||||
|
@ -43,7 +45,7 @@ class SongController extends Controller
|
|||
$streamer = new TranscodingStreamer(
|
||||
$song,
|
||||
$bitRate ?: config('koel.streaming.bitrate'),
|
||||
request()->input('time', 0)
|
||||
floatval($request->time)
|
||||
);
|
||||
} else {
|
||||
switch (config('koel.streaming.method')) {
|
||||
|
|
|
@ -19,10 +19,8 @@ class iTunesController extends Controller
|
|||
public function viewSong(ViewSongRequest $request, Album $album)
|
||||
{
|
||||
$url = iTunes::getTrackUrl($request->q, $album->name, $album->artist->name);
|
||||
if ($url) {
|
||||
return redirect($url);
|
||||
} else {
|
||||
abort(404, "Koel can't find such a song on iTunes Store.");
|
||||
}
|
||||
abort_unless($url, 404, "Koel can't find such a song on iTunes Store.");
|
||||
|
||||
return redirect($url);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ class Album extends Model
|
|||
protected $guarded = ['id'];
|
||||
protected $hidden = ['updated_at'];
|
||||
protected $casts = ['artist_id' => 'integer'];
|
||||
protected $appends = ['is_compilation'];
|
||||
|
||||
public function artist()
|
||||
{
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class ContributingArtist extends Artist
|
||||
{
|
||||
protected $table = 'artists';
|
||||
}
|
|
@ -93,7 +93,7 @@ class File
|
|||
/**
|
||||
* Get all applicable ID3 info from the file.
|
||||
*
|
||||
* @return array|void
|
||||
* @return array
|
||||
*/
|
||||
public function getInfo()
|
||||
{
|
||||
|
@ -176,7 +176,7 @@ class File
|
|||
array_get($comments, 'part_of_a_compilation', [false])[0] || $props['albumartist']
|
||||
);
|
||||
|
||||
return $this->info = $props;
|
||||
return $props;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -264,12 +264,7 @@ class File
|
|||
}
|
||||
|
||||
$info['album_id'] = $album->id;
|
||||
|
||||
// If the song is part of a compilation, make sure we properly set its
|
||||
// artist and contributing artist attributes.
|
||||
if ($isCompilation) {
|
||||
$info['contributing_artist_id'] = $artist->id;
|
||||
}
|
||||
$info['artist_id'] = $artist->id;
|
||||
|
||||
// Remove these values from the info array, so that we can just use the array as model's input data.
|
||||
array_forget($info, ['artist', 'albumartist', 'album', 'cover', 'compilation']);
|
||||
|
@ -318,7 +313,7 @@ class File
|
|||
/**
|
||||
* Get the last parsing error's text.
|
||||
*
|
||||
* @return syncError
|
||||
* @return string
|
||||
*/
|
||||
public function getSyncError()
|
||||
{
|
||||
|
|
|
@ -130,11 +130,11 @@ class Interaction extends Model
|
|||
*/
|
||||
public static function batchUnlike(array $songIds, User $user)
|
||||
{
|
||||
foreach (self::whereIn('song_id', $songIds)->whereUserId($user->id)->get() as $interaction) {
|
||||
self::whereIn('song_id', $songIds)->whereUserId($user->id)->get()->each(function ($interaction) {
|
||||
$interaction->liked = false;
|
||||
$interaction->save();
|
||||
|
||||
event(new SongLikeToggled($interaction));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@ use YouTube;
|
|||
* @property string path
|
||||
* @property string title
|
||||
* @property Album album
|
||||
* @property int contributing_artist_id
|
||||
* @property Artist contributingArtist
|
||||
* @property Artist artist
|
||||
* @property string s3_params
|
||||
* @property float length
|
||||
|
@ -47,7 +45,6 @@ class Song extends Model
|
|||
'length' => 'float',
|
||||
'mtime' => 'int',
|
||||
'track' => 'int',
|
||||
'contributing_artist_id' => 'int',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -57,9 +54,9 @@ class Song extends Model
|
|||
*/
|
||||
public $incrementing = false;
|
||||
|
||||
public function contributingArtist()
|
||||
public function artist()
|
||||
{
|
||||
return $this->belongsTo(ContributingArtist::class);
|
||||
return $this->belongsTo(Artist::class);
|
||||
}
|
||||
|
||||
public function album()
|
||||
|
@ -75,11 +72,12 @@ class Song extends Model
|
|||
/**
|
||||
* Scrobble the song using Last.fm service.
|
||||
*
|
||||
* @param User $user
|
||||
* @param string $timestamp The UNIX timestamp in which the song started playing.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function scrobble($timestamp)
|
||||
public function scrobble(User $user, $timestamp)
|
||||
{
|
||||
// Don't scrobble the unknown guys. No one knows them.
|
||||
if ($this->artist->isUnknown()) {
|
||||
|
@ -87,7 +85,7 @@ class Song extends Model
|
|||
}
|
||||
|
||||
// If the current user hasn't connected to Last.fm, don't do shit.
|
||||
if (!$sessionKey = auth()->user()->lastfm_session_key) {
|
||||
if (!$user->connectedToLastfm()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -96,7 +94,7 @@ class Song extends Model
|
|||
$this->title,
|
||||
$timestamp,
|
||||
$this->album->name === Album::UNKNOWN_NAME ? '' : $this->album->name,
|
||||
$sessionKey
|
||||
$user->lastfm_session_key
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -129,11 +127,11 @@ class Song extends Model
|
|||
public static function updateInfo($ids, $data)
|
||||
{
|
||||
/*
|
||||
* An array of the updated songs.
|
||||
* A collection of the updated songs.
|
||||
*
|
||||
* @var array
|
||||
* @var \Illuminate\Support\Collection
|
||||
*/
|
||||
$updatedSongs = [];
|
||||
$updatedSongs = collect();
|
||||
|
||||
$ids = (array) $ids;
|
||||
// If we're updating only one song, take into account the title, lyrics, and track number.
|
||||
|
@ -144,22 +142,26 @@ class Song extends Model
|
|||
continue;
|
||||
}
|
||||
|
||||
$updatedSongs[] = $song->updateSingle(
|
||||
$updatedSongs->push($song->updateSingle(
|
||||
$single ? trim($data['title']) : $song->title,
|
||||
trim($data['albumName'] ?: $song->album->name),
|
||||
trim($data['artistName']) ?: $song->artist->name,
|
||||
$single ? trim($data['lyrics']) : $song->lyrics,
|
||||
$single ? (int) $data['track'] : $song->track,
|
||||
(int) $data['compilationState']
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
// Our library may have been changed. Broadcast an event to tidy it up if need be.
|
||||
if ($updatedSongs) {
|
||||
if ($updatedSongs->count()) {
|
||||
event(new LibraryChanged());
|
||||
}
|
||||
|
||||
return $updatedSongs;
|
||||
return [
|
||||
'artists' => Artist::whereIn('id', $updatedSongs->pluck('artist_id'))->get(),
|
||||
'albums' => Album::whereIn('id', $updatedSongs->pluck('album_id'))->get(),
|
||||
'songs' => $updatedSongs,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,30 +178,30 @@ class Song extends Model
|
|||
*/
|
||||
public function updateSingle($title, $albumName, $artistName, $lyrics, $track, $compilationState)
|
||||
{
|
||||
// If the artist name is "Various Artists", it's a compilation song no matter what.
|
||||
if ($artistName === Artist::VARIOUS_NAME) {
|
||||
// If the artist name is "Various Artists", it's a compilation song no matter what.
|
||||
$compilationState = 1;
|
||||
// and since we can't determine the real contributing artist, it's "Unknown"
|
||||
$artistName = Artist::UNKNOWN_NAME;
|
||||
}
|
||||
|
||||
// If the compilation state is "no change," we determine it via the current
|
||||
// "contributing_artist_id" field value.
|
||||
if ($compilationState === 2) {
|
||||
$compilationState = $this->contributing_artist_id ? 1 : 0;
|
||||
$artist = Artist::get($artistName);
|
||||
|
||||
switch ($compilationState) {
|
||||
case 1: // ALL, or forcing compilation status to be Yes
|
||||
$isCompilation = true;
|
||||
break;
|
||||
case 2: // Keep current compilation status
|
||||
$isCompilation = $this->album->artist_id === Artist::VARIOUS_ID;
|
||||
break;
|
||||
default:
|
||||
$isCompilation = false;
|
||||
break;
|
||||
}
|
||||
|
||||
$album = null;
|
||||
|
||||
if ($compilationState === 0) {
|
||||
// Not a compilation song
|
||||
$this->contributing_artist_id = null;
|
||||
$albumArtist = Artist::get($artistName);
|
||||
$album = Album::get($albumArtist, $albumName, false);
|
||||
} else {
|
||||
$contributingArtist = Artist::get($artistName);
|
||||
$this->contributing_artist_id = $contributingArtist->id;
|
||||
$album = Album::get(Artist::getVarious(), $albumName, true);
|
||||
}
|
||||
$album = Album::get($artist, $albumName, $isCompilation);
|
||||
|
||||
$this->artist_id = $artist->id;
|
||||
$this->album_id = $album->id;
|
||||
$this->title = $title;
|
||||
$this->lyrics = $lyrics;
|
||||
|
@ -207,12 +209,13 @@ class Song extends Model
|
|||
|
||||
$this->save();
|
||||
|
||||
// Get the updated record, with album and all.
|
||||
$updatedSong = self::with('album', 'album.artist', 'contributingArtist')->find($this->id);
|
||||
// Make sure lyrics is included in the returned JSON.
|
||||
$updatedSong->makeVisible('lyrics');
|
||||
// Clean up unnecessary data from the object
|
||||
unset($this->album);
|
||||
unset($this->artist);
|
||||
// and make sure the lyrics is shown
|
||||
$this->makeVisible('lyrics');
|
||||
|
||||
return $updatedSong;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -241,10 +244,7 @@ class Song extends Model
|
|||
*/
|
||||
public static function getFavorites(User $user, $toArray = false)
|
||||
{
|
||||
$songs = Interaction::where([
|
||||
'user_id' => $user->id,
|
||||
'liked' => true,
|
||||
])
|
||||
$songs = Interaction::whereUserIdAndLike($user->id, true)
|
||||
->with('song')
|
||||
->get()
|
||||
->pluck('song');
|
||||
|
@ -336,20 +336,6 @@ class Song extends Model
|
|||
return str_replace(["\r\n", "\r", "\n"], '<br />', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct artist of the song.
|
||||
* If it's part of a compilation, that would be the contributing artist.
|
||||
* Otherwise, it's the album artist.
|
||||
*
|
||||
* @return Artist
|
||||
*/
|
||||
public function getArtistAttribute()
|
||||
{
|
||||
return $this->contributing_artist_id
|
||||
? $this->contributingArtist
|
||||
: $this->album->artist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the song is an AWS S3 Object.
|
||||
*
|
||||
|
@ -357,7 +343,7 @@ class Song extends Model
|
|||
*/
|
||||
public function isS3ObjectAttribute()
|
||||
{
|
||||
return strpos($this->path, 's3://') === 0;
|
||||
return starts_with($this->path, 's3://');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,15 +24,15 @@ class Download
|
|||
*/
|
||||
public function from($mixed)
|
||||
{
|
||||
if (is_a($mixed, Song::class)) {
|
||||
if ($mixed instanceof Song) {
|
||||
return $this->fromSong($mixed);
|
||||
} elseif (is_a($mixed, Collection::class)) {
|
||||
} elseif (mixed instanceof Collection) {
|
||||
return $this->fromMultipleSongs($mixed);
|
||||
} elseif (is_a($mixed, Album::class)) {
|
||||
} elseif ($mixed instanceof Album) {
|
||||
return $this->fromAlbum($mixed);
|
||||
} elseif (is_a($mixed, Artist::class)) {
|
||||
} elseif ($mixed instanceof Artist) {
|
||||
return $this->fromArtist($mixed);
|
||||
} elseif (is_a($mixed, Playlist::class)) {
|
||||
} elseif ($mixed instanceof Playlist) {
|
||||
return $this->fromPlaylist($mixed);
|
||||
} else {
|
||||
throw new Exception('Unsupport download type.');
|
||||
|
|
|
@ -117,13 +117,15 @@ class Media
|
|||
*/
|
||||
public function gatherFiles($path)
|
||||
{
|
||||
return Finder::create()
|
||||
->ignoreUnreadableDirs()
|
||||
->ignoreDotFiles((bool) config('koel.ignore_dot_files')) // https://github.com/phanan/koel/issues/450
|
||||
->files()
|
||||
->followLinks()
|
||||
->name('/\.(mp3|ogg|m4a|flac)$/i')
|
||||
->in($path);
|
||||
return iterator_to_array(
|
||||
Finder::create()
|
||||
->ignoreUnreadableDirs()
|
||||
->ignoreDotFiles((bool) config('koel.ignore_dot_files')) // https://github.com/phanan/koel/issues/450
|
||||
->files()
|
||||
->followLinks()
|
||||
->name('/\.(mp3|ogg|m4a|flac)$/i')
|
||||
->in($path)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -222,23 +224,22 @@ class Media
|
|||
*/
|
||||
public function tidy()
|
||||
{
|
||||
$inUseAlbums = Song::select('album_id')->groupBy('album_id')->get()->pluck('album_id')->toArray();
|
||||
$inUseAlbums = Song::select('album_id')
|
||||
->groupBy('album_id')
|
||||
->get()
|
||||
->pluck('album_id')
|
||||
->toArray();
|
||||
$inUseAlbums[] = Album::UNKNOWN_ID;
|
||||
Album::deleteWhereIDsNotIn($inUseAlbums);
|
||||
|
||||
$inUseArtists = Album::select('artist_id')->groupBy('artist_id')->get()->pluck('artist_id')->toArray();
|
||||
|
||||
$contributingArtists = Song::distinct()
|
||||
->select('contributing_artist_id')
|
||||
->groupBy('contributing_artist_id')
|
||||
$inUseArtists = Song::distinct()
|
||||
->select('artist_id')
|
||||
->groupBy('artist_id')
|
||||
->get()
|
||||
->pluck('contributing_artist_id')
|
||||
->pluck('artist_id')
|
||||
->toArray();
|
||||
|
||||
$inUseArtists = array_merge($inUseArtists, $contributingArtists);
|
||||
$inUseArtists[] = Artist::UNKNOWN_ID;
|
||||
$inUseArtists[] = Artist::VARIOUS_ID;
|
||||
|
||||
Artist::deleteWhereIDsNotIn(array_filter($inUseArtists));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
||||
use App\Models\Song;
|
||||
use Cache;
|
||||
|
||||
class MediaCache
|
||||
|
@ -12,18 +14,32 @@ class MediaCache
|
|||
public function get()
|
||||
{
|
||||
if (!config('koel.cache_media')) {
|
||||
return Artist::orderBy('name')->with('albums', with('albums.songs'))->get();
|
||||
return $this->query();
|
||||
}
|
||||
|
||||
$data = Cache::get($this->keyName);
|
||||
if (!$data) {
|
||||
$data = Artist::orderBy('name')->with('albums', with('albums.songs'))->get();
|
||||
$data = $this->query();
|
||||
Cache::forever($this->keyName, $data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query fresh data from the database.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function query()
|
||||
{
|
||||
return [
|
||||
'albums' => Album::orderBy('name')->get(),
|
||||
'artists' => Artist::orderBy('name')->get(),
|
||||
'songs' => Song::all(),
|
||||
];
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
Cache::forget($this->keyName);
|
||||
|
|
|
@ -40,6 +40,8 @@ trait SupportsDeleteWhereIDsNotIn
|
|||
// If that's not possible (i.e. this array has more than 65535 elements, too)
|
||||
// then we'll delete chunk by chunk.
|
||||
static::deleteByChunk($ids, $key);
|
||||
|
||||
return $whereInIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
"tymon/jwt-auth": "^0.5.6",
|
||||
"aws/aws-sdk-php-laravel": "^3.1",
|
||||
"pusher/pusher-php-server": "^2.2",
|
||||
"predis/predis": "~1.0"
|
||||
"predis/predis": "~1.0",
|
||||
"doctrine/dbal": "^2.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"fzaninotto/faker": "~1.4",
|
||||
|
|
405
composer.lock
generated
405
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "c7714d02ea086eb67d42299b88015650",
|
||||
"content-hash": "b2aa59c478d7e97ce1a96c24405ca7c7",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
|
@ -142,6 +142,355 @@
|
|||
],
|
||||
"time": "2016-01-18T06:57:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/annotations",
|
||||
"version": "v1.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/annotations.git",
|
||||
"reference": "54cacc9b81758b14e3ce750f205a393d52339e97"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97",
|
||||
"reference": "54cacc9b81758b14e3ce750f205a393d52339e97",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/lexer": "1.*",
|
||||
"php": "^5.6 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/cache": "1.*",
|
||||
"phpunit/phpunit": "^5.7"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.4.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de"
|
||||
},
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Jonathan Wage",
|
||||
"email": "jonwage@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Johannes Schmitt",
|
||||
"email": "schmittjoh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Docblock Annotations Parser",
|
||||
"homepage": "http://www.doctrine-project.org",
|
||||
"keywords": [
|
||||
"annotations",
|
||||
"docblock",
|
||||
"parser"
|
||||
],
|
||||
"time": "2017-02-24T16:22:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/cache",
|
||||
"version": "v1.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/cache.git",
|
||||
"reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/cache/zipball/b6f544a20f4807e81f7044d31e679ccbb1866dc3",
|
||||
"reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "~5.5|~7.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/common": ">2.2,<2.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.8|~5.0",
|
||||
"predis/predis": "~1.0",
|
||||
"satooshi/php-coveralls": "~0.6"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.6.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de"
|
||||
},
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Jonathan Wage",
|
||||
"email": "jonwage@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Johannes Schmitt",
|
||||
"email": "schmittjoh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Caching library offering an object-oriented API for many cache backends",
|
||||
"homepage": "http://www.doctrine-project.org",
|
||||
"keywords": [
|
||||
"cache",
|
||||
"caching"
|
||||
],
|
||||
"time": "2016-10-29T11:16:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/collections",
|
||||
"version": "v1.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/collections.git",
|
||||
"reference": "1a4fb7e902202c33cce8c55989b945612943c2ba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba",
|
||||
"reference": "1a4fb7e902202c33cce8c55989b945612943c2ba",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.6 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "~0.1@dev",
|
||||
"phpunit/phpunit": "^5.7"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Doctrine\\Common\\Collections\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de"
|
||||
},
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Jonathan Wage",
|
||||
"email": "jonwage@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Johannes Schmitt",
|
||||
"email": "schmittjoh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Collections Abstraction library",
|
||||
"homepage": "http://www.doctrine-project.org",
|
||||
"keywords": [
|
||||
"array",
|
||||
"collections",
|
||||
"iterator"
|
||||
],
|
||||
"time": "2017-01-03T10:49:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/common",
|
||||
"version": "v2.7.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/common.git",
|
||||
"reference": "930297026c8009a567ac051fd545bf6124150347"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/common/zipball/930297026c8009a567ac051fd545bf6124150347",
|
||||
"reference": "930297026c8009a567ac051fd545bf6124150347",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/annotations": "1.*",
|
||||
"doctrine/cache": "1.*",
|
||||
"doctrine/collections": "1.*",
|
||||
"doctrine/inflector": "1.*",
|
||||
"doctrine/lexer": "1.*",
|
||||
"php": "~5.6|~7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.4.6"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.7.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Common\\": "lib/Doctrine/Common"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de"
|
||||
},
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Jonathan Wage",
|
||||
"email": "jonwage@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Johannes Schmitt",
|
||||
"email": "schmittjoh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Common Library for Doctrine projects",
|
||||
"homepage": "http://www.doctrine-project.org",
|
||||
"keywords": [
|
||||
"annotations",
|
||||
"collections",
|
||||
"eventmanager",
|
||||
"persistence",
|
||||
"spl"
|
||||
],
|
||||
"time": "2017-01-13T14:02:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/dbal",
|
||||
"version": "v2.5.12",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/dbal.git",
|
||||
"reference": "7b9e911f9d8b30d43b96853dab26898c710d8f44"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/7b9e911f9d8b30d43b96853dab26898c710d8f44",
|
||||
"reference": "7b9e911f9d8b30d43b96853dab26898c710d8f44",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/common": ">=2.4,<2.8-dev",
|
||||
"php": ">=5.3.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*",
|
||||
"symfony/console": "2.*||^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/console": "For helpful console commands such as SQL execution and import of files."
|
||||
},
|
||||
"bin": [
|
||||
"bin/doctrine-dbal"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.5.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Doctrine\\DBAL\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de"
|
||||
},
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Jonathan Wage",
|
||||
"email": "jonwage@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Database Abstraction Layer",
|
||||
"homepage": "http://www.doctrine-project.org",
|
||||
"keywords": [
|
||||
"database",
|
||||
"dbal",
|
||||
"persistence",
|
||||
"queryobject"
|
||||
],
|
||||
"time": "2017-02-08T12:53:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/inflector",
|
||||
"version": "v1.1.0",
|
||||
|
@ -209,6 +558,60 @@
|
|||
],
|
||||
"time": "2015-11-06T14:35:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/lexer",
|
||||
"version": "v1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/lexer.git",
|
||||
"reference": "83893c552fd2045dd78aef794c31e694c37c0b8c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c",
|
||||
"reference": "83893c552fd2045dd78aef794c31e694c37c0b8c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Doctrine\\Common\\Lexer\\": "lib/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Johannes Schmitt",
|
||||
"email": "schmittjoh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.",
|
||||
"homepage": "http://www.doctrine-project.org",
|
||||
"keywords": [
|
||||
"lexer",
|
||||
"parser"
|
||||
],
|
||||
"time": "2014-09-09T13:34:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "erusev/parsedown",
|
||||
"version": "1.6.2",
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Song;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CopyArtistToContributingArtist extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Song::with('album', 'album.artist')->get()->each(function (Song $song) {
|
||||
if (!$song->contributing_artist_id) {
|
||||
$song->contributing_artist_id = $song->album->artist->id;
|
||||
$song->save();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class DropIsComplicationFromAlbums extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('albums', function ($table) {
|
||||
$table->dropColumn('is_compilation');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class RenameContributingArtistId extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('songs', function ($table) {
|
||||
$table->renameColumn('contributing_artist_id', 'artist_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('songs', function ($table) {
|
||||
$table->renameColumn('artist_id', 'contributing_artist_id');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -38,5 +38,6 @@
|
|||
<env name="ADMIN_NAME" value="Koel"/>
|
||||
<env name="ADMIN_PASSWORD" value="SoSecureK0el"/>
|
||||
<env name="BROADCAST_DRIVER" value="log"/>
|
||||
<env name="CACHE_MEDIA" value="true"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
{{ album.name }}
|
||||
<controls-toggler :showing-controls="showingControls" @toggleControls="toggleControls"/>
|
||||
|
||||
<span class="meta" v-show="meta.songCount">
|
||||
<span class="meta" v-show="album.songs.length">
|
||||
by
|
||||
<a class="artist" v-if="isNormalArtist" :href="`/#!/artist/${album.artist.id}`">{{ album.artist.name }}</a>
|
||||
<span class="nope" v-else>{{ album.artist.name }}</span>
|
||||
•
|
||||
{{ meta.songCount | pluralize('song') }}
|
||||
{{ album.songs.length | pluralize('song') }}
|
||||
•
|
||||
{{ meta.totalLength }}
|
||||
{{ fmtLength }}
|
||||
|
||||
<template v-if="sharedState.useLastfm">
|
||||
•
|
||||
|
@ -55,12 +55,13 @@ import { albumStore, artistStore, sharedStore } from '../../../stores'
|
|||
import { playback, download, albumInfo as albumInfoService } from '../../../services'
|
||||
import router from '../../../router'
|
||||
import hasSongList from '../../../mixins/has-song-list'
|
||||
import albumAttributes from '../../../mixins/album-attributes'
|
||||
import albumInfo from '../extra/album-info.vue'
|
||||
import soundBar from '../../shared/sound-bar.vue'
|
||||
|
||||
export default {
|
||||
name: 'main-wrapper--main-content--album',
|
||||
mixins: [hasSongList],
|
||||
mixins: [hasSongList, albumAttributes],
|
||||
components: { albumInfo, soundBar },
|
||||
filters: { pluralize },
|
||||
|
||||
|
|
|
@ -2,16 +2,16 @@
|
|||
<section id="artistWrapper">
|
||||
<h1 class="heading">
|
||||
<span class="overview">
|
||||
<img :src="artist.image" width="64" height="64" class="cover">
|
||||
<img :src="image" width="64" height="64" class="cover">
|
||||
{{ artist.name }}
|
||||
<controls-toggler :showing-controls="showingControls" @toggleControls="toggleControls"/>
|
||||
|
||||
<span class="meta" v-show="meta.songCount">
|
||||
<span class="meta" v-show="artist.songs.length">
|
||||
{{ artist.albums.length | pluralize('album') }}
|
||||
•
|
||||
{{ meta.songCount | pluralize('song') }}
|
||||
{{ artist.songs.length | pluralize('song') }}
|
||||
•
|
||||
{{ meta.totalLength }}
|
||||
{{ fmtLength }}
|
||||
|
||||
<template v-if="sharedState.useLastfm">
|
||||
•
|
||||
|
@ -54,12 +54,13 @@ import { sharedStore, artistStore } from '../../../stores'
|
|||
import { playback, download, artistInfo as artistInfoService } from '../../../services'
|
||||
import router from '../../../router'
|
||||
import hasSongList from '../../../mixins/has-song-list'
|
||||
import artistAttributes from '../../../mixins/artist-attributes'
|
||||
import artistInfo from '../extra/artist-info.vue'
|
||||
import soundBar from '../../shared/sound-bar.vue'
|
||||
|
||||
export default {
|
||||
name: 'main-wrapper--main-content--artist',
|
||||
mixins: [hasSongList],
|
||||
mixins: [hasSongList, artistAttributes],
|
||||
components: { artistInfo, soundBar },
|
||||
filters: { pluralize },
|
||||
|
||||
|
|
|
@ -49,9 +49,6 @@
|
|||
<input type="checkbox" @change="changeCompilationState" ref="compilationStateChk" />
|
||||
Album is a compilation of songs by various artists
|
||||
</label>
|
||||
<label class="small warning" v-if="needsReload">
|
||||
Koel will reload after saving.
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row" v-show="editSingle">
|
||||
<label>Track</label>
|
||||
|
@ -77,11 +74,12 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { every, filter } from 'lodash'
|
||||
import { every, filter, union } from 'lodash'
|
||||
|
||||
import { br2nl, forceReloadWindow } from '../../utils'
|
||||
import { br2nl } from '../../utils'
|
||||
import { songInfo } from '../../services/info'
|
||||
import { artistStore, albumStore, songStore } from '../../stores'
|
||||
import config from '../../config'
|
||||
|
||||
import soundBar from '../shared/sound-bar.vue'
|
||||
import typeahead from '../shared/typeahead.vue'
|
||||
|
@ -101,7 +99,6 @@ export default {
|
|||
songs: [],
|
||||
currentView: '',
|
||||
loading: false,
|
||||
needsReload: false,
|
||||
|
||||
artistState: artistStore.state,
|
||||
artistTypeaheadOptions: {
|
||||
|
@ -166,7 +163,7 @@ export default {
|
|||
* @return {string}
|
||||
*/
|
||||
coverUrl () {
|
||||
return this.inSameAlbum ? this.songs[0].album.cover : '/public/img/covers/unknown-album.png'
|
||||
return this.inSameAlbum ? this.songs[0].album.cover : config.unknownCover
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -175,11 +172,15 @@ export default {
|
|||
* @return {Number}
|
||||
*/
|
||||
compilationState () {
|
||||
const contributedSongs = filter(this.songs, song => song.contributing_artist_id)
|
||||
const albums = this.songs.reduce((acc, song) => {
|
||||
return union(acc, [song.album])
|
||||
}, [])
|
||||
|
||||
if (!contributedSongs.length) {
|
||||
const compiledAlbums = filter(albums, album => album.is_compilation)
|
||||
|
||||
if (!compiledAlbums.length) {
|
||||
this.formData.compilationState = COMPILATION_STATES.NONE
|
||||
} else if (contributedSongs.length === this.songs.length) {
|
||||
} else if (compiledAlbums.length === albums.length) {
|
||||
this.formData.compilationState = COMPILATION_STATES.ALL
|
||||
} else {
|
||||
this.formData.compilationState = COMPILATION_STATES.SOME
|
||||
|
@ -229,7 +230,6 @@ export default {
|
|||
this.shown = true
|
||||
this.songs = songs
|
||||
this.currentView = 'details'
|
||||
this.needsReload = false
|
||||
|
||||
if (this.editSingle) {
|
||||
this.formData.title = this.songs[0].title
|
||||
|
@ -294,7 +294,6 @@ export default {
|
|||
*/
|
||||
changeCompilationState (e) {
|
||||
this.formData.compilationState = e.target.checked ? COMPILATION_STATES.ALL : COMPILATION_STATES.NONE
|
||||
this.needsReload = true
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -310,11 +309,10 @@ export default {
|
|||
submit () {
|
||||
this.loading = true
|
||||
|
||||
songStore.update(this.songs, this.formData).then(r => {
|
||||
songStore.update(this.songs, this.formData).then(() => {
|
||||
this.loading = false
|
||||
this.close()
|
||||
this.needsReload && forceReloadWindow()
|
||||
}).catch(r => {
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<article class="item" v-if="album.songs.length" draggable="true" @dragstart="dragStart">
|
||||
<span class="cover" :style="{ backgroundImage: 'url('+album.cover+')' }">
|
||||
<span class="cover" :style="{ backgroundImage: `url(${album.cover})` }">
|
||||
<a class="control" @click.prevent="play">
|
||||
<i class="fa fa-play"></i>
|
||||
</a>
|
||||
|
@ -16,7 +16,7 @@
|
|||
<span class="left">
|
||||
{{ album.songs.length | pluralize('song') }}
|
||||
•
|
||||
{{ album.fmtLength }}
|
||||
{{ fmtLength }}
|
||||
•
|
||||
{{ album.playCount | pluralize('play') }}
|
||||
</span>
|
||||
|
@ -39,11 +39,13 @@
|
|||
import { pluralize } from '../../utils'
|
||||
import { queueStore, artistStore, sharedStore } from '../../stores'
|
||||
import { playback, download } from '../../services'
|
||||
import albumAttributes from '../../mixins/album-attributes'
|
||||
|
||||
export default {
|
||||
name: 'shared--album-item',
|
||||
props: ['album'],
|
||||
filters: { pluralize },
|
||||
mixins: [albumAttributes],
|
||||
|
||||
data () {
|
||||
return {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<article class="item" v-if="showing" draggable="true" @dragstart="dragStart">
|
||||
<span class="cover" :style="{ backgroundImage: 'url('+artist.image+')' }">
|
||||
<span class="cover" :style="{ backgroundImage: `url(${image})` }">
|
||||
<a class="control" @click.prevent="play">
|
||||
<i class="fa fa-play"></i>
|
||||
</a>
|
||||
|
@ -13,7 +13,7 @@
|
|||
<span class="left">
|
||||
{{ artist.albums.length | pluralize('album') }}
|
||||
•
|
||||
{{ artist.songCount | pluralize('song') }}
|
||||
{{ artist.songs.length | pluralize('song') }}
|
||||
•
|
||||
{{ artist.playCount | pluralize('play') }}
|
||||
</span>
|
||||
|
@ -31,11 +31,13 @@
|
|||
import { pluralize } from '../../utils'
|
||||
import { artistStore, queueStore, sharedStore } from '../../stores'
|
||||
import { playback, download } from '../../services'
|
||||
import artistAttributes from '../../mixins/artist-attributes'
|
||||
|
||||
export default {
|
||||
name: 'shared--artist-item',
|
||||
props: ['artist'],
|
||||
filters: { pluralize },
|
||||
mixins: [artistAttributes],
|
||||
|
||||
data () {
|
||||
return {
|
||||
|
@ -51,7 +53,7 @@ export default {
|
|||
* @return {Boolean}
|
||||
*/
|
||||
showing () {
|
||||
return this.artist.songCount && !artistStore.isVariousArtists(this.artist)
|
||||
return this.artist.songs.length && !artistStore.isVariousArtists(this.artist)
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
unknownCover: (typeof window !== 'undefined' ? window.location.href : '/') + 'public/img/covers/unknown-album.png',
|
||||
unknownCover: (typeof window !== 'undefined' ? window.location.href.replace(window.location.hash, '') : '/') + 'public/img/covers/unknown-album.png',
|
||||
appTitle: 'Koel'
|
||||
}
|
||||
|
|
15
resources/assets/js/mixins/album-attributes.js
Normal file
15
resources/assets/js/mixins/album-attributes.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { secondsToHis } from '../utils'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
length () {
|
||||
return this.album.songs.reduce((acc, song) => {
|
||||
return acc + song.length
|
||||
}, 0)
|
||||
},
|
||||
|
||||
fmtLength () {
|
||||
return secondsToHis(this.length)
|
||||
}
|
||||
}
|
||||
}
|
33
resources/assets/js/mixins/artist-attributes.js
Normal file
33
resources/assets/js/mixins/artist-attributes.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { secondsToHis } from '../utils'
|
||||
import config from '../config'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
length () {
|
||||
return this.artist.songs.reduce((acc, song) => {
|
||||
return acc + song.length
|
||||
}, 0)
|
||||
},
|
||||
|
||||
fmtLength () {
|
||||
return secondsToHis(this.length)
|
||||
},
|
||||
|
||||
image () {
|
||||
if (!this.artist.image) {
|
||||
this.artist.image = config.unknownCover
|
||||
|
||||
this.artist.albums.every(album => {
|
||||
// If there's a "real" cover, use it.
|
||||
if (album.image !== config.unknownCover) {
|
||||
this.artist.image = album.cover
|
||||
// I want to break free.
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return this.artist.image
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,9 +3,8 @@
|
|||
import Vue from 'vue'
|
||||
import { reduce, each, union, difference, take, filter, orderBy } from 'lodash'
|
||||
|
||||
import { secondsToHis } from '../utils'
|
||||
import stub from '../stubs/album'
|
||||
import { songStore, artistStore } from '.'
|
||||
import { artistStore } from '.'
|
||||
|
||||
export const albumStore = {
|
||||
stub,
|
||||
|
@ -18,26 +17,23 @@ export const albumStore = {
|
|||
/**
|
||||
* Init the store.
|
||||
*
|
||||
* @param {Array.<Object>} artists The array of artists to extract album data from.
|
||||
* @param {Array.<Object>} albums The array of album objects
|
||||
*/
|
||||
init (artists) {
|
||||
init (albums) {
|
||||
// Traverse through the artists array and add their albums into our master album list.
|
||||
this.all = reduce(artists, (albums, artist) => {
|
||||
// While we're doing so, for each album, we get its length
|
||||
// and keep a back reference to the artist too.
|
||||
each(artist.albums, album => this.setupAlbum(album, artist))
|
||||
return albums.concat(artist.albums)
|
||||
}, [])
|
||||
|
||||
// Then we init the song store.
|
||||
songStore.init(this.all)
|
||||
this.all = albums
|
||||
each(this.all, album => this.setupAlbum(album))
|
||||
},
|
||||
|
||||
setupAlbum (album, artist) {
|
||||
Vue.set(album, 'playCount', 0)
|
||||
setupAlbum (album) {
|
||||
const artist = artistStore.byId(album.artist_id)
|
||||
artist.albums = union(artist.albums, [album])
|
||||
|
||||
Vue.set(album, 'artist', artist)
|
||||
Vue.set(album, 'info', null)
|
||||
this.getLength(album)
|
||||
Vue.set(album, 'songs', [])
|
||||
Vue.set(album, 'playCount', 0)
|
||||
|
||||
this.cache[album.id] = album
|
||||
|
||||
return album
|
||||
|
@ -65,21 +61,6 @@ export const albumStore = {
|
|||
return this.cache[id]
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the total length of an album by summing up its songs' duration.
|
||||
* The length will also be converted into a H:i:s format and stored as fmtLength.
|
||||
*
|
||||
* @param {Object} album
|
||||
*
|
||||
* @return {String} The H:i:s format of the album length.
|
||||
*/
|
||||
getLength (album) {
|
||||
Vue.set(album, 'length', reduce(album.songs, (length, song) => length + song.length, 0))
|
||||
Vue.set(album, 'fmtLength', secondsToHis(album.length))
|
||||
|
||||
return album.fmtLength
|
||||
},
|
||||
|
||||
/**
|
||||
* Add new album/albums into the current collection.
|
||||
*
|
||||
|
@ -95,65 +76,21 @@ export const albumStore = {
|
|||
this.all = union(this.all, albums)
|
||||
},
|
||||
|
||||
/**
|
||||
* Add song(s) into an album.
|
||||
*
|
||||
* @param {Object} album
|
||||
* @param {Array.<Object>|Object} song
|
||||
*/
|
||||
addSongsIntoAlbum (album, songs) {
|
||||
songs = [].concat(songs)
|
||||
|
||||
album.songs = union(album.songs || [], songs)
|
||||
|
||||
each(songs, song => {
|
||||
song.album_id = album.id
|
||||
song.album = album
|
||||
})
|
||||
|
||||
album.playCount = reduce(album.songs, (count, song) => count + song.playCount, 0)
|
||||
this.getLength(album)
|
||||
purify () {
|
||||
this.compact()
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove song(s) from an album.
|
||||
*
|
||||
* @param {Object} album
|
||||
* @param {Array.<Object>|Object} songs
|
||||
* Remove empty albums from the store.
|
||||
*/
|
||||
removeSongsFromAlbum (album, songs) {
|
||||
album.songs = difference(album.songs, [].concat(songs))
|
||||
album.playCount = reduce(album.songs, (count, song) => count + song.playCount, 0)
|
||||
this.getLength(album)
|
||||
},
|
||||
compact () {
|
||||
const emptyAlbums = filter(this.all, album => album.songs.length === 0)
|
||||
if (!emptyAlbums.length) {
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an album is empty.
|
||||
*
|
||||
* @param {Object} album
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
isAlbumEmpty (album) {
|
||||
return !album.songs.length
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove album(s) from the store.
|
||||
*
|
||||
* @param {Array.<Object>|Object} albums
|
||||
*/
|
||||
remove (albums) {
|
||||
albums = [].concat(albums)
|
||||
this.all = difference(this.all, albums)
|
||||
|
||||
// Remove from the artist as well
|
||||
each(albums, album => {
|
||||
artistStore.removeAlbumsFromArtist(album.artist, album)
|
||||
|
||||
// Delete the cache while we're here
|
||||
delete this.cache[album.id]
|
||||
})
|
||||
this.all = difference(this.all, emptyAlbums)
|
||||
each(emptyAlbums, album => delete this.cache[album.id])
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
import Vue from 'vue'
|
||||
import { reduce, each, union, difference, take, filter, orderBy } from 'lodash'
|
||||
|
||||
import config from '../config'
|
||||
import stub from '../stubs/artist'
|
||||
import { albumStore } from '.'
|
||||
|
||||
const UNKNOWN_ARTIST_ID = 1
|
||||
const VARIOUS_ARTISTS_ID = 2
|
||||
|
@ -28,7 +26,6 @@ export const artistStore = {
|
|||
|
||||
// Traverse through artists array to get the cover and number of songs for each.
|
||||
each(this.all, artist => this.setupArtist(artist))
|
||||
albumStore.init(this.all)
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -37,24 +34,11 @@ export const artistStore = {
|
|||
* @param {Object} artist
|
||||
*/
|
||||
setupArtist (artist) {
|
||||
this.getImage(artist)
|
||||
Vue.set(artist, 'playCount', 0)
|
||||
|
||||
// Here we build a list of songs performed by the artist, so that we don't need to traverse
|
||||
// down the "artist > albums > items" route later.
|
||||
// This also makes sure songs in compilation albums are counted as well.
|
||||
Vue.set(artist, 'songs', reduce(artist.albums, (songs, album) => {
|
||||
// If the album is compilation, we cater for the songs contributed by this artist only.
|
||||
if (album.is_compilation) {
|
||||
return songs.concat(filter(album.songs, { contributing_artist_id: artist.id }))
|
||||
}
|
||||
|
||||
// Otherwise, just use all songs in the album.
|
||||
return songs.concat(album.songs)
|
||||
}, []))
|
||||
|
||||
Vue.set(artist, 'songCount', artist.songs.length)
|
||||
Vue.set(artist, 'info', null)
|
||||
Vue.set(artist, 'albums', [])
|
||||
Vue.set(artist, 'songs', [])
|
||||
|
||||
this.cache[artist.id] = artist
|
||||
|
||||
return artist
|
||||
|
@ -94,66 +78,29 @@ export const artistStore = {
|
|||
*/
|
||||
add (artists) {
|
||||
artists = [].concat(artists)
|
||||
each(artists, artist => this.setupArtist(artist))
|
||||
each(artists, artist => {
|
||||
this.setupArtist(artist)
|
||||
artist.playCount = reduce(artist.songs, (count, song) => count + song.playCount, 0)
|
||||
})
|
||||
|
||||
this.all = union(this.all, artists)
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove artist(s) from the store.
|
||||
*
|
||||
* @param {Array.<Object>|Object} artists
|
||||
*/
|
||||
remove (artists) {
|
||||
artists = [].concat(artists)
|
||||
this.all = difference(this.all, artists)
|
||||
|
||||
// Remember to clear the cache
|
||||
each(artists, artist => delete this.cache[artist.id])
|
||||
purify () {
|
||||
this.compact()
|
||||
},
|
||||
|
||||
/**
|
||||
* Add album(s) into an artist.
|
||||
*
|
||||
* @param {Object} artist
|
||||
* @param {Array.<Object>|Object} albums
|
||||
*
|
||||
* Remove empty artists from the store.
|
||||
*/
|
||||
addAlbumsIntoArtist (artist, albums) {
|
||||
albums = [].concat(albums)
|
||||
compact () {
|
||||
const emptyArtists = filter(this.all, artist => artist.songs.length === 0)
|
||||
if (!emptyArtists.length) {
|
||||
return
|
||||
}
|
||||
|
||||
artist.albums = union(artist.albums || [], albums)
|
||||
|
||||
each(albums, album => {
|
||||
album.artist_id = artist.id
|
||||
album.artist = artist
|
||||
artist.playCount += album.playCount
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove album(s) from an artist.
|
||||
*
|
||||
* @param {Object} artist
|
||||
* @param {Array.<Object>|Object} albums
|
||||
*/
|
||||
removeAlbumsFromArtist (artist, albums) {
|
||||
albums = [].concat(albums)
|
||||
artist.albums = difference(artist.albums, albums)
|
||||
each(albums, album => {
|
||||
artist.playCount -= album.playCount
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if an artist is empty.
|
||||
*
|
||||
* @param {Object} artist
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
isArtistEmpty (artist) {
|
||||
return !artist.albums.length
|
||||
this.all = difference(this.all, emptyArtists)
|
||||
each(emptyArtists, artist => delete this.cache[artist.id])
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -189,32 +136,6 @@ export const artistStore = {
|
|||
return artist.songs
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the artist's image.
|
||||
*
|
||||
* @param {Object} artist
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
getImage (artist) {
|
||||
if (!artist.image) {
|
||||
// Try to get an image from one of the albums.
|
||||
artist.image = config.unknownCover
|
||||
|
||||
artist.albums.every(album => {
|
||||
// If there's a "real" cover, use it.
|
||||
if (album.image !== config.unknownCover) {
|
||||
artist.image = album.cover
|
||||
|
||||
// I want to break free.
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return artist.image
|
||||
},
|
||||
|
||||
/**
|
||||
* Get top n most-played artists.
|
||||
*
|
||||
|
|
|
@ -2,7 +2,7 @@ import { assign } from 'lodash'
|
|||
import isMobile from 'ismobilejs'
|
||||
|
||||
import { http } from '../services'
|
||||
import { userStore, preferenceStore, artistStore, songStore, playlistStore, queueStore, settingStore } from '.'
|
||||
import { userStore, preferenceStore, artistStore, albumStore, songStore, playlistStore, queueStore, settingStore } from '.'
|
||||
|
||||
export const sharedStore = {
|
||||
state: {
|
||||
|
@ -43,7 +43,9 @@ export const sharedStore = {
|
|||
|
||||
userStore.init(this.state.users, this.state.currentUser)
|
||||
preferenceStore.init(this.state.preferences)
|
||||
artistStore.init(this.state.artists) // This will init album and song stores as well.
|
||||
artistStore.init(this.state.artists)
|
||||
albumStore.init(this.state.albums)
|
||||
songStore.init(this.state.songs)
|
||||
songStore.initInteractions(this.state.interactions)
|
||||
playlistStore.init(this.state.playlists)
|
||||
queueStore.init()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Vue from 'vue'
|
||||
import slugify from 'slugify'
|
||||
import { without, map, take, remove, orderBy, each, union, compact } from 'lodash'
|
||||
import { assign, without, map, take, remove, orderBy, each, unionBy, compact } from 'lodash'
|
||||
import isMobile from 'ismobilejs'
|
||||
|
||||
import { secondsToHis, alerts, pluralize } from '../utils'
|
||||
|
@ -32,40 +32,41 @@ export const songStore = {
|
|||
/**
|
||||
* Init the store.
|
||||
*
|
||||
* @param {Array.<Object>} albums The array of albums to extract our songs from
|
||||
* @param {Array.<Object>} songs The array of song objects
|
||||
*/
|
||||
init (albums) {
|
||||
// Iterate through the albums. With each, add its songs into our master song list.
|
||||
// While doing so, we populate some other information into the songs as well.
|
||||
this.all = albums.reduce((songs, album) => {
|
||||
each(album.songs, song => this.setupSong(song, album))
|
||||
return songs.concat(album.songs)
|
||||
}, [])
|
||||
|
||||
init (songs) {
|
||||
this.all = songs
|
||||
each(this.all, song => this.setupSong(song))
|
||||
this.state.recentlyPlayed = this.gatherRecentlyPlayedFromLocalStorage()
|
||||
},
|
||||
|
||||
setupSong (song, album) {
|
||||
setupSong (song) {
|
||||
song.fmtLength = secondsToHis(song.length)
|
||||
|
||||
// Manually set these additional properties to be reactive
|
||||
Vue.set(song, 'playCount', 0)
|
||||
Vue.set(song, 'album', album)
|
||||
Vue.set(song, 'liked', false)
|
||||
Vue.set(song, 'lyrics', null)
|
||||
Vue.set(song, 'playbackState', 'stopped')
|
||||
const album = albumStore.byId(song.album_id)
|
||||
const artist = artistStore.byId(song.artist_id)
|
||||
|
||||
if (song.contributing_artist_id) {
|
||||
const artist = artistStore.byId(song.contributing_artist_id)
|
||||
artist.albums = union(artist.albums, [album])
|
||||
artistStore.setupArtist(artist)
|
||||
Vue.set(song, 'artist', artist)
|
||||
} else {
|
||||
Vue.set(song, 'artist', artistStore.byId(song.album.artist.id))
|
||||
// Manually set these additional properties to be reactive
|
||||
Vue.set(song, 'playCount', song.playCount || 0)
|
||||
Vue.set(song, 'album', album)
|
||||
Vue.set(song, 'artist', artist)
|
||||
Vue.set(song, 'liked', song.liked || false)
|
||||
Vue.set(song, 'lyrics', song.lyrics || null)
|
||||
Vue.set(song, 'playbackState', song.playbackState || 'stopped')
|
||||
|
||||
artist.songs = unionBy(artist.songs || [], [song], 'id')
|
||||
album.songs = unionBy(album.songs || [], [song], 'id')
|
||||
|
||||
// now if the song is part of a compilation album, the album must be added
|
||||
// into its artist as well
|
||||
if (album.is_compilation) {
|
||||
artist.albums = unionBy(artist.albums, [album], 'id')
|
||||
}
|
||||
|
||||
// Cache the song, so that byId() is faster
|
||||
this.cache[song.id] = song
|
||||
|
||||
return song
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -230,107 +231,38 @@ export const songStore = {
|
|||
http.put('songs', {
|
||||
data,
|
||||
songs: map(songs, 'id')
|
||||
}, ({ data: songs }) => {
|
||||
each(songs, song => this.syncUpdatedSong(song))
|
||||
}, ({ data: { songs, artists, albums }}) => {
|
||||
// Add the artist and album into stores if they're new
|
||||
each(artists, artist => !artistStore.byId(artist.id) && artistStore.add(artist))
|
||||
each(albums, album => !albumStore.byId(album.id) && albumStore.add(album))
|
||||
|
||||
each(songs, song => {
|
||||
const originalSong = this.byId(song.id)
|
||||
|
||||
if (originalSong.album_id !== song.album_id) {
|
||||
// album has been changed. Remove the song from its old album.
|
||||
originalSong.album.songs = without(originalSong.album.songs, originalSong)
|
||||
}
|
||||
|
||||
if (originalSong.artist_id !== song.artist_id) {
|
||||
// artist has been changed. Remove the song from its old artist
|
||||
originalSong.artist.songs = without(originalSong.artist.songs, originalSong)
|
||||
}
|
||||
|
||||
assign(originalSong, song)
|
||||
// re-setup the song
|
||||
this.setupSong(originalSong)
|
||||
})
|
||||
|
||||
artistStore.compact()
|
||||
albumStore.compact()
|
||||
|
||||
alerts.success(`Updated ${pluralize(songs.length, 'song')}.`)
|
||||
resolve(songs)
|
||||
}, error => reject(error))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Sync an updated song into our current library.
|
||||
*
|
||||
* This is one of the most ugly functions I've written, if not the worst itself.
|
||||
* Sorry, future me.
|
||||
* Sorry guys.
|
||||
* Forgive me.
|
||||
*
|
||||
* @param {Object} updatedSong The updated song, with albums and whatnot.
|
||||
*
|
||||
* @return {?Object} The updated song.
|
||||
*/
|
||||
syncUpdatedSong (updatedSong) {
|
||||
// Cases:
|
||||
// 1. Album doesn't change (and then, artist doesn't either)
|
||||
// 2. Album changes (note that a new album might have been created) and
|
||||
// 2.a. Artist remains the same.
|
||||
// 2.b. Artist changes as well. Note that an artist might have been created.
|
||||
|
||||
// Find the original song,
|
||||
const originalSong = this.byId(updatedSong.id)
|
||||
|
||||
if (!originalSong) {
|
||||
return
|
||||
}
|
||||
|
||||
// and keep track of original album/artist.
|
||||
const originalAlbumId = originalSong.album.id
|
||||
const originalArtistId = originalSong.artist.id
|
||||
|
||||
// First, we update the title, lyrics, and track #
|
||||
originalSong.title = updatedSong.title
|
||||
originalSong.lyrics = updatedSong.lyrics
|
||||
originalSong.track = updatedSong.track
|
||||
|
||||
if (updatedSong.album.id === originalAlbumId) { // case 1
|
||||
// Nothing to do
|
||||
} else { // case 2
|
||||
// First, remove it from its old album
|
||||
albumStore.removeSongsFromAlbum(originalSong.album, originalSong)
|
||||
|
||||
const existingAlbum = albumStore.byId(updatedSong.album.id)
|
||||
const newAlbumCreated = !existingAlbum
|
||||
|
||||
if (!newAlbumCreated) {
|
||||
// The song changed to an existing album. We now add it to such album.
|
||||
albumStore.addSongsIntoAlbum(existingAlbum, originalSong)
|
||||
} else {
|
||||
// A new album was created. We:
|
||||
// - Add the new album into our collection
|
||||
// - Add the song into it
|
||||
albumStore.addSongsIntoAlbum(updatedSong.album, originalSong)
|
||||
albumStore.add(updatedSong.album)
|
||||
}
|
||||
|
||||
if (updatedSong.album.artist.id === originalArtistId) { // case 2.a
|
||||
// Same artist, but what if the album is new?
|
||||
if (newAlbumCreated) {
|
||||
artistStore.addAlbumsIntoArtist(artistStore.byId(originalArtistId), updatedSong.album)
|
||||
}
|
||||
} else { // case 2.b
|
||||
// The artist changes.
|
||||
const existingArtist = artistStore.byId(updatedSong.album.artist.id)
|
||||
|
||||
if (existingArtist) {
|
||||
originalSong.artist = existingArtist
|
||||
} else {
|
||||
// New artist created. We:
|
||||
// - Add the album into it, because now it MUST BE a new album
|
||||
// (there's no "new artist with existing album" in our system).
|
||||
// - Add the new artist into our collection
|
||||
artistStore.addAlbumsIntoArtist(updatedSong.album.artist, updatedSong.album)
|
||||
artistStore.add(updatedSong.album.artist)
|
||||
originalSong.artist = updatedSong.album.artist
|
||||
}
|
||||
}
|
||||
|
||||
// As a last step, we purify our library of empty albums/artists.
|
||||
if (albumStore.isAlbumEmpty(albumStore.byId(originalAlbumId))) {
|
||||
albumStore.remove(albumStore.byId(originalAlbumId))
|
||||
}
|
||||
|
||||
if (artistStore.isArtistEmpty(artistStore.byId(originalArtistId))) {
|
||||
artistStore.remove(artistStore.byId(originalArtistId))
|
||||
}
|
||||
|
||||
// Now we make sure the next call to info() get the refreshed, correct info.
|
||||
originalSong.infoRetrieved = false
|
||||
}
|
||||
|
||||
return originalSong
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a song's playable source URL.
|
||||
*
|
||||
|
|
223
resources/assets/js/tests/blobs/data.js
Normal file
223
resources/assets/js/tests/blobs/data.js
Normal file
|
@ -0,0 +1,223 @@
|
|||
export default {
|
||||
artists: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Unknown Artist'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Various Artists'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'All-4-One'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Boy Dylan'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'James Blunt'
|
||||
}
|
||||
],
|
||||
albums: [
|
||||
{
|
||||
id: 1193,
|
||||
artist_id: 3,
|
||||
name: 'All-4-One',
|
||||
cover: '/public/img/covers/565c0f7067425.jpeg'
|
||||
},
|
||||
{
|
||||
id: 1194,
|
||||
artist_id: 3,
|
||||
name: 'And The Music Speaks',
|
||||
cover: '/public/img/covers/unknown-album.png'
|
||||
},
|
||||
{
|
||||
id: 1195,
|
||||
artist_id: 3,
|
||||
name: 'Space Jam',
|
||||
cover: '/public/img/covers/565c0f7115e0f.png'
|
||||
},
|
||||
{
|
||||
id: 1217,
|
||||
artist_id: 4,
|
||||
name: 'Highway 61 Revisited',
|
||||
cover: '/public/img/covers/565c0f76dc6e8.jpeg'
|
||||
},
|
||||
{
|
||||
id: 1218,
|
||||
artist_id: 4,
|
||||
name: 'Pat Garrett & Billy the Kid',
|
||||
cover: '/public/img/covers/unknown-album.png'
|
||||
},
|
||||
{
|
||||
id: 1219,
|
||||
artist_id: 4,
|
||||
name: "The Times They Are A-Changin",
|
||||
cover: '/public/img/covers/unknown-album.png'
|
||||
},
|
||||
{
|
||||
id: 1268,
|
||||
artist_id: 5,
|
||||
name: 'Back To Bedlam',
|
||||
cover: '/public/img/covers/unknown-album.png'
|
||||
}
|
||||
],
|
||||
|
||||
songs: [
|
||||
{
|
||||
id: '39189f4545f9d5671fb3dc964f0080a0',
|
||||
album_id: 1193,
|
||||
artist_id: 3,
|
||||
title: 'I Swear',
|
||||
length: 259.92,
|
||||
playCount: 4
|
||||
},
|
||||
{
|
||||
id: 'a6a550f7d950d2a2520f9bf1a60f025a',
|
||||
album_id: 1194,
|
||||
artist_id: 3,
|
||||
title: 'I can love you like that',
|
||||
length: 262.61,
|
||||
playCount: 2
|
||||
},
|
||||
{
|
||||
id: 'd86c30fd34f13c1aff8db59b7fc9c610',
|
||||
album_id: 1195,
|
||||
artist_id: 3,
|
||||
title: 'I turn to you',
|
||||
length: 293.04
|
||||
},
|
||||
{
|
||||
id: 'e6d3977f3ffa147801ca5d1fdf6fa55e',
|
||||
album_id: 1217,
|
||||
artist_id: 4,
|
||||
title: 'Like a rolling stone',
|
||||
length: 373.63
|
||||
},
|
||||
{
|
||||
id: 'aa16bbef6a9710eb9a0f41ecc534fad5',
|
||||
album_id: 1218,
|
||||
artist_id: 4,
|
||||
title: "Knockin' on heaven's door",
|
||||
length: 151.9
|
||||
},
|
||||
{
|
||||
id: 'cb7edeac1f097143e65b1b2cde102482',
|
||||
album_id: 1219,
|
||||
artist_id: 4,
|
||||
title: "The times they are a-changin'",
|
||||
length: 196
|
||||
},
|
||||
{
|
||||
id: '0ba9fb128427b32683b9eb9140912a70',
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: 'No bravery',
|
||||
length: 243.12
|
||||
},
|
||||
{
|
||||
id: '123fd1ad32240ecab28a4e86ed5173',
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: 'So long, Jimmy',
|
||||
length: 265.04
|
||||
},
|
||||
{
|
||||
id: '6a54c674d8b16732f26df73f59c63e21',
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: 'Wisemen',
|
||||
length: 223.14
|
||||
},
|
||||
{
|
||||
id: '6df7d82a9a8701e40d1c291cf14a16bc',
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: 'Goodbye my lover',
|
||||
length: 258.61
|
||||
},
|
||||
{
|
||||
id: '74a2000d343e4587273d3ad14e2fd741',
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: 'High',
|
||||
length: 245.86
|
||||
},
|
||||
{
|
||||
id: '7900ab518f51775fe6cf06092c074ee5',
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: "You're beautiful",
|
||||
length: 213.29
|
||||
},
|
||||
{
|
||||
id: '803910a51f9893347e087af851e38777',
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: 'Cry',
|
||||
length: 246.91
|
||||
},
|
||||
{
|
||||
id: 'd82b0d4d4803ebbcb61000a5b6a868f5',
|
||||
album_id: 1268,
|
||||
artist_id: 5,
|
||||
title: 'Tears and rain',
|
||||
length: 244.45
|
||||
}
|
||||
],
|
||||
interactions: [
|
||||
{
|
||||
id: 1,
|
||||
song_id: '7900ab518f51775fe6cf06092c074ee5',
|
||||
liked: false,
|
||||
play_count: 1
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
song_id: '95c0ffc33c08c8c14ea5de0a44d5df3c',
|
||||
liked: false,
|
||||
play_count: 2
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
song_id: 'c83b201502eb36f1084f207761fa195c',
|
||||
liked: false,
|
||||
play_count: 1
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
song_id: 'cb7edeac1f097143e65b1b2cde102482',
|
||||
liked: true,
|
||||
play_count: 3
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
song_id: 'ccc38cc14bb95aefdf6da4b34adcf548',
|
||||
liked: false,
|
||||
play_count: 4
|
||||
}
|
||||
],
|
||||
currentUser: {
|
||||
id: 1,
|
||||
name: 'Phan An',
|
||||
email: 'me@phanan.net',
|
||||
is_admin: true
|
||||
},
|
||||
users: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Phan An',
|
||||
email: 'me@phanan.net',
|
||||
is_admin: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'John Doe',
|
||||
email: 'john@doe.tld',
|
||||
is_admin: false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
export default [
|
||||
{
|
||||
id: 1,
|
||||
song_id: '7900ab518f51775fe6cf06092c074ee5',
|
||||
liked: false,
|
||||
play_count: 1
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
song_id: '95c0ffc33c08c8c14ea5de0a44d5df3c',
|
||||
liked: false,
|
||||
play_count: 2
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
song_id: 'c83b201502eb36f1084f207761fa195c',
|
||||
liked: false,
|
||||
play_count: 1
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
song_id: 'cb7edeac1f097143e65b1b2cde102482',
|
||||
liked: true,
|
||||
play_count: 3
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
song_id: 'ccc38cc14bb95aefdf6da4b34adcf548',
|
||||
liked: false,
|
||||
play_count: 4
|
||||
}
|
||||
]
|
|
@ -1,214 +0,0 @@
|
|||
export default [
|
||||
{
|
||||
id: 1,
|
||||
name: 'All-4-One',
|
||||
albums: [
|
||||
{
|
||||
id: 1193,
|
||||
artist_id: 1,
|
||||
name: 'All-4-One',
|
||||
cover: '/public/img/covers/565c0f7067425.jpeg',
|
||||
songs: [
|
||||
{
|
||||
id: '39189f4545f9d5671fb3dc964f0080a0',
|
||||
album_id: 1193,
|
||||
title: 'I Swear',
|
||||
length: 259.92,
|
||||
playCount: 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 1194,
|
||||
artist_id: 1,
|
||||
name: 'And The Music Speaks',
|
||||
cover: '/public/img/covers/unknown-album.png',
|
||||
songs: [
|
||||
{
|
||||
id: 'a6a550f7d950d2a2520f9bf1a60f025a',
|
||||
album_id: 1194,
|
||||
title: 'I can love you like that',
|
||||
length: 262.61,
|
||||
playCount: 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 1195,
|
||||
artist_id: 1,
|
||||
name: 'Space Jam',
|
||||
cover: '/public/img/covers/565c0f7115e0f.png',
|
||||
songs: [
|
||||
{
|
||||
id: 'd86c30fd34f13c1aff8db59b7fc9c610',
|
||||
album_id: 1195,
|
||||
title: 'I turn to you',
|
||||
length: 293.04
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Bob Dylan',
|
||||
albums: [
|
||||
{
|
||||
id: 1217,
|
||||
artist_id: 2,
|
||||
name: 'Highway 61 Revisited',
|
||||
cover: '/public/img/covers/565c0f76dc6e8.jpeg',
|
||||
songs: [
|
||||
{
|
||||
id: 'e6d3977f3ffa147801ca5d1fdf6fa55e',
|
||||
album_id: 1217,
|
||||
title: 'Like a rolling stone',
|
||||
length: 373.63
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 1218,
|
||||
artist_id: 2,
|
||||
name: 'Pat Garrett & Billy the Kid',
|
||||
cover: '/public/img/covers/unknown-album.png',
|
||||
songs: [
|
||||
{
|
||||
id: 'aa16bbef6a9710eb9a0f41ecc534fad5',
|
||||
album_id: 1218,
|
||||
title: 'Knockin\' on heaven\'s door',
|
||||
length: 151.9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 1219,
|
||||
artist_id: 2,
|
||||
name: 'The Times They Are A-Changin\'',
|
||||
cover: '/public/img/covers/unknown-album.png',
|
||||
songs: [
|
||||
{
|
||||
id: 'cb7edeac1f097143e65b1b2cde102482',
|
||||
album_id: 1219,
|
||||
title: 'The times they are a-changin\'',
|
||||
length: 196
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'James Blunt',
|
||||
albums: [
|
||||
{
|
||||
id: 1268,
|
||||
artist_id: 3,
|
||||
name: 'Back To Bedlam',
|
||||
cover: '/public/img/covers/unknown-album.png',
|
||||
songs: [
|
||||
{
|
||||
id: '0ba9fb128427b32683b9eb9140912a70',
|
||||
album_id: 1268,
|
||||
title: 'No bravery',
|
||||
length: 243.12
|
||||
},
|
||||
{
|
||||
id: '123fd1ad32240ecab28a4e86ed5173',
|
||||
album_id: 1268,
|
||||
title: 'So long, Jimmy',
|
||||
length: 265.04
|
||||
},
|
||||
{
|
||||
id: '6a54c674d8b16732f26df73f59c63e21',
|
||||
album_id: 1268,
|
||||
title: 'Wisemen',
|
||||
length: 223.14
|
||||
},
|
||||
{
|
||||
id: '6df7d82a9a8701e40d1c291cf14a16bc',
|
||||
album_id: 1268,
|
||||
title: 'Goodbye my lover',
|
||||
length: 258.61
|
||||
},
|
||||
{
|
||||
id: '74a2000d343e4587273d3ad14e2fd741',
|
||||
album_id: 1268,
|
||||
title: 'High',
|
||||
length: 245.86
|
||||
},
|
||||
{
|
||||
id: '7900ab518f51775fe6cf06092c074ee5',
|
||||
album_id: 1268,
|
||||
title: 'You\'re beautiful',
|
||||
length: 213.29
|
||||
},
|
||||
{
|
||||
id: '803910a51f9893347e087af851e38777',
|
||||
album_id: 1268,
|
||||
title: 'Cry',
|
||||
length: 246.91
|
||||
},
|
||||
{
|
||||
id: 'd82b0d4d4803ebbcb61000a5b6a868f5',
|
||||
album_id: 1268,
|
||||
title: 'Tears and rain',
|
||||
length: 244.45
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
export const singleAlbum = {
|
||||
id: 9999,
|
||||
artist_id: 99,
|
||||
name: 'Foo bar',
|
||||
cover: '/foo.jpg',
|
||||
songs: [
|
||||
{
|
||||
id: '39189f4545f0d5671fc3dc964f0080a0',
|
||||
album_id: 9999,
|
||||
title: 'A Foo Song',
|
||||
length: 100,
|
||||
playCount: 4
|
||||
}, {
|
||||
id: '39189f4545f9d5671fc3dc96cf1080a0',
|
||||
album_id: 9999,
|
||||
title: 'A Bar Song',
|
||||
length: 200,
|
||||
playCount: 7
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const singleArtist = {
|
||||
id: 999,
|
||||
name: 'John Cena',
|
||||
albums: [
|
||||
{
|
||||
id: 9991,
|
||||
artist_id: 999,
|
||||
name: 'It\'s John Cena!!!!',
|
||||
cover: '/tmp/john.jpg',
|
||||
songs: [
|
||||
{
|
||||
id: 'e6d3977f3ffa147801ca5d1fdf6fa55f',
|
||||
album_id: 9991,
|
||||
title: 'John Cena to the Rescue',
|
||||
length: 300
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const singleSong = {
|
||||
id: 'dccb0d4d4803ebbcb61000a5b6a868f5',
|
||||
album_id: 1193,
|
||||
title: 'Foo and Bar',
|
||||
length: 100,
|
||||
playCount: 4,
|
||||
lyrics: ''
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
export default {
|
||||
currentUser: {
|
||||
id: 1,
|
||||
name: 'Phan An',
|
||||
email: 'me@phanan.net',
|
||||
is_admin: true
|
||||
},
|
||||
|
||||
users: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Phan An',
|
||||
email: 'me@phanan.net',
|
||||
is_admin: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'John Doe',
|
||||
email: 'john@doe.tld',
|
||||
is_admin: false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -2,101 +2,47 @@ require('chai').should()
|
|||
import { cloneDeep, last } from 'lodash'
|
||||
|
||||
import { albumStore, artistStore } from '../../stores'
|
||||
import { default as artists, singleAlbum, singleSong } from '../blobs/media'
|
||||
import data from '../blobs/data'
|
||||
|
||||
const { artists, albums } = data
|
||||
|
||||
describe('stores/album', () => {
|
||||
beforeEach(() => albumStore.init(cloneDeep(artists)))
|
||||
beforeEach(() => {
|
||||
artistStore.init(cloneDeep(artists))
|
||||
albumStore.init(cloneDeep(albums))
|
||||
})
|
||||
|
||||
afterEach(() => albumStore.state.albums = [])
|
||||
afterEach(() => {
|
||||
artistStore.state.artists = []
|
||||
albumStore.state.albums = []
|
||||
})
|
||||
|
||||
describe('#init', () => {
|
||||
it('correctly gathers albums', () => {
|
||||
albumStore.state.albums.length.should.equal(7)
|
||||
})
|
||||
|
||||
it('correctly sets albums length', () => {
|
||||
albumStore.state.albums[0].length.should.equal(259.92)
|
||||
})
|
||||
|
||||
it('correctly sets album artists', () => {
|
||||
albumStore.state.albums[0].artist.id.should.equal(1)
|
||||
albumStore.state.albums[0].artist.id.should.equal(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#byId', () => {
|
||||
it('correctly gets an album by ID', () => {
|
||||
albumStore.byId(1193).name.should.equal('All-4-One')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#compact', () => {
|
||||
it('correctly compacts albums', () => {
|
||||
albumStore.compact()
|
||||
albumStore.state.albums.length.should.equal(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#all', () => {
|
||||
it('correctly returns all songs', () => {
|
||||
it('correctly returns all albums', () => {
|
||||
albumStore.all.length.should.equal(7)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getLength', () => {
|
||||
it('correctly calculates an album’s length', () => {
|
||||
albumStore.getLength(albumStore.state.albums[6])
|
||||
albumStore.state.albums[6].length.should.equal(1940.42); // I'm sorry…
|
||||
})
|
||||
})
|
||||
|
||||
describe('#add', () => {
|
||||
beforeEach(() => {
|
||||
albumStore.add(cloneDeep(singleAlbum))
|
||||
})
|
||||
|
||||
it('correctly adds a new album into the state', () => {
|
||||
last(albumStore.state.albums).id.should.equal(9999)
|
||||
})
|
||||
|
||||
it('correctly recalculates the length', () => {
|
||||
last(albumStore.state.albums).length.should.equal(300)
|
||||
})
|
||||
|
||||
it('correctly recalculates the play count', () => {
|
||||
last(albumStore.state.albums).playCount.should.equal(11)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#remove', () => {
|
||||
beforeEach(() => {
|
||||
albumStore.remove(albumStore.state.albums[0]); // ID 1193
|
||||
})
|
||||
|
||||
it('correctly removes an album', () => {
|
||||
albumStore.state.albums.length.should.equal(6)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#addSongsIntoAlbum', () => {
|
||||
beforeEach(() => {
|
||||
albumStore.addSongsIntoAlbum(albumStore.state.albums[0], cloneDeep(singleSong))
|
||||
})
|
||||
|
||||
it('correctly adds a song into an album', () => {
|
||||
albumStore.state.albums[0].songs.length.should.equal(2)
|
||||
})
|
||||
|
||||
it('correctly recalculates the play count', () => {
|
||||
albumStore.state.albums[0].playCount.should.equal(4)
|
||||
})
|
||||
|
||||
it ('correctly recalculates album length', () => {
|
||||
albumStore.state.albums[0].length.should.equal(359.92)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#removeSongsFromAlbum', () => {
|
||||
beforeEach(() => {
|
||||
albumStore.removeSongsFromAlbum(albumStore.state.albums[0], albumStore.state.albums[0].songs[0])
|
||||
})
|
||||
|
||||
it('correctly removes a song from an album', () => {
|
||||
albumStore.state.albums[0].songs.length.should.equal(0)
|
||||
})
|
||||
|
||||
it('correctly recalculates the play count', () => {
|
||||
albumStore.state.albums[0].playCount.should.equal(0)
|
||||
})
|
||||
|
||||
it('correctly recalculates the length', () => {
|
||||
albumStore.state.albums[0].length.should.equal(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -2,7 +2,8 @@ require('chai').should()
|
|||
import { cloneDeep, last } from 'lodash'
|
||||
|
||||
import { artistStore } from '../../stores'
|
||||
import { default as artists, singleAlbum, singleArtist } from '../blobs/media'
|
||||
import data from '../blobs/data'
|
||||
const artists = data.artists
|
||||
|
||||
describe('stores/artist', () => {
|
||||
beforeEach(() => artistStore.init(cloneDeep(artists)))
|
||||
|
@ -10,64 +11,22 @@ describe('stores/artist', () => {
|
|||
|
||||
describe('#init', () => {
|
||||
it('correctly gathers artists', () => {
|
||||
artistStore.state.artists.length.should.equal(3)
|
||||
})
|
||||
|
||||
it('correctly gets artist images', () => {
|
||||
artistStore.state.artists[0].image.should.equal('/public/img/covers/565c0f7067425.jpeg')
|
||||
})
|
||||
|
||||
it('correctly counts songs by artists', () => {
|
||||
artistStore.state.artists[0].songCount = 3
|
||||
artistStore.state.artists.length.should.equal(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getImage', () => {
|
||||
it('correctly gets an artist’s image', () => {
|
||||
artistStore.getImage(artistStore.state.artists[0]).should.equal('/public/img/covers/565c0f7067425.jpeg')
|
||||
describe('#byId', () => {
|
||||
it('correctly gets an artist by ID', () => {
|
||||
artistStore.byId(3).name.should.equal('All-4-One')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#add', () => {
|
||||
beforeEach(() => artistStore.add(cloneDeep(singleArtist)))
|
||||
|
||||
it('correctly adds an artist', () => {
|
||||
last(artistStore.state.artists).name.should.equal('John Cena')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#remove', () => {
|
||||
beforeEach(() => artistStore.remove(artistStore.state.artists[0]))
|
||||
|
||||
it('correctly removes an artist', () => {
|
||||
artistStore.state.artists.length.should.equal(2)
|
||||
artistStore.state.artists[0].name.should.equal('Bob Dylan')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#addAlbumsIntoArtist', () => {
|
||||
beforeEach(() => {
|
||||
artistStore.addAlbumsIntoArtist(artistStore.state.artists[0], cloneDeep(singleAlbum))
|
||||
})
|
||||
|
||||
it('correctly adds albums into an artist', () => {
|
||||
artistStore.state.artists[0].albums.length.should.equal(4)
|
||||
})
|
||||
|
||||
it('correctly sets the album artist', () => {
|
||||
const addedAlbum = last(artistStore.state.artists[0].albums)
|
||||
addedAlbum.artist.should.equal(artistStore.state.artists[0])
|
||||
addedAlbum.artist_id.should.equal(artistStore.state.artists[0].id)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#removeAlbumsFromArtist', () => {
|
||||
beforeEach(() => {
|
||||
artistStore.removeAlbumsFromArtist(artistStore.state.artists[0], artistStore.state.artists[0].albums[0])
|
||||
})
|
||||
|
||||
it('correctly removes an album from an artist', () => {
|
||||
artistStore.state.artists[0].albums.length.should.equal(2)
|
||||
describe('#compact', () => {
|
||||
it('correctly compact artists', () => {
|
||||
artistStore.compact()
|
||||
// because we've not processed songs/albums, all artists here have no songs
|
||||
// and should be removed after compact()ing
|
||||
artistStore.state.artists.length.should.equal(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
require('chai').should()
|
||||
|
||||
import { queueStore } from '../../stores'
|
||||
import artists from '../blobs/media'
|
||||
import data from '../blobs/data'
|
||||
|
||||
const songs = artists[2].albums[0].songs
|
||||
const { songs: allSongs } = data
|
||||
// only get the songs by James Blunt
|
||||
const songs = allSongs.filter(song => song.artist_id === 5)
|
||||
|
||||
describe('stores/queue', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -32,7 +34,7 @@ describe('stores/queue', () => {
|
|||
describe('#queue', () => {
|
||||
beforeEach(() => queueStore.state.songs = songs)
|
||||
|
||||
const song = artists[0].albums[0].songs[0]
|
||||
const song = allSongs[0]
|
||||
|
||||
it('correctly appends a song to end of the queue', () => {
|
||||
queueStore.queue(song)
|
||||
|
|
|
@ -2,12 +2,15 @@ require('chai').should()
|
|||
import { cloneDeep, last } from 'lodash'
|
||||
|
||||
import { songStore, albumStore, artistStore, preferenceStore } from '../../stores'
|
||||
import artists from '../blobs/media'
|
||||
import interactions from '../blobs/interactions'
|
||||
import data from '../blobs/data'
|
||||
|
||||
const { songs, artists, albums, interactions } = data
|
||||
|
||||
describe('stores/song', () => {
|
||||
beforeEach(() => {
|
||||
artistStore.init(artists)
|
||||
albumStore.init(albums)
|
||||
songStore.init(songs)
|
||||
})
|
||||
|
||||
describe('#init', () => {
|
||||
|
@ -54,97 +57,6 @@ describe('stores/song', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('#syncUpdatedSong', () => {
|
||||
beforeEach(() => artistStore.init(artists))
|
||||
|
||||
const updatedSong = {
|
||||
id: "39189f4545f9d5671fb3dc964f0080a0",
|
||||
album_id: 1193,
|
||||
title: "I Swear A Lot",
|
||||
album: {
|
||||
id: 1193,
|
||||
arist_id: 1,
|
||||
artist: {
|
||||
id: 1,
|
||||
name: 'All-4-One'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
it ('correctly syncs an updated song with no album changes', () => {
|
||||
songStore.syncUpdatedSong(cloneDeep(updatedSong))
|
||||
songStore.byId(updatedSong.id).title.should.equal('I Swear A Lot')
|
||||
})
|
||||
|
||||
it ('correctly syncs an updated song into an existing album of same artist', () => {
|
||||
const song = cloneDeep(updatedSong)
|
||||
song.album_id = 1194
|
||||
song.album = {
|
||||
id: 1194,
|
||||
artist_id: 1,
|
||||
artist: {
|
||||
id: 1,
|
||||
name: 'All-4-One',
|
||||
},
|
||||
}
|
||||
|
||||
songStore.syncUpdatedSong(song)
|
||||
songStore.byId(song.id).album.name.should.equal('And The Music Speaks')
|
||||
})
|
||||
|
||||
it ('correctly syncs an updated song into a new album of same artist', () => {
|
||||
const song = cloneDeep(updatedSong)
|
||||
song.album_id = 1000
|
||||
song.album = {
|
||||
id: 1000,
|
||||
artist_id: 1,
|
||||
name: 'Brand New Album from All-4-One',
|
||||
artist: {
|
||||
id: 1,
|
||||
name: 'All-4-One'
|
||||
}
|
||||
}
|
||||
|
||||
songStore.syncUpdatedSong(song)
|
||||
|
||||
// A new album should be created...
|
||||
last(albumStore.all).name.should.equal('Brand New Album from All-4-One')
|
||||
|
||||
// ...and assigned with the song.
|
||||
songStore.byId(song.id).album.name.should.equal('Brand New Album from All-4-One')
|
||||
})
|
||||
|
||||
it ('correctly syncs an updated song into a new album of a new artist', () => {
|
||||
const song = cloneDeep(updatedSong)
|
||||
song.album_id = 10000
|
||||
song.album = {
|
||||
id: 10000,
|
||||
name: "It's... John Cena!!!",
|
||||
artist_id: 10000,
|
||||
artist: {
|
||||
id: 10000,
|
||||
name: 'John Cena'
|
||||
}
|
||||
}
|
||||
|
||||
songStore.syncUpdatedSong(song)
|
||||
|
||||
// A new artist should be created...
|
||||
const lastArtist = last(artistStore.all)
|
||||
lastArtist.name.should.equal('John Cena')
|
||||
|
||||
// A new album should be created
|
||||
const lastAlbum = last(albumStore.all)
|
||||
lastAlbum.name.should.equal("It's... John Cena!!!")
|
||||
|
||||
// The album must belong to John Cena of course!
|
||||
last(lastArtist.albums).should.equal(lastAlbum)
|
||||
|
||||
// And the song belongs to the album.
|
||||
songStore.byId(song.id).album.should.equal(lastAlbum)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#addRecentlyPlayed', () => {
|
||||
it('correctly adds a recently played song', () => {
|
||||
songStore.addRecentlyPlayed(songStore.byId('cb7edeac1f097143e65b1b2cde102482'))
|
||||
|
@ -156,4 +68,10 @@ describe('stores/song', () => {
|
|||
songStore.gatherRecentlyPlayedFromLocalStorage()[0].id.should.equal('cb7edeac1f097143e65b1b2cde102482')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#guess', () => {
|
||||
it('correcty guesses a song', () => {
|
||||
songStore.guess('i swear', albumStore.byId(1193)).id.should.equal('39189f4545f9d5671fb3dc964f0080a0')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
require('chai').should()
|
||||
|
||||
import { userStore } from '../../stores'
|
||||
import data from '../blobs/users'
|
||||
import data from '../blobs/data'
|
||||
|
||||
const { users } = data
|
||||
|
||||
describe('stores/user', () => {
|
||||
beforeEach(() => userStore.init(data.users, data.currentUser))
|
||||
|
|
|
@ -81,6 +81,7 @@ abstract class BrowserKitTestCase extends BaseBrowserKitTestCase
|
|||
foreach ($albums as $album) {
|
||||
factory(Song::class, random_int(7, 15))->create([
|
||||
'album_id' => $album->id,
|
||||
'artist_id' => $artist->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -156,7 +156,8 @@ class LastfmTest extends BrowserKitTestCase
|
|||
|
||||
public function testControllerCallback()
|
||||
{
|
||||
$request = m::mock(Request::class, ['input' => 'token']);
|
||||
$request = m::mock(Request::class);
|
||||
$request->token = 'foo';
|
||||
$lastfm = m::mock(Lastfm::class, ['getSessionKey' => 'bar']);
|
||||
|
||||
$user = factory(User::class)->create();
|
||||
|
|
21
tests/Feature/MediaCacheTest.php
Normal file
21
tests/Feature/MediaCacheTest.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Cache;
|
||||
use MediaCache;
|
||||
use Tests\BrowserKitTestCase;
|
||||
|
||||
class MediaCacheTest extends BrowserKitTestCase
|
||||
{
|
||||
public function testGetCache()
|
||||
{
|
||||
// first clear all cache
|
||||
Cache::flush();
|
||||
// now make sure the get() function returns data
|
||||
$data = MediaCache::get();
|
||||
$this->assertNotNull($data);
|
||||
// and the data are seen in the cache as well
|
||||
$this->assertEquals($data, Cache::get('media_cache'));
|
||||
}
|
||||
}
|
|
@ -62,7 +62,7 @@ class MediaTest extends BrowserKitTestCase
|
|||
|
||||
// Compilation albums, artists and songs must be recognized
|
||||
$song = Song::whereTitle('This song belongs to a compilation')->first();
|
||||
$this->assertNotNull($song->contributing_artist_id);
|
||||
$this->assertNotNull($song->artist_id);
|
||||
$this->assertTrue($song->album->is_compilation);
|
||||
$this->assertEquals(Artist::VARIOUS_ID, $song->album->artist_id);
|
||||
|
||||
|
|
|
@ -177,12 +177,12 @@ class SongTest extends BrowserKitTestCase
|
|||
$compilationAlbum = Album::whereArtistIdAndName(Artist::VARIOUS_ID, 'One by One')->first();
|
||||
$this->assertNotNull($compilationAlbum);
|
||||
|
||||
$contributingArtist = Artist::whereName('John Cena')->first();
|
||||
$this->assertNotNull($contributingArtist);
|
||||
$artist = Artist::whereName('John Cena')->first();
|
||||
$this->assertNotNull($artist);
|
||||
|
||||
$this->seeInDatabase('songs', [
|
||||
'id' => $song->id,
|
||||
'contributing_artist_id' => $contributingArtist->id,
|
||||
'artist_id' => $artist->id,
|
||||
'album_id' => $compilationAlbum->id,
|
||||
'lyrics' => 'Lorem ipsum dolor sic amet.',
|
||||
'track' => 1,
|
||||
|
@ -211,7 +211,7 @@ class SongTest extends BrowserKitTestCase
|
|||
|
||||
$this->seeInDatabase('songs', [
|
||||
'id' => $song->id,
|
||||
'contributing_artist_id' => $contributingArtist->id,
|
||||
'artist_id' => $contributingArtist->id,
|
||||
'album_id' => $compilationAlbum->id,
|
||||
]);
|
||||
|
||||
|
@ -237,7 +237,7 @@ class SongTest extends BrowserKitTestCase
|
|||
|
||||
$this->seeInDatabase('songs', [
|
||||
'id' => $song->id,
|
||||
'contributing_artist_id' => $contributingArtist->id,
|
||||
'artist_id' => $contributingArtist->id,
|
||||
'album_id' => $compilationAlbum->id,
|
||||
]);
|
||||
|
||||
|
@ -258,10 +258,11 @@ class SongTest extends BrowserKitTestCase
|
|||
$artist = Artist::whereName('Foo Fighters')->first();
|
||||
$this->assertNotNull($artist);
|
||||
$album = Album::whereArtistIdAndName($artist->id, 'One by One')->first();
|
||||
$this->assertNotNull($album);
|
||||
|
||||
$this->seeInDatabase('songs', [
|
||||
'id' => $song->id,
|
||||
'contributing_artist_id' => null,
|
||||
'artist_id' => $artist->id,
|
||||
'album_id' => $album->id,
|
||||
]);
|
||||
|
||||
|
@ -297,7 +298,7 @@ class SongTest extends BrowserKitTestCase
|
|||
$this->assertNotNull($album);
|
||||
$this->seeInDatabase('songs', [
|
||||
'id' => $song->id,
|
||||
'contributing_artist_id' => null,
|
||||
'artist_id' => $artist->id,
|
||||
'album_id' => $album->id,
|
||||
'lyrics' => 'Thor! Nanananananana Batman.', // haha
|
||||
]);
|
||||
|
|
Loading…
Reference in a new issue