mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2024-11-25 14:37:59 +01:00
Merge pull request #17 from Poniverse/feature/cache_tracks
feature/cache_tracks
This commit is contained in:
commit
74ae2931dc
29 changed files with 1329 additions and 117 deletions
|
@ -21,17 +21,19 @@
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Helpers;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Auth;
|
||||||
use Illuminate\Support\Facades\URL;
|
use Cache;
|
||||||
|
use Poniverse\Ponyfm\Traits\TrackCollection;
|
||||||
|
use URL;
|
||||||
use Poniverse\Ponyfm\Traits\SlugTrait;
|
use Poniverse\Ponyfm\Traits\SlugTrait;
|
||||||
use Helpers;
|
|
||||||
|
|
||||||
class Album extends Model
|
class Album extends Model
|
||||||
{
|
{
|
||||||
use SoftDeletes, SlugTrait;
|
use SoftDeletes, SlugTrait, DispatchesJobs, TrackCollection;
|
||||||
|
|
||||||
protected $dates = ['deleted_at'];
|
protected $dates = ['deleted_at'];
|
||||||
|
|
||||||
|
@ -81,12 +83,16 @@ class Album extends Model
|
||||||
return $this->hasMany('Poniverse\Ponyfm\Track')->orderBy('track_number', 'asc');
|
return $this->hasMany('Poniverse\Ponyfm\Track')->orderBy('track_number', 'asc');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function trackFiles() {
|
||||||
|
return $this->hasManyThrough(TrackFile::class, Track::class, 'album_id', 'track_id');
|
||||||
|
}
|
||||||
|
|
||||||
public function comments()
|
public function comments()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\Comment')->orderBy('created_at', 'desc');
|
return $this->hasMany('Poniverse\Ponyfm\Comment')->orderBy('created_at', 'desc');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function mapPublicAlbumShow($album)
|
public static function mapPublicAlbumShow(Album $album)
|
||||||
{
|
{
|
||||||
$tracks = [];
|
$tracks = [];
|
||||||
foreach ($album->tracks as $track) {
|
foreach ($album->tracks as $track) {
|
||||||
|
@ -99,7 +105,8 @@ class Album extends Model
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'extension' => $format['extension'],
|
'extension' => $format['extension'],
|
||||||
'url' => $album->getDownloadUrl($name),
|
'url' => $album->getDownloadUrl($name),
|
||||||
'size' => Helpers::formatBytes($album->getFilesize($name))
|
'size' => Helpers::formatBytes($album->getFilesize($name)),
|
||||||
|
'isCacheable' => (in_array($name, Track::$CacheableFormats) ? true : false)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +138,7 @@ class Album extends Model
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function mapPublicAlbumSummary($album)
|
public static function mapPublicAlbumSummary(Album $album)
|
||||||
{
|
{
|
||||||
$userData = [
|
$userData = [
|
||||||
'stats' => [
|
'stats' => [
|
||||||
|
@ -145,24 +152,24 @@ class Album extends Model
|
||||||
$userRow = $album->users[0];
|
$userRow = $album->users[0];
|
||||||
$userData = [
|
$userData = [
|
||||||
'stats' => [
|
'stats' => [
|
||||||
'views' => (int) $userRow->view_count,
|
'views' => (int)$userRow->view_count,
|
||||||
'downloads' => (int) $userRow->download_count,
|
'downloads' => (int)$userRow->download_count,
|
||||||
],
|
],
|
||||||
'is_favourited' => (bool) $userRow->is_favourited
|
'is_favourited' => (bool)$userRow->is_favourited
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => (int) $album->id,
|
'id' => (int)$album->id,
|
||||||
'track_count' => (int) $album->track_count,
|
'track_count' => (int)$album->track_count,
|
||||||
'title' => $album->title,
|
'title' => $album->title,
|
||||||
'slug' => $album->slug,
|
'slug' => $album->slug,
|
||||||
'created_at' => $album->created_at,
|
'created_at' => $album->created_at,
|
||||||
'stats' => [
|
'stats' => [
|
||||||
'views' => (int) $album->view_count,
|
'views' => (int)$album->view_count,
|
||||||
'downloads' => (int) $album->download_count,
|
'downloads' => (int)$album->download_count,
|
||||||
'comments' => (int) $album->comment_count,
|
'comments' => (int)$album->comment_count,
|
||||||
'favourites' => (int) $album->favourite_count
|
'favourites' => (int)$album->favourite_count
|
||||||
],
|
],
|
||||||
'covers' => [
|
'covers' => [
|
||||||
'small' => $album->getCoverUrl(Image::SMALL),
|
'small' => $album->getCoverUrl(Image::SMALL),
|
||||||
|
@ -170,7 +177,7 @@ class Album extends Model
|
||||||
],
|
],
|
||||||
'url' => $album->url,
|
'url' => $album->url,
|
||||||
'user' => [
|
'user' => [
|
||||||
'id' => (int) $album->user->id,
|
'id' => (int)$album->user->id,
|
||||||
'name' => $album->user->display_name,
|
'name' => $album->user->display_name,
|
||||||
'url' => $album->user->url,
|
'url' => $album->user->url,
|
||||||
],
|
],
|
||||||
|
@ -206,10 +213,18 @@ class Album extends Model
|
||||||
|
|
||||||
return Cache::remember($this->getCacheKey('filesize-' . $format), 1440, function () use ($tracks, $format) {
|
return Cache::remember($this->getCacheKey('filesize-' . $format), 1440, function () use ($tracks, $format) {
|
||||||
$size = 0;
|
$size = 0;
|
||||||
|
|
||||||
foreach ($tracks as $track) {
|
foreach ($tracks as $track) {
|
||||||
|
/** @var $track Track */
|
||||||
|
|
||||||
// Ensure that only downloadable tracks are added onto the file size
|
// Ensure that only downloadable tracks are added onto the file size
|
||||||
if ($track->is_downloadable == 1) {
|
if ($track->is_downloadable == 1) {
|
||||||
$size += $track->getFilesize($format);
|
try {
|
||||||
|
$size += $track->getFilesize($format);
|
||||||
|
|
||||||
|
} catch (TrackFileNotFoundException $e) {
|
||||||
|
// do nothing - this track won't be included in the download
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,6 +270,8 @@ class Album extends Model
|
||||||
$index = 1;
|
$index = 1;
|
||||||
|
|
||||||
foreach ($tracks as $track) {
|
foreach ($tracks as $track) {
|
||||||
|
/** @var $track Track */
|
||||||
|
|
||||||
$track->track_number = $index;
|
$track->track_number = $index;
|
||||||
$index++;
|
$index++;
|
||||||
$track->updateTags();
|
$track->updateTags();
|
||||||
|
@ -301,7 +318,9 @@ class Album extends Model
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @var $track Track */
|
||||||
$track = Track::find($trackId);
|
$track = Track::find($trackId);
|
||||||
|
|
||||||
if ($track->album_id != null && $track->album_id != $this->id) {
|
if ($track->album_id != null && $track->album_id != $this->id) {
|
||||||
$albumsToFix[] = $track->album;
|
$albumsToFix[] = $track->album;
|
||||||
}
|
}
|
||||||
|
@ -316,6 +335,8 @@ class Album extends Model
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($tracksToRemove as $track) {
|
foreach ($tracksToRemove as $track) {
|
||||||
|
/** @var $track Track */
|
||||||
|
|
||||||
$track->album_id = null;
|
$track->album_id = null;
|
||||||
$track->track_number = null;
|
$track->track_number = null;
|
||||||
$track->updateTags();
|
$track->updateTags();
|
||||||
|
@ -323,6 +344,8 @@ class Album extends Model
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($albumsToFix as $album) {
|
foreach ($albumsToFix as $album) {
|
||||||
|
/** @var $album Album */
|
||||||
|
|
||||||
$album->updateTrackNumbers();
|
$album->updateTrackNumbers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,7 +354,7 @@ class Album extends Model
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getCacheKey($key)
|
public function getCacheKey($key)
|
||||||
{
|
{
|
||||||
return 'album-' . $this->id . '-' . $key;
|
return 'album-' . $this->id . '-' . $key;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ use AudioCache;
|
||||||
use File;
|
use File;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
class UploadTrackCommand extends CommandBase
|
class UploadTrackCommand extends CommandBase
|
||||||
{
|
{
|
||||||
|
@ -94,8 +96,6 @@ class UploadTrackCommand extends CommandBase
|
||||||
$source = $trackFile->getPathname();
|
$source = $trackFile->getPathname();
|
||||||
$index = 0;
|
$index = 0;
|
||||||
|
|
||||||
$processes = [];
|
|
||||||
|
|
||||||
// Lossy uploads need to be identified and set as the master file
|
// Lossy uploads need to be identified and set as the master file
|
||||||
// without being re-encoded.
|
// without being re-encoded.
|
||||||
$audioObject = AudioCache::get($source);
|
$audioObject = AudioCache::get($source);
|
||||||
|
@ -136,9 +136,16 @@ class UploadTrackCommand extends CommandBase
|
||||||
$trackFile = new TrackFile();
|
$trackFile = new TrackFile();
|
||||||
$trackFile->is_master = $name === 'FLAC' ? true : false;
|
$trackFile->is_master = $name === 'FLAC' ? true : false;
|
||||||
$trackFile->format = $name;
|
$trackFile->format = $name;
|
||||||
|
|
||||||
|
if (in_array($name, Track::$CacheableFormats) && $trackFile->is_master == false) {
|
||||||
|
$trackFile->is_cacheable = true;
|
||||||
|
} else {
|
||||||
|
$trackFile->is_cacheable = false;
|
||||||
|
}
|
||||||
$track->trackFiles()->save($trackFile);
|
$track->trackFiles()->save($trackFile);
|
||||||
|
|
||||||
$target = $destination . '/' . $trackFile->getFilename(); //$track->getFilenameFor($name);
|
// Encode track file
|
||||||
|
$target = $trackFile->getFile();
|
||||||
|
|
||||||
$command = $format['command'];
|
$command = $format['command'];
|
||||||
$command = str_replace('{$source}', '"' . $source . '"', $command);
|
$command = str_replace('{$source}', '"' . $source . '"', $command);
|
||||||
|
@ -147,13 +154,16 @@ class UploadTrackCommand extends CommandBase
|
||||||
Log::info('Encoding ' . $track->id . ' into ' . $target);
|
Log::info('Encoding ' . $track->id . ' into ' . $target);
|
||||||
$this->notify('Encoding ' . $name, $index / count(Track::$Formats) * 100);
|
$this->notify('Encoding ' . $name, $index / count(Track::$Formats) * 100);
|
||||||
|
|
||||||
$pipes = [];
|
$process = new Process($command);
|
||||||
$proc = proc_open($command, [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'a']], $pipes);
|
$process->mustRun();
|
||||||
$processes[] = $proc;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($processes as $proc) {
|
// Update file size for track file
|
||||||
proc_close($proc);
|
$trackFile->updateFilesize();
|
||||||
|
|
||||||
|
// Delete track file if it is cacheable
|
||||||
|
if ($trackFile->is_cacheable == true) {
|
||||||
|
File::delete($trackFile->getFile());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$track->updateTags();
|
$track->updateTags();
|
||||||
|
|
111
app/Console/Commands/ClearTrackCache.php
Normal file
111
app/Console/Commands/ClearTrackCache.php
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2015 Kelvin Zhang
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Poniverse\Ponyfm\Console\Commands;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use File;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Poniverse\Ponyfm\TrackFile;
|
||||||
|
|
||||||
|
class ClearTrackCache extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'track-cache:clear
|
||||||
|
{--tracks=expired : Clear only [expired] (default) or [all] cached tracks.}
|
||||||
|
{--force : Skip all prompts.}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Clears cached tracks. Defaults to expired tracks. Usage: php artisan track-cache:clear [--tracks=expired|all]';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new command instance.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
if ($this->option('tracks') === 'all') {
|
||||||
|
// Get all cacheable track files
|
||||||
|
$trackFiles = TrackFile::where('is_cacheable', true)
|
||||||
|
->with('track.album')
|
||||||
|
->get();
|
||||||
|
} else {
|
||||||
|
// Get all expired track files
|
||||||
|
$trackFiles = TrackFile::where('is_cacheable', true)
|
||||||
|
->where('expires_at', '<=', Carbon::now())
|
||||||
|
->with('track.album')
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete above track files
|
||||||
|
if (count($trackFiles) === 0) {
|
||||||
|
$this->info('No tracks found. Exiting.');
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if ($this->option('force') || $this->confirm(count($trackFiles) . ' cacheable track files found. Proceed to delete their files if they exist? [y|N]', false)) {
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
foreach ($trackFiles as $trackFile) {
|
||||||
|
|
||||||
|
// Set expiration to null (so can be re-cached upon request)
|
||||||
|
$trackFile->expires_at = null;
|
||||||
|
$trackFile->update();
|
||||||
|
|
||||||
|
// Delete file if exists
|
||||||
|
if (File::exists($trackFile->getFile())) {
|
||||||
|
$count++;
|
||||||
|
File::delete($trackFile->getFile());
|
||||||
|
|
||||||
|
$this->info('Deleted ' . $trackFile->getFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the cached file size for the album
|
||||||
|
Cache::forget($trackFile->track->album->getCacheKey('filesize-' . $trackFile->format));
|
||||||
|
|
||||||
|
}
|
||||||
|
$this->info($count . ' files deleted. Deletion complete. Exiting.');
|
||||||
|
} else {
|
||||||
|
$this->info('Deletion cancelled. Exiting.');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
91
app/Console/Commands/RebuildFilesizes.php
Normal file
91
app/Console/Commands/RebuildFilesizes.php
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2015 Kelvin Zhang
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Poniverse\Ponyfm\Console\Commands;
|
||||||
|
|
||||||
|
use File;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Poniverse\Ponyfm\TrackFile;
|
||||||
|
|
||||||
|
class RebuildFilesizes extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'rebuild:filesizes
|
||||||
|
{--force : Skip all prompts.}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Rebuilds the filesize cache for each track file which currently exists on disk.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new command instance.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->info('This will only rebuild the cache for track files which exist on disk; non-existent files will be skipped.');
|
||||||
|
|
||||||
|
if ($this->option('force') || $this->confirm('Are you sure you want to rebuild the filesize cache? [y|N]',
|
||||||
|
false)
|
||||||
|
) {
|
||||||
|
|
||||||
|
TrackFile::chunk(200, function ($trackFiles) {
|
||||||
|
|
||||||
|
$this->info('========== Start Chunk ==========');
|
||||||
|
|
||||||
|
foreach ($trackFiles as $trackFile) {
|
||||||
|
/** @var TrackFile $trackFile */
|
||||||
|
|
||||||
|
if (File::exists($trackFile->getFile())) {
|
||||||
|
$size = $trackFile->updateFilesize();
|
||||||
|
$this->info('ID ' . $trackFile->id . ' processed - ' . $size . ' bytes');
|
||||||
|
} else {
|
||||||
|
$this->info('ID ' . $trackFile->id . ' skipped');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('=========== End Chunk ===========');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->info('Rebuild complete. Exiting.');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->info('Rebuild cancelled. Exiting.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
258
app/Console/Commands/RebuildTrackCache.php
Normal file
258
app/Console/Commands/RebuildTrackCache.php
Normal file
|
@ -0,0 +1,258 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2015 Kelvin Zhang
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Poniverse\Ponyfm\Console\Commands;
|
||||||
|
|
||||||
|
use File;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
|
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
||||||
|
use Poniverse\Ponyfm\Track;
|
||||||
|
use Poniverse\Ponyfm\TrackFile;
|
||||||
|
|
||||||
|
class RebuildTrackCache extends Command
|
||||||
|
{
|
||||||
|
|
||||||
|
use DispatchesJobs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'rebuild:track-cache
|
||||||
|
{--force : Skip all prompts.}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Rebuilds the track cache for when $CacheableFormats is changed. Deletes cacheable files and encodes missing files which are not cacheable.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new command instance.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->info('***');
|
||||||
|
$this->info('If this is your first time running this command, it is *highly* recommended that you ensure the file sizes for all track files have been populated.');
|
||||||
|
$this->info('***');
|
||||||
|
|
||||||
|
if ($this->option('force') || $this->confirm('Are you sure you want to delete all to-be-cached track files and encode missing non-cached track files?',
|
||||||
|
false)
|
||||||
|
) {
|
||||||
|
|
||||||
|
//==========================================================================================================
|
||||||
|
// Delete previously cached track files
|
||||||
|
//==========================================================================================================
|
||||||
|
|
||||||
|
$this->output->newLine(1);
|
||||||
|
$this->info('========== Step 1/4 - Deleting previously cached track files. ==========');
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
// Chunk track files which are cacheable and NOT master
|
||||||
|
TrackFile::where('is_cacheable', true)
|
||||||
|
->where('is_master', false)
|
||||||
|
->chunk(200, function ($trackFiles) use (&$count) {
|
||||||
|
// Delete chunked track files
|
||||||
|
foreach ($trackFiles as $trackFile) {
|
||||||
|
// Clear expiration so will be re-cached on next request
|
||||||
|
$trackFile->expires_at = null;
|
||||||
|
$trackFile->update();
|
||||||
|
|
||||||
|
// Delete files
|
||||||
|
if (File::exists($trackFile->getFile())) {
|
||||||
|
$count++;
|
||||||
|
File::delete($trackFile->getFile());
|
||||||
|
$this->info('Deleted ' . $trackFile->getFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info($count . ' track files deleted. Deletion complete. Continuing.');
|
||||||
|
});
|
||||||
|
|
||||||
|
//==========================================================================================================
|
||||||
|
// Update the database entries for cacheable track files - non-cacheable to cacheable
|
||||||
|
//==========================================================================================================
|
||||||
|
|
||||||
|
$this->output->newLine(3);
|
||||||
|
$this->info('========== Step 2/4 - Updating is_cacheable entries in database. ==========');
|
||||||
|
|
||||||
|
$trackFileCount = 0;
|
||||||
|
$formats = [];
|
||||||
|
|
||||||
|
// Find track files which are meant to be cacheable and NOT master, but currently not cacheable
|
||||||
|
TrackFile::where('is_cacheable', false)
|
||||||
|
->whereIn('format', Track::$CacheableFormats)
|
||||||
|
->where('is_master', false)
|
||||||
|
->chunk(200, function ($trackFiles) use (&$trackFileCount, &$formats) {
|
||||||
|
$this->output->newLine(1);
|
||||||
|
$this->info('---------- Start Chunk ----------');
|
||||||
|
|
||||||
|
// Set above files to cacheable in the database
|
||||||
|
foreach ($trackFiles as $trackFile) {
|
||||||
|
$trackFileCount++;
|
||||||
|
|
||||||
|
// Let user know which formats, previously not cached, were made cacheable
|
||||||
|
$formats[] = $trackFile->format;
|
||||||
|
|
||||||
|
$trackFile->expires_at = null;
|
||||||
|
$trackFile->is_cacheable = true;
|
||||||
|
$trackFile->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('----------- End Chunk -----------');
|
||||||
|
$this->output->newLine(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->info('Format(s) set from non-cacheable to cacheable: ' . implode(' ', array_unique($formats)));
|
||||||
|
$this->info($trackFileCount . ' non-cacheable track files set to cacheable.');
|
||||||
|
|
||||||
|
$this->output->newLine(2);
|
||||||
|
|
||||||
|
//==========================================================================================================
|
||||||
|
// Update the database entries for cacheable track files - cacheable to non-cacheable
|
||||||
|
//==========================================================================================================
|
||||||
|
|
||||||
|
$trackFileCount = 0;
|
||||||
|
$formats = [];
|
||||||
|
|
||||||
|
// Chunk track files which are NOT meant to be cacheable, but currently cacheable
|
||||||
|
TrackFile::where('is_cacheable', true)
|
||||||
|
->whereNotIn('format', Track::$CacheableFormats)
|
||||||
|
->chunk(200, function ($trackFiles) use (&$trackFileCount, &$formats) {
|
||||||
|
$this->output->newLine(1);
|
||||||
|
$this->info('---------- Start Chunk ----------');
|
||||||
|
|
||||||
|
// Set chunked track files to non-cacheable in the database
|
||||||
|
foreach ($trackFiles as $trackFile) {
|
||||||
|
$trackFileCount++;
|
||||||
|
|
||||||
|
// Let user know which formats, previously not cached, were made cacheable
|
||||||
|
$formats[] = $trackFile->format;
|
||||||
|
|
||||||
|
$trackFile->expires_at = null;
|
||||||
|
$trackFile->is_cacheable = false;
|
||||||
|
$trackFile->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('----------- End Chunk -----------');
|
||||||
|
$this->output->newLine(1);
|
||||||
|
$this->output->newLine(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$this->info('Format(s) set from cacheable to non-cacheable: ' . implode(' ', array_unique($formats)));
|
||||||
|
$this->info($trackFileCount . ' cacheable track files set to non-cacheable.');
|
||||||
|
|
||||||
|
//==========================================================================================================
|
||||||
|
// Delete track files which have now been marked as cacheable
|
||||||
|
//==========================================================================================================
|
||||||
|
|
||||||
|
$this->output->newLine(3);
|
||||||
|
$this->info('========== Step 3/4 - Deleting now-cacheable track files. ==========');
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
$trackFileCount = 0;
|
||||||
|
|
||||||
|
// Find track files which are cacheable and NOT master
|
||||||
|
TrackFile::whereIn('format', Track::$CacheableFormats)
|
||||||
|
->where('is_master', false)
|
||||||
|
->chunk(200, function ($trackFiles) use (&$count, &$trackFileCount) {
|
||||||
|
$this->output->newLine(1);
|
||||||
|
$this->info('---------- Start Chunk ----------');
|
||||||
|
|
||||||
|
foreach ($trackFiles as $trackFile) {
|
||||||
|
$trackFileCount++;
|
||||||
|
|
||||||
|
// Delete track files if track files exist; double-check that they are NOT master files
|
||||||
|
if (File::exists($trackFile->getFile()) && $trackFile->is_master == false) {
|
||||||
|
$count++;
|
||||||
|
|
||||||
|
File::delete($trackFile->getFile());
|
||||||
|
$this->info('Deleted ' . $trackFile->getFile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('----------- End Chunk -----------');
|
||||||
|
$this->output->newLine(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$this->info(sprintf('%d track files deleted out of %d track files. Continuing.', $count, $trackFileCount));
|
||||||
|
|
||||||
|
//==========================================================================================================
|
||||||
|
// Encode missing (i.e., now non-cacheable) track files
|
||||||
|
//==========================================================================================================
|
||||||
|
|
||||||
|
$this->output->newLine(3);
|
||||||
|
$this->info('========== Step 4/4 - Encoding missing track files. ==========');
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
// Chunk non-cacheable track files
|
||||||
|
TrackFile::where('is_cacheable', false)->chunk(200, function ($trackFiles) use (&$count) {
|
||||||
|
$this->output->newLine(1);
|
||||||
|
$this->info('---------- Start Chunk ----------');
|
||||||
|
|
||||||
|
// Record the track files which do not exist (i.e., have not been encoded yet)
|
||||||
|
$emptyTrackFiles = [];
|
||||||
|
|
||||||
|
foreach ($trackFiles as $trackFile) {
|
||||||
|
if (!File::exists($trackFile->getFile())) {
|
||||||
|
$count++;
|
||||||
|
$emptyTrackFiles[] = $trackFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode recorded track files
|
||||||
|
foreach ($emptyTrackFiles as $emptyTrackFile) {
|
||||||
|
$this->info("Started encoding track file ID {$emptyTrackFile->id}");
|
||||||
|
$this->dispatch(new EncodeTrackFile($emptyTrackFile, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('----------- End Chunk -----------');
|
||||||
|
$this->output->newLine(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$this->info($count . ' track files encoded.');
|
||||||
|
$this->output->newLine(1);
|
||||||
|
|
||||||
|
$this->info('Rebuild complete. Exiting.');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->info('Rebuild cancelled. Exiting.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,6 +41,9 @@ class Kernel extends ConsoleKernel
|
||||||
\Poniverse\Ponyfm\Console\Commands\FixYearZeroLogs::class,
|
\Poniverse\Ponyfm\Console\Commands\FixYearZeroLogs::class,
|
||||||
\Poniverse\Ponyfm\Console\Commands\BootstrapLocalEnvironment::class,
|
\Poniverse\Ponyfm\Console\Commands\BootstrapLocalEnvironment::class,
|
||||||
\Poniverse\Ponyfm\Console\Commands\PoniverseApiSetup::class,
|
\Poniverse\Ponyfm\Console\Commands\PoniverseApiSetup::class,
|
||||||
|
\Poniverse\Ponyfm\Console\Commands\ClearTrackCache::class,
|
||||||
|
\Poniverse\Ponyfm\Console\Commands\RebuildTrackCache::class,
|
||||||
|
\Poniverse\Ponyfm\Console\Commands\RebuildFilesizes::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
14
app/Exceptions.php
Normal file
14
app/Exceptions.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php namespace Poniverse\Ponyfm;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class TrackFileNotFoundException
|
||||||
|
* @package Poniverse\Ponyfm
|
||||||
|
*
|
||||||
|
* This exception is used to indicate that the requested `TrackFile` object
|
||||||
|
* does not exist. This is useful when dealing with albums or playlists that
|
||||||
|
* contain tracks for which no lossless master is available (and thus, lossless
|
||||||
|
* `TrackFiles` don't exist for).
|
||||||
|
*/
|
||||||
|
class TrackFileNotFoundException extends ModelNotFoundException {}
|
|
@ -20,16 +20,20 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
use Poniverse\Ponyfm\Album;
|
use Poniverse\Ponyfm\Album;
|
||||||
use Poniverse\Ponyfm\Commands\CreateAlbumCommand;
|
use Poniverse\Ponyfm\Commands\CreateAlbumCommand;
|
||||||
use Poniverse\Ponyfm\Commands\DeleteAlbumCommand;
|
use Poniverse\Ponyfm\Commands\DeleteAlbumCommand;
|
||||||
use Poniverse\Ponyfm\Commands\EditAlbumCommand;
|
use Poniverse\Ponyfm\Commands\EditAlbumCommand;
|
||||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||||
use Poniverse\Ponyfm\Image;
|
use Poniverse\Ponyfm\Image;
|
||||||
|
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
||||||
use Poniverse\Ponyfm\ResourceLogItem;
|
use Poniverse\Ponyfm\ResourceLogItem;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Input;
|
use Illuminate\Support\Facades\Input;
|
||||||
use Illuminate\Support\Facades\Response;
|
use Illuminate\Support\Facades\Response;
|
||||||
|
use Poniverse\Ponyfm\Track;
|
||||||
|
|
||||||
class AlbumsController extends ApiControllerBase
|
class AlbumsController extends ApiControllerBase
|
||||||
{
|
{
|
||||||
|
@ -83,6 +87,34 @@ class AlbumsController extends ApiControllerBase
|
||||||
], 200);
|
], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCachedAlbum($id, $format)
|
||||||
|
{
|
||||||
|
// Validation
|
||||||
|
try {
|
||||||
|
/** @var Album $album */
|
||||||
|
$album = Album::with('tracks.trackFiles')->findOrFail($id);
|
||||||
|
|
||||||
|
} catch (ModelNotFoundException $e) {
|
||||||
|
return $this->notFound('Album not found!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($format, Track::$CacheableFormats)) {
|
||||||
|
return $this->notFound('Format not found!');
|
||||||
|
}
|
||||||
|
|
||||||
|
$trackCount = $album->countDownloadableTracks($format);
|
||||||
|
$availableFilesCount = $album->countAvailableTrackFiles($format);
|
||||||
|
|
||||||
|
if ($trackCount === $availableFilesCount) {
|
||||||
|
$url = $album->getDownloadUrl($format);
|
||||||
|
} else {
|
||||||
|
$album->encodeCacheableTrackFiles($format);
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response::json(['url' => $url], 200);
|
||||||
|
}
|
||||||
|
|
||||||
public function getIndex()
|
public function getIndex()
|
||||||
{
|
{
|
||||||
$page = 1;
|
$page = 1;
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
use Poniverse\Ponyfm\Commands\AddTrackToPlaylistCommand;
|
use Poniverse\Ponyfm\Commands\AddTrackToPlaylistCommand;
|
||||||
use Poniverse\Ponyfm\Commands\CreatePlaylistCommand;
|
use Poniverse\Ponyfm\Commands\CreatePlaylistCommand;
|
||||||
use Poniverse\Ponyfm\Commands\DeletePlaylistCommand;
|
use Poniverse\Ponyfm\Commands\DeletePlaylistCommand;
|
||||||
|
@ -31,6 +32,7 @@ use Poniverse\Ponyfm\ResourceLogItem;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Input;
|
use Illuminate\Support\Facades\Input;
|
||||||
use Illuminate\Support\Facades\Response;
|
use Illuminate\Support\Facades\Response;
|
||||||
|
use Poniverse\Ponyfm\Track;
|
||||||
|
|
||||||
class PlaylistsController extends ApiControllerBase
|
class PlaylistsController extends ApiControllerBase
|
||||||
{
|
{
|
||||||
|
@ -110,6 +112,37 @@ class PlaylistsController extends ApiControllerBase
|
||||||
return Response::json(Playlist::mapPublicPlaylistShow($playlist), 200);
|
return Response::json(Playlist::mapPublicPlaylistShow($playlist), 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCachedPlaylist($id, $format)
|
||||||
|
{
|
||||||
|
// Validation
|
||||||
|
try {
|
||||||
|
/** @var $playlist Playlist */
|
||||||
|
$playlist = Playlist::with('tracks.trackFiles')->findOrFail($id);
|
||||||
|
} catch (ModelNotFoundException $e) {
|
||||||
|
return $this->notFound('Playlist not found!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!$playlist->is_public && !Auth::check()) || (!$playlist->is_public && ($playlist->user_id !== Auth::user()->id))) {
|
||||||
|
return $this->notFound('Playlist not found!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($format, Track::$CacheableFormats)) {
|
||||||
|
return $this->notFound('Format not found!');
|
||||||
|
}
|
||||||
|
|
||||||
|
$trackCount = $playlist->countDownloadableTracks($format);
|
||||||
|
$availableFilesCount = $playlist->countAvailableTrackFiles($format);
|
||||||
|
|
||||||
|
if ($trackCount === $availableFilesCount) {
|
||||||
|
$url = $playlist->getDownloadUrl($format);
|
||||||
|
} else {
|
||||||
|
$playlist->encodeCacheableTrackFiles($format);
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response::json(['url' => $url], 200);
|
||||||
|
}
|
||||||
|
|
||||||
public function getPinned()
|
public function getPinned()
|
||||||
{
|
{
|
||||||
$query = Playlist
|
$query = Playlist
|
||||||
|
|
|
@ -20,10 +20,13 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
use Poniverse\Ponyfm\Commands\DeleteTrackCommand;
|
use Poniverse\Ponyfm\Commands\DeleteTrackCommand;
|
||||||
use Poniverse\Ponyfm\Commands\EditTrackCommand;
|
use Poniverse\Ponyfm\Commands\EditTrackCommand;
|
||||||
use Poniverse\Ponyfm\Commands\UploadTrackCommand;
|
use Poniverse\Ponyfm\Commands\UploadTrackCommand;
|
||||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||||
|
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
||||||
use Poniverse\Ponyfm\ResourceLogItem;
|
use Poniverse\Ponyfm\ResourceLogItem;
|
||||||
use Poniverse\Ponyfm\Track;
|
use Poniverse\Ponyfm\Track;
|
||||||
use Cover;
|
use Cover;
|
||||||
|
@ -70,6 +73,44 @@ class TracksController extends ApiControllerBase
|
||||||
return Response::json(['track' => $returned_track], 200);
|
return Response::json(['track' => $returned_track], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCachedTrack($id, $format)
|
||||||
|
{
|
||||||
|
// Validation
|
||||||
|
try {
|
||||||
|
$track = Track::findOrFail($id);
|
||||||
|
} catch (ModelNotFoundException $e) {
|
||||||
|
return $this->notFound('Track not found!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$track->canView(Auth::user()))
|
||||||
|
return $this->notFound('Track not found!');
|
||||||
|
|
||||||
|
if ($track->is_downloadable == false)
|
||||||
|
return $this->notFound('Track not found!');
|
||||||
|
|
||||||
|
if (!in_array($format, Track::$CacheableFormats)) {
|
||||||
|
return $this->notFound('Format not found!');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$trackFile = $track->trackFiles()->where('format', $format)->firstOrFail();
|
||||||
|
} catch (ModelNotFoundException $e) {
|
||||||
|
return $this->notFound('Track file not found!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return URL or begin encoding
|
||||||
|
if ($trackFile->expires_at != null && File::exists($trackFile->getFile())) {
|
||||||
|
$url = $track->getUrlFor($format);
|
||||||
|
} elseif ($trackFile->is_in_progress === true) {
|
||||||
|
$url = null;
|
||||||
|
} else {
|
||||||
|
$this->dispatch(new EncodeTrackFile($trackFile, true));
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response::json(['url' => $url], 200);
|
||||||
|
}
|
||||||
|
|
||||||
public function getIndex()
|
public function getIndex()
|
||||||
{
|
{
|
||||||
$page = 1;
|
$page = 1;
|
||||||
|
|
|
@ -79,12 +79,15 @@ Route::group(['prefix' => 'api/web'], function() {
|
||||||
|
|
||||||
Route::get('/tracks', 'Api\Web\TracksController@getIndex');
|
Route::get('/tracks', 'Api\Web\TracksController@getIndex');
|
||||||
Route::get('/tracks/{id}', 'Api\Web\TracksController@getShow')->where('id', '\d+');
|
Route::get('/tracks/{id}', 'Api\Web\TracksController@getShow')->where('id', '\d+');
|
||||||
|
Route::get('/tracks/cached/{id}/{format}', 'Api\Web\TracksController@getCachedTrack')->where(['id' => '\d+', 'format' => '.+']);
|
||||||
|
|
||||||
Route::get('/albums', 'Api\Web\AlbumsController@getIndex');
|
Route::get('/albums', 'Api\Web\AlbumsController@getIndex');
|
||||||
Route::get('/albums/{id}', 'Api\Web\AlbumsController@getShow')->where('id', '\d+');
|
Route::get('/albums/{id}', 'Api\Web\AlbumsController@getShow')->where('id', '\d+');
|
||||||
|
Route::get('/albums/cached/{id}/{format}', 'Api\Web\AlbumsController@getCachedAlbum')->where(['id' => '\d+', 'format' => '.+']);
|
||||||
|
|
||||||
Route::get('/playlists', 'Api\Web\PlaylistsController@getIndex');
|
Route::get('/playlists', 'Api\Web\PlaylistsController@getIndex');
|
||||||
Route::get('/playlists/{id}', 'Api\Web\PlaylistsController@getShow')->where('id', '\d+');
|
Route::get('/playlists/{id}', 'Api\Web\PlaylistsController@getShow')->where('id', '\d+');
|
||||||
|
Route::get('/playlists/cached/{id}/{format}', 'Api\Web\PlaylistsController@getCachedPlaylist')->where(['id' => '\d+', 'format' => '.+']);
|
||||||
|
|
||||||
Route::get('/comments/{type}/{id}', 'Api\Web\CommentsController@getIndex')->where('id', '\d+');
|
Route::get('/comments/{type}/{id}', 'Api\Web\CommentsController@getIndex')->where('id', '\d+');
|
||||||
|
|
||||||
|
|
129
app/Jobs/EncodeTrackFile.php
Normal file
129
app/Jobs/EncodeTrackFile.php
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2015 Kelvin Zhang
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Poniverse\Ponyfm\Jobs;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use OAuth2\Exception;
|
||||||
|
use Poniverse\Ponyfm\Jobs\Job;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Contracts\Bus\SelfHandling;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Poniverse\Ponyfm\Track;
|
||||||
|
use Poniverse\Ponyfm\TrackFile;
|
||||||
|
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
||||||
|
{
|
||||||
|
use InteractsWithQueue, SerializesModels;
|
||||||
|
/**
|
||||||
|
* @var TrackFile
|
||||||
|
*/
|
||||||
|
private $trackFile;
|
||||||
|
/**
|
||||||
|
* @var
|
||||||
|
*/
|
||||||
|
private $isExpirable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new job instance.
|
||||||
|
* @param TrackFile $trackFile
|
||||||
|
* @param $isExpirable
|
||||||
|
*/
|
||||||
|
public function __construct(TrackFile $trackFile, $isExpirable)
|
||||||
|
{
|
||||||
|
$this->trackFile = $trackFile;
|
||||||
|
$this->isExpirable = $isExpirable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the job.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
// Start the job
|
||||||
|
$this->trackFile->is_in_progress = true;
|
||||||
|
$this->trackFile->update();
|
||||||
|
|
||||||
|
// Use the track's master file as the source
|
||||||
|
$source = TrackFile::where('track_id', $this->trackFile->track_id)
|
||||||
|
->where('is_master', true)
|
||||||
|
->first()
|
||||||
|
->getFile();
|
||||||
|
|
||||||
|
// Assign the target
|
||||||
|
$this->trackFile->track->ensureDirectoryExists();
|
||||||
|
$target = $this->trackFile->getFile();
|
||||||
|
|
||||||
|
// Prepare the command
|
||||||
|
$format = Track::$Formats[$this->trackFile->format];
|
||||||
|
$command = $format['command'];
|
||||||
|
$command = str_replace('{$source}', '"' . $source . '"', $command);
|
||||||
|
$command = str_replace('{$target}', '"' . $target . '"', $command);
|
||||||
|
|
||||||
|
Log::info('Encoding track file ' . $this->trackFile->id . ' into ' . $target);
|
||||||
|
|
||||||
|
// Start a synchronous process to encode the file
|
||||||
|
$process = new Process($command);
|
||||||
|
try {
|
||||||
|
$process->mustRun();
|
||||||
|
} catch (ProcessFailedException $e) {
|
||||||
|
Log::error('An exception occured in the encoding process for track file ' . $this->trackFile->id . ' - ' . $e->getMessage());
|
||||||
|
// Ensure queue fails
|
||||||
|
throw $e;
|
||||||
|
} finally {
|
||||||
|
Log::info($process->getOutput());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the tags of the track
|
||||||
|
$this->trackFile->track->updateTags($this->trackFile->format);
|
||||||
|
|
||||||
|
// Insert the expiration time for cached tracks
|
||||||
|
if ($this->isExpirable) {
|
||||||
|
$this->trackFile->expires_at = Carbon::now()->addMinutes(Config::get('ponyfm.track_file_cache_duration'));
|
||||||
|
$this->trackFile->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update file size
|
||||||
|
$this->trackFile->updateFilesize();
|
||||||
|
|
||||||
|
// Complete the job
|
||||||
|
$this->trackFile->is_in_progress = false;
|
||||||
|
$this->trackFile->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a job failure.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function failed()
|
||||||
|
{
|
||||||
|
$this->trackFile->is_in_progress = false;
|
||||||
|
$this->trackFile->expires_at = null;
|
||||||
|
$this->trackFile->update();
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,16 +23,18 @@ namespace Poniverse\Ponyfm;
|
||||||
use Helpers;
|
use Helpers;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Auth;
|
||||||
use Illuminate\Support\Facades\URL;
|
use Cache;
|
||||||
|
use Poniverse\Ponyfm\Traits\TrackCollection;
|
||||||
|
use URL;
|
||||||
use Poniverse\Ponyfm\Traits\SlugTrait;
|
use Poniverse\Ponyfm\Traits\SlugTrait;
|
||||||
|
|
||||||
class Playlist extends Model
|
class Playlist extends Model
|
||||||
{
|
{
|
||||||
protected $table = 'playlists';
|
use SoftDeletes, SlugTrait, DispatchesJobs, TrackCollection;
|
||||||
|
|
||||||
use SoftDeletes, SlugTrait;
|
protected $table = 'playlists';
|
||||||
|
|
||||||
protected $dates = ['deleted_at'];
|
protected $dates = ['deleted_at'];
|
||||||
|
|
||||||
|
@ -55,10 +57,12 @@ class Playlist extends Model
|
||||||
return !$query;
|
return !$query;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function mapPublicPlaylistShow($playlist)
|
public static function mapPublicPlaylistShow(Playlist $playlist)
|
||||||
{
|
{
|
||||||
$tracks = [];
|
$tracks = [];
|
||||||
foreach ($playlist->tracks as $track) {
|
foreach ($playlist->tracks as $track) {
|
||||||
|
/** @var $track Track */
|
||||||
|
|
||||||
$tracks[] = Track::mapPublicTrackSummary($track);
|
$tracks[] = Track::mapPublicTrackSummary($track);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +72,8 @@ class Playlist extends Model
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'extension' => $format['extension'],
|
'extension' => $format['extension'],
|
||||||
'url' => $playlist->getDownloadUrl($name),
|
'url' => $playlist->getDownloadUrl($name),
|
||||||
'size' => Helpers::formatBytes($playlist->getFilesize($name))
|
'size' => Helpers::formatBytes($playlist->getFilesize($name)),
|
||||||
|
'isCacheable' => (in_array($name, Track::$CacheableFormats) ? true : false)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +95,7 @@ class Playlist extends Model
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function mapPublicPlaylistSummary($playlist)
|
public static function mapPublicPlaylistSummary(Playlist $playlist)
|
||||||
{
|
{
|
||||||
$userData = [
|
$userData = [
|
||||||
'stats' => [
|
'stats' => [
|
||||||
|
@ -104,25 +109,25 @@ class Playlist extends Model
|
||||||
$userRow = $playlist->users[0];
|
$userRow = $playlist->users[0];
|
||||||
$userData = [
|
$userData = [
|
||||||
'stats' => [
|
'stats' => [
|
||||||
'views' => (int) $userRow->view_count,
|
'views' => (int)$userRow->view_count,
|
||||||
'downloads' => (int) $userRow->download_count,
|
'downloads' => (int)$userRow->download_count,
|
||||||
],
|
],
|
||||||
'is_favourited' => (bool) $userRow->is_favourited
|
'is_favourited' => (bool)$userRow->is_favourited
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => (int) $playlist->id,
|
'id' => (int)$playlist->id,
|
||||||
'track_count' => $playlist->track_count,
|
'track_count' => $playlist->track_count,
|
||||||
'title' => $playlist->title,
|
'title' => $playlist->title,
|
||||||
'slug' => $playlist->slug,
|
'slug' => $playlist->slug,
|
||||||
'created_at' => $playlist->created_at,
|
'created_at' => $playlist->created_at,
|
||||||
'is_public' => (bool) $playlist->is_public,
|
'is_public' => (bool)$playlist->is_public,
|
||||||
'stats' => [
|
'stats' => [
|
||||||
'views' => (int) $playlist->view_count,
|
'views' => (int)$playlist->view_count,
|
||||||
'downloads' => (int) $playlist->download_count,
|
'downloads' => (int)$playlist->download_count,
|
||||||
'comments' => (int) $playlist->comment_count,
|
'comments' => (int)$playlist->comment_count,
|
||||||
'favourites' => (int) $playlist->favourite_count
|
'favourites' => (int)$playlist->favourite_count
|
||||||
],
|
],
|
||||||
'covers' => [
|
'covers' => [
|
||||||
'small' => $playlist->getCoverUrl(Image::SMALL),
|
'small' => $playlist->getCoverUrl(Image::SMALL),
|
||||||
|
@ -130,7 +135,7 @@ class Playlist extends Model
|
||||||
],
|
],
|
||||||
'url' => $playlist->url,
|
'url' => $playlist->url,
|
||||||
'user' => [
|
'user' => [
|
||||||
'id' => (int) $playlist->user->id,
|
'id' => (int)$playlist->user->id,
|
||||||
'name' => $playlist->user->display_name,
|
'name' => $playlist->user->display_name,
|
||||||
'url' => $playlist->user->url,
|
'url' => $playlist->user->url,
|
||||||
],
|
],
|
||||||
|
@ -151,6 +156,12 @@ class Playlist extends Model
|
||||||
->orderBy('position', 'asc');
|
->orderBy('position', 'asc');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function trackFiles()
|
||||||
|
{
|
||||||
|
$trackIds = $this->tracks->lists('id');
|
||||||
|
return TrackFile::whereIn('track_id', $trackIds);
|
||||||
|
}
|
||||||
|
|
||||||
public function users()
|
public function users()
|
||||||
{
|
{
|
||||||
return $this->hasMany('Poniverse\Ponyfm\ResourceUser');
|
return $this->hasMany('Poniverse\Ponyfm\ResourceUser');
|
||||||
|
@ -207,7 +218,17 @@ class Playlist extends Model
|
||||||
return Cache::remember($this->getCacheKey('filesize-' . $format), 1440, function () use ($tracks, $format) {
|
return Cache::remember($this->getCacheKey('filesize-' . $format), 1440, function () use ($tracks, $format) {
|
||||||
$size = 0;
|
$size = 0;
|
||||||
foreach ($tracks as $track) {
|
foreach ($tracks as $track) {
|
||||||
$size += $track->getFilesize($format);
|
/** @var $track Track */
|
||||||
|
|
||||||
|
// Ensure that only downloadable tracks are added onto the file size
|
||||||
|
if ($track->is_downloadable == 1) {
|
||||||
|
try {
|
||||||
|
$size += $track->getFilesize($format);
|
||||||
|
|
||||||
|
} catch (TrackFileNotFoundException $e) {
|
||||||
|
// do nothing - this track won't be included in the download
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $size;
|
return $size;
|
||||||
|
|
118
app/Track.php
118
app/Track.php
|
@ -20,7 +20,11 @@
|
||||||
|
|
||||||
namespace Poniverse\Ponyfm;
|
namespace Poniverse\Ponyfm;
|
||||||
|
|
||||||
use Illuminate\Database\Query\Builder;
|
use Auth;
|
||||||
|
use Cache;
|
||||||
|
use Config;
|
||||||
|
use DB;
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
use Poniverse\Ponyfm\Traits\SlugTrait;
|
use Poniverse\Ponyfm\Traits\SlugTrait;
|
||||||
use Exception;
|
use Exception;
|
||||||
use External;
|
use External;
|
||||||
|
@ -28,13 +32,9 @@ use getid3_writetags;
|
||||||
use Helpers;
|
use Helpers;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Auth;
|
use Illuminate\Support\Str;
|
||||||
use Cache;
|
|
||||||
use Config;
|
|
||||||
use DB;
|
|
||||||
use Log;
|
use Log;
|
||||||
use URL;
|
use URL;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class Track extends Model
|
class Track extends Model
|
||||||
{
|
{
|
||||||
|
@ -94,9 +94,27 @@ class Track extends Model
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `TrackFiles` in these formats, with the exception of any master files, will
|
||||||
|
* be generated upon user request and kept around temporarily.
|
||||||
|
*
|
||||||
|
* After updating this array, run `php artisan rebuild:track-cache` to bring
|
||||||
|
* the track store into a consistent state.
|
||||||
|
*
|
||||||
|
* The strings in this array must match keys in the `Track::$Formats` array.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public static $CacheableFormats = [
|
||||||
|
'OGG Vorbis',
|
||||||
|
'ALAC',
|
||||||
|
'AAC'
|
||||||
|
];
|
||||||
|
|
||||||
public static function summary()
|
public static function summary()
|
||||||
{
|
{
|
||||||
return self::select('tracks.id', 'title', 'user_id', 'slug', 'is_vocal', 'is_explicit', 'created_at', 'published_at',
|
return self::select('tracks.id', 'title', 'user_id', 'slug', 'is_vocal', 'is_explicit', 'created_at',
|
||||||
|
'published_at',
|
||||||
'duration', 'is_downloadable', 'genre_id', 'track_type_id', 'cover_id', 'album_id', 'comment_count',
|
'duration', 'is_downloadable', 'genre_id', 'track_type_id', 'cover_id', 'album_id', 'comment_count',
|
||||||
'download_count', 'view_count', 'play_count', 'favourite_count');
|
'download_count', 'view_count', 'play_count', 'favourite_count');
|
||||||
}
|
}
|
||||||
|
@ -197,7 +215,7 @@ class Track extends Model
|
||||||
return $processed;
|
return $processed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function mapPublicTrackShow($track)
|
public static function mapPublicTrackShow(Track $track)
|
||||||
{
|
{
|
||||||
$returnValue = self::mapPublicTrackSummary($track);
|
$returnValue = self::mapPublicTrackSummary($track);
|
||||||
$returnValue['description'] = $track->description;
|
$returnValue['description'] = $track->description;
|
||||||
|
@ -225,7 +243,8 @@ class Track extends Model
|
||||||
'name' => $trackFile->format,
|
'name' => $trackFile->format,
|
||||||
'extension' => $trackFile->extension,
|
'extension' => $trackFile->extension,
|
||||||
'url' => $trackFile->url,
|
'url' => $trackFile->url,
|
||||||
'size' => $trackFile->size
|
'size' => $trackFile->size,
|
||||||
|
'isCacheable' => (bool) $trackFile->is_cacheable
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,7 +262,7 @@ class Track extends Model
|
||||||
return $returnValue;
|
return $returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function mapPublicTrackSummary($track)
|
public static function mapPublicTrackSummary(Track $track)
|
||||||
{
|
{
|
||||||
$userData = [
|
$userData = [
|
||||||
'stats' => [
|
'stats' => [
|
||||||
|
@ -258,41 +277,41 @@ class Track extends Model
|
||||||
$userRow = $track->users[0];
|
$userRow = $track->users[0];
|
||||||
$userData = [
|
$userData = [
|
||||||
'stats' => [
|
'stats' => [
|
||||||
'views' => (int) $userRow->view_count,
|
'views' => (int)$userRow->view_count,
|
||||||
'plays' => (int) $userRow->play_count,
|
'plays' => (int)$userRow->play_count,
|
||||||
'downloads' => $userRow->download_count,
|
'downloads' => $userRow->download_count,
|
||||||
],
|
],
|
||||||
'is_favourited' => (bool) $userRow->is_favourited
|
'is_favourited' => (bool)$userRow->is_favourited
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => (int) $track->id,
|
'id' => (int)$track->id,
|
||||||
'title' => $track->title,
|
'title' => $track->title,
|
||||||
'user' => [
|
'user' => [
|
||||||
'id' => (int) $track->user->id,
|
'id' => (int)$track->user->id,
|
||||||
'name' => $track->user->display_name,
|
'name' => $track->user->display_name,
|
||||||
'url' => $track->user->url
|
'url' => $track->user->url
|
||||||
],
|
],
|
||||||
'stats' => [
|
'stats' => [
|
||||||
'views' => (int) $track->view_count,
|
'views' => (int)$track->view_count,
|
||||||
'plays' => (int) $track->play_count,
|
'plays' => (int)$track->play_count,
|
||||||
'downloads' => (int) $track->download_count,
|
'downloads' => (int)$track->download_count,
|
||||||
'comments' => (int) $track->comment_count,
|
'comments' => (int)$track->comment_count,
|
||||||
'favourites' => (int) $track->favourite_count
|
'favourites' => (int)$track->favourite_count
|
||||||
],
|
],
|
||||||
'url' => $track->url,
|
'url' => $track->url,
|
||||||
'slug' => $track->slug,
|
'slug' => $track->slug,
|
||||||
'is_vocal' => (bool) $track->is_vocal,
|
'is_vocal' => (bool)$track->is_vocal,
|
||||||
'is_explicit' => (bool) $track->is_explicit,
|
'is_explicit' => (bool)$track->is_explicit,
|
||||||
'is_downloadable' => (bool) $track->is_downloadable,
|
'is_downloadable' => (bool)$track->is_downloadable,
|
||||||
'is_published' => (bool) $track->isPublished(),
|
'is_published' => (bool)$track->isPublished(),
|
||||||
'published_at' => $track->published_at,
|
'published_at' => $track->published_at,
|
||||||
'duration' => $track->duration,
|
'duration' => $track->duration,
|
||||||
'genre' => $track->genre != null
|
'genre' => $track->genre != null
|
||||||
?
|
?
|
||||||
[
|
[
|
||||||
'id' => (int) $track->genre->id,
|
'id' => (int)$track->genre->id,
|
||||||
'slug' => $track->genre->slug,
|
'slug' => $track->genre->slug,
|
||||||
'name' => $track->genre->name
|
'name' => $track->genre->name
|
||||||
] : null,
|
] : null,
|
||||||
|
@ -315,7 +334,7 @@ class Track extends Model
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function mapPrivateTrackShow($track)
|
public static function mapPrivateTrackShow(Track $track)
|
||||||
{
|
{
|
||||||
$showSongs = [];
|
$showSongs = [];
|
||||||
foreach ($track->showSongs as $showSong) {
|
foreach ($track->showSongs as $showSong) {
|
||||||
|
@ -336,7 +355,7 @@ class Track extends Model
|
||||||
return $returnValue;
|
return $returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function mapPrivateTrackSummary($track)
|
public static function mapPrivateTrackSummary(Track $track)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'id' => $track->id,
|
'id' => $track->id,
|
||||||
|
@ -420,18 +439,22 @@ class Track extends Model
|
||||||
$this->updateHash();
|
$this->updateHash();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size of this track's file in the given format.
|
||||||
|
*
|
||||||
|
* @param $formatName
|
||||||
|
* @return int filesize in bytes
|
||||||
|
* @throws TrackFileNotFoundException
|
||||||
|
*/
|
||||||
public function getFilesize($formatName)
|
public function getFilesize($formatName)
|
||||||
{
|
{
|
||||||
return Cache::remember($this->getCacheKey('filesize-' . $formatName), 1440, function () use ($formatName) {
|
$trackFile = $this->trackFiles()->where('format', $formatName)->first();
|
||||||
$file = $this->getFileFor($formatName);
|
|
||||||
$size = 0;
|
|
||||||
|
|
||||||
if (is_file($file)) {
|
if ($trackFile) {
|
||||||
$size = filesize($file);
|
return (int) $trackFile->filesize;
|
||||||
}
|
} else {
|
||||||
|
throw new TrackFileNotFoundException();
|
||||||
return $size;
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function canView($user)
|
public function canView($user)
|
||||||
|
@ -569,11 +592,22 @@ class Track extends Model
|
||||||
$this->hash = md5(Helpers::sanitizeInputForHashing($this->user->display_name) . ' - ' . Helpers::sanitizeInputForHashing($this->title));
|
$this->hash = md5(Helpers::sanitizeInputForHashing($this->user->display_name) . ' - ' . Helpers::sanitizeInputForHashing($this->title));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateTags()
|
public function updateTags($trackFileFormat = 'all')
|
||||||
{
|
{
|
||||||
$this->trackFiles()->touch();
|
if ($trackFileFormat === 'all') {
|
||||||
|
foreach ($this->trackFiles as $trackFile) {
|
||||||
|
$this->updateTagsForTrackFile($trackFile);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$trackFile = $this->trackFiles()->where('format', $trackFileFormat)->firstOrFail();
|
||||||
|
$this->updateTagsForTrackFile($trackFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($this->trackFiles as $trackFile) {
|
private function updateTagsForTrackFile($trackFile) {
|
||||||
|
$trackFile->touch();
|
||||||
|
|
||||||
|
if (\File::exists($trackFile->getFile())) {
|
||||||
$format = $trackFile->format;
|
$format = $trackFile->format;
|
||||||
$data = self::$Formats[$format];
|
$data = self::$Formats[$format];
|
||||||
|
|
||||||
|
@ -652,10 +686,12 @@ class Track extends Model
|
||||||
|
|
||||||
if ($tagWriter->WriteTags()) {
|
if ($tagWriter->WriteTags()) {
|
||||||
if (!empty($tagWriter->warnings)) {
|
if (!empty($tagWriter->warnings)) {
|
||||||
Log::warning('Track #'.$this->id.': There were some warnings:<br />' . implode('<br /><br />', $tagWriter->warnings));
|
Log::warning('Track #' . $this->id . ': There were some warnings:<br />' . implode('<br /><br />',
|
||||||
|
$tagWriter->warnings));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log::error('Track #' . $this->id . ': Failed to write tags!<br />' . implode('<br /><br />', $tagWriter->errors));
|
Log::error('Track #' . $this->id . ': Failed to write tags!<br />' . implode('<br /><br />',
|
||||||
|
$tagWriter->errors));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,15 +22,15 @@ namespace Poniverse\Ponyfm;
|
||||||
|
|
||||||
use Helpers;
|
use Helpers;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Facades\App;
|
use App;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use File;
|
||||||
use Illuminate\Support\Facades\URL;
|
use URL;
|
||||||
|
|
||||||
class TrackFile extends Model
|
class TrackFile extends Model
|
||||||
{
|
{
|
||||||
public function track()
|
public function track()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('Poniverse\Ponyfm\Track');
|
return $this->belongsTo('Poniverse\Ponyfm\Track')->withTrashed();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,9 +84,9 @@ class TrackFile extends Model
|
||||||
return URL::to('/t' . $this->track_id . '/dl.' . $this->extension);
|
return URL::to('/t' . $this->track_id . '/dl.' . $this->extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSizeAttribute($value)
|
public function getSizeAttribute()
|
||||||
{
|
{
|
||||||
return Helpers::formatBytes($this->getFilesize($this->getFile()));
|
return Helpers::formatBytes($this->getFilesize());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFormat()
|
public function getFormat()
|
||||||
|
@ -96,16 +96,7 @@ class TrackFile extends Model
|
||||||
|
|
||||||
protected function getFilesize()
|
protected function getFilesize()
|
||||||
{
|
{
|
||||||
return Cache::remember($this->getCacheKey('filesize'), 1440, function () {
|
return $this->filesize;
|
||||||
$file = $this->getFile();
|
|
||||||
$size = 0;
|
|
||||||
|
|
||||||
if (is_file($file)) {
|
|
||||||
$size = filesize($file);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $size;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDirectory()
|
public function getDirectory()
|
||||||
|
@ -134,4 +125,23 @@ class TrackFile extends Model
|
||||||
{
|
{
|
||||||
return 'track_file-' . $this->id . '-' . $key;
|
return 'track_file-' . $this->id . '-' . $key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this file exists, update its estimated filesize in the database.
|
||||||
|
*
|
||||||
|
* @return int $size
|
||||||
|
*/
|
||||||
|
public function updateFilesize()
|
||||||
|
{
|
||||||
|
$file = $this->getFile();
|
||||||
|
|
||||||
|
if (File::exists($file)) {
|
||||||
|
$size = File::size($file);
|
||||||
|
|
||||||
|
$this->filesize = $size;
|
||||||
|
$this->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->filesize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
127
app/Traits/TrackCollection.php
Normal file
127
app/Traits/TrackCollection.php
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2015 Peter Deltchev
|
||||||
|
* Copyright (C) 2015 Kelvin Zhang
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Poniverse\Ponyfm\Traits;
|
||||||
|
|
||||||
|
|
||||||
|
use File;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
|
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
||||||
|
use Poniverse\Ponyfm\TrackFile;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class TrackCollection
|
||||||
|
* @package Poniverse\Ponyfm\Traits
|
||||||
|
*
|
||||||
|
* Contains common logic between albums and playlists. They share some functionality
|
||||||
|
* because they're both a form of downloadable track collection.
|
||||||
|
*/
|
||||||
|
trait TrackCollection
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This relation represents all tracks contained by the collection.
|
||||||
|
*
|
||||||
|
* @return Relation
|
||||||
|
*/
|
||||||
|
abstract public function tracks();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This relation represents all track files belonging to this collection's
|
||||||
|
* tracks.
|
||||||
|
*
|
||||||
|
* @return Relation
|
||||||
|
*/
|
||||||
|
abstract public function trackFiles();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of tracks that are available in the given format.
|
||||||
|
*
|
||||||
|
* @param string $format
|
||||||
|
* @return int the number of downloadable tracks in this collection
|
||||||
|
*/
|
||||||
|
public function countDownloadableTracks($format) {
|
||||||
|
return $this->downloadableTrackFiles($format)->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of currently-available track files (master files +
|
||||||
|
* currently cached files) for this collection in the given format.
|
||||||
|
*
|
||||||
|
* @param string $format
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function countAvailableTrackFiles($format) {
|
||||||
|
$trackFiles = $this->downloadableTrackFiles($format);
|
||||||
|
$availableCount = 0;
|
||||||
|
|
||||||
|
foreach ($trackFiles as $trackFile) {
|
||||||
|
/** @var TrackFile $trackFile */
|
||||||
|
|
||||||
|
if (
|
||||||
|
$trackFile->is_master ||
|
||||||
|
($trackFile->expires_at != null && File::exists($trackFile->getFile()))
|
||||||
|
) {
|
||||||
|
$availableCount ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $availableCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kicks off the encoding of any cacheable files in this collection that
|
||||||
|
* do not currently exist.
|
||||||
|
*
|
||||||
|
* @param $format
|
||||||
|
*/
|
||||||
|
public function encodeCacheableTrackFiles($format) {
|
||||||
|
$trackFiles = $this->downloadableTrackFiles($format);
|
||||||
|
|
||||||
|
foreach ($trackFiles as $trackFile) {
|
||||||
|
/** @var TrackFile $trackFile */
|
||||||
|
|
||||||
|
if (!File::exists($trackFile->getFile()) && $trackFile->is_in_progress != true) {
|
||||||
|
$this->dispatch(new EncodeTrackFile($trackFile, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an Eloquent collection of downloadable TrackFiles for this {@link TrackCollection}.
|
||||||
|
* A {@link TrackFile} is considered downloadable if its associated {@link Track} is.
|
||||||
|
*
|
||||||
|
* @param $format
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
protected function downloadableTrackFiles($format) {
|
||||||
|
return $this->trackFiles()->with([
|
||||||
|
'track' => function ($query) {
|
||||||
|
$query->where('is_downloadable', true);
|
||||||
|
}
|
||||||
|
])->where('format', $format)->get();
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,4 +54,15 @@ return [
|
||||||
|
|
||||||
'use_powered_by_footer' => env('USE_POWERED_BY_FOOTER', true),
|
'use_powered_by_footer' => env('USE_POWERED_BY_FOOTER', true),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Cache Duration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Duration in minutes for track files to be stored in cache.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'track_file_cache_duration' => 1440,
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2015 Kelvin Zhang
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class UpdateTrackFilesWithCache extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('track_files', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_cacheable')->default(false)->index();
|
||||||
|
$table->boolean('is_in_progress')->default(false);
|
||||||
|
$table->dateTime('expires_at')->nullable()->index();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('track_files', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_cacheable');
|
||||||
|
$table->dropColumn('expires_at');
|
||||||
|
$table->dropColumn('is_in_progress');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pony.fm - A community for pony fan music.
|
||||||
|
* Copyright (C) 2015 Kelvin Zhang
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
|
||||||
|
class UpdateTrackFilesWithFilesize extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('track_files', function (Blueprint $table) {
|
||||||
|
$table->integer('filesize')->nullable()->unsigned();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Populate file sizes
|
||||||
|
Artisan::call('rebuild:filesizes', [
|
||||||
|
'--force' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('track_files', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('filesize');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
BIN
public/images/loading.gif
Normal file
BIN
public/images/loading.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
|
@ -1,11 +1,22 @@
|
||||||
<div class="resource-details album-details" bindonce="album">
|
<div class="resource-details album-details" bindonce="album">
|
||||||
<ul class="dropdowns">
|
<ul class="dropdowns">
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a href="#" class="btn btn-small btn-info dropdown-toggle" ng-disabled="album.is_downloadable == 0">
|
<a href="#" class="btn btn-small btn-info dropdown-toggle" ng-disabled="album.is_downloadable == 0" auto-close="outsideClick">
|
||||||
Downloads
|
Downloads
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu" ng-show="album.is_downloadable == 1">
|
<ul class="dropdown-menu" ng-show="album.is_downloadable == 1">
|
||||||
<li bindonce ng-repeat="format in album.formats"><a target="_blank" bo-href="format.url"><span bo-text="format.name"></span> <small bo-text="'(' + format.size + ')'"></small></a></li>
|
<li bindonce ng-repeat="format in album.formats" ng-hide="isInProgress">
|
||||||
|
<a target="_blank" ng-if="!format.isCacheable" bo-href="format.url">
|
||||||
|
<span bo-text="format.name"></span>
|
||||||
|
<small bo-text="'(' + format.size + ')'"></small>
|
||||||
|
</a>
|
||||||
|
<a ng-if="format.isCacheable" ng-click="getCachedAlbum(album.id, format.name);" href="">
|
||||||
|
<span bo-text="format.name"></span>
|
||||||
|
<small bo-text="'(' + format.size + ')'"></small>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li ng-show="isInProgress" class="cache-loading"><img src="/images/loading.gif" /></li>
|
||||||
|
<li ng-show="isInProgress" class="cache-loading"><small>We're getting your download ready! This may take up to a few minutes.</small></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#" class="btn" pfm-eat-click ng-click="share()">Share</a></li>
|
<li><a href="#" class="btn" pfm-eat-click ng-click="share()">Share</a></li>
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
<div class="resource-details playlist-details" bindonce="playlist">
|
<div class="resource-details playlist-details" bindonce="playlist">
|
||||||
<ul class="dropdowns">
|
<ul class="dropdowns">
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a href="#" class="btn btn-small btn-info dropdown-toggle">
|
<a href="#" class="btn btn-small btn-info dropdown-toggle" auto-close="outsideClick">
|
||||||
Downloads
|
Downloads
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li bindonce ng-repeat="format in playlist.formats"><a target="_blank" bo-href="format.url"><span bo-text="format.name"></span> <small bo-text="'(' + format.size + ')'"></small></a></li>
|
<li bindonce ng-repeat="format in playlist.formats" ng-hide="isInProgress">
|
||||||
|
<a target="_blank" ng-if="!format.isCacheable" bo-href="format.url">
|
||||||
|
<span bo-text="format.name"></span>
|
||||||
|
<small bo-text="'(' + format.size + ')'"></small>
|
||||||
|
</a>
|
||||||
|
<a ng-if="format.isCacheable" ng-click="getCachedPlaylist(playlist.id, format.name);" href="">
|
||||||
|
<span bo-text="format.name"></span>
|
||||||
|
<small bo-text="'(' + format.size + ')'"></small>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li ng-show="isInProgress" class="cache-loading"><img src="/images/loading.gif" /></li>
|
||||||
|
<li ng-show="isInProgress" class="cache-loading"><small>We're getting your download ready! This may take up to a few minutes.</small></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#" class="btn" pfm-eat-click ng-click="share()">Share</a></li>
|
<li><a href="#" class="btn" pfm-eat-click ng-click="share()">Share</a></li>
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
<div class="resource-details track-details" bindonce="track">
|
<div class="resource-details track-details" bindonce="track">
|
||||||
<ul class="dropdowns">
|
<ul class="dropdowns">
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a href="#" class="btn btn-small btn-info dropdown-toggle" ng-disabled="track.is_downloadable == 0">
|
<a href="#" class="btn btn-small btn-info dropdown-toggle" ng-disabled="track.is_downloadable == 0" auto-close="outsideClick">
|
||||||
Downloads
|
Downloads
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu" ng-show="track.is_downloadable == 1">
|
<ul class="dropdown-menu" ng-show="track.is_downloadable == 1">
|
||||||
<li bindonce ng-repeat="format in track.formats"><a target="_blank" bo-href="format.url"><span bo-text="format.name"></span> <small bo-text="'(' + format.size + ')'"></small></a></li>
|
<li bindonce ng-repeat="format in track.formats" ng-hide="isInProgress">
|
||||||
|
<a target="_blank" ng-if="!format.isCacheable" bo-href="format.url">
|
||||||
|
<span bo-text="format.name"></span>
|
||||||
|
<small bo-text="'(' + format.size + ')'"></small>
|
||||||
|
</a>
|
||||||
|
<a ng-if="format.isCacheable" ng-click="getCachedTrack(track.id, format.name);" href="">
|
||||||
|
<span bo-text="format.name"></span>
|
||||||
|
<small bo-text="'(' + format.size + ')'"></small>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li ng-show="isInProgress" class="cache-loading"><img src="/images/loading.gif" /></li>
|
||||||
|
<li ng-show="isInProgress" class="cache-loading"><small>We're getting your download ready! This'll take a few seconds.</small></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
|
|
|
@ -21,8 +21,8 @@ window.pfm.preloaders['album'] = [
|
||||||
]
|
]
|
||||||
|
|
||||||
angular.module('ponyfm').controller "album", [
|
angular.module('ponyfm').controller "album", [
|
||||||
'$scope', 'albums', '$state', 'playlists', 'auth', '$dialog'
|
'$scope', 'albums', '$state', 'playlists', 'auth', '$dialog', 'download-cached', '$window', '$timeout'
|
||||||
($scope, albums, $state, playlists, auth, $dialog) ->
|
($scope, albums, $state, playlists, auth, $dialog, cachedAlbum, $window, $timeout) ->
|
||||||
album = null
|
album = null
|
||||||
|
|
||||||
albums.fetch($state.params.id).done (albumResponse) ->
|
albums.fetch($state.params.id).done (albumResponse) ->
|
||||||
|
@ -40,4 +40,21 @@ angular.module('ponyfm').controller "album", [
|
||||||
if auth.data.isLogged
|
if auth.data.isLogged
|
||||||
playlists.refreshOwned().done (lists) ->
|
playlists.refreshOwned().done (lists) ->
|
||||||
$scope.playlists.push list for list in lists
|
$scope.playlists.push list for list in lists
|
||||||
|
|
||||||
|
$scope.getCachedAlbum = (id, format) ->
|
||||||
|
$scope.isInProgress = true
|
||||||
|
|
||||||
|
cachedAlbum.download('albums', id, format).then (response) ->
|
||||||
|
$scope.albumUrl = response
|
||||||
|
if $scope.albumUrl == 'error'
|
||||||
|
$scope.isInProgress = false
|
||||||
|
else if $scope.albumUrl == 'pending'
|
||||||
|
# $timeout takes a callback function
|
||||||
|
# https://stackoverflow.com/a/23391203/3225811
|
||||||
|
$timeout(
|
||||||
|
$scope.getCachedAlbum(id, format)
|
||||||
|
, 5000)
|
||||||
|
else
|
||||||
|
$scope.isInProgress = false
|
||||||
|
$window.open $scope.albumUrl
|
||||||
]
|
]
|
||||||
|
|
|
@ -21,8 +21,8 @@ window.pfm.preloaders['playlist'] = [
|
||||||
]
|
]
|
||||||
|
|
||||||
angular.module('ponyfm').controller 'playlist', [
|
angular.module('ponyfm').controller 'playlist', [
|
||||||
'$scope', '$state', 'playlists', '$dialog'
|
'$scope', '$state', 'playlists', '$dialog', 'download-cached', '$window', '$timeout'
|
||||||
($scope, $state, playlists, $dialog) ->
|
($scope, $state, playlists, $dialog, cachedPlaylist, $window, $timeout) ->
|
||||||
playlist = null
|
playlist = null
|
||||||
|
|
||||||
playlists.fetch($state.params.id).done (playlistResponse) ->
|
playlists.fetch($state.params.id).done (playlistResponse) ->
|
||||||
|
@ -34,4 +34,22 @@ angular.module('ponyfm').controller 'playlist', [
|
||||||
templateUrl: '/templates/partials/playlist-share-dialog.html',
|
templateUrl: '/templates/partials/playlist-share-dialog.html',
|
||||||
controller: ['$scope', ($scope) -> $scope.playlist = playlist; $scope.close = () -> dialog.close()]
|
controller: ['$scope', ($scope) -> $scope.playlist = playlist; $scope.close = () -> dialog.close()]
|
||||||
dialog.open()
|
dialog.open()
|
||||||
|
|
||||||
|
$scope.getCachedPlaylist = (id, format) ->
|
||||||
|
$scope.isInProgress = true
|
||||||
|
|
||||||
|
cachedPlaylist.download('playlists', id, format).then (response) ->
|
||||||
|
$scope.playlistUrl = response
|
||||||
|
if $scope.playlistUrl == 'error'
|
||||||
|
$scope.isInProgress = false
|
||||||
|
else if $scope.playlistUrl == 'pending'
|
||||||
|
# $timeout takes a callback function
|
||||||
|
# https://stackoverflow.com/a/23391203/3225811
|
||||||
|
$timeout(
|
||||||
|
() ->$scope.getCachedPlaylist(id, format)
|
||||||
|
, 5000)
|
||||||
|
else
|
||||||
|
$scope.isInProgress = false
|
||||||
|
$window.open $scope.playlistUrl
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,8 @@ window.pfm.preloaders['track'] = [
|
||||||
]
|
]
|
||||||
|
|
||||||
angular.module('ponyfm').controller "track", [
|
angular.module('ponyfm').controller "track", [
|
||||||
'$scope', 'tracks', '$state', 'playlists', 'auth', 'favourites', '$dialog'
|
'$scope', 'tracks', '$state', 'playlists', 'auth', 'favourites', '$dialog', 'download-cached', '$window', '$timeout'
|
||||||
($scope, tracks, $state, playlists, auth, favourites, $dialog) ->
|
($scope, tracks, $state, playlists, auth, favourites, $dialog, cachedTrack, $window, $timeout) ->
|
||||||
track = null
|
track = null
|
||||||
|
|
||||||
tracks.fetch($state.params.id).done (trackResponse) ->
|
tracks.fetch($state.params.id).done (trackResponse) ->
|
||||||
|
@ -72,4 +72,21 @@ angular.module('ponyfm').controller "track", [
|
||||||
|
|
||||||
playlists.addTrackToPlaylist(playlist.id, $scope.track.id).done (res) ->
|
playlists.addTrackToPlaylist(playlist.id, $scope.track.id).done (res) ->
|
||||||
playlist.message = res.message
|
playlist.message = res.message
|
||||||
|
|
||||||
|
$scope.getCachedTrack = (id, format) ->
|
||||||
|
$scope.isInProgress = true
|
||||||
|
|
||||||
|
cachedTrack.download('tracks', id, format).then (response) ->
|
||||||
|
$scope.trackUrl = response
|
||||||
|
if $scope.trackUrl == 'error'
|
||||||
|
$scope.isInProgress = false
|
||||||
|
else if $scope.trackUrl == 'pending'
|
||||||
|
# $timeout takes a callback function
|
||||||
|
# https://stackoverflow.com/a/23391203/3225811
|
||||||
|
$timeout(
|
||||||
|
()-> $scope.getCachedTrack(id, format)
|
||||||
|
, 5000)
|
||||||
|
else
|
||||||
|
$scope.isInProgress = false
|
||||||
|
$window.location = $scope.trackUrl
|
||||||
]
|
]
|
||||||
|
|
36
resources/assets/scripts/app/services/download-cached.coffee
Normal file
36
resources/assets/scripts/app/services/download-cached.coffee
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Pony.fm - A community for pony fan music.
|
||||||
|
# Copyright (C) 2015 Kelvin Zhang
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
angular.module('ponyfm').factory('download-cached', [
|
||||||
|
'$rootScope', '$http', '$log'
|
||||||
|
($rootScope, $http, $log) ->
|
||||||
|
download = (type, id, format) ->
|
||||||
|
url = "/api/web/#{type}/cached/#{id}/#{format}"
|
||||||
|
|
||||||
|
encodingComplete = (response) ->
|
||||||
|
if response.data.url == null
|
||||||
|
'pending'
|
||||||
|
else
|
||||||
|
response.data.url
|
||||||
|
|
||||||
|
encodingFailed = (error) ->
|
||||||
|
$log.error 'Error downloading encoded file - Status: ' + error.status + '- Message: ' + error.data
|
||||||
|
'error'
|
||||||
|
|
||||||
|
$http.get(url).then(encodingComplete).catch encodingFailed
|
||||||
|
|
||||||
|
{download: download}
|
||||||
|
])
|
|
@ -1269,10 +1269,29 @@ angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['
|
||||||
element.parent().addClass('open');
|
element.parent().addClass('open');
|
||||||
openElement = element;
|
openElement = element;
|
||||||
closeMenu = function (event) {
|
closeMenu = function (event) {
|
||||||
|
|
||||||
|
// Dropdowns with with the `auto-close="outsideClick"` attribute are handled differently.
|
||||||
|
// They will only close if the user clicks somewhere outside of the dropdown menu.
|
||||||
|
//
|
||||||
|
// => Partially backported from: https://github.com/angular-ui/bootstrap/pull/3045/files
|
||||||
|
if ('autoClose' in attrs && attrs.autoClose === 'outsideClick') {
|
||||||
|
if (typeof event !== 'undefined' && !element.parent()[0].contains(event.target)) {
|
||||||
|
// Only close the menu if we detect a click outside the element.
|
||||||
|
$document.unbind('click', closeMenu);
|
||||||
|
element.parent().removeClass('open');
|
||||||
|
closeMenu = angular.noop;
|
||||||
|
openElement = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this isn't an "outside click", handle it as usual.
|
||||||
if (event) {
|
if (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
$document.unbind('click', closeMenu);
|
$document.unbind('click', closeMenu);
|
||||||
element.parent().removeClass('open');
|
element.parent().removeClass('open');
|
||||||
closeMenu = angular.noop;
|
closeMenu = angular.noop;
|
||||||
|
|
11
resources/assets/styles/content.less
vendored
11
resources/assets/styles/content.less
vendored
|
@ -360,6 +360,17 @@ html {
|
||||||
background-color: lighten(@pfm-purple, 30%);
|
background-color: lighten(@pfm-purple, 30%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cache-loading {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 100%;
|
||||||
|
font-size: 93%;
|
||||||
|
padding-left: 3px;
|
||||||
|
padding-right: 3px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue