Merge branch 'compilation'

This commit is contained in:
Phan An 2017-05-02 00:47:46 +07:00
commit 899602bc7a
No known key found for this signature in database
GPG key ID: 4AF3D4E287BF423F
51 changed files with 1107 additions and 966 deletions

View file

@ -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(),

View file

@ -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()));
}
}

View file

@ -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();
}

View file

@ -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'),

View file

@ -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');

View file

@ -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));

View file

@ -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));
}
}

View file

@ -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.

View file

@ -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')) {

View file

@ -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);
}
}

View file

@ -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()
{

View file

@ -1,8 +0,0 @@
<?php
namespace App\Models;
class ContributingArtist extends Artist
{
protected $table = 'artists';
}

View file

@ -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()
{

View file

@ -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));
}
});
}
}

View file

@ -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://');
}
/**

View file

@ -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.');

View file

@ -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));
}
}

View file

@ -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);

View file

@ -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;
}
/**

View file

@ -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
View file

@ -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",

View file

@ -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()
{
//
}
}

View file

@ -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()
{
//
}
}

View file

@ -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');
});
}
}

View file

@ -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>

View file

@ -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 },

View file

@ -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 },

View file

@ -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
})
}

View file

@ -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 {

View file

@ -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)
}
},

View file

@ -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'
}

View 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)
}
}
}

View 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
}
}
}

View file

@ -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])
},
/**

View file

@ -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.
*

View file

@ -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()

View file

@ -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.
*

View 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
}
]
}

View file

@ -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
}
]

View file

@ -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: ''
}

View file

@ -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
}
]
}

View file

@ -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 albums 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)
})
})
})

View file

@ -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 artists 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)
})
})
})

View file

@ -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)

View file

@ -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')
})
})
})

View file

@ -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))

View file

@ -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,
]);
}
}

View file

@ -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();

View 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'));
}
}

View file

@ -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);

View file

@ -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
]);