[#9] Add functionality to add/change versions of tracks

This commit is contained in:
Kelvin Zhang 2016-08-24 12:48:02 +01:00 committed by Peter Deltchev
parent cf7bd8b9e6
commit 775de16bfe
14 changed files with 489 additions and 89 deletions

View file

@ -20,15 +20,15 @@
namespace Poniverse\Ponyfm\Commands;
use AudioCache;
use FFmpegMovie;
use File;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Support\Str;
use Poniverse\Ponyfm\Exceptions\InvalidEncodeOptionsException;
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
use Poniverse\Ponyfm\Models\Track;
use Poniverse\Ponyfm\Models\TrackFile;
use AudioCache;
use File;
use Illuminate\Support\Str;
use SplFileInfo;
/**
@ -45,6 +45,9 @@ class GenerateTrackFilesCommand extends CommandBase
private $track;
private $autoPublish;
private $sourceFile;
private $isForUpload;
private $isReplacingTrack;
private $version;
protected static $_losslessFormats = [
'flac',
@ -53,11 +56,14 @@ class GenerateTrackFilesCommand extends CommandBase
'alac'
];
public function __construct(Track $track, SplFileInfo $sourceFile, bool $autoPublish = false)
public function __construct(Track $track, SplFileInfo $sourceFile, bool $autoPublish = false, bool $isForUpload = false, bool $isReplacingTrack = false, int $version = 1)
{
$this->track = $track;
$this->autoPublish = $autoPublish;
$this->sourceFile = $sourceFile;
$this->isForUpload = $isForUpload;
$this->isReplacingTrack = $isReplacingTrack;
$this->version = $version;
}
/**
@ -98,6 +104,7 @@ class GenerateTrackFilesCommand extends CommandBase
$trackFile->is_master = true;
$trackFile->format = $masterFormat;
$trackFile->track_id = $this->track->id;
$trackFile->version = $this->version;
$trackFile->save();
}
@ -126,13 +133,14 @@ class GenerateTrackFilesCommand extends CommandBase
$trackFile->is_master = $name === 'FLAC' ? true : false;
$trackFile->format = $name;
$trackFile->status = TrackFile::STATUS_PROCESSING_PENDING;
$trackFile->version = $this->version;
if (in_array($name, Track::$CacheableFormats) && !$trackFile->is_master) {
$trackFile->is_cacheable = true;
} else {
$trackFile->is_cacheable = false;
}
$this->track->trackFiles()->save($trackFile);
$this->track->trackFilesForAllVersions()->save($trackFile);
// All TrackFile records we need are synchronously created
// before kicking off the encode jobs in order to avoid a race
@ -142,16 +150,31 @@ class GenerateTrackFilesCommand extends CommandBase
try {
foreach ($trackFiles as $trackFile) {
$this->dispatch(new EncodeTrackFile($trackFile, false, true, $this->autoPublish));
// Don't re-encode master files when replacing tracks with an already-uploaded version
if ($trackFile->is_master && !$this->isForUpload && $this->isReplacingTrack) {
continue;
}
$this->dispatch(new EncodeTrackFile($trackFile, false, false, $this->isForUpload, $this->isReplacingTrack));
}
} catch (InvalidEncodeOptionsException $e) {
$this->track->delete();
// Only delete the track if the track is not being replaced
if ($this->isReplacingTrack) {
$this->track->version_upload_status = Track::STATUS_ERROR;
$this->track->update();
} else {
$this->track->delete();
}
return CommandResponse::fail(['track' => [$e->getMessage()]]);
}
} catch (\Exception $e) {
$this->track->delete();
if ($this->isReplacingTrack) {
$this->track->version_upload_status = Track::STATUS_ERROR;
$this->track->update();
} else {
$this->track->delete();
}
throw $e;
}
@ -168,7 +191,8 @@ class GenerateTrackFilesCommand extends CommandBase
* @param FFmpegMovie|string $file object or full path of the file we're checking
* @return bool whether the given file is lossless
*/
private function isLosslessFile($file) {
private function isLosslessFile($file)
{
if (is_string($file)) {
$file = AudioCache::get($file);
}
@ -180,7 +204,8 @@ class GenerateTrackFilesCommand extends CommandBase
* @param string $format
* @return TrackFile|null
*/
private function trackFileExists(string $format) {
return $this->track->trackFiles()->where('format', $format)->first();
private function trackFileExists(string $format)
{
return $this->track->trackFilesForAllVersions()->where('format', $format)->where('version', $this->version)->first();
}
}

View file

@ -27,7 +27,6 @@ use Gate;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Input;
use Poniverse\Ponyfm\Models\Track;
use AudioCache;
use Poniverse\Ponyfm\Models\User;
use Validator;
@ -40,6 +39,21 @@ class UploadTrackCommand extends CommandBase
private $_allowShortTrack;
private $_customTrackSource;
private $_autoPublishByDefault;
private $_track;
private $_version;
private $_isReplacingTrack;
/**
* @return bool
*/
public function authorize()
{
if ($this->_isReplacingTrack) {
return $this->_track && Gate::allows('edit', $this->_track);
} else {
return Gate::allows('create-track', $this->_artist);
}
}
/**
* UploadTrackCommand constructor.
@ -48,12 +62,16 @@ class UploadTrackCommand extends CommandBase
* @param bool $allowShortTrack allow tracks shorter than 30 seconds
* @param string|null $customTrackSource value to set in the track's "source" field; if left blank, "direct_upload" is used
* @param bool $autoPublishByDefault
* @param int $version
* @param Track $track | null
*/
public function __construct(
bool $allowLossy = false,
bool $allowShortTrack = false,
string $customTrackSource = null,
bool $autoPublishByDefault = false
bool $autoPublishByDefault = false,
int $version = 1,
$track = null
) {
$userSlug = Input::get('user_slug', null);
$this->_artist =
@ -65,14 +83,9 @@ class UploadTrackCommand extends CommandBase
$this->_allowShortTrack = $allowShortTrack;
$this->_customTrackSource = $customTrackSource;
$this->_autoPublishByDefault = $autoPublishByDefault;
}
/**
* @return bool
*/
public function authorize()
{
return Gate::allows('create-track', $this->_artist);
$this->_version = $version;
$this->_track = $track;
$this->_isReplacingTrack = $this->_track !== null && $version > 1;
}
/**
@ -82,22 +95,32 @@ class UploadTrackCommand extends CommandBase
public function execute()
{
$trackFile = Input::file('track', null);
$coverFile = Input::file('cover', null);
if (!$this->_isReplacingTrack) {
$coverFile = Input::file('cover', null);
}
if (null === $trackFile) {
if ($this->_isReplacingTrack) {
$this->_track->version_upload_status = Track::STATUS_ERROR;
$this->_track->update();
}
return CommandResponse::fail(['track' => ['You must upload an audio file!']]);
}
$audio = \AudioCache::get($trackFile->getPathname());
$track = new Track();
$track->user_id = $this->_artist->id;
// The title set here is a placeholder; it'll be replaced by ParseTrackTagsCommand
// if the file contains a title tag.
$track->title = Input::get('title', pathinfo($trackFile->getClientOriginalName(), PATHINFO_FILENAME));
$track->duration = $audio->getDuration();
$track->save();
$track->ensureDirectoryExists();
if (!$this->_isReplacingTrack) {
$this->_track = new Track();
$this->_track->user_id = $this->_artist->id;
// The title set here is a placeholder; it'll be replaced by ParseTrackTagsCommand
// if the file contains a title tag.
$this->_track->title = Input::get('title', pathinfo($trackFile->getClientOriginalName(), PATHINFO_FILENAME));
// The duration/version of the track cannot be changed until the encoding is successful
$this->_track->duration = $audio->getDuration();
$this->_track->current_version = $this->_version;
$this->_track->save();
}
$this->_track->ensureDirectoryExists();
if (!is_dir(Config::get('ponyfm.files_directory').'/tmp')) {
mkdir(Config::get('ponyfm.files_directory').'/tmp', 0755, true);
@ -106,13 +129,15 @@ class UploadTrackCommand extends CommandBase
if (!is_dir(Config::get('ponyfm.files_directory').'/queued-tracks')) {
mkdir(Config::get('ponyfm.files_directory').'/queued-tracks', 0755, true);
}
$trackFile = $trackFile->move(Config::get('ponyfm.files_directory').'/queued-tracks', $track->id);
$trackFile = $trackFile->move(Config::get('ponyfm.files_directory').'/queued-tracks', $this->_track->id . 'v' . $this->_version);
$input = Input::all();
$input['track'] = $trackFile;
$input['cover'] = $coverFile;
if (!$this->_isReplacingTrack) {
$input['cover'] = $coverFile;
}
$validator = \Validator::make($input, [
$rules = [
'track' =>
'required|'
. ($this->_allowLossy
@ -120,44 +145,61 @@ class UploadTrackCommand extends CommandBase
: 'audio_format:flac,alac,pcm,adpcm|')
. ($this->_allowShortTrack ? '' : 'min_duration:30|')
. 'audio_channels:1,2',
'auto_publish' => 'boolean',
'title' => 'string',
'track_type_id' => 'exists:track_types,id',
'genre' => 'string',
'album' => 'string',
'track_number' => 'integer',
'released_at' => 'date_format:'.Carbon::ISO8601,
'description' => 'string',
'lyrics' => 'string',
'is_vocal' => 'boolean',
'is_explicit' => 'boolean',
'is_downloadable' => 'boolean',
'is_listed' => 'boolean',
'cover' => 'image|mimes:png,jpeg|min_width:350|min_height:350',
'metadata' => 'json',
]);
];
if (!$this->_isReplacingTrack) {
array_push($rules, [
'cover' => 'image|mimes:png,jpeg|min_width:350|min_height:350',
'auto_publish' => 'boolean',
'title' => 'string',
'track_type_id' => 'exists:track_types,id',
'genre' => 'string',
'album' => 'string',
'track_number' => 'integer',
'released_at' => 'date_format:'.Carbon::ISO8601,
'description' => 'string',
'lyrics' => 'string',
'is_vocal' => 'boolean',
'is_explicit' => 'boolean',
'is_downloadable' => 'boolean',
'is_listed' => 'boolean',
'metadata' => 'json'
]);
}
$validator = \Validator::make($input, $rules);
if ($validator->fails()) {
$track->delete();
if ($this->_isReplacingTrack) {
$this->_track->version_upload_status = Track::STATUS_ERROR;
$this->_track->update();
} else {
$this->_track->delete();
}
return CommandResponse::fail($validator);
}
$autoPublish = (bool) ($input['auto_publish'] ?? $this->_autoPublishByDefault);
$track->source = $this->_customTrackSource ?? 'direct_upload';
// If json_decode() isn't called here, Laravel will surround the JSON
// string with quotes when storing it in the database, which breaks things.
$track->metadata = json_decode(Input::get('metadata', null));
$track->save();
if (!$this->_isReplacingTrack) {
// If json_decode() isn't called here, Laravel will surround the JSON
// string with quotes when storing it in the database, which breaks things.
$this->_track->metadata = json_decode(Input::get('metadata', null));
}
$autoPublish = (bool)($input['auto_publish'] ?? $this->_autoPublishByDefault);
$this->_track->source = $this->_customTrackSource ?? 'direct_upload';
$this->_track->save();
// Parse any tags in the uploaded files.
$parseTagsCommand = new ParseTrackTagsCommand($track, $trackFile, $input);
$result = $parseTagsCommand->execute();
if ($result->didFail()) {
return $result;
if (!$this->_isReplacingTrack) {
// Parse any tags in the uploaded files.
$parseTagsCommand = new ParseTrackTagsCommand($this->_track, $trackFile, $input);
$result = $parseTagsCommand->execute();
if ($result->didFail()) {
if ($this->_isReplacingTrack) {
$this->_track->version_upload_status = Track::STATUS_ERROR;
$this->_track->update();
}
return $result;
}
}
$generateTrackFiles = new GenerateTrackFilesCommand($track, $trackFile, $autoPublish);
$generateTrackFiles = new GenerateTrackFilesCommand($this->_track, $trackFile, $autoPublish, true, $this->_isReplacingTrack, $this->_version);
return $generateTrackFiles->execute();
}
}

View file

@ -72,7 +72,7 @@ class RebuildTrack extends Command
$track->restore();
$this->info("Attempting to finish this track's upload...");
$sourceFile = new \SplFileInfo($track->getTemporarySourceFile());
$sourceFile = new \SplFileInfo($track->getTemporarySourceFileForVersion($track->current_version));
$generateTrackFiles = new GenerateTrackFilesCommand($track, $sourceFile, false);
$result = $generateTrackFiles->execute();
// The GenerateTrackFiles command will re-encode all TrackFiles.

View file

@ -0,0 +1,97 @@
<?php
/**
* Pony.fm - A community for pony fan music.
* Copyright (C) 2016 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\Models\TrackFile;
class VersionFiles extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'version-files
{--force : Skip all prompts.}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Replaces track files of format [name].[ext] with [name]-v[version].[ext]';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$this->info('This will only version track files which exist on disk; non-existent files will be skipped.');
if ($this->option('force') || $this->confirm('Are you sure you want to rename all unversioned track files? [y|N]', false)) {
TrackFile::chunk(200, function ($trackFiles) {
$this->info('========== Start Chunk ==========');
foreach ($trackFiles as $trackFile) {
/** @var TrackFile $trackFile */
// Check whether the unversioned file exists
if (!File::exists($trackFile->getUnversionedFile())) {
$this->info('ID ' . $trackFile->id . ' skipped - file not found');
continue;
}
// Version the file and check the outcome
if (File::move($trackFile->getUnversionedFile(), $trackFile->getFile())) {
$this->info('ID ' . $trackFile->id . ' processed');
} else {
$this->error('ID ' . $trackFile->id . ' was unable to be renamed');
}
}
$this->info('=========== End Chunk ===========');
});
$this->info('Rebuild complete. Exiting.');
} else {
$this->info('Rebuild cancelled. Exiting.');
}
}
}

View file

@ -45,6 +45,7 @@ class Kernel extends ConsoleKernel
\Poniverse\Ponyfm\Console\Commands\RebuildSearchIndex::class,
\Poniverse\Ponyfm\Console\Commands\MergeAccounts::class,
\Poniverse\Ponyfm\Console\Commands\FixMLPMAImages::class,
\Poniverse\Ponyfm\Console\Commands\VersionFiles::class
];
/**

View file

@ -20,23 +20,24 @@
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Auth;
use File;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Input;
use Poniverse\Ponyfm\Commands\DeleteTrackCommand;
use Poniverse\Ponyfm\Commands\EditTrackCommand;
use Poniverse\Ponyfm\Commands\GenerateTrackFilesCommand;
use Poniverse\Ponyfm\Commands\UploadTrackCommand;
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
use Poniverse\Ponyfm\Models\Genre;
use Poniverse\Ponyfm\Models\ResourceLogItem;
use Poniverse\Ponyfm\Models\TrackFile;
use Poniverse\Ponyfm\Models\Track;
use Poniverse\Ponyfm\Models\TrackType;
use Poniverse\Ponyfm\Models\TrackTypes;
use Auth;
use Input;
use Poniverse\Ponyfm\Models\TrackFile;
use Poniverse\Ponyfm\Models\User;
use Response;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class TracksController extends ApiControllerBase
{
@ -74,6 +75,75 @@ class TracksController extends ApiControllerBase
return $this->execute(new EditTrackCommand($id, Input::all()));
}
public function postUploadNewVersion($trackId)
{
session_write_close();
$track = Track::find($trackId);
if (!$track) {
return $this->notFound('Track not found!');
}
$this->authorize('edit', $track);
$track->version_upload_status = Track::STATUS_PROCESSING;
$track->update();
return $this->execute(new UploadTrackCommand(true, false, null, false, $track->getNextVersion(), $track));
}
public function getVersionUploadStatus($trackId)
{
$track = Track::findOrFail($trackId);
$this->authorize('edit', $track);
if ($track->version_upload_status === Track::STATUS_PROCESSING) {
return Response::json(['message' => 'Processing...'], 202);
} elseif ($track->version_upload_status === Track::STATUS_COMPLETE) {
return Response::json(['message' => 'Processing complete!'], 201);
} else {
// something went wrong
return Response::json(['error' => 'Processing failed!'], 500);
}
}
public function getVersionList($trackId)
{
$track = Track::findOrFail($trackId);
$this->authorize('edit', $track);
$versions = [];
$trackFiles = $track->trackFilesForAllVersions()->where('is_master', 'true')->get();
foreach($trackFiles as $trackFile) {
$versions[] = [
'version' => $trackFile->version,
'url' => '/tracks/' . $track->id . '/version-change/' . $trackFile->version,
'created_at' => $trackFile->created_at->timestamp
];
}
return Response::json(['current_version' => $track->current_version, 'versions' => $versions], 200);
}
public function getChangeVersion($trackId, $newVersion)
{
$track = Track::find($trackId);
if (!$track) {
return $this->notFound('Track not found!');
}
$this->authorize('edit', $track);
$masterTrackFile = $track->trackFilesForVersion($newVersion)->where('is_master', true)->first();
if (!$masterTrackFile) {
return $this->notFound('Version not found!');
}
$track->version_upload_status = Track::STATUS_PROCESSING;
$track->update();
$sourceFile = new UploadedFile($masterTrackFile->getFile(), $masterTrackFile->getFilename());
return $this->execute(new GenerateTrackFilesCommand($track, $sourceFile, false, false, true, $newVersion));
}
public function getShow($id)
{
$track = Track::userDetails()->withComments()->find($id);

View file

@ -114,6 +114,11 @@ Route::group(['prefix' => 'api/web'], function() {
Route::post('/tracks/delete/{id}', 'Api\Web\TracksController@postDelete');
Route::post('/tracks/edit/{id}', 'Api\Web\TracksController@postEdit');
Route::post('/tracks/{id}/version-upload', 'Api\Web\TracksController@postUploadNewVersion');
Route::get('/tracks/{id}/version-change/{version}', 'Api\Web\TracksController@getChangeVersion');
Route::get('/tracks/{id}/version-upload-status', 'Api\Web\TracksController@getVersionUploadStatus');
Route::get('/tracks/{id}/versions', 'Api\Web\TracksController@getVersionList');
Route::post('/albums/create', 'Api\Web\AlbumsController@postCreate');
Route::post('/albums/delete/{id}', 'Api\Web\AlbumsController@postDelete');
Route::post('/albums/edit/{id}', 'Api\Web\AlbumsController@postEdit');

View file

@ -22,15 +22,15 @@
namespace Poniverse\Ponyfm\Jobs;
use Carbon\Carbon;
use Config;
use DB;
use File;
use Config;
use Log;
use Poniverse\Ponyfm\Exceptions\InvalidEncodeOptionsException;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Bus\SelfHandling;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Log;
use Poniverse\Ponyfm\Exceptions\InvalidEncodeOptionsException;
use Poniverse\Ponyfm\Models\Track;
use Poniverse\Ponyfm\Models\TrackFile;
use Symfony\Component\Process\Exception\ProcessFailedException;
@ -47,6 +47,10 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
* @var
*/
protected $isExpirable;
/**
* @var bool
*/
protected $autoPublishWhenComplete;
/**
* @var bool
*/
@ -54,16 +58,17 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
/**
* @var bool
*/
protected $autoPublishWhenComplete;
protected $isReplacingTrack;
/**
* Create a new job instance.
* @param TrackFile $trackFile
* @param bool $isExpirable
* @param bool $isForUpload indicates whether this encode job is for an upload
* @param bool $autoPublish
* @param bool $isForUpload indicates whether this encode job is for an upload
* @param bool $isReplacingTrack
*/
public function __construct(TrackFile $trackFile, $isExpirable, $isForUpload = false, $autoPublish = false)
public function __construct(TrackFile $trackFile, $isExpirable, $autoPublish = false, $isForUpload = false, $isReplacingTrack = false)
{
if (
(!$isForUpload && $trackFile->is_master) ||
@ -74,8 +79,9 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
$this->trackFile = $trackFile;
$this->isExpirable = $isExpirable;
$this->isForUpload = $isForUpload;
$this->autoPublishWhenComplete = $autoPublish;
$this->isForUpload = $isForUpload;
$this->isReplacingTrack = $isReplacingTrack;
}
/**
@ -92,7 +98,7 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
Log::warning('Track file #'.$this->trackFile->id.' (track #'.$this->trackFile->track_id.') is already being processed!');
return;
} elseif (!$this->trackFile->is_expired) {
} elseif (!$this->trackFile->is_expired && File::exists($this->trackFile->getFile())) {
Log::warning('Track file #'.$this->trackFile->id.' (track #'.$this->trackFile->track_id.') is still valid! No need to re-encode it.');
return;
}
@ -103,11 +109,11 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
// Use the track's master file as the source
if ($this->isForUpload) {
$source = $this->trackFile->track->getTemporarySourceFile();
$source = $this->trackFile->track->getTemporarySourceFileForVersion($this->trackFile->version);
} else {
$source = TrackFile::where('track_id', $this->trackFile->track_id)
->where('is_master', true)
->where('version', $this->trackFile->version)
->first()
->getFile();
}
@ -151,7 +157,7 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
$this->trackFile->status = TrackFile::STATUS_NOT_BEING_PROCESSED;
$this->trackFile->save();
if ($this->isForUpload) {
if ($this->isForUpload || $this->isReplacingTrack) {
if (!$this->trackFile->is_master && $this->trackFile->is_cacheable) {
File::delete($this->trackFile->getFile());
}
@ -166,7 +172,29 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
$this->trackFile->track->save();
}
File::delete($this->trackFile->track->getTemporarySourceFile());
if ($this->isReplacingTrack) {
$oldVersion = $this->trackFile->track->current_version;
// Update the version of the track being uploaded
$this->trackFile->track->duration = \AudioCache::get($source)->getDuration();
$this->trackFile->track->current_version = $this->trackFile->version;
$this->trackFile->track->version_upload_status = Track::STATUS_COMPLETE;
$this->trackFile->track->update();
// Delete the non-master files for the old version
if ($oldVersion !== $this->trackFile->version) {
$trackFilesToDelete = $this->trackFile->track->trackFilesForVersion($oldVersion)->where('is_master', false)->get();
foreach ($trackFilesToDelete as $trackFileToDelete) {
if (File::exists($trackFileToDelete->getFile())) {
File::delete($trackFileToDelete->getFile());
}
}
}
}
if ($this->isForUpload) {
File::delete($this->trackFile->track->getTemporarySourceFileForVersion($this->trackFile->version));
}
}
}
}
@ -181,5 +209,21 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
$this->trackFile->status = TrackFile::STATUS_PROCESSING_ERROR;
$this->trackFile->expires_at = null;
$this->trackFile->save();
if ($this->isReplacingTrack) {
// If a new version is being uploaded to replace a file, yet the upload fails,
// all track files for that version should be deleted as it would other clutter the version
if ($this->isForUpload) {
$trackFiles = $this->trackFile->track->trackFilesForVersion($this->trackFile->version)->get();
foreach ($trackFiles as $trackFile) {
if (File::exists($trackFile->getFile())) {
File::delete($trackFile->getFile());
}
$trackFile->delete();
}
}
$this->trackFile->track->version_upload_status = Track::STATUS_ERROR;
$this->trackFile->track->update();
}
}
}

View file

@ -123,7 +123,8 @@ class Album extends Model implements Searchable, Commentable, Favouritable
}
public function trackFiles() {
return $this->hasManyThrough(TrackFile::class, Track::class, 'album_id', 'track_id');
$trackIds = $this->tracks->lists('id');
return TrackFile::join('tracks', 'tracks.current_version', '=', 'track_files.version')->whereIn('track_id', $trackIds);
}
public function comments():HasMany

View file

@ -226,7 +226,7 @@ class Playlist extends Model implements Searchable, Commentable, Favouritable
public function trackFiles()
{
$trackIds = $this->tracks->lists('id');
return TrackFile::whereIn('track_id', $trackIds);
return TrackFile::join('tracks', 'tracks.current_version', '=', 'track_files.version')->whereIn('track_id', $trackIds);
}
public function users()

View file

@ -536,10 +536,21 @@ class Track extends Model implements Searchable, Commentable, Favouritable
}
public function trackFiles()
{
return $this->hasMany(TrackFile::class)->where('version', $this->current_version);
}
public function trackFilesForAllVersions()
{
return $this->hasMany(TrackFile::class);
}
public function trackFilesForVersion(int $version)
{
return $this->hasMany(TrackFile::class)->where('track_files.version', $version);
}
public function notifications()
{
return $this->morphMany(Activity::class, 'notification_type');
@ -688,7 +699,7 @@ class Track extends Model implements Searchable, Commentable, Favouritable
$format = self::$Formats[$format];
return "{$this->id}.{$format['extension']}";
return "{$this->id}-v{$this->current_version}.{$format['extension']}";
}
public function getDownloadFilenameFor($format)
@ -715,7 +726,7 @@ class Track extends Model implements Searchable, Commentable, Favouritable
$format = self::$Formats[$format];
return "{$this->getDirectory()}/{$this->id}.{$format['extension']}";
return "{$this->getDirectory()}/{$this->id}-v{$this->current_version}.{$format['extension']}";
}
/**
@ -723,10 +734,11 @@ class Track extends Model implements Searchable, Commentable, Favouritable
* This file is used during the upload process to generate the actual master
* file stored by Pony.fm.
*
* @param int $version
* @return string
*/
public function getTemporarySourceFile():string {
return Config::get('ponyfm.files_directory').'/queued-tracks/'.$this->id;
public function getTemporarySourceFileForVersion(int $version):string {
return Config::get('ponyfm.files_directory').'/queued-tracks/'.$this->id.'v'.$version;
}
@ -914,4 +926,9 @@ class Track extends Model implements Searchable, Commentable, Favouritable
public function getResourceType():string {
return 'track';
}
public function getNextVersion()
{
return $this->current_version + 1;
}
}

View file

@ -80,6 +80,11 @@ class TrackFile extends Model
*/
public static function findOrFailByExtension($trackId, $extension)
{
$track = Track::find($trackId);
if (!$track) {
App::abort(404);
}
// find the extension's format
$requestedFormatName = null;
foreach (Track::$Formats as $name => $format) {
@ -96,6 +101,7 @@ class TrackFile extends Model
with('track')
->where('track_id', $trackId)
->where('format', $requestedFormatName)
->where('version', $track->current_version)
->first();
if ($trackFile === null) {
@ -148,11 +154,21 @@ class TrackFile extends Model
}
public function getFile()
{
return "{$this->getDirectory()}/{$this->track_id}-v{$this->version}.{$this->extension}";
}
public function getUnversionedFile()
{
return "{$this->getDirectory()}/{$this->track_id}.{$this->extension}";
}
public function getFilename()
{
return "{$this->track_id}-v{$this->track->current_version}.{$this->extension}";
}
public function getUnversionedFilename()
{
return "{$this->track_id}.{$this->extension}";
}

View file

@ -0,0 +1,49 @@
<?php
/**
* Pony.fm - A community for pony fan music.
* Copyright (C) 2016 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 AddVersionColumnToTrackFilesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('track_files', function (Blueprint $table) {
$table->integer('version')->unsigned()->default(1);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('track_files', function (Blueprint $table) {
$table->dropColumn('version');
});
}
}

View file

@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddVersionColumnToTracksTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('tracks', function (Blueprint $table) {
$table->integer('current_version')->unsigned()->default(1);
$table->integer('version_upload_status')->unsigned()->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('tracks', function (Blueprint $table) {
$table->dropColumn('current_version');
$table->dropColumn('version_upload_status');
});
}
}