mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2024-11-25 14:37:59 +01:00
Merge branch 'master' into feature/track-removals
This commit is contained in:
commit
e63c18f6be
311 changed files with 6611 additions and 23058 deletions
19
README.md
19
README.md
|
@ -33,13 +33,14 @@ these are smaller in scope and easier to tackle if you're unfamiliar with the co
|
|||
Starting a dev environment
|
||||
==========================
|
||||
|
||||
To begin development, you must do three things:
|
||||
To begin development, do the following:
|
||||
|
||||
1. Install the `vagrant-hostmanager` plugin: `vagrant plugin install vagrant-hostmanager`
|
||||
1. Install [Vagrant](https://www.vagrantup.com/downloads.html) and
|
||||
[VirtualBox](https://www.virtualbox.org/wiki/Downloads) if you don't have them already.
|
||||
|
||||
2. Install the `vagrant-bindfs` plugin: `vagrant plugin install vagrant-bindfs`
|
||||
2. Install the `vagrant-hostmanager` plugin: `vagrant plugin install vagrant-hostmanager`
|
||||
|
||||
3. Create the directory `pony.fm.files` in the repository's parent directory
|
||||
3. Install the `vagrant-bindfs` plugin: `vagrant plugin install vagrant-bindfs`
|
||||
|
||||
4. Run `vagrant up` from the folder in which you cloned the repository
|
||||
|
||||
|
@ -62,21 +63,13 @@ And then install all of the required local packages by invoking:
|
|||
|
||||
npm install
|
||||
|
||||
Finally, build all of the scripts by executing:
|
||||
|
||||
gulp build
|
||||
|
||||
During development, you should make a point to run "gulp watch". You can do this simply by executing:
|
||||
Finally, to compile and serve the assets in real time, run the following (and leave it running while you develop):
|
||||
|
||||
gulp watch
|
||||
|
||||
This will watch and compile the `.less` and `.coffee` files in real time.
|
||||
|
||||
Configuring the servers
|
||||
-----------------------
|
||||
|
||||
Pony.fm uses nginx, php-fpm, redis, and MySQL. You can modify the configuration of these services by locating the appropriate config file in the `vagrant` folder. Once modified, you must reload the configuration by running the appropriate shell script (`reload-config.sh`) or bat files (`reload-config.bat` and `reload-config.vmware.bat`). These scripts simply tell Vagrant to run `copy-and-restart-config.sh` on the VM.
|
||||
|
||||
If you need to change any other configuration file on the VM - copy the entire file over into the vagrant folder, make your changes, and update the `copy-and-restart-config.sh` script to copy the modified config back into the proper folder. All potential configuration requirements should be represented in the `vagrant` folder **and never only on the VM itself** as changes will not be preserved.
|
||||
|
||||
**NOTE:** currently, Redis's configuration is not reloaded by the `copy-and-restart-config.sh`
|
||||
|
|
14
Vagrantfile
vendored
14
Vagrantfile
vendored
|
@ -3,11 +3,12 @@ Vagrant.configure("2") do |config|
|
|||
config.hostmanager.enabled = true
|
||||
config.hostmanager.manage_host = true
|
||||
|
||||
config.vm.box = 'laravel/homestead-7'
|
||||
config.vm.box_version = '0.2.1'
|
||||
config.vm.box = 'laravel/homestead'
|
||||
config.vm.box_version = '0.4.2'
|
||||
|
||||
config.vm.provider "virtualbox" do |v|
|
||||
v.cpus = 4
|
||||
v.memory = 2048
|
||||
v.memory = 1024
|
||||
end
|
||||
|
||||
config.vm.define 'default' do |node|
|
||||
|
@ -17,13 +18,8 @@ Vagrant.configure("2") do |config|
|
|||
end
|
||||
|
||||
config.vm.synced_folder ".", "/vagrant", type: "nfs"
|
||||
|
||||
config.vm.provision "shell", path: "vagrant/install.sh"
|
||||
|
||||
config.vm.network "forwarded_port", guest: 3306, host: 33060
|
||||
|
||||
config.vm.synced_folder "../pony.fm.files", "/vagrant-files", type: "nfs"
|
||||
config.bindfs.bind_folder "/vagrant", "/vagrant"
|
||||
|
||||
config.vm.provision "shell", path: "vagrant/install.sh"
|
||||
config.vm.provision "shell", path: "vagrant/copy-and-restart-configs.sh", run: "always"
|
||||
end
|
||||
|
|
|
@ -20,11 +20,19 @@
|
|||
|
||||
namespace Poniverse\Ponyfm;
|
||||
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use ZipStream;
|
||||
|
||||
class AlbumDownloader
|
||||
{
|
||||
/**
|
||||
* @var Album
|
||||
*/
|
||||
private $_album;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $_format;
|
||||
|
||||
function __construct($album, $format)
|
||||
|
|
|
@ -20,14 +20,18 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\Playlist;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Auth;
|
||||
use DB;
|
||||
use Validator;
|
||||
|
||||
class AddTrackToPlaylistCommand extends CommandBase
|
||||
{
|
||||
/** @var Track */
|
||||
private $_track;
|
||||
|
||||
/** @var Playlist */
|
||||
private $_playlist;
|
||||
|
||||
function __construct($playlistId, $trackId)
|
||||
|
@ -52,10 +56,22 @@ class AddTrackToPlaylistCommand extends CommandBase
|
|||
*/
|
||||
public function execute()
|
||||
{
|
||||
// check if this track is already in the playlist
|
||||
$validator = Validator::make(
|
||||
['track_id' => $this->_track->id],
|
||||
['track_id' => "unique:playlist_track,track_id,null,id,playlist_id,{$this->_playlist->id}",]
|
||||
);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return CommandResponse::fail($validator);
|
||||
}
|
||||
|
||||
|
||||
$songIndex = $this->_playlist->tracks()->count() + 1;
|
||||
$this->_playlist->tracks()->attach($this->_track, ['position' => $songIndex]);
|
||||
$this->_playlist->touch();
|
||||
|
||||
Playlist::whereId($this->_playlist->id)->update([
|
||||
Playlist::where('id', $this->_playlist->id)->update([
|
||||
'track_count' => DB::raw('(SELECT COUNT(id) FROM playlist_track WHERE playlist_id = ' . $this->_playlist->id . ')')
|
||||
]);
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\Album;
|
||||
use Poniverse\Ponyfm\Image;
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use Poniverse\Ponyfm\Models\Image;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
|
|
|
@ -20,11 +20,11 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\Album;
|
||||
use Poniverse\Ponyfm\Comment;
|
||||
use Poniverse\Ponyfm\Playlist;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\User;
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use Poniverse\Ponyfm\Models\Comment;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
|
|
75
app/Commands/CreateGenreCommand.php
Normal file
75
app/Commands/CreateGenreCommand.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* 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\Commands;
|
||||
|
||||
use Gate;
|
||||
use Illuminate\Support\Str;
|
||||
use Poniverse\Ponyfm\Models\Genre;
|
||||
use Validator;
|
||||
|
||||
class CreateGenreCommand extends CommandBase
|
||||
{
|
||||
/** @var Genre */
|
||||
private $_genreName;
|
||||
|
||||
public function __construct($genreName)
|
||||
{
|
||||
$this->_genreName = $genreName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return Gate::allows('create-genre');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
* @return CommandResponse
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$slug = Str::slug($this->_genreName);
|
||||
|
||||
$rules = [
|
||||
'name' => 'required|unique:genres,name,NULL,id,deleted_at,NULL|max:50',
|
||||
'slug' => 'required|unique:genres,slug,NULL,id,deleted_at,NULL'
|
||||
];
|
||||
|
||||
$validator = Validator::make([
|
||||
'name' => $this->_genreName,
|
||||
'slug' => $slug
|
||||
], $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return CommandResponse::fail($validator);
|
||||
}
|
||||
|
||||
Genre::create([
|
||||
'name' => $this->_genreName,
|
||||
'slug' => $slug
|
||||
]);
|
||||
|
||||
return CommandResponse::succeed(['message' => 'Genre created!']);
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\Playlist;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
|
|
|
@ -20,18 +20,21 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\Album;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use Auth;
|
||||
|
||||
class DeleteAlbumCommand extends CommandBase
|
||||
{
|
||||
/** @var int */
|
||||
private $_albumId;
|
||||
|
||||
/** @var Album */
|
||||
private $_album;
|
||||
|
||||
function __construct($albumId)
|
||||
{
|
||||
$this->_albumId = $albumId;
|
||||
$this->_album = ALbum::find($albumId);
|
||||
$this->_album = Album::find($albumId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Poniverse\Ponyfm\Commands;
|
|||
|
||||
use Gate;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Poniverse\Ponyfm\Genre;
|
||||
use Poniverse\Ponyfm\Models\Genre;
|
||||
use Poniverse\Ponyfm\Jobs\DeleteGenre;
|
||||
use Validator;
|
||||
|
||||
|
|
|
@ -20,12 +20,15 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\Playlist;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Auth;
|
||||
|
||||
class DeletePlaylistCommand extends CommandBase
|
||||
{
|
||||
/** @var int */
|
||||
private $_playlistId;
|
||||
|
||||
/** @var Playlist */
|
||||
private $_playlist;
|
||||
|
||||
function __construct($playlistId)
|
||||
|
|
|
@ -20,11 +20,15 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Gate;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
|
||||
class DeleteTrackCommand extends CommandBase
|
||||
{
|
||||
/** @var int */
|
||||
private $_trackId;
|
||||
|
||||
/** @var Track */
|
||||
private $_track;
|
||||
|
||||
function __construct($trackId)
|
||||
|
@ -38,9 +42,7 @@ class DeleteTrackCommand extends CommandBase
|
|||
*/
|
||||
public function authorize()
|
||||
{
|
||||
$user = \Auth::user();
|
||||
|
||||
return $this->_track && $user != null && $this->_track->user_id == $user->id;
|
||||
return Gate::allows('delete', $this->_track);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,16 +20,18 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\Album;
|
||||
use Poniverse\Ponyfm\Image;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use Poniverse\Ponyfm\Models\Image;
|
||||
use Auth;
|
||||
use DB;
|
||||
use Validator;
|
||||
|
||||
class EditAlbumCommand extends CommandBase
|
||||
{
|
||||
private $_input;
|
||||
/** @var int */
|
||||
private $_albumId;
|
||||
/** @var Album */
|
||||
private $_album;
|
||||
|
||||
function __construct($trackId, $input)
|
||||
|
@ -88,10 +90,6 @@ class EditAlbumCommand extends CommandBase
|
|||
$this->_album->syncTrackIds($trackIds);
|
||||
$this->_album->save();
|
||||
|
||||
Album::where('id', $this->_album->id)->update([
|
||||
'track_count' => DB::raw('(SELECT COUNT(id) FROM tracks WHERE album_id = ' . $this->_album->id . ')')
|
||||
]);
|
||||
|
||||
return CommandResponse::succeed(['real_cover_url' => $this->_album->getCoverUrl(Image::NORMAL)]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\PinnedPlaylist;
|
||||
use Poniverse\Ponyfm\Playlist;
|
||||
use Poniverse\Ponyfm\Models\PinnedPlaylist;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
|
|
|
@ -20,11 +20,12 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\Album;
|
||||
use Poniverse\Ponyfm\Image;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\TrackType;
|
||||
use Poniverse\Ponyfm\User;
|
||||
use Gate;
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use Poniverse\Ponyfm\Models\Image;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\TrackType;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use Auth;
|
||||
use DB;
|
||||
|
||||
|
@ -46,9 +47,7 @@ class EditTrackCommand extends CommandBase
|
|||
*/
|
||||
public function authorize()
|
||||
{
|
||||
$user = \Auth::user();
|
||||
|
||||
return $this->_track && $user != null && $this->_track->user_id == $user->id;
|
||||
return $this->_track && Gate::allows('edit', $this->_track);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,8 +60,11 @@ class EditTrackCommand extends CommandBase
|
|||
|
||||
$rules = [
|
||||
'title' => 'required|min:3|max:80',
|
||||
'released_at' => 'before:' . (date('Y-m-d',
|
||||
time() + (86400 * 2))) . (isset($this->_input['released_at']) && $this->_input['released_at'] != "" ? '|date' : ''),
|
||||
'released_at' => 'before:' .
|
||||
(date('Y-m-d', time() + (86400 * 2))) . (
|
||||
isset($this->_input['released_at']) && $this->_input['released_at'] != ""
|
||||
? '|date'
|
||||
: ''),
|
||||
'license_id' => 'required|exists:licenses,id',
|
||||
'genre_id' => 'required|exists:genres,id',
|
||||
'cover' => 'image|mimes:png,jpeg|min_width:350|min_height:350',
|
||||
|
@ -140,7 +142,7 @@ class EditTrackCommand extends CommandBase
|
|||
} else {
|
||||
if (isset($this->_input['cover'])) {
|
||||
$cover = $this->_input['cover'];
|
||||
$track->cover_id = Image::upload($cover, Auth::user())->id;
|
||||
$track->cover_id = Image::upload($cover, $track->user_id)->id;
|
||||
} else {
|
||||
if ($this->_input['remove_cover'] == 'true') {
|
||||
$track->cover_id = null;
|
||||
|
|
187
app/Commands/GenerateTrackFilesCommand.php
Normal file
187
app/Commands/GenerateTrackFilesCommand.php
Normal file
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* 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\Commands;
|
||||
|
||||
use FFmpegMovie;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
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;
|
||||
use Validator;
|
||||
|
||||
/**
|
||||
* This command is the "second phase" of the upload process - once metadata has
|
||||
* been parsed and the track object is created, this generates the track's
|
||||
* corresponding TrackFile objects and ensures that all of them have been encoded.
|
||||
*
|
||||
* @package Poniverse\Ponyfm\Commands
|
||||
*/
|
||||
class GenerateTrackFilesCommand extends CommandBase
|
||||
{
|
||||
use DispatchesJobs;
|
||||
|
||||
private $track;
|
||||
private $autoPublish;
|
||||
private $sourceFile;
|
||||
|
||||
protected static $_losslessFormats = [
|
||||
'flac',
|
||||
'pcm',
|
||||
'adpcm',
|
||||
'alac'
|
||||
];
|
||||
|
||||
public function __construct(Track $track, SplFileInfo $sourceFile, bool $autoPublish = false)
|
||||
{
|
||||
$this->track = $track;
|
||||
$this->autoPublish = $autoPublish;
|
||||
$this->sourceFile = $sourceFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
* @return CommandResponse
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
try {
|
||||
$source = $this->sourceFile->getPathname();
|
||||
|
||||
// Lossy uploads need to be identified and set as the master file
|
||||
// without being re-encoded.
|
||||
$audioObject = AudioCache::get($source);
|
||||
$isLossyUpload = !$this->isLosslessFile($audioObject);
|
||||
$codecString = $audioObject->getAudioCodec();
|
||||
|
||||
if ($isLossyUpload) {
|
||||
if ($codecString === 'mp3') {
|
||||
$masterFormat = 'MP3';
|
||||
|
||||
} else if (Str::startsWith($codecString, 'aac')) {
|
||||
$masterFormat = 'AAC';
|
||||
|
||||
} else if ($codecString === 'vorbis') {
|
||||
$masterFormat = 'OGG Vorbis';
|
||||
|
||||
} else {
|
||||
$this->track->delete();
|
||||
return CommandResponse::fail(['track' => "The track does not contain audio in a known lossy format. The format read from the file is: {$codecString}"]);
|
||||
}
|
||||
|
||||
// Sanity check: skip creating this TrackFile if it already exists.
|
||||
$trackFile = $this->trackFileExists($masterFormat);
|
||||
|
||||
if (!$trackFile) {
|
||||
$trackFile = new TrackFile();
|
||||
$trackFile->is_master = true;
|
||||
$trackFile->format = $masterFormat;
|
||||
$trackFile->track_id = $this->track->id;
|
||||
$trackFile->save();
|
||||
}
|
||||
|
||||
// Lossy masters are copied into the datastore - no re-encoding involved.
|
||||
File::copy($source, $trackFile->getFile());
|
||||
}
|
||||
|
||||
|
||||
$trackFiles = [];
|
||||
|
||||
foreach (Track::$Formats as $name => $format) {
|
||||
// Don't bother with lossless transcodes of lossy uploads, and
|
||||
// don't re-encode the lossy master.
|
||||
if ($isLossyUpload && ($format['is_lossless'] || $name === $masterFormat)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sanity check: skip creating this TrackFile if it already exists.
|
||||
// But, we'll still encode it!
|
||||
if ($trackFile = $this->trackFileExists($name)) {
|
||||
$trackFiles[] = $trackFile;
|
||||
continue;
|
||||
}
|
||||
|
||||
$trackFile = new TrackFile();
|
||||
$trackFile->is_master = $name === 'FLAC' ? true : false;
|
||||
$trackFile->format = $name;
|
||||
$trackFile->status = TrackFile::STATUS_PROCESSING_PENDING;
|
||||
|
||||
if (in_array($name, Track::$CacheableFormats) && !$trackFile->is_master) {
|
||||
$trackFile->is_cacheable = true;
|
||||
} else {
|
||||
$trackFile->is_cacheable = false;
|
||||
}
|
||||
$this->track->trackFiles()->save($trackFile);
|
||||
|
||||
// All TrackFile records we need are synchronously created
|
||||
// before kicking off the encode jobs in order to avoid a race
|
||||
// condition with the "temporary" source file getting deleted.
|
||||
$trackFiles[] = $trackFile;
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($trackFiles as $trackFile) {
|
||||
$this->dispatch(new EncodeTrackFile($trackFile, false, true, $this->autoPublish));
|
||||
}
|
||||
|
||||
} catch (InvalidEncodeOptionsException $e) {
|
||||
$this->track->delete();
|
||||
return CommandResponse::fail(['track' => [$e->getMessage()]]);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->track->delete();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return CommandResponse::succeed([
|
||||
'id' => $this->track->id,
|
||||
'name' => $this->track->name,
|
||||
'title' => $this->track->title,
|
||||
'slug' => $this->track->slug,
|
||||
'autoPublish' => $this->autoPublish,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
if (is_string($file)) {
|
||||
$file = AudioCache::get($file);
|
||||
}
|
||||
|
||||
return Str::startsWith($file->getAudioCodec(), static::$_losslessFormats);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $format
|
||||
* @return TrackFile|null
|
||||
*/
|
||||
private function trackFileExists(string $format) {
|
||||
return $this->track->trackFiles()->where('format', $format)->first();
|
||||
}
|
||||
}
|
120
app/Commands/MergeAccountsCommand.php
Normal file
120
app/Commands/MergeAccountsCommand.php
Normal file
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* 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\Commands;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use Poniverse\Ponyfm\Models\Comment;
|
||||
use Poniverse\Ponyfm\Models\Favourite;
|
||||
use Poniverse\Ponyfm\Models\Follower;
|
||||
use Poniverse\Ponyfm\Models\Image;
|
||||
use Poniverse\Ponyfm\Models\PinnedPlaylist;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||
use Poniverse\Ponyfm\Models\ResourceUser;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
|
||||
class MergeAccountsCommand extends CommandBase
|
||||
{
|
||||
private $sourceAccount;
|
||||
private $destinationAccount;
|
||||
|
||||
function __construct(User $sourceAccount, User $destinationAccount)
|
||||
{
|
||||
$this->sourceAccount = $sourceAccount;
|
||||
$this->destinationAccount = $destinationAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
* @return CommandResponse
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
DB::transaction(function() {
|
||||
$accountIds = [$this->sourceAccount->id];
|
||||
|
||||
foreach (Album::whereIn('user_id', $accountIds)->get() as $album) {
|
||||
$album->user_id = $this->destinationAccount->id;
|
||||
$album->save();
|
||||
}
|
||||
|
||||
foreach (Comment::whereIn('user_id', $accountIds)->get() as $comment) {
|
||||
$comment->user_id = $this->destinationAccount->id;
|
||||
$comment->save();
|
||||
}
|
||||
|
||||
foreach (Favourite::whereIn('user_id', $accountIds)->get() as $favourite) {
|
||||
$favourite->user_id = $this->destinationAccount->id;
|
||||
$favourite->save();
|
||||
}
|
||||
|
||||
foreach (Follower::whereIn('artist_id', $accountIds)->get() as $follow) {
|
||||
$follow->artist_id = $this->destinationAccount->id;
|
||||
$follow->save();
|
||||
}
|
||||
|
||||
foreach (Image::whereIn('uploaded_by', $accountIds)->get() as $image) {
|
||||
$image->uploaded_by = $this->destinationAccount->id;
|
||||
$image->save();
|
||||
}
|
||||
|
||||
foreach (Image::whereIn('uploaded_by', $accountIds)->get() as $image) {
|
||||
$image->uploaded_by = $this->destinationAccount->id;
|
||||
$image->save();
|
||||
}
|
||||
|
||||
DB::table('oauth2_tokens')->whereIn('user_id', $accountIds)->update(['user_id' => $this->destinationAccount->id]);
|
||||
|
||||
foreach (PinnedPlaylist::whereIn('user_id', $accountIds)->get() as $playlist) {
|
||||
$playlist->user_id = $this->destinationAccount->id;
|
||||
$playlist->save();
|
||||
}
|
||||
|
||||
foreach (Playlist::whereIn('user_id', $accountIds)->get() as $playlist) {
|
||||
$playlist->user_id = $this->destinationAccount->id;
|
||||
$playlist->save();
|
||||
}
|
||||
|
||||
foreach (ResourceLogItem::whereIn('user_id', $accountIds)->get() as $item) {
|
||||
$item->user_id = $this->destinationAccount->id;
|
||||
$item->save();
|
||||
}
|
||||
|
||||
foreach (ResourceUser::whereIn('user_id', $accountIds)->get() as $item) {
|
||||
$item->user_id = $this->destinationAccount->id;
|
||||
$item->save();
|
||||
}
|
||||
|
||||
foreach (Track::whereIn('user_id', $accountIds)->get() as $track) {
|
||||
$track->user_id = $this->destinationAccount->id;
|
||||
$track->save();
|
||||
}
|
||||
|
||||
$this->sourceAccount->disabled_at = Carbon::now();
|
||||
$this->sourceAccount->save();
|
||||
});
|
||||
|
||||
return CommandResponse::succeed();
|
||||
}
|
||||
}
|
441
app/Commands/ParseTrackTagsCommand.php
Normal file
441
app/Commands/ParseTrackTagsCommand.php
Normal file
|
@ -0,0 +1,441 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* 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\Commands;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Config;
|
||||
use getID3;
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use Poniverse\Ponyfm\Models\Genre;
|
||||
use Poniverse\Ponyfm\Models\Image;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use AudioCache;
|
||||
use File;
|
||||
use Illuminate\Support\Str;
|
||||
use Poniverse\Ponyfm\Models\TrackType;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
|
||||
|
||||
class ParseTrackTagsCommand extends CommandBase
|
||||
{
|
||||
private $track;
|
||||
private $fileToParse;
|
||||
private $input;
|
||||
|
||||
public function __construct(Track $track, \Symfony\Component\HttpFoundation\File\File $fileToParse, $inputTags = [])
|
||||
{
|
||||
$this->track = $track;
|
||||
$this->fileToParse = $fileToParse;
|
||||
$this->input = $inputTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
* @return CommandResponse
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$audio = AudioCache::get($this->fileToParse->getPathname());
|
||||
list($parsedTags, $rawTags) = $this->parseOriginalTags($this->fileToParse, $this->track->user, $audio->getAudioCodec());
|
||||
$this->track->original_tags = ['parsed_tags' => $parsedTags, 'raw_tags' => $rawTags];
|
||||
|
||||
|
||||
if ($this->input['cover'] !== null) {
|
||||
$this->track->cover_id = Image::upload($this->input['cover'], $this->track->user_id)->id;
|
||||
} else {
|
||||
$this->track->cover_id = $parsedTags['cover_id'];
|
||||
}
|
||||
|
||||
$this->track->title = $this->input['title'] ?? $parsedTags['title'] ?? $this->track->title;
|
||||
$this->track->track_type_id = $this->input['track_type_id'] ?? TrackType::UNCLASSIFIED_TRACK;
|
||||
|
||||
$this->track->genre_id = isset($this->input['genre'])
|
||||
? $this->getGenreId($this->input['genre'])
|
||||
: $parsedTags['genre_id'];
|
||||
|
||||
$this->track->album_id = isset($this->input['album'])
|
||||
? $this->getAlbumId($this->track->user_id, $this->input['album'])
|
||||
: $parsedTags['album_id'];
|
||||
|
||||
if ($this->track->album_id === null) {
|
||||
$this->track->track_number = null;
|
||||
} else {
|
||||
$this->track->track_number = $this->input['track_number'] ?? $parsedTags['track_number'];
|
||||
}
|
||||
|
||||
$this->track->released_at = isset($this->input['released_at'])
|
||||
? Carbon::createFromFormat(Carbon::ISO8601, $this->input['released_at'])
|
||||
: $parsedTags['release_date'];
|
||||
|
||||
$this->track->description = $this->input['description'] ?? $parsedTags['comments'];
|
||||
$this->track->lyrics = $this->input['lyrics'] ?? $parsedTags['lyrics'];
|
||||
|
||||
$this->track->is_vocal = $this->input['is_vocal'] ?? $parsedTags['is_vocal'];
|
||||
$this->track->is_explicit = $this->input['is_explicit'] ?? false;
|
||||
$this->track->is_downloadable = $this->input['is_downloadable'] ?? true;
|
||||
$this->track->is_listed = $this->input['is_listed'] ?? true;
|
||||
|
||||
$this->track->save();
|
||||
return CommandResponse::succeed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the given genre, creating it if necessary.
|
||||
*
|
||||
* @param string $genreName
|
||||
* @return int
|
||||
*/
|
||||
protected function getGenreId(string $genreName) {
|
||||
return Genre::firstOrCreate([
|
||||
'name' => $genreName,
|
||||
'slug' => Str::slug($genreName)
|
||||
])->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the given album, creating it if necessary.
|
||||
* The cover ID is only used if a new album is created - it will not be
|
||||
* written to an existing album.
|
||||
*
|
||||
* @param int $artistId
|
||||
* @param string|null $albumName
|
||||
* @param null $coverId
|
||||
* @return int|null
|
||||
*/
|
||||
protected function getAlbumId(int $artistId, $albumName, $coverId = null) {
|
||||
if (null !== $albumName) {
|
||||
$album = Album::firstOrNew([
|
||||
'user_id' => $artistId,
|
||||
'title' => $albumName
|
||||
]);
|
||||
|
||||
if (null === $album->id) {
|
||||
$album->description = '';
|
||||
$album->track_count = 0;
|
||||
$album->cover_id = $coverId;
|
||||
$album->save();
|
||||
}
|
||||
|
||||
return $album->id;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a file's tags.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\File\File $file
|
||||
* @param User $artist
|
||||
* @param string $audioCodec
|
||||
* @return array the "processed" and raw tags extracted from the file
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function parseOriginalTags(\Symfony\Component\HttpFoundation\File\File $file, User $artist, string $audioCodec) {
|
||||
//==========================================================================================================
|
||||
// Extract the original tags.
|
||||
//==========================================================================================================
|
||||
$getId3 = new getID3;
|
||||
|
||||
// all tags read by getID3, including the cover art
|
||||
$allTags = $getId3->analyze($file->getPathname());
|
||||
|
||||
// $rawTags => tags specific to a file format (ID3 or Atom), pre-normalization but with cover art removed
|
||||
// $parsedTags => normalized tags used by Pony.fm
|
||||
|
||||
if ($audioCodec === 'mp3') {
|
||||
list($parsedTags, $rawTags) = $this->getId3Tags($allTags);
|
||||
|
||||
} elseif (Str::startsWith($audioCodec, ['aac', 'alac'])) {
|
||||
list($parsedTags, $rawTags) = $this->getAtomTags($allTags);
|
||||
|
||||
} elseif (in_array($audioCodec, ['vorbis', 'flac'])) {
|
||||
list($parsedTags, $rawTags) = $this->getVorbisTags($allTags);
|
||||
|
||||
} elseif (Str::startsWith($audioCodec, ['pcm', 'adpcm'])) {
|
||||
list($parsedTags, $rawTags) = $this->getAtomTags($allTags);
|
||||
|
||||
} else {
|
||||
// Assume the file is untagged if it's in an unknown format.
|
||||
$parsedTags = [
|
||||
'title' => null,
|
||||
'artist' => null,
|
||||
'band' => null,
|
||||
'genre' => null,
|
||||
'track_number' => null,
|
||||
'album' => null,
|
||||
'year' => null,
|
||||
'release_date' => null,
|
||||
'comments' => null,
|
||||
'lyrics' => null,
|
||||
];
|
||||
$rawTags = [];
|
||||
}
|
||||
|
||||
//==========================================================================================================
|
||||
// Determine the release date.
|
||||
//==========================================================================================================
|
||||
if ($parsedTags['release_date'] === null && $parsedTags['year'] !== null) {
|
||||
$parsedTags['release_date'] = Carbon::create($parsedTags['year'], 1, 1);
|
||||
}
|
||||
|
||||
//==========================================================================================================
|
||||
// Does this track have vocals?
|
||||
//==========================================================================================================
|
||||
$parsedTags['is_vocal'] = $parsedTags['lyrics'] !== null;
|
||||
|
||||
|
||||
//==========================================================================================================
|
||||
// Determine the genre.
|
||||
//==========================================================================================================
|
||||
$genreName = $parsedTags['genre'];
|
||||
|
||||
if ($genreName !== null) {
|
||||
$parsedTags['genre_id'] = $this->getGenreId($genreName);
|
||||
|
||||
} else {
|
||||
$parsedTags['genre_id'] = null;
|
||||
}
|
||||
|
||||
//==========================================================================================================
|
||||
// Extract the cover art, if any exists.
|
||||
//==========================================================================================================
|
||||
|
||||
$coverId = null;
|
||||
if (array_key_exists('comments', $allTags) && array_key_exists('picture', $allTags['comments'])) {
|
||||
$image = $allTags['comments']['picture'][0];
|
||||
|
||||
if ($image['image_mime'] === 'image/png') {
|
||||
$extension = 'png';
|
||||
|
||||
} elseif ($image['image_mime'] === 'image/jpeg') {
|
||||
$extension = 'jpg';
|
||||
|
||||
} else {
|
||||
throw new BadRequestHttpException('Unknown cover format embedded in the track file!');
|
||||
}
|
||||
|
||||
// write temporary image file
|
||||
$tmpPath = Config::get('ponyfm.files_directory') . '/tmp';
|
||||
|
||||
$filename = $file->getFilename() . ".cover.${extension}";
|
||||
$imageFilePath = "${tmpPath}/${filename}";
|
||||
|
||||
File::put($imageFilePath, $image['data']);
|
||||
$imageFile = new UploadedFile($imageFilePath, $filename, $image['image_mime']);
|
||||
|
||||
$cover = Image::upload($imageFile, $artist);
|
||||
$coverId = $cover->id;
|
||||
|
||||
} else {
|
||||
// no cover art was found - carry on
|
||||
}
|
||||
|
||||
$parsedTags['cover_id'] = $coverId;
|
||||
|
||||
|
||||
//==========================================================================================================
|
||||
// Is this part of an album?
|
||||
//==========================================================================================================
|
||||
$albumId = null;
|
||||
$albumName = $parsedTags['album'];
|
||||
|
||||
if ($albumName !== null) {
|
||||
$albumId = $this->getAlbumId($artist->id, $albumName, $coverId);
|
||||
}
|
||||
|
||||
$parsedTags['album_id'] = $albumId;
|
||||
|
||||
|
||||
return [$parsedTags, $rawTags];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $rawTags
|
||||
* @return array
|
||||
*/
|
||||
protected function getId3Tags($rawTags) {
|
||||
if (array_key_exists('tags', $rawTags) && array_key_exists('id3v2', $rawTags['tags'])) {
|
||||
$tags = $rawTags['tags']['id3v2'];
|
||||
} elseif (array_key_exists('tags', $rawTags) && array_key_exists('id3v1', $rawTags['tags'])) {
|
||||
$tags = $rawTags['tags']['id3v1'];
|
||||
} else {
|
||||
$tags = [];
|
||||
}
|
||||
|
||||
|
||||
$comment = null;
|
||||
|
||||
if (isset($tags['comment'])) {
|
||||
// The "comment" tag comes in with a badly encoded string index
|
||||
// so its array key has to be used implicitly.
|
||||
$key = array_keys($tags['comment'])[0];
|
||||
|
||||
// The comment may have a null byte at the end. trim() removes it.
|
||||
$comment = trim($tags['comment'][$key]);
|
||||
|
||||
// Replace the malformed comment with the "fixed" one.
|
||||
unset($tags['comment'][$key]);
|
||||
$tags['comment'][0] = $comment;
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
||||
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
||||
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
||||
'track_number' => isset($tags['track_number']) ? $tags['track_number'][0] : null,
|
||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
||||
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
||||
'release_date' => isset($tags['release_date']) ? $this->parseDateString($tags['release_date'][0]) : null,
|
||||
'comments' => $comment,
|
||||
'lyrics' => isset($tags['unsynchronised_lyric']) ? $tags['unsynchronised_lyric'][0] : null,
|
||||
],
|
||||
$tags
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $rawTags
|
||||
* @return array
|
||||
*/
|
||||
protected function getAtomTags($rawTags) {
|
||||
if (array_key_exists('tags', $rawTags) && array_key_exists('quicktime', $rawTags['tags'])) {
|
||||
$tags = $rawTags['tags']['quicktime'];
|
||||
} else {
|
||||
$tags = [];
|
||||
}
|
||||
|
||||
$trackNumber = null;
|
||||
if (isset($tags['track_number'])) {
|
||||
$trackNumberComponents = explode('/', $tags['track_number'][0]);
|
||||
$trackNumber = $trackNumberComponents[0];
|
||||
}
|
||||
|
||||
if (isset($tags['release_date'])) {
|
||||
$releaseDate = $this->parseDateString($tags['release_date'][0]);
|
||||
|
||||
} elseif (isset($tags['creation_date'])) {
|
||||
$releaseDate = $this->parseDateString($tags['creation_date'][0]);
|
||||
} else {
|
||||
$releaseDate = 0;
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
||||
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
||||
'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null,
|
||||
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
||||
'track_number' => $trackNumber,
|
||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
||||
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
||||
'release_date' => $releaseDate,
|
||||
'comments' => isset($tags['comments']) ? $tags['comments'][0] : null,
|
||||
'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null,
|
||||
],
|
||||
$tags
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $rawTags
|
||||
* @return array
|
||||
*/
|
||||
protected function getVorbisTags($rawTags) {
|
||||
if (array_key_exists('tags', $rawTags) && array_key_exists('vorbiscomment', $rawTags['tags'])) {
|
||||
$tags = $rawTags['tags']['vorbiscomment'];
|
||||
} else {
|
||||
$tags = [];
|
||||
}
|
||||
|
||||
$trackNumber = null;
|
||||
if (isset($tags['track_number'])) {
|
||||
$trackNumberComponents = explode('/', $tags['track_number'][0]);
|
||||
$trackNumber = $trackNumberComponents[0];
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
||||
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
||||
'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null,
|
||||
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
||||
'track_number' => $trackNumber,
|
||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
||||
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
||||
'release_date' => isset($tags['date']) ? $this->parseDateString($tags['date'][0]) : null,
|
||||
'comments' => isset($tags['comments']) ? $tags['comments'][0] : null,
|
||||
'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null,
|
||||
],
|
||||
$tags
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a potentially-partial date string into a proper date object.
|
||||
*
|
||||
* The tagging formats we deal with base their date format on ISO 8601, but
|
||||
* the timestamp may be incomplete.
|
||||
*
|
||||
* @link https://code.google.com/p/mp4v2/wiki/iTunesMetadata
|
||||
* @link https://wiki.xiph.org/VorbisComment#Date_and_time
|
||||
* @link http://id3.org/id3v2.4.0-frames
|
||||
*
|
||||
* @param string $dateString
|
||||
* @return null|Carbon
|
||||
*/
|
||||
protected function parseDateString(string $dateString) {
|
||||
switch (Str::length($dateString)) {
|
||||
// YYYY
|
||||
case 4:
|
||||
return Carbon::createFromFormat('Y', $dateString)
|
||||
->month(1)
|
||||
->day(1);
|
||||
|
||||
// YYYY-MM
|
||||
case 7:
|
||||
return Carbon::createFromFormat('Y-m', $dateString)
|
||||
->day(1);
|
||||
|
||||
// YYYY-MM-DD
|
||||
case 10:
|
||||
return Carbon::createFromFormat('Y-m-d', $dateString);
|
||||
break;
|
||||
|
||||
default:
|
||||
// We might have an ISO-8601 string in our hooves.
|
||||
// If not, give up.
|
||||
try {
|
||||
return Carbon::createFromFormat(Carbon::ISO8601, $dateString);
|
||||
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,12 +21,16 @@
|
|||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use Gate;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Support\Str;
|
||||
use Poniverse\Ponyfm\Genre;
|
||||
use Poniverse\Ponyfm\Jobs\UpdateTagsForRenamedGenre;
|
||||
use Poniverse\Ponyfm\Models\Genre;
|
||||
use Validator;
|
||||
|
||||
class RenameGenreCommand extends CommandBase
|
||||
{
|
||||
use DispatchesJobs;
|
||||
|
||||
/** @var Genre */
|
||||
private $_genre;
|
||||
private $_newName;
|
||||
|
@ -72,6 +76,8 @@ class RenameGenreCommand extends CommandBase
|
|||
$this->_genre->slug = $slug;
|
||||
$this->_genre->save();
|
||||
|
||||
$this->dispatch(new UpdateTagsForRenamedGenre($this->_genre));
|
||||
|
||||
return CommandResponse::succeed(['message' => 'Genre renamed!']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\Image;
|
||||
use Poniverse\Ponyfm\Models\Image;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\Favourite;
|
||||
use Poniverse\Ponyfm\ResourceUser;
|
||||
use Poniverse\Ponyfm\Models\Favourite;
|
||||
use Poniverse\Ponyfm\Models\ResourceUser;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\Follower;
|
||||
use Poniverse\Ponyfm\ResourceUser;
|
||||
use Poniverse\Ponyfm\Models\Follower;
|
||||
use Poniverse\Ponyfm\Models\ResourceUser;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class ToggleFollowingCommand extends CommandBase
|
||||
|
|
|
@ -22,46 +22,30 @@ namespace Poniverse\Ponyfm\Commands;
|
|||
|
||||
use Carbon\Carbon;
|
||||
use Config;
|
||||
use getID3;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Input;
|
||||
use Poniverse\Ponyfm\Album;
|
||||
use Poniverse\Ponyfm\Exceptions\InvalidEncodeOptionsException;
|
||||
use Poniverse\Ponyfm\Genre;
|
||||
use Poniverse\Ponyfm\Image;
|
||||
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\TrackFile;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use AudioCache;
|
||||
use File;
|
||||
use Illuminate\Support\Str;
|
||||
use Poniverse\Ponyfm\TrackType;
|
||||
use Poniverse\Ponyfm\User;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Validator;
|
||||
|
||||
class UploadTrackCommand extends CommandBase
|
||||
{
|
||||
use DispatchesJobs;
|
||||
|
||||
|
||||
private $_allowLossy;
|
||||
private $_allowShortTrack;
|
||||
private $_customTrackSource;
|
||||
private $_autoPublishByDefault;
|
||||
|
||||
private $_losslessFormats = [
|
||||
'flac',
|
||||
'pcm_s16le ([1][0][0][0] / 0x0001)',
|
||||
'pcm_s16be',
|
||||
'adpcm_ms ([2][0][0][0] / 0x0002)',
|
||||
'pcm_s24le ([1][0][0][0] / 0x0001)',
|
||||
'pcm_s24be',
|
||||
'pcm_f32le ([3][0][0][0] / 0x0003)',
|
||||
'pcm_f32be (fl32 / 0x32336C66)'
|
||||
];
|
||||
|
||||
public function __construct($allowLossy = false, $allowShortTrack = false, $customTrackSource = null, $autoPublishByDefault = false)
|
||||
/**
|
||||
* UploadTrackCommand constructor.
|
||||
*
|
||||
* @param bool $allowLossy
|
||||
* @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
|
||||
*/
|
||||
public function __construct(bool $allowLossy = false, bool $allowShortTrack = false, string $customTrackSource = null, bool $autoPublishByDefault = false)
|
||||
{
|
||||
$this->_allowLossy = $allowLossy;
|
||||
$this->_allowShortTrack = $allowShortTrack;
|
||||
|
@ -84,22 +68,21 @@ class UploadTrackCommand extends CommandBase
|
|||
public function execute()
|
||||
{
|
||||
$user = \Auth::user();
|
||||
$trackFile = \Input::file('track', null);
|
||||
$trackFile = Input::file('track', null);
|
||||
$coverFile = Input::file('cover', null);
|
||||
|
||||
if (null === $trackFile) {
|
||||
return CommandResponse::fail(['track' => ['You must upload an audio file!']]);
|
||||
}
|
||||
|
||||
$audio = \AudioCache::get($trackFile->getPathname());
|
||||
list($parsedTags, $rawTags) = $this->parseOriginalTags($trackFile, $user, $audio->getAudioCodec());
|
||||
|
||||
|
||||
$track = new Track();
|
||||
$track->user_id = $user->id;
|
||||
$track->title = Input::get('title', $parsedTags['title']);
|
||||
// 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();
|
||||
|
||||
|
@ -110,11 +93,14 @@ class UploadTrackCommand extends CommandBase
|
|||
|
||||
$input = Input::all();
|
||||
$input['track'] = $trackFile;
|
||||
$input['cover'] = $coverFile;
|
||||
|
||||
$validator = \Validator::make($input, [
|
||||
'track' =>
|
||||
'required|'
|
||||
. ($this->_allowLossy ? '' : 'audio_format:'. implode(',', $this->_losslessFormats).'|')
|
||||
. ($this->_allowLossy
|
||||
? 'audio_format:flac,alac,pcm,adpcm,aac,mp3,vorbis|'
|
||||
: 'audio_format:flac,alac,pcm,adpcm|')
|
||||
. ($this->_allowShortTrack ? '' : 'min_duration:30|')
|
||||
. 'audio_channels:1,2',
|
||||
|
||||
|
@ -139,467 +125,22 @@ class UploadTrackCommand extends CommandBase
|
|||
$track->delete();
|
||||
return CommandResponse::fail($validator);
|
||||
}
|
||||
|
||||
|
||||
// Process optional track fields
|
||||
$autoPublish = (bool) ($input['auto_publish'] ?? $this->_autoPublishByDefault);
|
||||
|
||||
if (Input::hasFile('cover')) {
|
||||
$track->cover_id = Image::upload(Input::file('cover'), $track->user_id)->id;
|
||||
} else {
|
||||
$track->cover_id = $parsedTags['cover_id'];
|
||||
}
|
||||
|
||||
$track->title = $input['title'] ?? $parsedTags['title'] ?? $track->title;
|
||||
$track->track_type_id = $input['track_type_id'] ?? TrackType::UNCLASSIFIED_TRACK;
|
||||
|
||||
$track->genre_id = isset($input['genre'])
|
||||
? $this->getGenreId($input['genre'])
|
||||
: $parsedTags['genre_id'];
|
||||
|
||||
$track->album_id = isset($input['album'])
|
||||
? $this->getAlbumId($user->id, $input['album'])
|
||||
: $parsedTags['album_id'];
|
||||
|
||||
if ($track->album_id === null) {
|
||||
$track->track_number = null;
|
||||
} else {
|
||||
$track->track_number = $input['track_number'] ?? $parsedTags['track_number'];
|
||||
}
|
||||
|
||||
$track->released_at = isset($input['released_at'])
|
||||
? Carbon::createFromFormat(Carbon::ISO8601, $input['released_at'])
|
||||
: $parsedTags['release_date'];
|
||||
|
||||
$track->description = $input['description'] ?? $parsedTags['comments'];
|
||||
$track->lyrics = $input['lyrics'] ?? $parsedTags['lyrics'];
|
||||
|
||||
$track->is_vocal = $input['is_vocal'] ?? $parsedTags['is_vocal'];
|
||||
$track->is_explicit = $input['is_explicit'] ?? false;
|
||||
$track->is_downloadable = $input['is_downloadable'] ?? true;
|
||||
$track->is_listed = $input['is_listed'] ?? true;
|
||||
$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->original_tags = ['parsed_tags' => $parsedTags, 'raw_tags' => $rawTags];
|
||||
|
||||
$track->save();
|
||||
|
||||
|
||||
try {
|
||||
$source = $trackFile->getPathname();
|
||||
|
||||
// Lossy uploads need to be identified and set as the master file
|
||||
// without being re-encoded.
|
||||
$audioObject = AudioCache::get($source);
|
||||
$isLossyUpload = !in_array($audioObject->getAudioCodec(), $this->_losslessFormats);
|
||||
|
||||
if ($isLossyUpload) {
|
||||
if ($audioObject->getAudioCodec() === 'mp3') {
|
||||
$masterFormat = 'MP3';
|
||||
|
||||
} else if (Str::startsWith($audioObject->getAudioCodec(), 'aac')) {
|
||||
$masterFormat = 'AAC';
|
||||
|
||||
} else if ($audioObject->getAudioCodec() === 'vorbis') {
|
||||
$masterFormat = 'OGG Vorbis';
|
||||
|
||||
} else {
|
||||
$validator->messages()->add('track', 'The track does not contain audio in a known lossy format.');
|
||||
$track->delete();
|
||||
return CommandResponse::fail($validator);
|
||||
// Parse any tags in the uploaded files.
|
||||
$parseTagsCommand = new ParseTrackTagsCommand($track, $trackFile, $input);
|
||||
$result = $parseTagsCommand->execute();
|
||||
if ($result->didFail()) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$trackFile = new TrackFile();
|
||||
$trackFile->is_master = true;
|
||||
$trackFile->format = $masterFormat;
|
||||
$trackFile->track_id = $track->id;
|
||||
$trackFile->save();
|
||||
|
||||
// Lossy masters are copied into the datastore - no re-encoding involved.
|
||||
File::copy($source, $trackFile->getFile());
|
||||
}
|
||||
|
||||
|
||||
$trackFiles = [];
|
||||
|
||||
foreach (Track::$Formats as $name => $format) {
|
||||
// Don't bother with lossless transcodes of lossy uploads, and
|
||||
// don't re-encode the lossy master.
|
||||
if ($isLossyUpload && ($format['is_lossless'] || $name === $masterFormat)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$trackFile = new TrackFile();
|
||||
$trackFile->is_master = $name === 'FLAC' ? true : false;
|
||||
$trackFile->format = $name;
|
||||
$trackFile->status = TrackFile::STATUS_PROCESSING;
|
||||
|
||||
if (in_array($name, Track::$CacheableFormats) && $trackFile->is_master == false) {
|
||||
$trackFile->is_cacheable = true;
|
||||
} else {
|
||||
$trackFile->is_cacheable = false;
|
||||
}
|
||||
$track->trackFiles()->save($trackFile);
|
||||
|
||||
// All TrackFile records we need are synchronously created
|
||||
// before kicking off the encode jobs in order to avoid a race
|
||||
// condition with the "temporary" source file getting deleted.
|
||||
$trackFiles[] = $trackFile;
|
||||
}
|
||||
|
||||
try {
|
||||
foreach($trackFiles as $trackFile) {
|
||||
$this->dispatch(new EncodeTrackFile($trackFile, false, true, $autoPublish));
|
||||
}
|
||||
|
||||
} catch (InvalidEncodeOptionsException $e) {
|
||||
$track->delete();
|
||||
return CommandResponse::fail(['track' => [$e->getMessage()]]);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$track->delete();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return CommandResponse::succeed([
|
||||
'id' => $track->id,
|
||||
'name' => $track->name,
|
||||
'title' => $track->title,
|
||||
'slug' => $track->slug,
|
||||
'autoPublish' => $autoPublish,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the given genre, creating it if necessary.
|
||||
*
|
||||
* @param string $genreName
|
||||
* @return int
|
||||
*/
|
||||
protected function getGenreId(string $genreName) {
|
||||
return Genre::firstOrCreate([
|
||||
'name' => $genreName,
|
||||
'slug' => Str::slug($genreName)
|
||||
])->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the given album, creating it if necessary.
|
||||
* The cover ID is only used if a new album is created - it will not be
|
||||
* written to an existing album.
|
||||
*
|
||||
* @param int $artistId
|
||||
* @param string|null $albumName
|
||||
* @param null $coverId
|
||||
* @return int|null
|
||||
*/
|
||||
protected function getAlbumId(int $artistId, $albumName, $coverId = null) {
|
||||
if (null !== $albumName) {
|
||||
$album = Album::firstOrNew([
|
||||
'user_id' => $artistId,
|
||||
'title' => $albumName
|
||||
]);
|
||||
|
||||
if (null === $album->id) {
|
||||
$album->description = '';
|
||||
$album->track_count = 0;
|
||||
$album->cover_id = $coverId;
|
||||
$album->save();
|
||||
}
|
||||
|
||||
return $album->id;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a file's tags.
|
||||
*
|
||||
* @param UploadedFile $file
|
||||
* @param User $artist
|
||||
* @param string $audioCodec
|
||||
* @return array the "processed" and raw tags extracted from the file
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function parseOriginalTags(UploadedFile $file, User $artist, string $audioCodec) {
|
||||
//==========================================================================================================
|
||||
// Extract the original tags.
|
||||
//==========================================================================================================
|
||||
$getId3 = new getID3;
|
||||
|
||||
// all tags read by getID3, including the cover art
|
||||
$allTags = $getId3->analyze($file->getPathname());
|
||||
|
||||
// tags specific to a file format (ID3 or Atom), pre-normalization but with cover art removed
|
||||
$rawTags = [];
|
||||
|
||||
// normalized tags used by Pony.fm
|
||||
$parsedTags = [];
|
||||
|
||||
if ($audioCodec === 'mp3') {
|
||||
list($parsedTags, $rawTags) = $this->getId3Tags($allTags);
|
||||
|
||||
} elseif (Str::startsWith($audioCodec, 'aac')) {
|
||||
list($parsedTags, $rawTags) = $this->getAtomTags($allTags);
|
||||
|
||||
} elseif ($audioCodec === 'vorbis') {
|
||||
list($parsedTags, $rawTags) = $this->getVorbisTags($allTags);
|
||||
|
||||
} elseif ($audioCodec === 'flac') {
|
||||
list($parsedTags, $rawTags) = $this->getVorbisTags($allTags);
|
||||
|
||||
} elseif (Str::startsWith($audioCodec, ['pcm', 'adpcm'])) {
|
||||
list($parsedTags, $rawTags) = $this->getAtomTags($allTags);
|
||||
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================================================
|
||||
// Fill in the title tag if it's missing
|
||||
//==========================================================================================================
|
||||
$parsedTags['title'] = $parsedTags['title'] ?? pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
|
||||
|
||||
|
||||
//==========================================================================================================
|
||||
// Determine the release date.
|
||||
//==========================================================================================================
|
||||
if ($parsedTags['release_date'] === null && $parsedTags['year'] !== null) {
|
||||
$parsedTags['release_date'] = Carbon::create($parsedTags['year'], 1, 1);
|
||||
}
|
||||
|
||||
//==========================================================================================================
|
||||
// Does this track have vocals?
|
||||
//==========================================================================================================
|
||||
$parsedTags['is_vocal'] = $parsedTags['lyrics'] !== null;
|
||||
|
||||
|
||||
//==========================================================================================================
|
||||
// Determine the genre.
|
||||
//==========================================================================================================
|
||||
$genreName = $parsedTags['genre'];
|
||||
|
||||
if ($genreName) {
|
||||
$parsedTags['genre_id'] = $this->getGenreId($genreName);
|
||||
|
||||
} else {
|
||||
$parsedTags['genre_id'] = $this->getGenreId('Unknown');
|
||||
}
|
||||
|
||||
//==========================================================================================================
|
||||
// Extract the cover art, if any exists.
|
||||
//==========================================================================================================
|
||||
|
||||
$coverId = null;
|
||||
if (array_key_exists('comments', $allTags) && array_key_exists('picture', $allTags['comments'])) {
|
||||
$image = $allTags['comments']['picture'][0];
|
||||
|
||||
if ($image['image_mime'] === 'image/png') {
|
||||
$extension = 'png';
|
||||
|
||||
} elseif ($image['image_mime'] === 'image/jpeg') {
|
||||
$extension = 'jpg';
|
||||
|
||||
} else {
|
||||
throw new BadRequestHttpException('Unknown cover format embedded in the track file!');
|
||||
}
|
||||
|
||||
// write temporary image file
|
||||
$tmpPath = Config::get('ponyfm.files_directory') . '/tmp';
|
||||
|
||||
$filename = $file->getFilename() . ".cover.${extension}";
|
||||
$imageFilePath = "${tmpPath}/${filename}";
|
||||
|
||||
File::put($imageFilePath, $image['data']);
|
||||
$imageFile = new UploadedFile($imageFilePath, $filename, $image['image_mime']);
|
||||
|
||||
$cover = Image::upload($imageFile, $artist);
|
||||
$coverId = $cover->id;
|
||||
|
||||
} else {
|
||||
// no cover art was found - carry on
|
||||
}
|
||||
|
||||
$parsedTags['cover_id'] = $coverId;
|
||||
|
||||
|
||||
//==========================================================================================================
|
||||
// Is this part of an album?
|
||||
//==========================================================================================================
|
||||
$albumId = null;
|
||||
$albumName = $parsedTags['album'];
|
||||
|
||||
if ($albumName !== null) {
|
||||
$albumId = $this->getAlbumId($artist->id, $albumName, $coverId);
|
||||
}
|
||||
|
||||
$parsedTags['album_id'] = $albumId;
|
||||
|
||||
|
||||
return [$parsedTags, $rawTags];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $rawTags
|
||||
* @return array
|
||||
*/
|
||||
protected function getId3Tags($rawTags) {
|
||||
if (array_key_exists('tags', $rawTags) && array_key_exists('id3v2', $rawTags['tags'])) {
|
||||
$tags = $rawTags['tags']['id3v2'];
|
||||
} elseif (array_key_exists('tags', $rawTags) && array_key_exists('id3v1', $rawTags['tags'])) {
|
||||
$tags = $rawTags['tags']['id3v1'];
|
||||
} else {
|
||||
$tags = [];
|
||||
}
|
||||
|
||||
|
||||
$comment = null;
|
||||
|
||||
if (isset($tags['comment'])) {
|
||||
// The "comment" tag comes in with a badly encoded string index
|
||||
// so its array key has to be used implicitly.
|
||||
$key = array_keys($tags['comment'])[0];
|
||||
|
||||
// The comment may have a null byte at the end. trim() removes it.
|
||||
$comment = trim($tags['comment'][$key]);
|
||||
|
||||
// Replace the malformed comment with the "fixed" one.
|
||||
unset($tags['comment'][$key]);
|
||||
$tags['comment'][0] = $comment;
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
||||
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
||||
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
||||
'track_number' => isset($tags['track_number']) ? $tags['track_number'][0] : null,
|
||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
||||
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
||||
'release_date' => isset($tags['release_date']) ? $this->parseDateString($tags['release_date'][0]) : null,
|
||||
'comments' => $comment,
|
||||
'lyrics' => isset($tags['unsynchronised_lyric']) ? $tags['unsynchronised_lyric'][0] : null,
|
||||
],
|
||||
$tags
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $rawTags
|
||||
* @return array
|
||||
*/
|
||||
protected function getAtomTags($rawTags) {
|
||||
if (array_key_exists('tags', $rawTags) && array_key_exists('quicktime', $rawTags['tags'])) {
|
||||
$tags = $rawTags['tags']['quicktime'];
|
||||
} else {
|
||||
$tags = [];
|
||||
}
|
||||
|
||||
$trackNumber = null;
|
||||
if (isset($tags['track_number'])) {
|
||||
$trackNumberComponents = explode('/', $tags['track_number'][0]);
|
||||
$trackNumber = $trackNumberComponents[0];
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
||||
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
||||
'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null,
|
||||
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
||||
'track_number' => $trackNumber,
|
||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
||||
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
||||
'release_date' => isset($tags['release_date']) ? $this->parseDateString($tags['release_date'][0]) : null,
|
||||
'comments' => isset($tags['comments']) ? $tags['comments'][0] : null,
|
||||
'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null,
|
||||
],
|
||||
$tags
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $rawTags
|
||||
* @return array
|
||||
*/
|
||||
protected function getVorbisTags($rawTags) {
|
||||
if (array_key_exists('tags', $rawTags) && array_key_exists('vorbiscomment', $rawTags['tags'])) {
|
||||
$tags = $rawTags['tags']['vorbiscomment'];
|
||||
} else {
|
||||
$tags = [];
|
||||
}
|
||||
|
||||
$trackNumber = null;
|
||||
if (isset($tags['track_number'])) {
|
||||
$trackNumberComponents = explode('/', $tags['track_number'][0]);
|
||||
$trackNumber = $trackNumberComponents[0];
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
||||
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
||||
'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null,
|
||||
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
||||
'track_number' => $trackNumber,
|
||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
||||
'year' => isset($tags['year']) ? (int) $tags['year'][0] : null,
|
||||
'release_date' => isset($tags['date']) ? $this->parseDateString($tags['date'][0]) : null,
|
||||
'comments' => isset($tags['comments']) ? $tags['comments'][0] : null,
|
||||
'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null,
|
||||
],
|
||||
$tags
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a potentially-partial date string into a proper date object.
|
||||
*
|
||||
* The tagging formats we deal with base their date format on ISO 8601, but
|
||||
* the timestamp may be incomplete.
|
||||
*
|
||||
* @link https://code.google.com/p/mp4v2/wiki/iTunesMetadata
|
||||
* @link https://wiki.xiph.org/VorbisComment#Date_and_time
|
||||
* @link http://id3.org/id3v2.4.0-frames
|
||||
*
|
||||
* @param string $dateString
|
||||
* @return null|Carbon
|
||||
*/
|
||||
protected function parseDateString(string $dateString) {
|
||||
switch (Str::length($dateString)) {
|
||||
// YYYY
|
||||
case 4:
|
||||
return Carbon::createFromFormat('Y', $dateString)
|
||||
->month(1)
|
||||
->day(1);
|
||||
|
||||
// YYYY-MM
|
||||
case 7:
|
||||
return Carbon::createFromFormat('Y-m', $dateString)
|
||||
->day(1);
|
||||
|
||||
// YYYY-MM-DD
|
||||
case 10:
|
||||
return Carbon::createFromFormat('Y-m-d', $dateString);
|
||||
break;
|
||||
|
||||
default:
|
||||
// We might have an ISO-8601 string in our hooves.
|
||||
// If not, give up.
|
||||
try {
|
||||
return Carbon::createFromFormat(Carbon::ISO8601, $dateString);
|
||||
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
$generateTrackFiles = new GenerateTrackFilesCommand($track, $trackFile, $autoPublish);
|
||||
return $generateTrackFiles->execute();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,9 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Console\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\ShowSong;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\TrackType;
|
||||
use Poniverse\Ponyfm\Models\ShowSong;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\TrackType;
|
||||
use DB;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Str;
|
||||
|
|
|
@ -24,7 +24,7 @@ use Carbon\Carbon;
|
|||
use File;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Poniverse\Ponyfm\TrackFile;
|
||||
use Poniverse\Ponyfm\Models\TrackFile;
|
||||
|
||||
class ClearTrackCache extends Command
|
||||
{
|
||||
|
@ -96,9 +96,6 @@ class ClearTrackCache extends Command
|
|||
$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 {
|
||||
|
|
154
app/Console/Commands/FixMLPMAImages.php
Normal file
154
app/Console/Commands/FixMLPMAImages.php
Normal file
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* 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 Config;
|
||||
use DB;
|
||||
use File;
|
||||
use getID3;
|
||||
use Illuminate\Console\Command;
|
||||
use Poniverse\Ponyfm\Models\Image;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
class FixMLPMAImages extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'mlpma:fix-images
|
||||
{--startAt=1 : Track to start importing from. Useful for resuming an interrupted import.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Re-imports MLPMA cover art';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected $currentFile;
|
||||
|
||||
|
||||
/**
|
||||
* File extensions to ignore when importing the archive.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $ignoredExtensions = [
|
||||
'db',
|
||||
'jpg',
|
||||
'png',
|
||||
'txt',
|
||||
'rtf',
|
||||
'wma',
|
||||
'wmv'
|
||||
];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$mlpmaPath = Config::get('ponyfm.files_directory') . '/mlpma';
|
||||
$tmpPath = Config::get('ponyfm.files_directory') . '/tmp';
|
||||
|
||||
$this->comment('Enumerating MLP Music Archive source files...');
|
||||
$files = File::allFiles($mlpmaPath);
|
||||
$this->info(sizeof($files) . ' files found!');
|
||||
|
||||
$this->comment('Importing tracks...');
|
||||
$totalFiles = sizeof($files);
|
||||
$fileToStartAt = (int) $this->option('startAt') - 1;
|
||||
|
||||
$this->comment("Skipping $fileToStartAt files..." . PHP_EOL);
|
||||
$files = array_slice($files, $fileToStartAt);
|
||||
$this->currentFile = $fileToStartAt;
|
||||
|
||||
|
||||
foreach ($files as $file) {
|
||||
$this->currentFile ++;
|
||||
|
||||
$this->info('[' . $this->currentFile . '/' . $totalFiles . '] Importing track [' . $file->getFilename() . ']...');
|
||||
if (in_array($file->getExtension(), $this->ignoredExtensions)) {
|
||||
$this->comment('This is not an audio file! Skipping...' . PHP_EOL);
|
||||
continue;
|
||||
}
|
||||
// Get this track's MLPMA record
|
||||
$importedTrack = DB::table('mlpma_tracks')
|
||||
->where('filename', '=', $file->getFilename())
|
||||
->join('tracks', 'mlpma_tracks.track_id', '=', 'tracks.id')
|
||||
->first();
|
||||
$artistId = $importedTrack->user_id;
|
||||
|
||||
|
||||
//==========================================================================================================
|
||||
// Extract the original tags.
|
||||
//==========================================================================================================
|
||||
|
||||
$getId3 = new getID3;
|
||||
// all tags read by getID3, including the cover art
|
||||
$allTags = $getId3->analyze($file->getPathname());
|
||||
|
||||
|
||||
//==========================================================================================================
|
||||
// Extract the cover art, if any exists.
|
||||
//==========================================================================================================
|
||||
$coverId = null;
|
||||
|
||||
if (array_key_exists('comments', $allTags) && array_key_exists('picture', $allTags['comments'])) {
|
||||
$this->comment('Extracting cover art!');
|
||||
$image = $allTags['comments']['picture'][0];
|
||||
if ($image['image_mime'] === 'image/png') {
|
||||
$extension = 'png';
|
||||
} elseif ($image['image_mime'] === 'image/jpeg') {
|
||||
$extension = 'jpg';
|
||||
} elseif ($image['image_mime'] === 'image/gif') {
|
||||
$extension = 'gif';
|
||||
} else {
|
||||
$this->error('Unknown cover art format!');
|
||||
}
|
||||
// write temporary image file
|
||||
$imageFilename = $file->getFilename() . ".cover.$extension";
|
||||
$imageFilePath = "$tmpPath/" . $imageFilename;
|
||||
File::put($imageFilePath, $image['data']);
|
||||
$imageFile = new UploadedFile($imageFilePath, $imageFilename, $image['image_mime']);
|
||||
$cover = Image::upload($imageFile, $artistId, true);
|
||||
$coverId = $cover->id;
|
||||
|
||||
} else {
|
||||
$this->comment('No cover art found!');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2015 Peter Deltchev
|
||||
*
|
||||
* 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 Poniverse\Ponyfm\ResourceLogItem;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class FixYearZeroLogs extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'poni:year-zero';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Fills in missing timestamps in the resource_log_items table.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$items = ResourceLogItem::where('created_at', '0000-00-00 00:00:00')->orderBy('id', 'asc')->get();
|
||||
$totalItems = $items->count();
|
||||
|
||||
// calculate the start and end of the logging gap
|
||||
$lastGoodId = (int) $items[0]->id - 1;
|
||||
$lastGoodItem = ResourceLogItem::find($lastGoodId);
|
||||
|
||||
$lastGoodDate = $lastGoodItem->created_at;
|
||||
$now = Carbon::now();
|
||||
|
||||
$secondsDifference = $now->diffInSeconds($lastGoodDate);
|
||||
$oneInterval = $secondsDifference / $totalItems;
|
||||
|
||||
$this->info('Correcting records...');
|
||||
$bar = $this->output->createProgressBar($totalItems);
|
||||
|
||||
foreach ($items as $i => $item) {
|
||||
$bar->advance();
|
||||
$dateOffset = (int) ($oneInterval * $i);
|
||||
$item->created_at = $lastGoodDate->copy()->addSeconds($dateOffset);
|
||||
$item->save();
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
$this->line('');
|
||||
$this->info('All done!');
|
||||
}
|
||||
}
|
|
@ -1,519 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2015 Peter Deltchev
|
||||
*
|
||||
* 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 Poniverse\Ponyfm\Album;
|
||||
use Poniverse\Ponyfm\Commands\UploadTrackCommand;
|
||||
use Poniverse\Ponyfm\Genre;
|
||||
use Poniverse\Ponyfm\Image;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\User;
|
||||
use Auth;
|
||||
use Carbon\Carbon;
|
||||
use Config;
|
||||
use DB;
|
||||
use File;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Str;
|
||||
use Input;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use getID3;
|
||||
|
||||
|
||||
class ImportMLPMA extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'mlpma:import
|
||||
{--startAt=1 : Track to start importing from. Useful for resuming an interrupted import.}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Imports the MLP Music Archive';
|
||||
|
||||
/**
|
||||
* File extensions to ignore when importing the archive.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $ignoredExtensions = ['db', 'jpg', 'png', 'txt', 'rtf', 'wma', 'wmv'];
|
||||
|
||||
/**
|
||||
* Used to stop the import process when a SIGINT is received.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isInterrupted = false;
|
||||
|
||||
/**
|
||||
* A counter for the number of processed tracks.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $currentFile;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handleInterrupt($signo)
|
||||
{
|
||||
$this->error('Import aborted!');
|
||||
$this->error('Resume it from here using: --startAt=' . $this->currentFile);
|
||||
$this->isInterrupted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
pcntl_signal(SIGINT, [$this, 'handleInterrupt']);
|
||||
|
||||
$mlpmaPath = Config::get('ponyfm.files_directory') . '/mlpma';
|
||||
$tmpPath = Config::get('ponyfm.files_directory') . '/tmp';
|
||||
|
||||
if (!File::exists($tmpPath)) {
|
||||
File::makeDirectory($tmpPath);
|
||||
}
|
||||
|
||||
$UNKNOWN_GENRE = Genre::firstOrCreate([
|
||||
'name' => 'Unknown',
|
||||
'slug' => 'unknown'
|
||||
]);
|
||||
|
||||
$this->comment('Enumerating MLP Music Archive source files...');
|
||||
$files = File::allFiles($mlpmaPath);
|
||||
$this->info(sizeof($files) . ' files found!');
|
||||
|
||||
$this->comment('Enumerating artists...');
|
||||
$artists = File::directories($mlpmaPath);
|
||||
$this->info(sizeof($artists) . ' artists found!');
|
||||
|
||||
$this->comment('Importing tracks...');
|
||||
|
||||
$totalFiles = sizeof($files);
|
||||
|
||||
$fileToStartAt = (int)$this->option('startAt') - 1;
|
||||
$this->comment("Skipping $fileToStartAt files..." . PHP_EOL);
|
||||
|
||||
$files = array_slice($files, $fileToStartAt);
|
||||
$this->currentFile = $fileToStartAt;
|
||||
|
||||
foreach ($files as $file) {
|
||||
$this->currentFile++;
|
||||
|
||||
pcntl_signal_dispatch();
|
||||
if ($this->isInterrupted) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->comment('[' . $this->currentFile . '/' . $totalFiles . '] Importing track [' . $file->getFilename() . ']...');
|
||||
|
||||
if (in_array($file->getExtension(), $this->ignoredExtensions)) {
|
||||
$this->comment('This is not an audio file! Skipping...' . PHP_EOL);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Has this track already been imported?
|
||||
$importedTrack = DB::table('mlpma_tracks')
|
||||
->where('filename', '=', $file->getFilename())
|
||||
->first();
|
||||
|
||||
if ($importedTrack) {
|
||||
$this->comment('This track has already been imported! Skipping...' . PHP_EOL);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================================================
|
||||
// Extract the original tags.
|
||||
//==========================================================================================================
|
||||
$getId3 = new getID3;
|
||||
|
||||
// all tags read by getID3, including the cover art
|
||||
$allTags = $getId3->analyze($file->getPathname());
|
||||
|
||||
// tags specific to a file format (ID3 or Atom), pre-normalization but with cover art removed
|
||||
$rawTags = [];
|
||||
|
||||
// normalized tags used by Pony.fm
|
||||
$parsedTags = [];
|
||||
|
||||
if (Str::lower($file->getExtension()) === 'mp3') {
|
||||
list($parsedTags, $rawTags) = $this->getId3Tags($allTags);
|
||||
|
||||
} elseif (Str::lower($file->getExtension()) === 'm4a') {
|
||||
list($parsedTags, $rawTags) = $this->getAtomTags($allTags);
|
||||
|
||||
} elseif (Str::lower($file->getExtension()) === 'ogg') {
|
||||
list($parsedTags, $rawTags) = $this->getVorbisTags($allTags);
|
||||
|
||||
} elseif (Str::lower($file->getExtension()) === 'flac') {
|
||||
list($parsedTags, $rawTags) = $this->getVorbisTags($allTags);
|
||||
|
||||
} elseif (Str::lower($file->getExtension()) === 'wav') {
|
||||
list($parsedTags, $rawTags) = $this->getAtomTags($allTags);
|
||||
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================================================
|
||||
// Determine the release date.
|
||||
//==========================================================================================================
|
||||
$modifiedDate = Carbon::createFromTimeStampUTC(File::lastModified($file->getPathname()));
|
||||
$taggedYear = $parsedTags['year'];
|
||||
|
||||
$this->info('Modification year: ' . $modifiedDate->year);
|
||||
$this->info('Tagged year: ' . $taggedYear);
|
||||
|
||||
if ($taggedYear !== null && $modifiedDate->year === $taggedYear) {
|
||||
$releasedAt = $modifiedDate;
|
||||
} elseif ($taggedYear !== null && Str::length((string)$taggedYear) !== 4) {
|
||||
$this->error('This track\'s tagged year makes no sense! Using the track\'s last modified date...');
|
||||
$releasedAt = $modifiedDate;
|
||||
} elseif ($taggedYear !== null && $modifiedDate->year !== $taggedYear) {
|
||||
$this->error('Release years don\'t match! Using the tagged year...');
|
||||
$releasedAt = Carbon::create($taggedYear);
|
||||
|
||||
} else {
|
||||
// $taggedYear is null
|
||||
$this->error('This track isn\'t tagged with its release year! Using the track\'s last modified date...');
|
||||
$releasedAt = $modifiedDate;
|
||||
}
|
||||
|
||||
// This is later used by the classification/publishing script to determine the publication date.
|
||||
$parsedTags['released_at'] = $releasedAt->toDateTimeString();
|
||||
|
||||
//==========================================================================================================
|
||||
// Does this track have vocals?
|
||||
//==========================================================================================================
|
||||
$isVocal = $parsedTags['lyrics'] !== null;
|
||||
|
||||
|
||||
//==========================================================================================================
|
||||
// Fill in the title tag if it's missing.
|
||||
//==========================================================================================================
|
||||
if (!$parsedTags['title']) {
|
||||
$parsedTags['title'] = $file->getBasename('.' . $file->getExtension());
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================================================
|
||||
// Determine the genre.
|
||||
//==========================================================================================================
|
||||
$genreName = $parsedTags['genre'];
|
||||
$genreSlug = Str::slug($genreName);
|
||||
$this->info('Genre: ' . $genreName);
|
||||
|
||||
if ($genreName && $genreSlug !== '') {
|
||||
$genre = Genre::where('name', '=', $genreName)->first();
|
||||
if ($genre) {
|
||||
$genreId = $genre->id;
|
||||
|
||||
} else {
|
||||
$genre = new Genre();
|
||||
$genre->name = $genreName;
|
||||
$genre->slug = $genreSlug;
|
||||
$genre->save();
|
||||
$genreId = $genre->id;
|
||||
$this->comment('Created a new genre!');
|
||||
}
|
||||
|
||||
} else {
|
||||
$genreId = $UNKNOWN_GENRE->id; // "Unknown" genre ID
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================================================
|
||||
// Determine which artist account this file belongs to using the containing directory.
|
||||
//==========================================================================================================
|
||||
$this->info('Path to file: ' . $file->getRelativePath());
|
||||
$path_components = explode(DIRECTORY_SEPARATOR, $file->getRelativePath());
|
||||
$artist_name = $path_components[0];
|
||||
$album_name = array_key_exists(1, $path_components) ? $path_components[1] : null;
|
||||
|
||||
$this->info('Artist: ' . $artist_name);
|
||||
$this->info('Album: ' . $album_name);
|
||||
|
||||
$artist = User::where('display_name', '=', $artist_name)->first();
|
||||
|
||||
if (!$artist) {
|
||||
$artist = new User;
|
||||
$artist->display_name = $artist_name;
|
||||
$artist->email = null;
|
||||
$artist->is_archived = true;
|
||||
|
||||
$artist->slug = Str::slug($artist_name);
|
||||
|
||||
$slugExists = User::where('slug', '=', $artist->slug)->first();
|
||||
if ($slugExists) {
|
||||
$this->error('Horsefeathers! The slug ' . $artist->slug . ' is already taken!');
|
||||
$artist->slug = $artist->slug . '-' . Str::random(4);
|
||||
}
|
||||
|
||||
$artist->save();
|
||||
}
|
||||
|
||||
//==========================================================================================================
|
||||
// Extract the cover art, if any exists.
|
||||
//==========================================================================================================
|
||||
|
||||
$this->comment('Extracting cover art!');
|
||||
$coverId = null;
|
||||
if (array_key_exists('comments', $allTags) && array_key_exists('picture', $allTags['comments'])) {
|
||||
$image = $allTags['comments']['picture'][0];
|
||||
|
||||
if ($image['image_mime'] === 'image/png') {
|
||||
$extension = 'png';
|
||||
|
||||
} elseif ($image['image_mime'] === 'image/jpeg') {
|
||||
$extension = 'jpg';
|
||||
|
||||
} elseif ($image['image_mime'] === 'image/gif') {
|
||||
$extension = 'gif';
|
||||
|
||||
} else {
|
||||
$this->error('Unknown cover art format!');
|
||||
}
|
||||
|
||||
// write temporary image file
|
||||
$imageFilename = $file->getFilename() . ".cover.$extension";
|
||||
$imageFilePath = "$tmpPath/" . $imageFilename;
|
||||
File::put($imageFilePath, $image['data']);
|
||||
|
||||
|
||||
$imageFile = new UploadedFile($imageFilePath, $imageFilename, $image['image_mime']);
|
||||
|
||||
$cover = Image::upload($imageFile, $artist);
|
||||
$coverId = $cover->id;
|
||||
|
||||
} else {
|
||||
$this->comment('No cover art found!');
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================================================
|
||||
// Is this part of an album?
|
||||
//==========================================================================================================
|
||||
|
||||
$albumId = null;
|
||||
$albumName = $parsedTags['album'];
|
||||
|
||||
if ($albumName !== null) {
|
||||
$album = Album::where('user_id', '=', $artist->id)
|
||||
->where('title', '=', $albumName)
|
||||
->first();
|
||||
|
||||
if (!$album) {
|
||||
$album = new Album;
|
||||
|
||||
$album->title = $albumName;
|
||||
$album->user_id = $artist->id;
|
||||
$album->cover_id = $coverId;
|
||||
|
||||
$album->save();
|
||||
}
|
||||
|
||||
$albumId = $album->id;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================================================
|
||||
// Save this track.
|
||||
//==========================================================================================================
|
||||
// "Upload" the track to Pony.fm
|
||||
$this->comment('Transcoding the track!');
|
||||
Auth::loginUsingId($artist->id);
|
||||
|
||||
$trackFile = new UploadedFile($file->getPathname(), $file->getFilename(), $allTags['mime_type']);
|
||||
Input::instance()->files->add(['track' => $trackFile]);
|
||||
|
||||
$upload = new UploadTrackCommand(true, true);
|
||||
$result = $upload->execute();
|
||||
|
||||
if ($result->didFail()) {
|
||||
$this->error(json_encode($result->getMessages(), JSON_PRETTY_PRINT));
|
||||
|
||||
} else {
|
||||
// Save metadata.
|
||||
$track = Track::find($result->getResponse()['id']);
|
||||
|
||||
$track->title = $parsedTags['title'];
|
||||
$track->cover_id = $coverId;
|
||||
$track->album_id = $albumId;
|
||||
$track->genre_id = $genreId;
|
||||
$track->track_number = $parsedTags['track_number'];
|
||||
$track->released_at = $releasedAt;
|
||||
$track->description = $parsedTags['comments'];
|
||||
$track->is_downloadable = true;
|
||||
$track->lyrics = $parsedTags['lyrics'];
|
||||
$track->is_vocal = $isVocal;
|
||||
$track->license_id = 2;
|
||||
$track->save();
|
||||
|
||||
// If we made it to here, the track is intact! Log the import.
|
||||
DB::table('mlpma_tracks')
|
||||
->insert([
|
||||
'track_id' => $result->getResponse()['id'],
|
||||
'path' => $file->getRelativePath(),
|
||||
'filename' => $file->getFilename(),
|
||||
'extension' => $file->getExtension(),
|
||||
'imported_at' => Carbon::now(),
|
||||
'parsed_tags' => json_encode($parsedTags),
|
||||
'raw_tags' => json_encode($rawTags),
|
||||
]);
|
||||
}
|
||||
|
||||
echo PHP_EOL . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $rawTags
|
||||
* @return array
|
||||
*/
|
||||
protected function getId3Tags($rawTags)
|
||||
{
|
||||
if (array_key_exists('tags', $rawTags) && array_key_exists('id3v2', $rawTags['tags'])) {
|
||||
$tags = $rawTags['tags']['id3v2'];
|
||||
} elseif (array_key_exists('tags', $rawTags) && array_key_exists('id3v1', $rawTags['tags'])) {
|
||||
$tags = $rawTags['tags']['id3v1'];
|
||||
} else {
|
||||
$tags = [];
|
||||
}
|
||||
|
||||
|
||||
$comment = null;
|
||||
|
||||
if (isset($tags['comment'])) {
|
||||
// The "comment" tag comes in with a badly encoded string index
|
||||
// so its array key has to be used implicitly.
|
||||
$key = array_keys($tags['comment'])[0];
|
||||
|
||||
// The comment may have a null byte at the end. trim() removes it.
|
||||
$comment = trim($tags['comment'][$key]);
|
||||
|
||||
// Replace the malformed comment with the "fixed" one.
|
||||
unset($tags['comment'][$key]);
|
||||
$tags['comment'][0] = $comment;
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
||||
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
||||
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
||||
'track_number' => isset($tags['track_number']) ? $tags['track_number'][0] : null,
|
||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
||||
'year' => isset($tags['year']) ? (int)$tags['year'][0] : null,
|
||||
'comments' => $comment,
|
||||
'lyrics' => isset($tags['unsynchronised_lyric']) ? $tags['unsynchronised_lyric'][0] : null,
|
||||
],
|
||||
$tags
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $rawTags
|
||||
* @return array
|
||||
*/
|
||||
protected function getAtomTags($rawTags)
|
||||
{
|
||||
if (array_key_exists('tags', $rawTags) && array_key_exists('quicktime', $rawTags['tags'])) {
|
||||
$tags = $rawTags['tags']['quicktime'];
|
||||
} else {
|
||||
$tags = [];
|
||||
}
|
||||
|
||||
$trackNumber = null;
|
||||
if (isset($tags['track_number'])) {
|
||||
$trackNumberComponents = explode('/', $tags['track_number'][0]);
|
||||
$trackNumber = $trackNumberComponents[0];
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
||||
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
||||
'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null,
|
||||
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
||||
'track_number' => $trackNumber,
|
||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
||||
'year' => isset($tags['year']) ? (int)$tags['year'][0] : null,
|
||||
'comments' => isset($tags['comments']) ? $tags['comments'][0] : null,
|
||||
'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null,
|
||||
],
|
||||
$tags
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $rawTags
|
||||
* @return array
|
||||
*/
|
||||
protected function getVorbisTags($rawTags)
|
||||
{
|
||||
if (array_key_exists('tags', $rawTags) && array_key_exists('vorbiscomment', $rawTags['tags'])) {
|
||||
$tags = $rawTags['tags']['vorbiscomment'];
|
||||
} else {
|
||||
$tags = [];
|
||||
}
|
||||
|
||||
$trackNumber = null;
|
||||
if (isset($tags['track_number'])) {
|
||||
$trackNumberComponents = explode('/', $tags['track_number'][0]);
|
||||
$trackNumber = $trackNumberComponents[0];
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
'title' => isset($tags['title']) ? $tags['title'][0] : null,
|
||||
'artist' => isset($tags['artist']) ? $tags['artist'][0] : null,
|
||||
'band' => isset($tags['band']) ? $tags['band'][0] : null,
|
||||
'album_artist' => isset($tags['album_artist']) ? $tags['album_artist'][0] : null,
|
||||
'genre' => isset($tags['genre']) ? $tags['genre'][0] : null,
|
||||
'track_number' => $trackNumber,
|
||||
'album' => isset($tags['album']) ? $tags['album'][0] : null,
|
||||
'year' => isset($tags['year']) ? (int)$tags['year'][0] : null,
|
||||
'comments' => isset($tags['comments']) ? $tags['comments'][0] : null,
|
||||
'lyrics' => isset($tags['lyrics']) ? $tags['lyrics'][0] : null,
|
||||
],
|
||||
$tags
|
||||
];
|
||||
}
|
||||
}
|
86
app/Console/Commands/MergeAccounts.php
Normal file
86
app/Console/Commands/MergeAccounts.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* 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 DB;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Collection;
|
||||
use Poniverse\Ponyfm\Commands\MergeAccountsCommand;
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use Poniverse\Ponyfm\Models\Comment;
|
||||
use Poniverse\Ponyfm\Models\Favourite;
|
||||
use Poniverse\Ponyfm\Models\Follower;
|
||||
use Poniverse\Ponyfm\Models\Image;
|
||||
use Poniverse\Ponyfm\Models\PinnedPlaylist;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||
use Poniverse\Ponyfm\Models\ResourceUser;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
|
||||
class MergeAccounts extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'accounts:merge
|
||||
{sourceAccountId : ID of the source account (the one being disabled and having content transferred out of it)}
|
||||
{destinationAccountId : ID of the destination account (the one gaining content)}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Merges two accounts';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$sourceAccountId = $this->argument('sourceAccountId');
|
||||
$destinationAccountId = $this->argument('destinationAccountId');
|
||||
|
||||
$sourceAccount = User::find($sourceAccountId);
|
||||
$destinationAccount = User::find($destinationAccountId);
|
||||
|
||||
$this->info("Merging {$sourceAccount->display_name} ({$sourceAccountId}) into {$destinationAccount->display_name} ({$destinationAccountId})...");
|
||||
|
||||
$command = new MergeAccountsCommand($sourceAccount, $destinationAccount);
|
||||
$command->execute();
|
||||
}
|
||||
}
|
|
@ -20,8 +20,8 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Console\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\Image;
|
||||
use Poniverse\Ponyfm\ResourceLogItem;
|
||||
use Poniverse\Ponyfm\Models\Image;
|
||||
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||
use DB;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2015 Peter Deltchev
|
||||
*
|
||||
* 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 DB;
|
||||
use Illuminate\Console\Command;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\TrackType;
|
||||
|
||||
class PublishUnclassifiedMlpmaTracks extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'mlpma:declassify';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'This publishes all unpublished MLPMA tracks as the "unclassified" track type.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$affectedTracks = Track::mlpma()->
|
||||
whereNull('published_at')
|
||||
->update([
|
||||
'track_type_id' => TrackType::UNCLASSIFIED_TRACK,
|
||||
'published_at' => DB::raw('released_at'),
|
||||
'updated_at' => Carbon::now(),
|
||||
]);
|
||||
|
||||
$this->info("Updated ${affectedTracks} tracks.");
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@
|
|||
namespace Poniverse\Ponyfm\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Poniverse\Ponyfm\User;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
|
||||
class RebuildArtists extends Command
|
||||
{
|
||||
|
@ -56,12 +56,19 @@ class RebuildArtists extends Command
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
$numberOfUsers = User::count();
|
||||
|
||||
$bar = $this->output->createProgressBar($numberOfUsers);
|
||||
|
||||
foreach(User::with(['tracks' => function($query) {
|
||||
$query->published()->listed();
|
||||
}])->get() as $user) {
|
||||
$bar->advance();
|
||||
$user->track_count = $user->tracks->count();
|
||||
$user->save();
|
||||
$this->info('Updated user #'.$user->id.'!');
|
||||
}
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
$this->line('');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Poniverse\Ponyfm\Console\Commands;
|
|||
|
||||
use File;
|
||||
use Illuminate\Console\Command;
|
||||
use Poniverse\Ponyfm\TrackFile;
|
||||
use Poniverse\Ponyfm\Models\TrackFile;
|
||||
|
||||
class RebuildFilesizes extends Command
|
||||
{
|
||||
|
|
121
app/Console/Commands/RebuildSearchIndex.php
Normal file
121
app/Console/Commands/RebuildSearchIndex.php
Normal file
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* 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 Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
|
||||
class RebuildSearchIndex extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'rebuild:search';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Rebuilds the Elasticsearch index.';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$totalTracks = Track::withTrashed()->count();
|
||||
$totalAlbums = Album::withTrashed()->count();
|
||||
$totalPlaylists = Playlist::withTrashed()->count();
|
||||
$totalUsers = User::count();
|
||||
|
||||
$trackProgress = $this->output->createProgressBar($totalTracks);
|
||||
$this->info("Processing tracks...");
|
||||
Track::withTrashed()->chunk(200, function(Collection $tracks) use ($trackProgress) {
|
||||
foreach($tracks as $track) {
|
||||
/** @var Track $track */
|
||||
$trackProgress->advance();
|
||||
$track->updateElasticsearchEntry();
|
||||
}
|
||||
});
|
||||
$trackProgress->finish();
|
||||
$this->line('');
|
||||
|
||||
|
||||
$albumProgress = $this->output->createProgressBar($totalAlbums);
|
||||
$this->info("Processing albums...");
|
||||
Album::withTrashed()->chunk(200, function(Collection $albums) use ($albumProgress) {
|
||||
foreach($albums as $album) {
|
||||
/** @var Album $album */
|
||||
$albumProgress->advance();
|
||||
$album->updateElasticsearchEntry();
|
||||
}
|
||||
});
|
||||
$albumProgress->finish();
|
||||
$this->line('');
|
||||
|
||||
|
||||
$playlistProgress = $this->output->createProgressBar($totalPlaylists);
|
||||
$this->info("Processing playlists...");
|
||||
Playlist::withTrashed()->chunk(200, function(Collection $playlists) use ($playlistProgress) {
|
||||
foreach($playlists as $playlist) {
|
||||
/** @var Playlist $playlist */
|
||||
$playlistProgress->advance();
|
||||
$playlist->updateElasticsearchEntry();
|
||||
}
|
||||
});
|
||||
$playlistProgress->finish();
|
||||
$this->line('');
|
||||
|
||||
|
||||
$userProgress = $this->output->createProgressBar($totalUsers);
|
||||
$this->info("Processing users...");
|
||||
User::chunk(200, function(Collection $users) use ($userProgress) {
|
||||
foreach($users as $user) {
|
||||
/** @var User $user */
|
||||
$userProgress->advance();
|
||||
$user->updateElasticsearchEntry();
|
||||
}
|
||||
});
|
||||
$userProgress->finish();
|
||||
$this->line('');
|
||||
$this->info('Everything has been queued for re-indexing!');
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Console\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class RebuildTags extends Command
|
||||
|
@ -61,16 +61,18 @@ class RebuildTags extends Command
|
|||
$tracks = [$track];
|
||||
|
||||
} else {
|
||||
$tracks = Track::whereNotNull('published_at')->orderBy('id', 'asc')->get();
|
||||
$tracks = Track::whereNotNull('published_at')->withTrashed()->orderBy('id', 'asc')->get();
|
||||
}
|
||||
|
||||
$bar = $this->output->createProgressBar(sizeof($tracks));
|
||||
$numberOfTracks = sizeof($tracks);
|
||||
|
||||
$this->info("Updating tags for ${numberOfTracks} tracks...");
|
||||
$bar = $this->output->createProgressBar($numberOfTracks);
|
||||
|
||||
foreach($tracks as $track) {
|
||||
$this->comment('Rewriting tags for track #'.$track->id.'...');
|
||||
/** @var $track Track */
|
||||
$track->updateTags();
|
||||
$bar->advance();
|
||||
$this->line('');
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
|
|
105
app/Console/Commands/RebuildTrack.php
Normal file
105
app/Console/Commands/RebuildTrack.php
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2015 Peter Deltchev
|
||||
*
|
||||
* 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 Illuminate\Console\Command;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Poniverse\Ponyfm\Commands\GenerateTrackFilesCommand;
|
||||
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
|
||||
class RebuildTrack extends Command
|
||||
{
|
||||
use DispatchesJobs;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'rebuild:track
|
||||
{trackId : ID of the track to rebuild}
|
||||
{--upload : Include this option to use the uploaded file as the encode source instead of the master file}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Re-encodes a track\'s files';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
/** @var Track $track */
|
||||
$track = Track::with('trackFiles')->withTrashed()->find((int) $this->argument('trackId'));
|
||||
$this->printTrackInfo($track);
|
||||
|
||||
if($this->option('upload')) {
|
||||
// The track would've been deleted if its original upload failed.
|
||||
// It should be restored so the user can publish the track!
|
||||
$track->restore();
|
||||
$this->info("Attempting to finish this track's upload...");
|
||||
|
||||
$sourceFile = new \SplFileInfo($track->getTemporarySourceFile());
|
||||
$generateTrackFiles = new GenerateTrackFilesCommand($track, $sourceFile, false);
|
||||
$result = $generateTrackFiles->execute();
|
||||
// The GenerateTrackFiles command will re-encode all TrackFiles.
|
||||
|
||||
if ($result->didFail()) {
|
||||
$this->error("Something went wrong!");
|
||||
print_r($result->getMessages());
|
||||
}
|
||||
|
||||
} else {
|
||||
$this->info("Re-encoding this track's files - there should be a line of output for each format!");
|
||||
|
||||
foreach ($track->trackFiles as $trackFile) {
|
||||
if (!$trackFile->is_master) {
|
||||
$this->info("Re-encoding this track's {$trackFile->format} file...");
|
||||
$this->dispatch(new EncodeTrackFile($trackFile, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function printTrackInfo(Track $track) {
|
||||
$this->comment("Track info:");
|
||||
$this->comment(" Title: {$track->title}");
|
||||
$this->comment(" Uploaded at: {$track->created_at}");
|
||||
$this->comment(" Artist: {$track->user->display_name} [User ID: {$track->user_id}]");
|
||||
$this->comment(" Artist email: {$track->user->email}");
|
||||
}
|
||||
}
|
|
@ -24,8 +24,8 @@ use File;
|
|||
use Illuminate\Console\Command;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\TrackFile;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\TrackFile;
|
||||
|
||||
class RebuildTrackCache extends Command
|
||||
{
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Console\Commands;
|
||||
|
||||
use Poniverse\Ponyfm\ResourceLogItem;
|
||||
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||
use DB;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
|
|
|
@ -33,17 +33,18 @@ class Kernel extends ConsoleKernel
|
|||
protected $commands = [
|
||||
\Poniverse\Ponyfm\Console\Commands\MigrateOldData::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\RefreshCache::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\ImportMLPMA::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\ClassifyMLPMA::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\PublishUnclassifiedMlpmaTracks::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\RebuildTags::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\RebuildArtists::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\FixYearZeroLogs::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\BootstrapLocalEnvironment::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\PoniverseApiSetup::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\ClearTrackCache::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\RebuildTrackCache::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\RebuildTrack::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\RebuildFilesizes::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\RebuildSearchIndex::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\MergeAccounts::class,
|
||||
\Poniverse\Ponyfm\Console\Commands\FixMLPMAImages::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
39
app/Contracts/Searchable.php
Normal file
39
app/Contracts/Searchable.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* 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\Contracts;
|
||||
|
||||
interface Searchable {
|
||||
/**
|
||||
* Returns this model in Elasticsearch-friendly form. The array returned by
|
||||
* this method should match the current mapping for this model's ES type.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toElasticsearch():array;
|
||||
|
||||
/**
|
||||
* @return bool whether this particular object should be indexed or not
|
||||
*/
|
||||
public function shouldBeIndexed():bool;
|
||||
|
||||
public function updateElasticsearchEntry();
|
||||
public function updateElasticsearchEntrySynchronously();
|
||||
}
|
|
@ -22,9 +22,9 @@ namespace Poniverse\Ponyfm\Http\Controllers;
|
|||
|
||||
use Poniverse\Ponyfm\AlbumDownloader;
|
||||
use App;
|
||||
use Poniverse\Ponyfm\Album;
|
||||
use Poniverse\Ponyfm\ResourceLogItem;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use View;
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Mobile;
|
||||
|
||||
use Poniverse\Ponyfm\Http\Controllers\Controller;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Response;
|
||||
|
||||
class TracksController extends Controller
|
||||
|
|
|
@ -22,8 +22,8 @@ namespace Poniverse\Ponyfm\Http\Controllers\Api\V1;
|
|||
|
||||
use Poniverse\Ponyfm\Commands\UploadTrackCommand;
|
||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||
use Poniverse\Ponyfm\Image;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\Models\Image;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Response;
|
||||
|
||||
class TracksController extends ApiControllerBase
|
||||
|
|
|
@ -34,6 +34,7 @@ class AccountController extends ApiControllerBase
|
|||
$user = Auth::user();
|
||||
|
||||
return Response::json([
|
||||
'id' => $user->id,
|
||||
'bio' => $user->bio,
|
||||
'can_see_explicit_content' => $user->can_see_explicit_content == 1,
|
||||
'display_name' => $user->display_name,
|
||||
|
|
|
@ -21,19 +21,18 @@
|
|||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Poniverse\Ponyfm\Album;
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use Poniverse\Ponyfm\Commands\CreateAlbumCommand;
|
||||
use Poniverse\Ponyfm\Commands\DeleteAlbumCommand;
|
||||
use Poniverse\Ponyfm\Commands\EditAlbumCommand;
|
||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||
use Poniverse\Ponyfm\Image;
|
||||
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
||||
use Poniverse\Ponyfm\ResourceLogItem;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\Models\Image;
|
||||
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||
use Auth;
|
||||
use Input;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use Response;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
|
||||
class AlbumsController extends ApiControllerBase
|
||||
{
|
||||
|
@ -142,10 +141,13 @@ class AlbumsController extends ApiControllerBase
|
|||
200);
|
||||
}
|
||||
|
||||
public function getOwned()
|
||||
public function getOwned(User $user)
|
||||
{
|
||||
$query = Album::summary()->where('user_id', \Auth::user()->id)->orderBy('created_at', 'desc')->get();
|
||||
$this->authorize('get-albums', $user);
|
||||
|
||||
$query = Album::summary()->where('user_id', $user->id)->orderBy('created_at', 'desc')->get();
|
||||
$albums = [];
|
||||
|
||||
foreach ($query as $album) {
|
||||
$albums[] = [
|
||||
'id' => $album->id,
|
||||
|
|
|
@ -20,14 +20,14 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||
|
||||
use Poniverse\Ponyfm\Album;
|
||||
use Poniverse\Ponyfm\Comment;
|
||||
use Poniverse\Ponyfm\Favourite;
|
||||
use Gate;
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use Poniverse\Ponyfm\Models\Comment;
|
||||
use Poniverse\Ponyfm\Models\Favourite;
|
||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||
use Poniverse\Ponyfm\Image;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\User;
|
||||
use Cover;
|
||||
use Poniverse\Ponyfm\Models\Image;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
|
@ -36,12 +36,13 @@ class ArtistsController extends ApiControllerBase
|
|||
{
|
||||
public function getFavourites($slug)
|
||||
{
|
||||
$user = User::whereSlug($slug)->first();
|
||||
$user = User::where('slug', $slug)->whereNull('disabled_at')->first();
|
||||
if (!$user) {
|
||||
App::abort(404);
|
||||
}
|
||||
|
||||
$favs = Favourite::whereUserId($user->id)->with([
|
||||
$favs = Favourite::where('user_id', $user->id)
|
||||
->with([
|
||||
'track.genre',
|
||||
'track.cover',
|
||||
'track.user',
|
||||
|
@ -59,10 +60,10 @@ class ArtistsController extends ApiControllerBase
|
|||
$albums = [];
|
||||
|
||||
foreach ($favs as $fav) {
|
||||
if ($fav->type == 'Poniverse\Ponyfm\Track') {
|
||||
if ($fav->type == 'Poniverse\Ponyfm\Models\Track') {
|
||||
$tracks[] = Track::mapPublicTrackSummary($fav->track);
|
||||
} else {
|
||||
if ($fav->type == 'Poniverse\Ponyfm\Album') {
|
||||
if ($fav->type == 'Poniverse\Ponyfm\Models\Album') {
|
||||
$albums[] = Album::mapPublicAlbumSummary($fav->album);
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +77,7 @@ class ArtistsController extends ApiControllerBase
|
|||
|
||||
public function getContent($slug)
|
||||
{
|
||||
$user = User::whereSlug($slug)->first();
|
||||
$user = User::where('slug', $slug)->whereNull('disabled_at')->first();
|
||||
if (!$user) {
|
||||
App::abort(404);
|
||||
}
|
||||
|
@ -111,7 +112,8 @@ class ArtistsController extends ApiControllerBase
|
|||
|
||||
public function getShow($slug)
|
||||
{
|
||||
$user = User::whereSlug($slug)
|
||||
$user = User::where('slug', $slug)
|
||||
->whereNull('disabled_at')
|
||||
->userDetails()
|
||||
->with([
|
||||
'comments' => function ($query) {
|
||||
|
@ -157,7 +159,7 @@ class ArtistsController extends ApiControllerBase
|
|||
|
||||
return Response::json([
|
||||
'artist' => [
|
||||
'id' => (int)$user->id,
|
||||
'id' => $user->id,
|
||||
'name' => $user->display_name,
|
||||
'slug' => $user->slug,
|
||||
'is_archived' => (bool)$user->is_archived,
|
||||
|
@ -173,7 +175,10 @@ class ArtistsController extends ApiControllerBase
|
|||
'bio' => $user->bio,
|
||||
'mlpforums_username' => $user->username,
|
||||
'message_url' => $user->message_url,
|
||||
'user_data' => $userData
|
||||
'user_data' => $userData,
|
||||
'permissions' => [
|
||||
'edit' => Gate::allows('edit', $user)
|
||||
]
|
||||
]
|
||||
], 200);
|
||||
}
|
||||
|
@ -195,18 +200,7 @@ class ArtistsController extends ApiControllerBase
|
|||
$users = [];
|
||||
|
||||
foreach ($query->get() as $user) {
|
||||
$users[] = [
|
||||
'id' => $user->id,
|
||||
'name' => $user->display_name,
|
||||
'slug' => $user->slug,
|
||||
'url' => $user->url,
|
||||
'is_archived' => $user->is_archived,
|
||||
'avatars' => [
|
||||
'small' => $user->getAvatarUrl(Image::SMALL),
|
||||
'normal' => $user->getAvatarUrl(Image::NORMAL)
|
||||
],
|
||||
'created_at' => $user->created_at
|
||||
];
|
||||
$users[] = User::mapPublicUserSummary($user);
|
||||
}
|
||||
|
||||
return Response::json(["artists" => $users, "current_page" => $page, "total_pages" => ceil($count / $perPage)],
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
|||
|
||||
use App;
|
||||
use Poniverse\Ponyfm\Commands\CreateCommentCommand;
|
||||
use Poniverse\Ponyfm\Comment;
|
||||
use Poniverse\Ponyfm\Models\Comment;
|
||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||
|
||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
|
|
|
@ -20,12 +20,12 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||
|
||||
use Poniverse\Ponyfm\Album;
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use Poniverse\Ponyfm\Commands\ToggleFavouriteCommand;
|
||||
use Poniverse\Ponyfm\Favourite;
|
||||
use Poniverse\Ponyfm\Models\Favourite;
|
||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||
use Poniverse\Ponyfm\Playlist;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
|
|
|
@ -21,9 +21,10 @@
|
|||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||
|
||||
use Input;
|
||||
use Poniverse\Ponyfm\Commands\CreateGenreCommand;
|
||||
use Poniverse\Ponyfm\Commands\DeleteGenreCommand;
|
||||
use Poniverse\Ponyfm\Commands\RenameGenreCommand;
|
||||
use Poniverse\Ponyfm\Genre;
|
||||
use Poniverse\Ponyfm\Models\Genre;
|
||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||
use Response;
|
||||
|
||||
|
@ -45,6 +46,11 @@ class GenresController extends ApiControllerBase
|
|||
], 200);
|
||||
}
|
||||
|
||||
public function postCreate()
|
||||
{
|
||||
$command = new CreateGenreCommand(Input::get('name'));
|
||||
return $this->execute($command);
|
||||
}
|
||||
|
||||
public function putRename($genreId)
|
||||
{
|
||||
|
|
|
@ -20,17 +20,21 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||
|
||||
use Auth;
|
||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||
use Poniverse\Ponyfm\Image;
|
||||
use Cover;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Poniverse\Ponyfm\Models\Image;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use Response;
|
||||
|
||||
class ImagesController extends ApiControllerBase
|
||||
{
|
||||
public function getOwned()
|
||||
public function getOwned(User $user)
|
||||
{
|
||||
$query = Image::where('uploaded_by', \Auth::user()->id);
|
||||
$this->authorize('get-images', $user);
|
||||
|
||||
$query = Image::where('uploaded_by', $user->id);
|
||||
$images = [];
|
||||
|
||||
foreach ($query->get() as $image) {
|
||||
$images[] = [
|
||||
'id' => $image->id,
|
||||
|
|
|
@ -27,13 +27,13 @@ use Poniverse\Ponyfm\Commands\DeletePlaylistCommand;
|
|||
use Poniverse\Ponyfm\Commands\EditPlaylistCommand;
|
||||
use Poniverse\Ponyfm\Commands\RemoveTrackFromPlaylistCommand;
|
||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||
use Poniverse\Ponyfm\Image;
|
||||
use Poniverse\Ponyfm\Playlist;
|
||||
use Poniverse\Ponyfm\ResourceLogItem;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\Models\Image;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||
use Auth;
|
||||
use Input;
|
||||
use Response;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
|
||||
class PlaylistsController extends ApiControllerBase
|
||||
{
|
||||
|
@ -175,8 +175,12 @@ class PlaylistsController extends ApiControllerBase
|
|||
|
||||
public function getOwned()
|
||||
{
|
||||
$query = Playlist::summary()->with('pins', 'tracks', 'tracks.cover')->where('user_id',
|
||||
\Auth::user()->id)->orderBy('title', 'asc')->get();
|
||||
$query = Playlist::summary()
|
||||
->with('pins', 'tracks', 'tracks.cover')
|
||||
->where('user_id', Auth::user()->id)
|
||||
->orderBy('title', 'asc')
|
||||
->get();
|
||||
|
||||
$playlists = [];
|
||||
foreach ($query as $playlist) {
|
||||
$playlists[] = [
|
||||
|
@ -191,7 +195,8 @@ class PlaylistsController extends ApiControllerBase
|
|||
'normal' => $playlist->getCoverUrl(Image::NORMAL)
|
||||
],
|
||||
'is_pinned' => $playlist->hasPinFor(Auth::user()->id),
|
||||
'is_public' => $playlist->is_public == 1
|
||||
'is_public' => $playlist->is_public == 1,
|
||||
'track_ids' => $playlist->tracks->pluck('id')
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2015 Peter Deltchev
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* 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
|
||||
|
@ -20,28 +20,20 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||
|
||||
use Poniverse\Ponyfm\Http\Controllers\Controller;
|
||||
use Poniverse\Ponyfm\ProfileRequest;
|
||||
use Cache;
|
||||
use Config;
|
||||
use Elasticsearch;
|
||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||
use Input;
|
||||
use Poniverse\Ponyfm\Library\Search;
|
||||
use Response;
|
||||
|
||||
class ProfilerController extends Controller
|
||||
class SearchController extends ApiControllerBase
|
||||
{
|
||||
public function getRequest($id)
|
||||
public function getSearch(Search $search)
|
||||
{
|
||||
if (!Config::get('app.debug')) {
|
||||
return;
|
||||
}
|
||||
$results = $search->searchAllContent(Input::query('query'));
|
||||
|
||||
$key = 'profiler-request-' . $id;
|
||||
$request = Cache::get($key);
|
||||
if (!$request) {
|
||||
exit();
|
||||
}
|
||||
|
||||
Cache::forget($key);
|
||||
|
||||
return Response::json(['request' => ProfileRequest::load($request)->toArray()], 200);
|
||||
return Response::json([
|
||||
'results' => $results,
|
||||
], 200);
|
||||
}
|
||||
}
|
|
@ -20,11 +20,11 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||
|
||||
use Poniverse\Ponyfm\Genre;
|
||||
use Poniverse\Ponyfm\Models\Genre;
|
||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||
use Poniverse\Ponyfm\License;
|
||||
use Poniverse\Ponyfm\ShowSong;
|
||||
use Poniverse\Ponyfm\TrackType;
|
||||
use Poniverse\Ponyfm\Models\License;
|
||||
use Poniverse\Ponyfm\Models\ShowSong;
|
||||
use Poniverse\Ponyfm\Models\TrackType;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TaxonomiesController extends ApiControllerBase
|
||||
|
@ -33,8 +33,10 @@ class TaxonomiesController extends ApiControllerBase
|
|||
{
|
||||
return \Response::json([
|
||||
'licenses' => License::all()->toArray(),
|
||||
'genres' => Genre::select('genres.*',
|
||||
DB::raw('(SELECT COUNT(id) FROM tracks WHERE tracks.genre_id = genres.id AND tracks.published_at IS NOT NULL) AS track_count'))->orderBy('name')->get()->toArray(),
|
||||
'genres' => Genre::with('trackCountRelation')
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->toArray(),
|
||||
'track_types' => TrackType::select('track_types.*',
|
||||
DB::raw('(SELECT COUNT(id) FROM tracks WHERE tracks.track_type_id = track_types.id AND tracks.published_at IS NOT NULL) AS track_count'))
|
||||
->where('id', '!=', TrackType::UNCLASSIFIED_TRACK)
|
||||
|
|
|
@ -22,15 +22,14 @@ namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
|||
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use File;
|
||||
use Poniverse\Ponyfm\Exceptions\InvalidEncodeOptionsException;
|
||||
use Poniverse\Ponyfm\Commands\DeleteTrackCommand;
|
||||
use Poniverse\Ponyfm\Commands\EditTrackCommand;
|
||||
use Poniverse\Ponyfm\Commands\UploadTrackCommand;
|
||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||
use Poniverse\Ponyfm\Jobs\EncodeTrackFile;
|
||||
use Poniverse\Ponyfm\ResourceLogItem;
|
||||
use Poniverse\Ponyfm\TrackFile;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||
use Poniverse\Ponyfm\Models\TrackFile;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Auth;
|
||||
use Input;
|
||||
use Response;
|
||||
|
@ -41,7 +40,7 @@ class TracksController extends ApiControllerBase
|
|||
{
|
||||
session_write_close();
|
||||
|
||||
return $this->execute(new UploadTrackCommand());
|
||||
return $this->execute(new UploadTrackCommand(true));
|
||||
}
|
||||
|
||||
public function getUploadStatus($trackId)
|
||||
|
@ -184,9 +183,7 @@ class TracksController extends ApiControllerBase
|
|||
return $this->notFound('Track ' . $id . ' not found!');
|
||||
}
|
||||
|
||||
if ($track->user_id != Auth::user()->id) {
|
||||
return $this->notAuthorized();
|
||||
}
|
||||
$this->authorize('edit', $track);
|
||||
|
||||
return Response::json(Track::mapPrivateTrackShow($track), 200);
|
||||
}
|
||||
|
@ -225,6 +222,9 @@ class TracksController extends ApiControllerBase
|
|||
}
|
||||
|
||||
if (Input::has('songs')) {
|
||||
// DISTINCT is needed here to avoid duplicate results
|
||||
// when a track is associated with multiple show songs.
|
||||
$query->distinct();
|
||||
$query->join('show_song_track', function ($join) {
|
||||
$join->on('tracks.id', '=', 'show_song_track.track_id');
|
||||
});
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
namespace Poniverse\Ponyfm\Http\Controllers;
|
||||
|
||||
use App;
|
||||
use Poniverse\Ponyfm\User;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use View;
|
||||
use Redirect;
|
||||
|
||||
|
@ -32,9 +32,17 @@ class ArtistsController extends Controller
|
|||
return View::make('artists.index');
|
||||
}
|
||||
|
||||
public function getFavourites($slug) {
|
||||
return $this->getProfile($slug);
|
||||
}
|
||||
|
||||
public function getContent($slug) {
|
||||
return $this->getProfile($slug);
|
||||
}
|
||||
|
||||
public function getProfile($slug)
|
||||
{
|
||||
$user = User::whereSlug($slug)->first();
|
||||
$user = User::whereSlug($slug)->whereNull('disabled_at')->first();
|
||||
if (!$user) {
|
||||
App::abort('404');
|
||||
}
|
||||
|
@ -45,10 +53,10 @@ class ArtistsController extends Controller
|
|||
public function getShortlink($id)
|
||||
{
|
||||
$user = User::find($id);
|
||||
if (!$user) {
|
||||
if (!$user || $user->disabled_at !== NULL) {
|
||||
App::abort('404');
|
||||
}
|
||||
|
||||
return Redirect::action('ArtistsController@getProfile', [$id]);
|
||||
return Redirect::action('ArtistsController@getProfile', [$user->slug]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Http\Controllers;
|
||||
|
||||
use Poniverse\Ponyfm\User;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use Auth;
|
||||
use Config;
|
||||
use DB;
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Http\Controllers;
|
||||
|
||||
use Poniverse\Ponyfm\Image;
|
||||
use Poniverse\Ponyfm\Models\Image;
|
||||
use Config;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
|
|
|
@ -21,9 +21,9 @@
|
|||
namespace Poniverse\Ponyfm\Http\Controllers;
|
||||
|
||||
use App;
|
||||
use Poniverse\Ponyfm\Playlist;
|
||||
use Poniverse\Ponyfm\ResourceLogItem;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\PlaylistDownloader;
|
||||
use Auth;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
|
|
|
@ -20,9 +20,9 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Http\Controllers;
|
||||
|
||||
use Poniverse\Ponyfm\ResourceLogItem;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\TrackFile;
|
||||
use Poniverse\Ponyfm\Models\ResourceLogItem;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\TrackFile;
|
||||
use Auth;
|
||||
use Config;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
@ -91,6 +91,11 @@ class TracksController extends Controller
|
|||
return View::make('tracks.show');
|
||||
}
|
||||
|
||||
public function getEdit($id, $slug)
|
||||
{
|
||||
return $this->getTrack($id, $slug);
|
||||
}
|
||||
|
||||
public function getShortlink($id)
|
||||
{
|
||||
$track = Track::find($id);
|
||||
|
|
|
@ -36,7 +36,7 @@ class Kernel extends HttpKernel
|
|||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\Poniverse\Ponyfm\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Poniverse\Ponyfm\Http\Middleware\Profiler::class,
|
||||
\Poniverse\Ponyfm\Http\Middleware\DisabledAccountCheck::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,11 +20,13 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Http\Middleware;
|
||||
|
||||
use Auth;
|
||||
use Closure;
|
||||
use GuzzleHttp;
|
||||
use Illuminate\Auth\Guard;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Session\Store;
|
||||
use Poniverse;
|
||||
use Poniverse\Ponyfm\User;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
class AuthenticateOAuth
|
||||
|
@ -34,8 +36,20 @@ class AuthenticateOAuth
|
|||
*/
|
||||
private $poniverse;
|
||||
|
||||
public function __construct(Poniverse $poniverse) {
|
||||
/**
|
||||
* @var Guard
|
||||
*/
|
||||
private $auth;
|
||||
|
||||
/**
|
||||
* @var Store
|
||||
*/
|
||||
private $session;
|
||||
|
||||
public function __construct(Poniverse $poniverse, Guard $auth, Store $session) {
|
||||
$this->poniverse = $poniverse;
|
||||
$this->auth = $auth;
|
||||
$this->session = $session;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,10 +61,10 @@ class AuthenticateOAuth
|
|||
* @return mixed
|
||||
* @throws \OAuth2\Exception
|
||||
*/
|
||||
public function handle($request, Closure $next, $requiredScope)
|
||||
public function handle(Request $request, Closure $next, $requiredScope)
|
||||
{
|
||||
// Ensure this is a valid OAuth client.
|
||||
$accessToken = $request->get('access_token');
|
||||
$accessToken = $this->determineAccessToken($request, false);
|
||||
|
||||
// check that access token is valid at Poniverse.net
|
||||
$accessTokenInfo = $this->poniverse->getAccessTokenInfo($accessToken);
|
||||
|
@ -65,13 +79,29 @@ class AuthenticateOAuth
|
|||
|
||||
// Log in as the given user, creating the account if necessary.
|
||||
$this->poniverse->setAccessToken($accessToken);
|
||||
session()->put('api_client_id', $accessTokenInfo->getClientId());
|
||||
$this->session->put('api_client_id', $accessTokenInfo->getClientId());
|
||||
|
||||
$poniverseUser = $this->poniverse->getUser();
|
||||
|
||||
$user = User::findOrCreate($poniverseUser['username'], $poniverseUser['display_name'], $poniverseUser['email']);
|
||||
Auth::login($user);
|
||||
$this->auth->onceUsingId($user);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
|
||||
private function determineAccessToken(Request $request, $headerOnly = true)
|
||||
{
|
||||
$header = $request->header('Authorization');
|
||||
|
||||
if ($header !== null && substr($header, 0, 7) === 'Bearer ') {
|
||||
return trim(substr($header, 7));
|
||||
}
|
||||
|
||||
if ($headerOnly) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $request->get('access_token');
|
||||
}
|
||||
}
|
||||
|
|
68
app/Http/Middleware/DisabledAccountCheck.php
Normal file
68
app/Http/Middleware/DisabledAccountCheck.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2015 Peter Deltchev
|
||||
*
|
||||
* 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\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
|
||||
class DisabledAccountCheck
|
||||
{
|
||||
/**
|
||||
* The Guard implementation.
|
||||
*
|
||||
* @var Guard
|
||||
*/
|
||||
protected $auth;
|
||||
|
||||
/**
|
||||
* Create a new filter instance.
|
||||
*
|
||||
* @param Guard $auth
|
||||
*/
|
||||
public function __construct(Guard $auth) {
|
||||
$this->auth = $auth;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
// TODO: don't automatically log the user out some time after
|
||||
// issue #29 is fixed or when disabled_at starts being used for
|
||||
// something other than merged accounts.
|
||||
if ($this->auth->check()
|
||||
&& $this->auth->user()->disabled_at !== null
|
||||
&& !($request->getMethod() === 'POST' && $request->getRequestUri() == '/auth/logout')
|
||||
) {
|
||||
$this->auth->logout();
|
||||
// return Response::view('home.account-disabled', ['username' => $this->auth->user()->username], 403);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2015 Peter Deltchev
|
||||
*
|
||||
* 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\Http\Middleware;
|
||||
|
||||
use App;
|
||||
use Closure;
|
||||
use Cache;
|
||||
use Config;
|
||||
use DB;
|
||||
use Log;
|
||||
use Poniverse\Ponyfm\ProfileRequest;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class Profiler
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
// Profiling magic time!
|
||||
if (Config::get('app.debug')) {
|
||||
DB::enableQueryLog();
|
||||
$profiler = ProfileRequest::create();
|
||||
|
||||
try {
|
||||
$response = $next($request);
|
||||
} catch (\Exception $e) {
|
||||
$response = \Response::make([
|
||||
'message' => $e->getMessage(),
|
||||
'lineNumber' => $e->getLine(),
|
||||
'exception' => $e->getTrace()
|
||||
], method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500);
|
||||
$profiler->log('error', $e->__toString(), []);
|
||||
}
|
||||
|
||||
$response = $this->processResponse($profiler, $response);
|
||||
|
||||
Log::listen(function ($level, $message, $context) use ($profiler, $request) {
|
||||
$profiler->log($level, $message, $context);
|
||||
});
|
||||
|
||||
} else {
|
||||
// Process the request the usual, boring way.
|
||||
$response = $next($request);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
protected function processResponse(ProfileRequest $profiler, Response $response) {
|
||||
$profiler->recordQueries();
|
||||
|
||||
Cache::put('profiler-request-' . $profiler->getId(), $profiler->toString(), 2);
|
||||
return $response->header('X-Request-Id', $profiler->getId());
|
||||
}
|
||||
}
|
|
@ -29,16 +29,13 @@
|
|||
|
|
||||
*/
|
||||
|
||||
if (Config::get('app.debug')) {
|
||||
Route::get('/api/web/profiler/{id}', 'Api\Web\ProfilerController@getRequest');
|
||||
}
|
||||
|
||||
Route::get('/dashboard', 'TracksController@getIndex');
|
||||
Route::get('/tracks', ['as' => 'tracks.discover', 'uses' => 'TracksController@getIndex']);
|
||||
Route::get('/tracks/popular', 'TracksController@getIndex');
|
||||
Route::get('/tracks/random', 'TracksController@getIndex');
|
||||
|
||||
Route::get('tracks/{id}-{slug}', 'TracksController@getTrack');
|
||||
Route::get('tracks/{id}-{slug}/edit', 'TracksController@getEdit');
|
||||
Route::get('t{id}', 'TracksController@getShortlink' )->where('id', '\d+');
|
||||
Route::get('t{id}/embed', 'TracksController@getEmbed' );
|
||||
Route::get('t{id}/stream.{extension}', 'TracksController@getStream' );
|
||||
|
@ -54,6 +51,7 @@ Route::get('playlists', 'PlaylistsController@getIndex');
|
|||
|
||||
Route::get('/register', 'AccountController@getRegister');
|
||||
Route::get('/login', 'AuthController@getLogin');
|
||||
Route::post('/auth/logout', 'AuthController@postLogout');
|
||||
Route::get('/auth/oauth', 'AuthController@getOAuth');
|
||||
|
||||
Route::get('/about', function() { return View::make('pages.about'); });
|
||||
|
@ -81,8 +79,7 @@ Route::group(['prefix' => 'api/v1', 'middleware' => 'json-exceptions'], function
|
|||
|
||||
Route::group(['prefix' => 'api/web'], function() {
|
||||
Route::get('/taxonomies/all', 'Api\Web\TaxonomiesController@getAll');
|
||||
|
||||
Route::get('/playlists/show/{id}', 'Api\Web\PlaylistsController@getShow');
|
||||
Route::get('/search', 'Api\Web\SearchController@getSearch');
|
||||
|
||||
Route::get('/tracks', 'Api\Web\TracksController@getIndex');
|
||||
Route::get('/tracks/{id}', 'Api\Web\TracksController@getShow')->where('id', '\d+');
|
||||
|
@ -93,6 +90,7 @@ Route::group(['prefix' => 'api/web'], function() {
|
|||
Route::get('/albums/cached/{id}/{format}', 'Api\Web\AlbumsController@getCachedAlbum')->where(['id' => '\d+', 'format' => '.+']);
|
||||
|
||||
Route::get('/playlists', 'Api\Web\PlaylistsController@getIndex');
|
||||
Route::get('/playlists/show/{id}', 'Api\Web\PlaylistsController@getShow');
|
||||
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' => '.+']);
|
||||
|
||||
|
@ -135,12 +133,12 @@ Route::group(['prefix' => 'api/web'], function() {
|
|||
Route::group(['middleware' => 'auth'], function() {
|
||||
Route::get('/account/settings', 'Api\Web\AccountController@getSettings');
|
||||
|
||||
Route::get('/images/owned', 'Api\Web\ImagesController@getOwned');
|
||||
|
||||
Route::get('/tracks/owned', 'Api\Web\TracksController@getOwned');
|
||||
Route::get('/tracks/edit/{id}', 'Api\Web\TracksController@getEdit');
|
||||
|
||||
Route::get('/albums/owned', 'Api\Web\AlbumsController@getOwned');
|
||||
Route::get('/users/{userId}/albums', 'Api\Web\AlbumsController@getOwned')->where('id', '\d+');
|
||||
Route::get('/users/{userId}/images', 'Api\Web\ImagesController@getOwned')->where('id', '\d+');
|
||||
|
||||
Route::get('/albums/edit/{id}', 'Api\Web\AlbumsController@getEdit');
|
||||
|
||||
Route::get('/playlists/owned', 'Api\Web\PlaylistsController@getOwned');
|
||||
|
@ -153,6 +151,7 @@ Route::group(['prefix' => 'api/web'], function() {
|
|||
|
||||
Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'can:access-admin-area']], function() {
|
||||
Route::get('/genres', 'Api\Web\GenresController@getIndex');
|
||||
Route::post('/genres', 'Api\Web\GenresController@postCreate');
|
||||
Route::put('/genres/{id}', 'Api\Web\GenresController@putRename')->where('id', '\d+');
|
||||
Route::delete('/genres/{id}', 'Api\Web\GenresController@deleteGenre')->where('id', '\d+');
|
||||
});
|
||||
|
@ -160,11 +159,23 @@ Route::group(['prefix' => 'api/web'], function() {
|
|||
Route::post('/auth/logout', 'Api\Web\AuthController@postLogout');
|
||||
});
|
||||
|
||||
Route::group(['prefix' => 'account', 'middleware' => 'auth'], function() {
|
||||
Route::get('/favourites/tracks', 'FavouritesController@getTracks');
|
||||
Route::get('/favourites/albums', 'FavouritesController@getAlbums');
|
||||
Route::get('/favourites/playlists', 'FavouritesController@getPlaylists');
|
||||
|
||||
Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'can:access-admin-area']], function() {
|
||||
Route::get('/genres', 'AdminController@getGenres');
|
||||
Route::get('/', 'AdminController@getIndex');
|
||||
});
|
||||
|
||||
Route::get('u{id}', 'ArtistsController@getShortlink')->where('id', '\d+');
|
||||
Route::get('users/{id}-{slug}', 'ArtistsController@getShortlink')->where('id', '\d+');
|
||||
|
||||
|
||||
Route::group(['prefix' => '{slug}'], function() {
|
||||
Route::get('/', 'ArtistsController@getProfile');
|
||||
Route::get('/content', 'ArtistsController@getContent');
|
||||
Route::get('/favourites', 'ArtistsController@getFavourites');
|
||||
|
||||
|
||||
Route::group(['prefix' => 'account', 'middleware' => 'auth'], function() {
|
||||
Route::get('/tracks', 'ContentController@getTracks');
|
||||
Route::get('/tracks/edit/{id}', 'ContentController@getTracks');
|
||||
Route::get('/albums', 'ContentController@getAlbums');
|
||||
|
@ -176,18 +187,8 @@ Route::group(['prefix' => 'account', 'middleware' => 'auth'], function() {
|
|||
|
||||
Route::get('/', 'AccountController@getIndex');
|
||||
});
|
||||
|
||||
Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'can:access-admin-area']], function() {
|
||||
Route::get('/genres', 'AdminController@getGenres');
|
||||
Route::get('/', 'AdminController@getIndex');
|
||||
});
|
||||
|
||||
Route::get('u{id}', 'ArtistsController@getShortlink')->where('id', '\d+');
|
||||
Route::get('users/{id}-{slug}', 'ArtistsController@getShortlink')->where('id', '\d+');
|
||||
Route::get('{slug}', 'ArtistsController@getProfile');
|
||||
Route::get('{slug}/content', 'ArtistsController@getProfile');
|
||||
Route::get('{slug}/favourites', 'ArtistsController@getProfile');
|
||||
|
||||
Route::get('/', 'HomeController@getIndex');
|
||||
|
||||
Route::group(['domain' => 'api.pony.fm'], function() {
|
||||
|
|
|
@ -21,11 +21,12 @@
|
|||
namespace Poniverse\Ponyfm\Jobs;
|
||||
|
||||
use Auth;
|
||||
use Poniverse\Ponyfm\Genre;
|
||||
use DB;
|
||||
use Poniverse\Ponyfm\Models\Genre;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Bus\SelfHandling;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use SerializesModels;
|
||||
|
||||
class DeleteGenre extends Job implements SelfHandling, ShouldQueue
|
||||
|
@ -60,6 +61,8 @@ class DeleteGenre extends Job implements SelfHandling, ShouldQueue
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->beforeHandle();
|
||||
|
||||
// The user who kicked off this job is used when generating revision log entries.
|
||||
Auth::login($this->executingUser);
|
||||
|
||||
|
|
|
@ -24,17 +24,15 @@ namespace Poniverse\Ponyfm\Jobs;
|
|||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use File;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use OAuth2\Exception;
|
||||
use Config;
|
||||
use Log;
|
||||
use Poniverse\Ponyfm\Exceptions\InvalidEncodeOptionsException;
|
||||
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 Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\TrackFile;
|
||||
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
|
@ -44,19 +42,19 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
|||
/**
|
||||
* @var TrackFile
|
||||
*/
|
||||
private $trackFile;
|
||||
protected $trackFile;
|
||||
/**
|
||||
* @var
|
||||
*/
|
||||
private $isExpirable;
|
||||
protected $isExpirable;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $isForUpload;
|
||||
protected $isForUpload;
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $autoPublishWhenComplete;
|
||||
protected $autoPublishWhenComplete;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
|
@ -87,9 +85,21 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->beforeHandle();
|
||||
|
||||
// Sanity check: was this file just generated, or is it already being processed?
|
||||
if ($this->trackFile->status === TrackFile::STATUS_PROCESSING) {
|
||||
Log::warning('Track file #'.$this->trackFile->id.' (track #'.$this->trackFile->track_id.') is already being processed!');
|
||||
return;
|
||||
|
||||
} elseif (!$this->trackFile->is_expired) {
|
||||
Log::warning('Track file #'.$this->trackFile->id.' (track #'.$this->trackFile->track_id.') is still valid! No need to re-encode it.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Start the job
|
||||
$this->trackFile->status = TrackFile::STATUS_PROCESSING;
|
||||
$this->trackFile->update();
|
||||
$this->trackFile->save();
|
||||
|
||||
// Use the track's master file as the source
|
||||
if ($this->isForUpload) {
|
||||
|
@ -120,19 +130,18 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
|||
$process->mustRun();
|
||||
} catch (ProcessFailedException $e) {
|
||||
Log::error('An exception occured in the encoding process for track file ' . $this->trackFile->id . ' - ' . $e->getMessage());
|
||||
Log::info($process->getOutput());
|
||||
// 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) {
|
||||
if ($this->isExpirable && $this->trackFile->is_cacheable) {
|
||||
$this->trackFile->expires_at = Carbon::now()->addMinutes(Config::get('ponyfm.track_file_cache_duration'));
|
||||
$this->trackFile->update();
|
||||
$this->trackFile->save();
|
||||
}
|
||||
|
||||
// Update file size
|
||||
|
@ -140,7 +149,7 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
|||
|
||||
// Complete the job
|
||||
$this->trackFile->status = TrackFile::STATUS_NOT_BEING_PROCESSED;
|
||||
$this->trackFile->update();
|
||||
$this->trackFile->save();
|
||||
|
||||
if ($this->isForUpload) {
|
||||
if (!$this->trackFile->is_master && $this->trackFile->is_cacheable) {
|
||||
|
@ -171,6 +180,6 @@ class EncodeTrackFile extends Job implements SelfHandling, ShouldQueue
|
|||
{
|
||||
$this->trackFile->status = TrackFile::STATUS_PROCESSING_ERROR;
|
||||
$this->trackFile->expires_at = null;
|
||||
$this->trackFile->update();
|
||||
$this->trackFile->save();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Jobs;
|
||||
|
||||
use App;
|
||||
use DB;
|
||||
use Illuminate\Bus\Queueable;
|
||||
|
||||
abstract class Job
|
||||
|
@ -36,4 +38,15 @@ abstract class Job
|
|||
*/
|
||||
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* This method should be called at the beginning of every job's handle()
|
||||
* method. It ensures that we don't lose the in-memory database during
|
||||
* testing by disconnecting from it - which causes tests to fail.
|
||||
*/
|
||||
protected function beforeHandle() {
|
||||
if (App::environment() !== 'testing') {
|
||||
DB::reconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
58
app/Jobs/UpdateSearchIndexForEntity.php
Normal file
58
app/Jobs/UpdateSearchIndexForEntity.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* 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 DB;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Poniverse\Ponyfm\Contracts\Searchable;
|
||||
use Poniverse\Ponyfm\Jobs\Job;
|
||||
use Illuminate\Contracts\Bus\SelfHandling;
|
||||
use SerializesModels;
|
||||
|
||||
class UpdateSearchIndexForEntity extends Job implements SelfHandling, ShouldQueue
|
||||
{
|
||||
use InteractsWithQueue, SerializesModels;
|
||||
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param Model $entity
|
||||
*/
|
||||
public function __construct(Searchable $entity)
|
||||
{
|
||||
$this->entity = $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->beforeHandle();
|
||||
$this->entity->updateElasticsearchEntrySynchronously();
|
||||
}
|
||||
}
|
100
app/Jobs/UpdateTagsForRenamedGenre.php
Normal file
100
app/Jobs/UpdateTagsForRenamedGenre.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* 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 Auth;
|
||||
use Cache;
|
||||
use DB;
|
||||
use Log;
|
||||
use Poniverse\Ponyfm\Models\Genre;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Bus\SelfHandling;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Class RenameGenre
|
||||
*
|
||||
* NOTE: It is assumed that the genre passed into this job has already been renamed!
|
||||
* All this job does is update the tags in that genre's tracks.
|
||||
*
|
||||
* @package Poniverse\Ponyfm\Jobs
|
||||
*/
|
||||
class UpdateTagsForRenamedGenre extends Job implements SelfHandling, ShouldQueue
|
||||
{
|
||||
use InteractsWithQueue, SerializesModels;
|
||||
|
||||
protected $executingUser;
|
||||
protected $genreThatWasRenamed;
|
||||
protected $lockKey;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param Genre $genreThatWasRenamed
|
||||
*/
|
||||
public function __construct(Genre $genreThatWasRenamed)
|
||||
{
|
||||
$this->executingUser = Auth::user();
|
||||
$this->genreThatWasRenamed = $genreThatWasRenamed;
|
||||
|
||||
$this->lockKey = "genre-{$this->genreThatWasRenamed->id}-tag-update-lock";
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->beforeHandle();
|
||||
|
||||
// The user who kicked off this job is used when generating revision log entries.
|
||||
Auth::login($this->executingUser);
|
||||
|
||||
// "Lock" this genre to prevent race conditions
|
||||
if (Cache::has($this->lockKey)) {
|
||||
Log::info("Tag updates for the \"{$this->genreThatWasRenamed->name}\" genre are currently in progress! Will try again in 30 seconds.");
|
||||
$this->release(30);
|
||||
return;
|
||||
|
||||
} else {
|
||||
Cache::forever($this->lockKey, true);
|
||||
}
|
||||
|
||||
|
||||
$this->genreThatWasRenamed->tracks()->chunk(200, function ($tracks) {
|
||||
foreach ($tracks as $track) {
|
||||
/** @var Track $track */
|
||||
$track->updateTags();
|
||||
}
|
||||
});
|
||||
|
||||
Cache::forget($this->lockKey);
|
||||
}
|
||||
|
||||
public function failed()
|
||||
{
|
||||
Cache::forget($this->lockKey);
|
||||
}
|
||||
}
|
|
@ -20,27 +20,38 @@
|
|||
|
||||
class Assets
|
||||
{
|
||||
public static function scriptIncludes($area = 'app')
|
||||
{
|
||||
if (!Config::get("app.debug")) {
|
||||
return '<script src="/build/scripts/' . $area . '.js?' . filemtime("./build/scripts/" . $area . ".js") . '"></script>';
|
||||
public static function scriptIncludes(string $area) {
|
||||
$scriptTags = '';
|
||||
|
||||
if ('app' === $area) {
|
||||
$scripts = ['app.js', 'templates.js'];
|
||||
} elseif ('embed' === $area) {
|
||||
$scripts = ['embed.js'];
|
||||
} else {
|
||||
throw new InvalidArgumentException('A valid app area must be specified!');
|
||||
}
|
||||
|
||||
$scripts = self::mergeGlobs(self::getScriptsForArea($area));
|
||||
$retVal = "";
|
||||
|
||||
foreach ($scripts as $script) {
|
||||
$filename = self::replaceExtensionWith($script, ".coffee", ".js");
|
||||
$retVal .= "<script src='/build/$filename?" . filemtime('./build/' . $filename) . "'></script>";
|
||||
foreach ($scripts as $filename) {
|
||||
if (Config::get('app.debug') && $filename !== 'templates.js') {
|
||||
$scriptTags .= "<script src='http://localhost:61999/build/scripts/{$filename}'></script>";
|
||||
} else {
|
||||
$scriptTags .= "<script src='/build/scripts/{$filename}?" . filemtime(public_path("build/scripts/{$filename}")) . "'></script>";
|
||||
}
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
if (Config::get('app.debug')) {
|
||||
$scriptTags .= '<script src="http://localhost:61999/webpack-dev-server.js"></script>';
|
||||
}
|
||||
|
||||
return $scriptTags;
|
||||
}
|
||||
|
||||
public static function styleIncludes($area = 'app')
|
||||
{
|
||||
if (!Config::get("app.debug")) {
|
||||
return '<script>document.write(\'<link rel="stylesheet" href="build/styles/' . $area . '.css?' . filemtime("build/styles/" . $area . ".css") . '" />\');</script>';
|
||||
return '<script>document.write(\'<link rel="stylesheet" href="build/styles/' . $area . '.css?' .
|
||||
filemtime(public_path("/build/styles/${area}.css"))
|
||||
. '" />\');</script>';
|
||||
}
|
||||
|
||||
$styles = self::mergeGlobs(self::getStylesForArea($area));
|
||||
|
@ -48,7 +59,7 @@ class Assets
|
|||
|
||||
foreach ($styles as $style) {
|
||||
$filename = self::replaceExtensionWith($style, ".less", ".css");
|
||||
$retVal .= "<link rel='stylesheet' href='/build/$filename?" . filemtime('./build/' . $filename) . "' />";
|
||||
$retVal .= "<link rel='stylesheet' href='/build/$filename?" .filemtime(public_path("/build/${filename}")). "' />";
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
|
@ -82,41 +93,6 @@ class Assets
|
|||
return $files;
|
||||
}
|
||||
|
||||
private static function getScriptsForArea($area)
|
||||
{
|
||||
if ($area == 'app') {
|
||||
return [
|
||||
"scripts/base/jquery-2.0.2.js",
|
||||
"scripts/base/angular.js",
|
||||
"scripts/base/marked.js",
|
||||
"scripts/base/*.{coffee,js}",
|
||||
"scripts/shared/*.{coffee,js}",
|
||||
"scripts/app/*.{coffee,js}",
|
||||
"scripts/app/services/*.{coffee,js}",
|
||||
"scripts/app/filters/*.{coffee,js}",
|
||||
"scripts/app/directives/*.{coffee,js}",
|
||||
"scripts/app/controllers/*.{coffee,js}",
|
||||
"scripts/**/*.{coffee,js}"
|
||||
];
|
||||
} else {
|
||||
if ($area == 'embed') {
|
||||
return [
|
||||
"scripts/base/jquery-2.0.2.js",
|
||||
"scripts/base/jquery.cookie.js",
|
||||
"scripts/base/jquery.viewport.js",
|
||||
"scripts/base/underscore.js",
|
||||
"scripts/base/moment.js",
|
||||
"scripts/base/jquery.timeago.js",
|
||||
"scripts/base/soundmanager2-nodebug.js",
|
||||
"scripts/shared/jquery-extensions.js",
|
||||
"scripts/embed/*.coffee"
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
private static function getStylesForArea($area)
|
||||
{
|
||||
if ($area == 'app') {
|
||||
|
@ -124,7 +100,6 @@ class Assets
|
|||
"styles/base/jquery-ui.css",
|
||||
"styles/base/colorbox.css",
|
||||
"styles/app.less",
|
||||
"styles/profiler.less"
|
||||
];
|
||||
} else {
|
||||
if ($area == 'embed') {
|
||||
|
|
|
@ -22,11 +22,7 @@ class AudioCache
|
|||
{
|
||||
private static $_movieCache = array();
|
||||
|
||||
/**
|
||||
* @param $filename
|
||||
* @return FFmpegMovie
|
||||
*/
|
||||
public static function get($filename)
|
||||
public static function get(string $filename):FFmpegMovie
|
||||
{
|
||||
if (isset(self::$_movieCache[$filename])) {
|
||||
return self::$_movieCache[$filename];
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2015 Peter Deltchev
|
||||
*
|
||||
* 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 Assetic\Asset\BaseAsset;
|
||||
use Assetic\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* Class CacheBusterAsset
|
||||
* OH GOD IT BUUUUUUURNS
|
||||
*
|
||||
* Well, I may as well tell you why this awful class exists. So... Assetic doesn't quite support less's import
|
||||
* directive. I mean; it supports it insofar as Less itself supports it - but it doesn't take into account the
|
||||
* last modified time for imported assets. Since we only have one less file that imports everything else... well
|
||||
* you can see where this is going. This asset will let us override the last modified time for an entire collection
|
||||
* which allows me to write a custom mechanism for cache busting.
|
||||
*/
|
||||
class CacheBusterAsset extends BaseAsset
|
||||
{
|
||||
private $_lastModified;
|
||||
|
||||
/**
|
||||
* @param int $lastModified
|
||||
*/
|
||||
public function __construct($lastModified)
|
||||
{
|
||||
$this->_lastModified = $lastModified;
|
||||
parent::__construct([], '', '', []);
|
||||
}
|
||||
|
||||
public function load(FilterInterface $additionalFilter = null)
|
||||
{
|
||||
}
|
||||
|
||||
public function getLastModified()
|
||||
{
|
||||
return $this->_lastModified;
|
||||
}
|
||||
}
|
|
@ -18,17 +18,17 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class External
|
||||
{
|
||||
public static function execute($command)
|
||||
{
|
||||
$output = [];
|
||||
$error = exec($command, $output);
|
||||
$process = new Process($command);
|
||||
$process->run();
|
||||
|
||||
if ($error != null) {
|
||||
Log::error('"' . $command . '" failed with "' . $error . '"');
|
||||
if (!$process->isSuccessful()) {
|
||||
Log::error('"' . $command . '" failed with "' . $process->getErrorOutput() . '"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use Illuminate\Support\Facades\URL;
|
||||
|
||||
class Gravatar
|
||||
{
|
||||
public static function getUrl($email, $size = 80, $default = null, $rating = 'g')
|
||||
|
@ -40,7 +38,10 @@ class Gravatar
|
|||
}
|
||||
}
|
||||
|
||||
$url .= "&d=" . urlencode(URL::to('/images/icons/profile_' . $size . '.png'));
|
||||
// Pony.fm's production URL is hardcoded here so Gravatar can
|
||||
// serve functioning default avatars in the dev environment,
|
||||
// which it won't be able to access.
|
||||
$url .= "&d=" . urlencode(URL::to('https://pony.fm/images/icons/profile_' . $size . '.png'));
|
||||
}
|
||||
|
||||
return $url;
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2015 Peter Deltchev
|
||||
*
|
||||
* 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\Contracts\Hashing\Hasher;
|
||||
|
||||
class IpsHasher implements Hasher
|
||||
{
|
||||
public function make($value, array $options = array())
|
||||
{
|
||||
return md5(md5($options['salt']) . md5(static::ips_sanitize($value)));
|
||||
}
|
||||
|
||||
public function check($value, $hashedValue, array $options = array())
|
||||
{
|
||||
return static::make($value, ['salt' => $options['salt']]) === $hashedValue;
|
||||
}
|
||||
|
||||
public function needsRehash($hashedValue, array $options = array())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static public function ips_sanitize($value)
|
||||
{
|
||||
$value = str_replace('&', '&', $value);
|
||||
$value = str_replace('\\', '\', $value);
|
||||
$value = str_replace('!', '!', $value);
|
||||
$value = str_replace('$', '$', $value);
|
||||
$value = str_replace('"', '"', $value);
|
||||
$value = str_replace('<', '<', $value);
|
||||
$value = str_replace('>', '>', $value);
|
||||
$value = str_replace('\'', ''', $value);
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
|
@ -45,6 +46,27 @@ class PfmValidator extends Illuminate\Validation\Validator
|
|||
// value is the file array itself
|
||||
// parameters is a list of formats the file can be, verified via ffmpeg
|
||||
$file = AudioCache::get($value->getPathname());
|
||||
$codecString = $file->getAudioCodec();
|
||||
|
||||
// PCM, ADPCM, and AAC come in several variations as far as FFmpeg
|
||||
// is concerned. They're all acceptable for Pony.fm, so we check what
|
||||
// the codec string returned by FFmpeg starts with instead of looking
|
||||
// for an exact match for these.
|
||||
if (in_array('adpcm', $parameters) && Str::startsWith($codecString, 'adpcm')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array('pcm', $parameters) && Str::startsWith($codecString, 'pcm')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array('aac', $parameters) && Str::startsWith($codecString, 'aac')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array('alac', $parameters) && Str::startsWith($codecString, 'alac')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array($file->getAudioCodec(), $parameters);
|
||||
}
|
||||
|
|
206
app/Library/Search.php
Normal file
206
app/Library/Search.php
Normal file
|
@ -0,0 +1,206 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 Peter Deltchev
|
||||
*
|
||||
* 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\Library;
|
||||
|
||||
use DB;
|
||||
use Elasticsearch\Client;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
|
||||
class Search {
|
||||
protected $elasticsearch;
|
||||
protected $index;
|
||||
|
||||
public function __construct(Client $connection, string $indexName) {
|
||||
$this->elasticsearch = $connection;
|
||||
$this->index = $indexName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
* @param int $resultsPerContentType
|
||||
* @return array
|
||||
*/
|
||||
public function searchAllContent(string $query) {
|
||||
$results = $this->elasticsearch->msearch([
|
||||
'index' => $this->index,
|
||||
'body' => [
|
||||
//===== Tracks=====//
|
||||
['type' => 'track'],
|
||||
[
|
||||
'query' => [
|
||||
'multi_match' => [
|
||||
'query' => $query,
|
||||
'fields' => [
|
||||
'title^3',
|
||||
'artist^2',
|
||||
'genre',
|
||||
'track_type',
|
||||
'show_songs^2',
|
||||
],
|
||||
'tie_breaker' => 0.3,
|
||||
],
|
||||
],
|
||||
'size' => 13
|
||||
],
|
||||
|
||||
//===== Albums =====//
|
||||
['type' => 'album'],
|
||||
[
|
||||
'query' => [
|
||||
'multi_match' => [
|
||||
'query' => $query,
|
||||
'fields' => [
|
||||
'title^2',
|
||||
'artist',
|
||||
'tracks',
|
||||
],
|
||||
'tie_breaker' => 0.3,
|
||||
],
|
||||
],
|
||||
'size' => 3
|
||||
],
|
||||
|
||||
//===== Playlists =====//
|
||||
['type' => 'playlist'],
|
||||
[
|
||||
'query' => [
|
||||
'multi_match' => [
|
||||
'query' => $query,
|
||||
'fields' => [
|
||||
'title^3',
|
||||
'curator',
|
||||
'tracks^2',
|
||||
],
|
||||
'tie_breaker' => 0.3,
|
||||
],
|
||||
],
|
||||
'size' => 3
|
||||
],
|
||||
|
||||
//===== Users =====//
|
||||
['type' => 'user'],
|
||||
[
|
||||
'query' => [
|
||||
'multi_match' => [
|
||||
'query' => $query,
|
||||
'fields' => [
|
||||
'display_name',
|
||||
'tracks',
|
||||
],
|
||||
'tie_breaker' => 0.3,
|
||||
],
|
||||
],
|
||||
'size' => 3
|
||||
],
|
||||
]
|
||||
]);
|
||||
|
||||
$tracks = $this->transformTracks($results['responses'][0]['hits']['hits']);
|
||||
$albums = $this->transformAlbums($results['responses'][1]['hits']['hits']);
|
||||
$playlists = $this->transformPlaylists($results['responses'][2]['hits']['hits']);
|
||||
$users = $this->transformUsers($results['responses'][3]['hits']['hits']);
|
||||
|
||||
return [
|
||||
'tracks' => $tracks,
|
||||
'albums' => $albums,
|
||||
'playlists' => $playlists,
|
||||
'users' => $users
|
||||
];
|
||||
}
|
||||
|
||||
protected function transformTracks(array $searchHits) {
|
||||
$tracks = $this->transformToEloquent(Track::class, $searchHits);
|
||||
$tracks = $tracks->map(function (Track $track) {
|
||||
return Track::mapPublicTrackSummary($track);
|
||||
});
|
||||
return $tracks;
|
||||
}
|
||||
|
||||
protected function transformAlbums(array $searchHits) {
|
||||
$albums = $this->transformToEloquent(Album::class, $searchHits);
|
||||
$albums = $albums->map(function (Album $album) {
|
||||
return Album::mapPublicAlbumSummary($album);
|
||||
});
|
||||
return $albums;
|
||||
}
|
||||
|
||||
protected function transformPlaylists(array $searchHits) {
|
||||
$playlists = $this->transformToEloquent(Playlist::class, $searchHits);
|
||||
$playlists = $playlists->map(function (Playlist $playlist) {
|
||||
return Playlist::mapPublicPlaylistSummary($playlist);
|
||||
});
|
||||
return $playlists;
|
||||
}
|
||||
|
||||
protected function transformUsers(array $searchHits) {
|
||||
$users = $this->transformToEloquent(User::class, $searchHits);
|
||||
$users = $users->map(function (User $user) {
|
||||
return User::mapPublicUserSummary($user);
|
||||
});
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the given Elasticsearch results into a collection of corresponding
|
||||
* Eloquent models.
|
||||
*
|
||||
* This method assumes that the given class uses soft deletes.
|
||||
*
|
||||
* @param string $modelClass The Eloquent model class to instantiate these results as
|
||||
* @param array $searchHits
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
*/
|
||||
protected function transformToEloquent(string $modelClass, array $searchHits) {
|
||||
if (empty($searchHits)) {
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
$caseStatement = 'CASE id ';
|
||||
|
||||
$i = 0;
|
||||
foreach ($searchHits as $result) {
|
||||
$ids[$result['_id']] = $result['_score'];
|
||||
$caseStatement .= "WHEN ${result['_id']} THEN $i ";
|
||||
$i++;
|
||||
}
|
||||
$caseStatement .= 'END';
|
||||
|
||||
/** @var Builder $modelInstances */
|
||||
$modelInstances = $modelClass::query();
|
||||
|
||||
if (method_exists($modelClass, 'withTrashed')) {
|
||||
$modelInstances = $modelInstances->withTrashed();
|
||||
}
|
||||
|
||||
$modelInstances = $modelInstances
|
||||
->whereIn('id', array_keys($ids))
|
||||
->orderBy(DB::raw($caseStatement))
|
||||
->get();
|
||||
|
||||
return $modelInstances;
|
||||
}
|
||||
}
|
|
@ -18,6 +18,24 @@
|
|||
Version History
|
||||
===============
|
||||
|
||||
1.9.11: [2015-12-24] James Heinrich
|
||||
* bugfix (G:64): update constructor syntax for PHP 7
|
||||
* bugfix (G:62): infinite loop in large PNG files
|
||||
* bugfix (G:61): ID3v2 remove BOM from frame descriptions
|
||||
* bugfix (G:60): missing "break" in module.audio-video.quicktime.php
|
||||
* bugfix (G:59): .gitignore comments
|
||||
* bugfix (G:58): inconsistency in relation to module.tag.id3v2.php
|
||||
* bugfix (G:57): comparing instead of assign
|
||||
* bugfix (G:56): unsupported MIME type "audio/x-wave"
|
||||
* bugfix (G:55): readme.md variable reference
|
||||
* bugfix (G:54): QuickTime false 1000fps
|
||||
* bugfix (G:53): Quicktime / ID3v2 multiple genres
|
||||
* bugfix (G:52): sys_get_temp_dir in GetDataImageSize
|
||||
* bugfix (#1903): Quicktime meta atom not parsed
|
||||
* demo.joinmp3.php enhancements
|
||||
* m4b (audiobook) chapters not parsed correctly
|
||||
* sqlite3 caching not working
|
||||
|
||||
1.9.10: [2015-09-14] James Heinrich
|
||||
* bugfix (G:49): Declaration of getID3_cached_sqlite3
|
||||
* bugfix (#1892): extension.cache.mysql
|
||||
|
|
|
@ -109,7 +109,7 @@ class getID3
|
|||
protected $startup_error = '';
|
||||
protected $startup_warning = '';
|
||||
|
||||
const VERSION = '1.9.10-201511241457';
|
||||
const VERSION = '1.9.11-201601190922';
|
||||
const FREAD_BUFFER_SIZE = 32768;
|
||||
|
||||
const ATTACHMENTS_NONE = false;
|
||||
|
|
|
@ -499,6 +499,18 @@ class getid3_quicktime extends getid3_handler
|
|||
$atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 8));
|
||||
break;
|
||||
|
||||
case 'covr':
|
||||
$atom_structure['data'] = substr($boxdata, 8);
|
||||
// not a foolproof check, but better than nothing
|
||||
if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) {
|
||||
$atom_structure['image_mime'] = 'image/jpeg';
|
||||
} elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) {
|
||||
$atom_structure['image_mime'] = 'image/png';
|
||||
} elseif (preg_match('#^GIF#', $atom_structure['data'])) {
|
||||
$atom_structure['image_mime'] = 'image/gif';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'atID':
|
||||
case 'cnID':
|
||||
case 'geID':
|
||||
|
@ -516,9 +528,9 @@ class getid3_quicktime extends getid3_handler
|
|||
$atom_structure['data'] = substr($boxdata, 8);
|
||||
if ($atomname == 'covr') {
|
||||
// not a foolproof check, but better than nothing
|
||||
if (preg_match('#^\xFF\xD8\xFF#', $atom_structure['data'])) {
|
||||
if (preg_match('#^\\xFF\\xD8\\xFF#', $atom_structure['data'])) {
|
||||
$atom_structure['image_mime'] = 'image/jpeg';
|
||||
} elseif (preg_match('#^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A#', $atom_structure['data'])) {
|
||||
} elseif (preg_match('#^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A#', $atom_structure['data'])) {
|
||||
$atom_structure['image_mime'] = 'image/png';
|
||||
} elseif (preg_match('#^GIF#', $atom_structure['data'])) {
|
||||
$atom_structure['image_mime'] = 'image/gif';
|
||||
|
@ -1579,6 +1591,10 @@ if (!empty($atom_structure['sample_description_table'][$i]['width']) && !empty($
|
|||
// Furthermore, for historical reasons the list of atoms is optionally
|
||||
// terminated by a 32-bit integer set to 0. If you are writing a program
|
||||
// to read user data atoms, you should allow for the terminating 0.
|
||||
if (strlen($atom_data) > 12) {
|
||||
$subatomoffset += 4;
|
||||
continue;
|
||||
}
|
||||
return $atom_structure;
|
||||
}
|
||||
|
||||
|
|
|
@ -1170,9 +1170,16 @@ class getid3_riff extends getid3_handler {
|
|||
}
|
||||
break;
|
||||
|
||||
case 'WEBP':
|
||||
// https://developers.google.com/speed/webp/docs/riff_container
|
||||
$info['fileformat'] = 'webp';
|
||||
$info['mime_type'] = 'image/webp';
|
||||
|
||||
$info['error'][] = 'WebP image parsing not supported in this version of getID3()';
|
||||
break;
|
||||
|
||||
default:
|
||||
$info['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA), found "'.$RIFFsubtype.'" instead';
|
||||
$info['error'][] = 'Unknown RIFF type: expecting one of (WAVE|RMP3|AVI |CDDA|AIFF|AIFC|8SVX|CDXA|WEBP), found "'.$RIFFsubtype.'" instead';
|
||||
//unset($info['fileformat']);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,23 +18,53 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Exception;
|
||||
use Helpers;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Auth;
|
||||
use Cache;
|
||||
use Poniverse\Ponyfm\Contracts\Searchable;
|
||||
use Poniverse\Ponyfm\Exceptions\TrackFileNotFoundException;
|
||||
use Poniverse\Ponyfm\Traits\IndexedInElasticsearchTrait;
|
||||
use Poniverse\Ponyfm\Traits\TrackCollection;
|
||||
use Poniverse\Ponyfm\Traits\SlugTrait;
|
||||
use Venturecraft\Revisionable\RevisionableTrait;
|
||||
|
||||
class Album extends Model
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\Album
|
||||
*
|
||||
* @property integer $id
|
||||
* @property integer $user_id
|
||||
* @property string $title
|
||||
* @property string $slug
|
||||
* @property string $description
|
||||
* @property integer $cover_id
|
||||
* @property integer $track_count
|
||||
* @property integer $view_count
|
||||
* @property integer $download_count
|
||||
* @property integer $favourite_count
|
||||
* @property integer $comment_count
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property string $updated_at
|
||||
* @property \Carbon\Carbon $deleted_at
|
||||
* @property-read \Poniverse\Ponyfm\Models\User $user
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\ResourceUser[] $users
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Favourite[] $favourites
|
||||
* @property-read \Poniverse\Ponyfm\Models\Image $cover
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Track[] $tracks
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Comment[] $comments
|
||||
* @property-read mixed $url
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Venturecraft\Revisionable\Revision[] $revisionHistory
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Album userDetails()
|
||||
*/
|
||||
class Album extends Model implements Searchable
|
||||
{
|
||||
use SoftDeletes, SlugTrait, DispatchesJobs, TrackCollection, RevisionableTrait;
|
||||
use SoftDeletes, SlugTrait, TrackCollection, RevisionableTrait, IndexedInElasticsearchTrait;
|
||||
|
||||
protected $elasticsearchType = 'album';
|
||||
|
||||
protected $dates = ['deleted_at'];
|
||||
protected $fillable = ['user_id', 'title', 'slug'];
|
||||
|
@ -62,27 +92,27 @@ class Album extends Model
|
|||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\User');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\User');
|
||||
}
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->hasMany('Poniverse\Ponyfm\ResourceUser');
|
||||
return $this->hasMany('Poniverse\Ponyfm\Models\ResourceUser');
|
||||
}
|
||||
|
||||
public function favourites()
|
||||
{
|
||||
return $this->hasMany('Poniverse\Ponyfm\Favourite');
|
||||
return $this->hasMany('Poniverse\Ponyfm\Models\Favourite');
|
||||
}
|
||||
|
||||
public function cover()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Image');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\Image');
|
||||
}
|
||||
|
||||
public function tracks()
|
||||
{
|
||||
return $this->hasMany('Poniverse\Ponyfm\Track')->orderBy('track_number', 'asc');
|
||||
return $this->hasMany('Poniverse\Ponyfm\Models\Track')->orderBy('track_number', 'asc');
|
||||
}
|
||||
|
||||
public function trackFiles() {
|
||||
|
@ -91,7 +121,7 @@ class Album extends Model
|
|||
|
||||
public function comments()
|
||||
{
|
||||
return $this->hasMany('Poniverse\Ponyfm\Comment')->orderBy('created_at', 'desc');
|
||||
return $this->hasMany('Poniverse\Ponyfm\Models\Comment')->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
public static function mapPublicAlbumShow(Album $album)
|
||||
|
@ -182,6 +212,7 @@ class Album extends Model
|
|||
'user' => [
|
||||
'id' => (int) $album->user->id,
|
||||
'name' => $album->user->display_name,
|
||||
'slug' => $album->user->slug,
|
||||
'url' => $album->user->url,
|
||||
],
|
||||
'user_data' => $userData,
|
||||
|
@ -361,4 +392,40 @@ class Album extends Model
|
|||
{
|
||||
return 'album-'.$this->id.'-'.$key;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of tracks in an album will always be in sync.
|
||||
*
|
||||
* @param array $options
|
||||
* @return bool
|
||||
*/
|
||||
public function save(array $options = []) {
|
||||
$this->recountTracks();
|
||||
return parent::save($options);
|
||||
}
|
||||
|
||||
protected function recountTracks() {
|
||||
$this->track_count = $this->tracks->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this model in Elasticsearch-friendly form. The array returned by
|
||||
* this method should match the current mapping for this model's ES type.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toElasticsearch():array {
|
||||
return [
|
||||
'title' => $this->title,
|
||||
'artist' => $this->user->display_name,
|
||||
'tracks' => $this->tracks->pluck('title'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function shouldBeIndexed():bool {
|
||||
return $this->track_count > 0 && !$this->trashed();
|
||||
}
|
||||
}
|
|
@ -18,11 +18,32 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\Comment
|
||||
*
|
||||
* @property integer $id
|
||||
* @property integer $user_id
|
||||
* @property string $ip_address
|
||||
* @property string $content
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property \Carbon\Carbon $deleted_at
|
||||
* @property integer $profile_id
|
||||
* @property integer $track_id
|
||||
* @property integer $album_id
|
||||
* @property integer $playlist_id
|
||||
* @property-read \Poniverse\Ponyfm\Models\User $user
|
||||
* @property-read \Poniverse\Ponyfm\Models\Track $track
|
||||
* @property-read \Poniverse\Ponyfm\Models\Album $album
|
||||
* @property-read \Poniverse\Ponyfm\Models\Playlist $playlist
|
||||
* @property-read \Poniverse\Ponyfm\Models\User $profile
|
||||
* @property-read mixed $resource
|
||||
*/
|
||||
class Comment extends Model
|
||||
{
|
||||
|
||||
|
@ -34,27 +55,27 @@ class Comment extends Model
|
|||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\User');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\User');
|
||||
}
|
||||
|
||||
public function track()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Track');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\Track');
|
||||
}
|
||||
|
||||
public function album()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Album');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\Album');
|
||||
}
|
||||
|
||||
public function playlist()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Playlist');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\Playlist');
|
||||
}
|
||||
|
||||
public function profile()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\User', 'profile_id');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\User', 'profile_id');
|
||||
}
|
||||
|
||||
public static function mapPublic($comment)
|
|
@ -18,10 +18,26 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\Favourite
|
||||
*
|
||||
* @property integer $id
|
||||
* @property integer $user_id
|
||||
* @property integer $track_id
|
||||
* @property integer $album_id
|
||||
* @property integer $playlist_id
|
||||
* @property string $created_at
|
||||
* @property-read \Poniverse\Ponyfm\Models\User $user
|
||||
* @property-read \Poniverse\Ponyfm\Models\Track $track
|
||||
* @property-read \Poniverse\Ponyfm\Models\Album $album
|
||||
* @property-read \Poniverse\Ponyfm\Models\Playlist $playlist
|
||||
* @property-read mixed $resource
|
||||
* @property-read mixed $type
|
||||
*/
|
||||
class Favourite extends Model
|
||||
{
|
||||
protected $table = 'favourites';
|
||||
|
@ -35,22 +51,22 @@ class Favourite extends Model
|
|||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\User');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\User');
|
||||
}
|
||||
|
||||
public function track()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Track');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\Track');
|
||||
}
|
||||
|
||||
public function album()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Album');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\Album');
|
||||
}
|
||||
|
||||
public function playlist()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Playlist');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\Playlist');
|
||||
}
|
||||
|
||||
/**
|
|
@ -18,10 +18,19 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\Follower
|
||||
*
|
||||
* @property integer $id
|
||||
* @property integer $user_id
|
||||
* @property integer $artist_id
|
||||
* @property integer $playlist_id
|
||||
* @property string $created_at
|
||||
*/
|
||||
class Follower extends Model
|
||||
{
|
||||
protected $table = 'followers';
|
|
@ -18,7 +18,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use DB;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
|
@ -27,6 +27,22 @@ use Poniverse\Ponyfm\Traits\SlugTrait;
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
use Venturecraft\Revisionable\RevisionableTrait;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\Genre
|
||||
*
|
||||
* @property integer $id
|
||||
* @property string $name
|
||||
* @property string $slug
|
||||
* @property string $deleted_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Track[] $tracks
|
||||
* @property-read \Poniverse\Ponyfm\Models\Track $trackCountRelation
|
||||
* @property-read mixed $track_count
|
||||
* @property-read mixed $url
|
||||
* @property-write mixed $title
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Venturecraft\Revisionable\Revision[] $revisionHistory
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
*/
|
||||
class Genre extends Model
|
||||
{
|
||||
protected $table = 'genres';
|
||||
|
@ -35,8 +51,6 @@ class Genre extends Model
|
|||
protected $appends = ['track_count', 'url'];
|
||||
protected $hidden = ['trackCountRelation'];
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
use SlugTrait, SoftDeletes, RevisionableTrait;
|
||||
|
||||
public function tracks(){
|
|
@ -18,13 +18,27 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use External;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Config;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\Image
|
||||
*
|
||||
* @property integer $id
|
||||
* @property string $filename
|
||||
* @property string $mime
|
||||
* @property string $extension
|
||||
* @property integer $size
|
||||
* @property string $hash
|
||||
* @property integer $uploaded_by
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
*/
|
||||
class Image extends Model
|
||||
{
|
||||
const NORMAL = 1;
|
||||
|
@ -52,7 +66,14 @@ class Image extends Model
|
|||
return null;
|
||||
}
|
||||
|
||||
public static function upload(UploadedFile $file, $user)
|
||||
/**
|
||||
* @param UploadedFile $file
|
||||
* @param int|User $user
|
||||
* @param bool $forceReupload forces the image to be re-processed even if a matching hash is found
|
||||
* @return Image
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function upload(UploadedFile $file, $user, bool $forceReupload = false)
|
||||
{
|
||||
$userId = $user;
|
||||
if ($user instanceof User) {
|
||||
|
@ -63,10 +84,25 @@ class Image extends Model
|
|||
$image = Image::whereHash($hash)->whereUploadedBy($userId)->first();
|
||||
|
||||
if ($image) {
|
||||
if ($forceReupload) {
|
||||
// delete existing versions of the image
|
||||
$filenames = scandir($image->getDirectory());
|
||||
$imagePrefix = $image->id.'_';
|
||||
|
||||
$filenames = array_filter($filenames, function(string $filename) use ($imagePrefix) {
|
||||
return Str::startsWith($filename, $imagePrefix);
|
||||
});
|
||||
|
||||
foreach($filenames as $filename) {
|
||||
unlink($image->getDirectory().'/'.$filename);
|
||||
}
|
||||
} else {
|
||||
return $image;
|
||||
}
|
||||
|
||||
} else {
|
||||
$image = new Image();
|
||||
}
|
||||
|
||||
try {
|
||||
$image->uploaded_by = $userId;
|
||||
$image->size = $file->getSize();
|
||||
|
@ -79,7 +115,7 @@ class Image extends Model
|
|||
$image->ensureDirectoryExists();
|
||||
foreach (self::$ImageTypes as $coverType) {
|
||||
if ($coverType['id'] === self::ORIGINAL && $image->mime === 'image/jpeg') {
|
||||
$command = 'cp '.$file->getPathname().' '.$image->getFile($coverType['id']);
|
||||
$command = 'cp "'.$file->getPathname().'" '.$image->getFile($coverType['id']);
|
||||
|
||||
} else {
|
||||
// ImageMagick options reference: http://www.imagemagick.org/script/command-line-options.php
|
||||
|
@ -100,6 +136,7 @@ class Image extends Model
|
|||
}
|
||||
|
||||
External::execute($command);
|
||||
chmod($image->getFile($coverType['id']), 0644);
|
||||
}
|
||||
|
||||
return $image;
|
|
@ -18,10 +18,20 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\License
|
||||
*
|
||||
* @property integer $id
|
||||
* @property string $title
|
||||
* @property string $description
|
||||
* @property boolean $affiliate_distribution
|
||||
* @property boolean $open_distribution
|
||||
* @property boolean $remix
|
||||
*/
|
||||
class License extends Model
|
||||
{
|
||||
protected $table = 'licenses';
|
|
@ -18,21 +18,32 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\PinnedPlaylist
|
||||
*
|
||||
* @property integer $id
|
||||
* @property integer $user_id
|
||||
* @property integer $playlist_id
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property-read \Poniverse\Ponyfm\Models\User $user
|
||||
* @property-read \Poniverse\Ponyfm\Models\Playlist $playlist
|
||||
*/
|
||||
class PinnedPlaylist extends Model
|
||||
{
|
||||
protected $table = 'pinned_playlists';
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\User');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\User');
|
||||
}
|
||||
|
||||
public function playlist()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Playlist');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\Playlist');
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Helpers;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
@ -26,18 +26,61 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
|||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Auth;
|
||||
use Cache;
|
||||
use Poniverse\Ponyfm\Contracts\Searchable;
|
||||
use Poniverse\Ponyfm\Exceptions\TrackFileNotFoundException;
|
||||
use Poniverse\Ponyfm\Traits\IndexedInElasticsearchTrait;
|
||||
use Poniverse\Ponyfm\Traits\TrackCollection;
|
||||
use Poniverse\Ponyfm\Traits\SlugTrait;
|
||||
use Venturecraft\Revisionable\RevisionableTrait;
|
||||
|
||||
class Playlist extends Model
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\Playlist
|
||||
*
|
||||
* @property integer $id
|
||||
* @property integer $user_id
|
||||
* @property string $title
|
||||
* @property string $slug
|
||||
* @property string $description
|
||||
* @property boolean $is_public
|
||||
* @property integer $track_count
|
||||
* @property integer $view_count
|
||||
* @property integer $download_count
|
||||
* @property integer $favourite_count
|
||||
* @property integer $follow_count
|
||||
* @property integer $comment_count
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property \Carbon\Carbon $deleted_at
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Track[] $tracks
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\ResourceUser[] $users
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Comment[] $comments
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\PinnedPlaylist[] $pins
|
||||
* @property-read \Poniverse\Ponyfm\Models\User $user
|
||||
* @property-read mixed $url
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Venturecraft\Revisionable\Revision[] $revisionHistory
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Playlist userDetails()
|
||||
*/
|
||||
class Playlist extends Model implements Searchable
|
||||
{
|
||||
use SoftDeletes, SlugTrait, DispatchesJobs, TrackCollection, RevisionableTrait;
|
||||
use SoftDeletes, SlugTrait, TrackCollection, RevisionableTrait, IndexedInElasticsearchTrait;
|
||||
|
||||
protected $elasticsearchType = 'playlist';
|
||||
|
||||
protected $table = 'playlists';
|
||||
|
||||
protected $dates = ['deleted_at'];
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'user_id' => 'integer',
|
||||
'title' => 'string',
|
||||
'description' => 'string',
|
||||
'is_public' => 'boolean',
|
||||
'track_count' => 'integer',
|
||||
'view_count' => 'integer',
|
||||
'download_count' => 'integer',
|
||||
'favourte_count' => 'integer',
|
||||
'follow_count' => 'integer',
|
||||
'comment_count' => 'integer',
|
||||
];
|
||||
|
||||
public static function summary()
|
||||
{
|
||||
|
@ -152,7 +195,7 @@ class Playlist extends Model
|
|||
public function tracks()
|
||||
{
|
||||
return $this
|
||||
->belongsToMany('Poniverse\Ponyfm\Track')
|
||||
->belongsToMany('Poniverse\Ponyfm\Models\Track')
|
||||
->withPivot('position')
|
||||
->withTimestamps()
|
||||
->orderBy('position', 'asc');
|
||||
|
@ -166,22 +209,22 @@ class Playlist extends Model
|
|||
|
||||
public function users()
|
||||
{
|
||||
return $this->hasMany('Poniverse\Ponyfm\ResourceUser');
|
||||
return $this->hasMany('Poniverse\Ponyfm\Models\ResourceUser');
|
||||
}
|
||||
|
||||
public function comments()
|
||||
{
|
||||
return $this->hasMany('Poniverse\Ponyfm\Comment')->orderBy('created_at', 'desc');
|
||||
return $this->hasMany('Poniverse\Ponyfm\Models\Comment')->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
public function pins()
|
||||
{
|
||||
return $this->hasMany('Poniverse\Ponyfm\PinnedPlaylist');
|
||||
return $this->hasMany('Poniverse\Ponyfm\Models\PinnedPlaylist');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\User');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\User');
|
||||
}
|
||||
|
||||
public function hasPinFor($userId)
|
||||
|
@ -258,4 +301,27 @@ class Playlist extends Model
|
|||
{
|
||||
return 'playlist-' . $this->id . '-' . $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this model in Elasticsearch-friendly form. The array returned by
|
||||
* this method should match the current mapping for this model's ES type.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toElasticsearch():array {
|
||||
return [
|
||||
'title' => $this->title,
|
||||
'curator' => $this->user->display_name,
|
||||
'tracks' => $this->tracks->pluck('title'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function shouldBeIndexed():bool {
|
||||
return $this->is_public &&
|
||||
$this->track_count > 0 &&
|
||||
!$this->trashed();
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Carbon\Carbon;
|
||||
|
@ -26,6 +26,19 @@ use Auth;
|
|||
use DB;
|
||||
use Request;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\ResourceLogItem
|
||||
*
|
||||
* @property integer $id
|
||||
* @property integer $user_id
|
||||
* @property integer $log_type
|
||||
* @property string $ip_address
|
||||
* @property integer $track_format_id
|
||||
* @property integer $track_id
|
||||
* @property integer $album_id
|
||||
* @property integer $playlist_id
|
||||
* @property \Carbon\Carbon $created_at
|
||||
*/
|
||||
class ResourceLogItem extends Model
|
||||
{
|
||||
protected $table = 'resource_log_items';
|
|
@ -18,10 +18,26 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\ResourceUser
|
||||
*
|
||||
* @property integer $id
|
||||
* @property integer $user_id
|
||||
* @property integer $track_id
|
||||
* @property integer $album_id
|
||||
* @property integer $playlist_id
|
||||
* @property integer $artist_id
|
||||
* @property boolean $is_followed
|
||||
* @property boolean $is_favourited
|
||||
* @property boolean $is_pinned
|
||||
* @property integer $view_count
|
||||
* @property integer $play_count
|
||||
* @property integer $download_count
|
||||
*/
|
||||
class ResourceUser extends Model
|
||||
{
|
||||
protected $table = 'resource_users';
|
|
@ -18,10 +18,17 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\Role
|
||||
*
|
||||
* @property integer $id
|
||||
* @property string $name
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\User[] $users
|
||||
*/
|
||||
class Role extends Model
|
||||
{
|
||||
protected $table = 'roles';
|
|
@ -18,10 +18,18 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\ShowSong
|
||||
*
|
||||
* @property integer $id
|
||||
* @property string $title
|
||||
* @property string $lyrics
|
||||
* @property string $slug
|
||||
*/
|
||||
class ShowSong extends Model
|
||||
{
|
||||
protected $table = 'show_songs';
|
|
@ -18,13 +18,16 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Auth;
|
||||
use Cache;
|
||||
use Config;
|
||||
use DB;
|
||||
use Gate;
|
||||
use Poniverse\Ponyfm\Contracts\Searchable;
|
||||
use Poniverse\Ponyfm\Exceptions\TrackFileNotFoundException;
|
||||
use Poniverse\Ponyfm\Traits\IndexedInElasticsearchTrait;
|
||||
use Poniverse\Ponyfm\Traits\SlugTrait;
|
||||
use Exception;
|
||||
use External;
|
||||
|
@ -36,9 +39,68 @@ use Illuminate\Support\Str;
|
|||
use Log;
|
||||
use Venturecraft\Revisionable\RevisionableTrait;
|
||||
|
||||
class Track extends Model
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\Track
|
||||
*
|
||||
* @property integer $id
|
||||
* @property integer $user_id
|
||||
* @property integer $license_id
|
||||
* @property integer $genre_id
|
||||
* @property integer $track_type_id
|
||||
* @property string $title
|
||||
* @property string $slug
|
||||
* @property string $description
|
||||
* @property string $lyrics
|
||||
* @property boolean $is_vocal
|
||||
* @property boolean $is_explicit
|
||||
* @property integer $cover_id
|
||||
* @property boolean $is_downloadable
|
||||
* @property float $duration
|
||||
* @property integer $play_count
|
||||
* @property integer $view_count
|
||||
* @property integer $download_count
|
||||
* @property integer $favourite_count
|
||||
* @property integer $comment_count
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property string $updated_at
|
||||
* @property \Carbon\Carbon $deleted_at
|
||||
* @property \Carbon\Carbon $published_at
|
||||
* @property \Carbon\Carbon $released_at
|
||||
* @property integer $album_id
|
||||
* @property integer $track_number
|
||||
* @property boolean $is_latest
|
||||
* @property string $hash
|
||||
* @property boolean $is_listed
|
||||
* @property string $source
|
||||
* @property string $original_tags
|
||||
* @property string $metadata
|
||||
* @property-read \Poniverse\Ponyfm\Models\Genre $genre
|
||||
* @property-read \Poniverse\Ponyfm\Models\TrackType $trackType
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Comment[] $comments
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Favourite[] $favourites
|
||||
* @property-read \Poniverse\Ponyfm\Models\Image $cover
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\ShowSong[] $showSongs
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\ResourceUser[] $users
|
||||
* @property-read \Poniverse\Ponyfm\Models\User $user
|
||||
* @property-read \Poniverse\Ponyfm\Models\Album $album
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\TrackFile[] $trackFiles
|
||||
* @property-read mixed $year
|
||||
* @property-read mixed $url
|
||||
* @property-read mixed $download_directory
|
||||
* @property-read mixed $status
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Venturecraft\Revisionable\Revision[] $revisionHistory
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Track userDetails()
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Track published()
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Track listed()
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Track explicitFilter()
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Track withComments()
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\Track mlpma()
|
||||
*/
|
||||
class Track extends Model implements Searchable
|
||||
{
|
||||
use SoftDeletes;
|
||||
use SoftDeletes, IndexedInElasticsearchTrait;
|
||||
|
||||
protected $elasticsearchType = 'track';
|
||||
|
||||
protected $dates = ['deleted_at', 'published_at', 'released_at'];
|
||||
protected $hidden = ['original_tags', 'metadata'];
|
||||
|
@ -192,6 +254,9 @@ class Track extends Model
|
|||
$query->join('mlpma_tracks', 'tracks.id', '=', 'mlpma_tracks.track_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $count
|
||||
*/
|
||||
public static function popular($count, $allowExplicit = false)
|
||||
{
|
||||
$trackIds = Cache::remember('popular_tracks'.$count.'-'.($allowExplicit ? 'explicit' : 'safe'), 5,
|
||||
|
@ -238,6 +303,10 @@ class Track extends Model
|
|||
$processed[] = Track::mapPublicTrackSummary($track);
|
||||
}
|
||||
|
||||
// Songs that get played more should drop down
|
||||
// in the list so they don't hog the top spots.
|
||||
array_reverse($processed);
|
||||
|
||||
return $processed;
|
||||
}
|
||||
|
||||
|
@ -328,11 +397,11 @@ class Track extends Model
|
|||
],
|
||||
'url' => $track->url,
|
||||
'slug' => $track->slug,
|
||||
'is_vocal' => (bool)$track->is_vocal,
|
||||
'is_explicit' => (bool)$track->is_explicit,
|
||||
'is_downloadable' => (bool)$track->is_downloadable,
|
||||
'is_published' => (bool)$track->isPublished(),
|
||||
'published_at' => $track->published_at->format('c'),
|
||||
'is_vocal' => $track->is_vocal,
|
||||
'is_explicit' => $track->is_explicit,
|
||||
'is_downloadable' => $track->is_downloadable,
|
||||
'is_published' => $track->isPublished(),
|
||||
'published_at' => $track->isPublished() ? $track->published_at->format('c') : null,
|
||||
'duration' => $track->duration,
|
||||
'genre' => $track->genre != null
|
||||
?
|
||||
|
@ -355,8 +424,8 @@ class Track extends Model
|
|||
],
|
||||
'user_data' => $userData,
|
||||
'permissions' => [
|
||||
'delete' => Auth::check() && Auth::user()->id == $track->user_id,
|
||||
'edit' => Auth::check() && Auth::user()->id == $track->user_id
|
||||
'delete' => Gate::allows('delete', $track),
|
||||
'edit' => Gate::allows('edit', $track)
|
||||
]
|
||||
];
|
||||
}
|
||||
|
@ -371,9 +440,10 @@ class Track extends Model
|
|||
$returnValue = self::mapPrivateTrackSummary($track);
|
||||
$returnValue['album_id'] = $track->album_id;
|
||||
$returnValue['show_songs'] = $showSongs;
|
||||
$returnValue['cover_id'] = $track->cover_id;
|
||||
$returnValue['real_cover_url'] = $track->getCoverUrl(Image::NORMAL);
|
||||
$returnValue['cover_url'] = $track->hasCover() ? $track->getCoverUrl(Image::NORMAL) : null;
|
||||
$returnValue['released_at'] = $track->released_at;
|
||||
$returnValue['released_at'] = $track->released_at ? $track->released_at->toDateString() : null;
|
||||
$returnValue['lyrics'] = $track->lyrics;
|
||||
$returnValue['description'] = $track->description;
|
||||
$returnValue['is_downloadable'] = !$track->isPublished() ? true : (bool) $track->is_downloadable;
|
||||
|
@ -407,52 +477,52 @@ class Track extends Model
|
|||
|
||||
public function genre()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Genre');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\Genre');
|
||||
}
|
||||
|
||||
public function trackType()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\TrackType', 'track_type_id');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\TrackType', 'track_type_id');
|
||||
}
|
||||
|
||||
public function comments()
|
||||
{
|
||||
return $this->hasMany('Poniverse\Ponyfm\Comment')->orderBy('created_at', 'desc');
|
||||
return $this->hasMany('Poniverse\Ponyfm\Models\Comment')->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
public function favourites()
|
||||
{
|
||||
return $this->hasMany('Poniverse\Ponyfm\Favourite');
|
||||
return $this->hasMany('Poniverse\Ponyfm\Models\Favourite');
|
||||
}
|
||||
|
||||
public function cover()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Image');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\Image');
|
||||
}
|
||||
|
||||
public function showSongs()
|
||||
{
|
||||
return $this->belongsToMany('Poniverse\Ponyfm\ShowSong');
|
||||
return $this->belongsToMany('Poniverse\Ponyfm\Models\ShowSong');
|
||||
}
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->hasMany('Poniverse\Ponyfm\ResourceUser');
|
||||
return $this->hasMany('Poniverse\Ponyfm\Models\ResourceUser');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\User');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\User');
|
||||
}
|
||||
|
||||
public function album()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Album');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\Album');
|
||||
}
|
||||
|
||||
public function trackFiles()
|
||||
{
|
||||
return $this->hasMany('Poniverse\Ponyfm\TrackFile');
|
||||
return $this->hasMany('Poniverse\Ponyfm\Models\TrackFile');
|
||||
}
|
||||
|
||||
public function getYearAttribute()
|
||||
|
@ -592,6 +662,9 @@ class Track extends Model
|
|||
return "{$this->title}.{$format['extension']}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFileFor($format)
|
||||
{
|
||||
if (!isset(self::$Formats[$format])) {
|
||||
|
@ -610,7 +683,7 @@ class Track extends Model
|
|||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTemporarySourceFile() {
|
||||
public function getTemporarySourceFile():string {
|
||||
return Config::get('ponyfm.files_directory').'/queued-tracks/'.$this->id;
|
||||
}
|
||||
|
||||
|
@ -637,11 +710,11 @@ class Track extends Model
|
|||
|
||||
} elseif (
|
||||
$carry !== static::STATUS_ERROR &&
|
||||
(int) $trackFile->status === TrackFile::STATUS_PROCESSING) {
|
||||
in_array($trackFile->status, [TrackFile::STATUS_PROCESSING, TrackFile::STATUS_PROCESSING_PENDING])) {
|
||||
return static::STATUS_PROCESSING;
|
||||
|
||||
} elseif (
|
||||
!in_array($carry, [static::STATUS_ERROR, static::STATUS_PROCESSING]) &&
|
||||
!in_array($carry, [static::STATUS_ERROR, static::STATUS_PROCESSING, TrackFile::STATUS_PROCESSING_PENDING]) &&
|
||||
(int) $trackFile->status === TrackFile::STATUS_NOT_BEING_PROCESSED
|
||||
) {
|
||||
return static::STATUS_COMPLETE;
|
||||
|
@ -670,7 +743,7 @@ class Track extends Model
|
|||
}
|
||||
}
|
||||
|
||||
private function updateTagsForTrackFile($trackFile) {
|
||||
private function updateTagsForTrackFile(TrackFile $trackFile) {
|
||||
$trackFile->touch();
|
||||
|
||||
if (\File::exists($trackFile->getFile())) {
|
||||
|
@ -699,7 +772,7 @@ class Track extends Model
|
|||
}
|
||||
|
||||
if ($this->cover !== null) {
|
||||
$command .= '--artwork ' . $this->cover->getFile() . ' ';
|
||||
$command .= '--artwork '.$this->cover->getFile(Image::ORIGINAL).' ';
|
||||
}
|
||||
|
||||
$command .= '--overWrite';
|
||||
|
@ -740,7 +813,7 @@ class Track extends Model
|
|||
|
||||
if ($format == 'MP3' && $this->cover_id != null && is_file($this->cover->getFile())) {
|
||||
$tagWriter->tag_data['attached_picture'][0] = [
|
||||
'data' => file_get_contents($this->cover->getFile()),
|
||||
'data' => file_get_contents($this->cover->getFile(Image::ORIGINAL)),
|
||||
'picturetypeid' => 2,
|
||||
'description' => 'cover',
|
||||
'mime' => $this->cover->mime
|
||||
|
@ -765,4 +838,28 @@ class Track extends Model
|
|||
{
|
||||
return 'track-'.$this->id.'-'.$key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function shouldBeIndexed():bool {
|
||||
return $this->is_listed &&
|
||||
$this->published_at !== null &&
|
||||
!$this->trashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function toElasticsearch():array {
|
||||
return [
|
||||
'title' => $this->title,
|
||||
'artist' => $this->user->display_name,
|
||||
'published_at' => $this->published_at ? $this->published_at->toIso8601String() : null,
|
||||
'genre' => $this->genre->name,
|
||||
'track_type' => $this->trackType->title,
|
||||
'show_songs' => $this->showSongs->pluck('title')
|
||||
];
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Config;
|
||||
use Helpers;
|
||||
|
@ -26,17 +26,47 @@ use Illuminate\Database\Eloquent\Model;
|
|||
use App;
|
||||
use File;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\TrackFile
|
||||
*
|
||||
* @property integer $id
|
||||
* @property integer $track_id
|
||||
* @property boolean $is_master
|
||||
* @property string $format
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property boolean $is_cacheable
|
||||
* @property boolean $status
|
||||
* @property \Carbon\Carbon $expires_at
|
||||
* @property integer $filesize
|
||||
* @property-read \Poniverse\Ponyfm\Models\Track $track
|
||||
* @property-read mixed $extension
|
||||
* @property-read mixed $url
|
||||
* @property-read mixed $size
|
||||
* @property-read mixed $is_expired
|
||||
*/
|
||||
class TrackFile extends Model
|
||||
{
|
||||
// used for the "status" property
|
||||
const STATUS_NOT_BEING_PROCESSED = 0;
|
||||
const STATUS_PROCESSING = 1;
|
||||
const STATUS_PROCESSING_ERROR = 2;
|
||||
const STATUS_PROCESSING_PENDING = 3;
|
||||
|
||||
protected $appends = ['is_expired'];
|
||||
protected $dates = ['expires_at'];
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'track_id' => 'integer',
|
||||
'is_master' => 'boolean',
|
||||
'format' => 'string',
|
||||
'is_cacheable' => 'boolean',
|
||||
'status' => 'integer',
|
||||
'filesize' => 'integer',
|
||||
];
|
||||
|
||||
public function track()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Track')->withTrashed();
|
||||
public function track() {
|
||||
return $this->belongsTo(Track::class)->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,6 +105,11 @@ class TrackFile extends Model
|
|||
}
|
||||
}
|
||||
|
||||
public function getIsExpiredAttribute() {
|
||||
return $this->attributes['expires_at'] === null ||
|
||||
$this->expires_at->isPast();
|
||||
}
|
||||
|
||||
public function getFormatAttribute($value)
|
||||
{
|
||||
return $value;
|
|
@ -18,10 +18,17 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\TrackType
|
||||
*
|
||||
* @property integer $id
|
||||
* @property string $title
|
||||
* @property string $editor_title
|
||||
*/
|
||||
class TrackType extends Model
|
||||
{
|
||||
protected $table = 'track_types';
|
|
@ -18,7 +18,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm;
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use Gravatar;
|
||||
use Illuminate\Auth\Authenticatable;
|
||||
|
@ -29,11 +29,46 @@ use Illuminate\Database\Eloquent\Model;
|
|||
use Illuminate\Foundation\Auth\Access\Authorizable;
|
||||
use Auth;
|
||||
use Illuminate\Support\Str;
|
||||
use Poniverse\Ponyfm\Contracts\Searchable;
|
||||
use Poniverse\Ponyfm\Traits\IndexedInElasticsearchTrait;
|
||||
use Venturecraft\Revisionable\RevisionableTrait;
|
||||
|
||||
class User extends Model implements AuthenticatableContract, CanResetPasswordContract, \Illuminate\Contracts\Auth\Access\Authorizable
|
||||
/**
|
||||
* Poniverse\Ponyfm\Models\User
|
||||
*
|
||||
* @property integer $id
|
||||
* @property string $display_name
|
||||
* @property string $username
|
||||
* @property boolean $sync_names
|
||||
* @property string $email
|
||||
* @property string $gravatar
|
||||
* @property string $slug
|
||||
* @property boolean $uses_gravatar
|
||||
* @property boolean $can_see_explicit_content
|
||||
* @property string $bio
|
||||
* @property integer $track_count
|
||||
* @property integer $comment_count
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property \Carbon\Carbon $updated_at
|
||||
* @property integer $avatar_id
|
||||
* @property string $remember_token
|
||||
* @property boolean $is_archived
|
||||
* @property \Carbon\Carbon $disabled_at
|
||||
* @property-read \Poniverse\Ponyfm\Models\Image $avatar
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\ResourceUser[] $users
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Role[] $roles
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Comment[] $comments
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Poniverse\Ponyfm\Models\Track[] $tracks
|
||||
* @property-read mixed $url
|
||||
* @property-read mixed $message_url
|
||||
* @property-read \Illuminate\Database\Eloquent\Collection|\Venturecraft\Revisionable\Revision[] $revisionHistory
|
||||
* @method static \Illuminate\Database\Query\Builder|\Poniverse\Ponyfm\Models\User userDetails()
|
||||
*/
|
||||
class User extends Model implements AuthenticatableContract, CanResetPasswordContract, \Illuminate\Contracts\Auth\Access\Authorizable, Searchable
|
||||
{
|
||||
use Authenticatable, CanResetPassword, Authorizable, RevisionableTrait;
|
||||
use Authenticatable, CanResetPassword, Authorizable, RevisionableTrait, IndexedInElasticsearchTrait;
|
||||
|
||||
protected $elasticsearchType = 'user';
|
||||
|
||||
protected $table = 'users';
|
||||
protected $casts = [
|
||||
|
@ -46,6 +81,8 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||
'avatar_id' => 'integer',
|
||||
'is_archived' => 'boolean',
|
||||
];
|
||||
protected $dates = ['created_at', 'updated_at', 'disabled_at'];
|
||||
protected $hidden = ['disabled_at'];
|
||||
|
||||
public function scopeUserDetails($query)
|
||||
{
|
||||
|
@ -89,12 +126,12 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||
|
||||
public function avatar()
|
||||
{
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Image');
|
||||
return $this->belongsTo('Poniverse\Ponyfm\Models\Image');
|
||||
}
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->hasMany('Poniverse\Ponyfm\ResourceUser', 'artist_id');
|
||||
return $this->hasMany('Poniverse\Ponyfm\Models\ResourceUser', 'artist_id');
|
||||
}
|
||||
|
||||
public function roles()
|
||||
|
@ -104,12 +141,12 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||
|
||||
public function comments()
|
||||
{
|
||||
return $this->hasMany('Poniverse\Ponyfm\Comment', 'profile_id')->orderBy('created_at', 'desc');
|
||||
return $this->hasMany('Poniverse\Ponyfm\Models\Comment', 'profile_id')->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
public function tracks()
|
||||
{
|
||||
return $this->hasMany('Poniverse\Ponyfm\Track', 'user_id');
|
||||
return $this->hasMany('Poniverse\Ponyfm\Models\Track', 'user_id');
|
||||
}
|
||||
|
||||
public function getIsArchivedAttribute()
|
||||
|
@ -201,10 +238,10 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||
/**
|
||||
* Returns true if this user has the given role.
|
||||
*
|
||||
* @param $roleName
|
||||
* @param string $roleName
|
||||
* @return bool
|
||||
*/
|
||||
public function hasRole($roleName)
|
||||
public function hasRole($roleName):bool
|
||||
{
|
||||
foreach ($this->roles as $role) {
|
||||
if ($role->name === $roleName) {
|
||||
|
@ -214,4 +251,40 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function mapPublicUserSummary(User $user) {
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'name' => $user->display_name,
|
||||
'slug' => $user->slug,
|
||||
'url' => $user->url,
|
||||
'is_archived' => $user->is_archived,
|
||||
'avatars' => [
|
||||
'small' => $user->getAvatarUrl(Image::SMALL),
|
||||
'normal' => $user->getAvatarUrl(Image::NORMAL)
|
||||
],
|
||||
'created_at' => $user->created_at
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this model in Elasticsearch-friendly form. The array returned by
|
||||
* this method should match the current mapping for this model's ES type.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toElasticsearch():array {
|
||||
return [
|
||||
'username' => $this->username,
|
||||
'display_name' => $this->display_name,
|
||||
'tracks' => $this->tracks->pluck('title'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function shouldBeIndexed():bool {
|
||||
return $this->disabled_at === null;
|
||||
}
|
||||
}
|
|
@ -20,11 +20,19 @@
|
|||
|
||||
namespace Poniverse\Ponyfm;
|
||||
|
||||
use Poniverse\Ponyfm\Models\Playlist;
|
||||
use ZipStream;
|
||||
|
||||
class PlaylistDownloader
|
||||
{
|
||||
/**
|
||||
* @var Playlist
|
||||
*/
|
||||
private $_playlist;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $_format;
|
||||
|
||||
function __construct($playlist, $format)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue