Merge branch 'development'

This commit is contained in:
nelsonlaquet 2013-10-06 21:53:18 -05:00
commit baad99a26c
22 changed files with 259 additions and 34 deletions

View file

@ -0,0 +1,94 @@
<?php
namespace Api\V1;
use Commands\DeleteTrackCommand;
use Commands\EditTrackCommand;
use Commands\UploadTrackCommand;
use Cover;
use Entities\Favourite;
use Entities\Image;
use Entities\ResourceLogItem;
use Entities\ResourceUser;
use Entities\Track;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Response;
class TracksController extends \ApiControllerBase {
public function getTrackRadioDetails($hash) {
$track = Track
::with('user', 'album', 'user.avatar', 'cover', 'comments', 'genre')
->published()
->whereHash($hash)->first();
if (!$track)
return Response::json(['message' => 'Track not found.'], 403);
$comments = [];
foreach ($track->comments as $comment) {
$comments[] = [
'id' => $comment->id,
'created_at' => $comment->created_at,
'content' => $comment->content,
'user' => [
'name' => $comment->user->display_name,
'id' => $comment->user->id,
'url' => $comment->user->url,
'avatars' => [
'normal' => $comment->user->getAvatarUrl(Image::NORMAL),
'thumbnail' => $comment->user->getAvatarUrl(Image::THUMBNAIL),
'small' => $comment->user->getAvatarUrl(Image::SMALL),
]
]
];
}
return Response::json([
'id' => $track->id,
'title' => $track->title,
'description' => $track->description,
'lyrics' => $track->lyrics,
'user' => [
'id' => $track->user->id,
'name' => $track->user->display_name,
'url' => $track->user->url,
'avatars' => [
'thumbnail' => $track->user->getAvatarUrl(Image::THUMBNAIL),
'small' => $track->user->getAvatarUrl(Image::SMALL),
'normal' => $track->user->getAvatarUrl(Image::NORMAL)
]
],
'stats' => [
'views' => $track->view_count,
'plays' => $track->play_count,
'downloads' => $track->download_count,
'comments' => $track->comment_count,
'favourites' => $track->favourite_count
],
'url' => $track->url,
'is_vocal' => !!$track->is_vocal,
'is_explicit' => !!$track->is_explicit,
'is_downloadable' => !!$track->is_downloadable,
'published_at' => $track->published_at,
'duration' => $track->duration,
'genre' => $track->genre != null
?
[
'id' => $track->genre->id,
'name' => $track->genre->name
] : null,
'type' => [
'id' => $track->track_type->id,
'name' => $track->track_type->title
],
'covers' => [
'thumbnail' => $track->getCoverUrl(Image::THUMBNAIL),
'small' => $track->getCoverUrl(Image::SMALL),
'normal' => $track->getCoverUrl(Image::NORMAL)
],
'comments' => $comments
], 200);
}
}

View file

@ -57,7 +57,7 @@
if (!$user)
App::abort(404);
$query = Track::summary()->with('genre', 'cover', 'user')->userDetails()->whereUserId($user->id)->whereNotNull('published_at');
$query = Track::summary()->published()->listed()->explicitFilter()->with('genre', 'cover', 'user')->userDetails()->whereUserId($user->id)->whereNotNull('published_at');
$tracks = [];
$singles = [];
@ -88,7 +88,7 @@
if (!$user)
App::abort(404);
$trackQuery = Track::summary()->with('genre', 'cover', 'user')->userDetails()->whereUserId($user->id)->whereNotNull('published_at')->orderBy('created_at', 'desc')->take(20);
$trackQuery = Track::summary()->published()->explicitFilter()->listed()->with('genre', 'cover', 'user')->userDetails()->whereUserId($user->id)->whereNotNull('published_at')->orderBy('created_at', 'desc')->take(20);
$latestTracks = [];
foreach ($trackQuery->get() as $track) {

View file

@ -18,6 +18,7 @@
$recentQuery = Track::summary()
->with(['genre', 'user', 'cover', 'user.avatar'])
->whereIsLatest(true)
->listed()
->userDetails()
->explicitFilter()
->published()

View file

@ -52,6 +52,7 @@
$query = Track::summary()
->userDetails()
->listed()
->explicitFilter()
->published()
->with('user', 'genre', 'cover', 'album', 'album.user');
@ -64,7 +65,7 @@
$tracks = [];
$ids = [];
foreach ($query->get() as $track) {
foreach ($query->get(['tracks.*']) as $track) {
$tracks[] = Track::mapPublicTrackSummary($track);
$ids[] = $track->id;
}
@ -93,7 +94,6 @@
return Response::json(Track::mapPrivateTrackShow($track), 200);
}
private function applyFilters($query) {
if (Input::has('order')) {
$order = \Input::get('order');
@ -127,7 +127,6 @@
$join->on('tracks.id', '=', 'show_song_track.track_id');
});
$query->whereIn('show_song_track.show_song_id', Input::get('songs'));
$query->select('tracks.*');
}
return $query;

View file

@ -138,7 +138,7 @@
$response->header('Content-Disposition', 'attachment; filename="' . $track->getDownloadFilenameFor($formatName) . '"');
} else {
$response->header('X-Accel-Redirect', $filename);
$response->header('Content-Disposition', 'attachment; filename=' . $track->getDownloadFilenameFor($formatName));
$response->header('Content-Disposition', 'attachment; filename="' . $track->getDownloadFilenameFor($formatName) . '"');
}
$time = gmdate(filemtime($filename));

View file

@ -0,0 +1,23 @@
<?php
use Entities\Track;
use Illuminate\Database\Migrations\Migration;
class CreateTrackHashes extends Migration {
public function up() {
Schema::table('tracks', function($table) {
$table->string('hash', 32)->notNullable()->indexed();
});
foreach (Track::with('user')->get() as $track) {
$track->updateHash();
$track->save();
}
}
public function down() {
Schema::table('tracks', function($table) {
$table->dropColumn('hash');
});
}
}

View file

@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
class TrackIsListed extends Migration {
public function up() {
Schema::table('tracks', function($table) {
$table->boolean('is_listed')->notNullable()->indexed();
});
DB::update('
UPDATE
tracks
SET
is_listed = true');
}
public function down() {
Schema::table('tracks', function($table) {
$table->dropColumn('is_listed');
});
}
}

View file

@ -1,6 +1,15 @@
<?php
class Helpers {
/**
* Removes whitespace and special characters from a string
* and sets all characters to lower case.
*/
public static function sanitizeInputForHashing($value) {
$value = preg_replace('/[^A-Za-z0-9]/', '', $value);
return strtolower($value);
}
public static function template($template) {
echo file_get_contents('templates/' . $template);
}

View file

@ -70,6 +70,7 @@
$track->track_type_id = $this->_input['track_type_id'];
$track->is_explicit = $this->_input['is_explicit'] == 'true';
$track->is_downloadable = $this->_input['is_downloadable'] == 'true';
$track->is_listed = $this->_input['is_listed'] == 'true';
$track->is_vocal = $isVocal;
if (isset($this->_input['album_id']) && strlen(trim($this->_input['album_id']))) {

View file

@ -40,6 +40,7 @@
$track->user_id = $user->id;
$track->title = pathinfo($trackFile->getClientOriginalName(), PATHINFO_FILENAME);
$track->duration = $audio->getDuration();
$track->is_listed = true;
$track->save();

View file

@ -92,6 +92,7 @@
public function ensureDirectoryExists() {
$destination = $this->getDirectory();
umask(0);
if (!is_dir($destination))
mkdir($destination, 777, true);

View file

@ -19,7 +19,9 @@
class Track extends \Eloquent {
protected $softDelete = true;
use SlugTrait;
use SlugTrait {
SlugTrait::setTitleAttribute as setTitleAttributeSlug;
}
public static $Formats = [
'FLAC' => ['index' => 0, 'extension' => 'flac', 'tag_format' => 'metaflac', 'tag_method' => 'updateTagsWithGetId3', 'mime_type' => 'audio/flac', 'command' => 'ffmpeg 2>&1 -y -i {$source} -acodec flac -aq 8 -f flac {$target}'],
@ -45,6 +47,10 @@
$query->whereNotNull('published_at');
}
public function scopeListed($query) {
$query->whereIsListed(true);
}
public function scopeExplicitFilter($query) {
if (!Auth::check() || !Auth::user()->can_see_explicit_content)
$query->whereIsExplicit(false);
@ -58,6 +64,7 @@
$trackIds = Cache::remember('popular_tracks' . $count . '-' . ($allowExplicit ? 'explicit' : 'safe'), 5, function() use ($allowExplicit, $count) {
$query = static
::published()
->listed()
->join(DB::raw('
( SELECT `track_id`, `created_at`
FROM `resource_log_items`
@ -247,7 +254,8 @@
'duration' => $track->duration,
'genre_id' => $track->genre_id,
'track_type_id' => $track->track_type_id,
'cover_url' => $track->getCoverUrl(Image::SMALL)
'cover_url' => $track->getCoverUrl(Image::SMALL),
'is_listed' => !!$track->is_listed
];
}
@ -257,6 +265,10 @@
return $this->belongsTo('Entities\Genre');
}
public function trackType() {
return $this->belongsTo('Entities\TrackType', 'track_type_id');
}
public function comments(){
return $this->hasMany('Entities\Comment')->orderBy('created_at', 'desc');
}
@ -289,6 +301,11 @@
return date('Y', strtotime($this->release_date));
}
public function setTitleAttribute($value) {
$this->setTitleAttributeSlug($value);;
$this->updateHash();
}
public function getFilesize($formatName) {
return Cache::remember($this->getCacheKey('filesize-' . $formatName), 1440, function () use ($formatName) {
$file = $this->getFileFor($formatName);
@ -332,6 +349,7 @@
public function ensureDirectoryExists() {
$destination = $this->getDirectory();
umask(0);
if (!is_dir($destination))
mkdir($destination, 777);
@ -401,6 +419,10 @@
return URL::to('/t' . $this->id . '/dl.' . $format['extension']);
}
public function updateHash() {
$this->hash = md5(Helpers::sanitizeInputForHashing($this->user->display_name) . '-' . Helpers::sanitizeInputForHashing($this->title));
}
public function updateTags() {
foreach (self::$Formats as $format => $data) {
$this->{$data['tag_method']}($format);

View file

@ -57,7 +57,7 @@
return $this->email;
}
public function setDisplayName($value) {
public function setDisplayNameAttribute($value) {
$this->attributes['display_name'] = $value;
$this->attributes['slug'] = Str::slug($value);
}

View file

@ -49,6 +49,11 @@
Route::get('p{id}', 'PlaylistsController@getShortlink')->where('id', '\d+');
Route::get('p{id}/dl.{extension}', 'PlaylistsController@getDownload' );
Route::group(['prefix' => 'api/v1'], function() {
Route::get('/tracks/radio-details/{hash}', 'Api\V1\TracksController@getTrackRadioDetails');
Route::post('/tracks/radio-details/{hash}', 'Api\V1\TracksController@getTrackRadioDetails');
});
Route::group(['prefix' => 'api/web'], function() {
Route::get('/taxonomies/all', 'Api\Web\TaxonomiesController@getAll');

View file

@ -24,7 +24,7 @@
</script>
<header>
<a href="/">pony.fm</a>
<a href="/">pony.fm <small>(beta)</small></a>
<div class="now-playing">
@if (Auth::check())
<div class="user-details dropdown">

View file

@ -302,7 +302,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\RedirectResponse as SymfonyRedirect;
class Application extends Container implements HttpKernelInterface, ResponsePreparerInterface
{
const VERSION = '4.0.6';
const VERSION = '4.0.7';
protected $booted = false;
protected $bootingCallbacks = array();
protected $bootedCallbacks = array();
@ -949,11 +949,19 @@ class Request
$query = $parameters;
break;
}
$queryString = '';
if (isset($components['query'])) {
parse_str(html_entity_decode($components['query']), $qs);
if ($query) {
$query = array_replace($qs, $query);
}
$queryString = http_build_query($query, '', '&');
} else {
$query = $qs;
$queryString = $components['query'];
}
} elseif ($query) {
$queryString = http_build_query($query, '', '&');
}
$server['REQUEST_URI'] = $components['path'] . ('' !== $queryString ? '?' . $queryString : '');
$server['QUERY_STRING'] = $queryString;
return new static($query, $request, array(), $cookies, $files, $server, $content);
@ -989,8 +997,11 @@ class Request
$dup->basePath = null;
$dup->method = null;
$dup->format = null;
if (!$dup->get('_format')) {
$dup->setRequestFormat($this->getRequestFormat());
if (!$dup->get('_format') && $this->get('_format')) {
$dup->attributes->set('_format', $this->get('_format'));
}
if (!$dup->getRequestFormat(null)) {
$dup->setRequestFormat($format = $this->getRequestFormat(null));
}
return $dup;
}
@ -1176,6 +1187,12 @@ class Request
return 443;
}
}
if ($host = $this->headers->get('HOST')) {
if (false !== ($pos = strrpos($host, ':'))) {
return intval(substr($host, $pos + 1));
}
return 'https' === $this->getScheme() ? 443 : 80;
}
return $this->server->get('SERVER_PORT');
}
public function getUser()
@ -1509,14 +1526,14 @@ class Request
return rtrim($prefix, '/');
}
$truncatedRequestUri = $requestUri;
if (($pos = strpos($requestUri, '?')) !== false) {
if (false !== ($pos = strpos($requestUri, '?'))) {
$truncatedRequestUri = substr($requestUri, 0, $pos);
}
$basename = basename($baseUrl);
if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) {
return '';
}
if (strlen($requestUri) >= strlen($baseUrl) && (false !== ($pos = strpos($requestUri, $baseUrl)) && $pos !== 0)) {
if (strlen($requestUri) >= strlen($baseUrl) && false !== ($pos = strpos($requestUri, $baseUrl)) && $pos !== 0) {
$baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
}
return rtrim($baseUrl, '/');
@ -1787,15 +1804,22 @@ class ServerBag extends ParameterBag
} elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) {
$authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION'];
}
if (null !== $authorizationHeader && 0 === stripos($authorizationHeader, 'basic')) {
if (null !== $authorizationHeader) {
if (0 === stripos($authorizationHeader, 'basic')) {
$exploded = explode(':', base64_decode(substr($authorizationHeader, 6)));
if (count($exploded) == 2) {
list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded;
}
} elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && 0 === stripos($authorizationHeader, 'digest')) {
$headers['PHP_AUTH_DIGEST'] = $authorizationHeader;
$this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader;
}
}
}
if (isset($headers['PHP_AUTH_USER'])) {
$headers['AUTHORIZATION'] = 'Basic ' . base64_encode($headers['PHP_AUTH_USER'] . ':' . $headers['PHP_AUTH_PW']);
} elseif (isset($headers['PHP_AUTH_DIGEST'])) {
$headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST'];
}
return $headers;
}
@ -2225,6 +2249,7 @@ class NativeSessionStorage implements SessionStorageInterface
} else {
session_start();
}
$this->loadSession();
}
return $ret;
}
@ -3257,8 +3282,7 @@ class ExceptionServiceProvider extends ServiceProvider
}
protected function getResourcePath()
{
$base = $this->app['path.base'];
return $base . '/vendor/laravel/framework/src/Illuminate/Exception/resources';
return 'F:\\Nelson\\My Documents - Personal\\Visual Studio 2010\\Projects\\Poniverse\\spa.pony.fm\\vendor\\laravel\\framework\\src\\Illuminate\\Exception' . '/resources';
}
}
namespace Illuminate\Routing;
@ -3611,7 +3635,7 @@ class ErrorHandler
if (null === ($error = error_get_last())) {
return;
}
unset($this->reservedMemory);
$this->reservedMemory = '';
$type = $error['type'];
if (0 === $this->level || !in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) {
return;
@ -4218,10 +4242,6 @@ class ProviderRepository
$this->files->put($path, json_encode($manifest));
return $manifest;
}
protected function getManifestPath($app)
{
return $this->manifestPath;
}
protected function freshManifest(array $providers)
{
list($eager, $deferred) = array(array(), array());
@ -5381,6 +5401,7 @@ abstract class Model implements ArrayAccess, ArrayableInterface, JsonableInterfa
protected $appends = array();
protected $fillable = array();
protected $guarded = array('*');
protected $dates = array();
protected $touches = array();
protected $with = array();
public $exists = false;
@ -5853,7 +5874,7 @@ abstract class Model implements ArrayAccess, ArrayableInterface, JsonableInterfa
}
public function freshTimestamp()
{
return new DateTime();
return new Carbon();
}
public function freshTimestampString()
{
@ -6153,7 +6174,8 @@ abstract class Model implements ArrayAccess, ArrayableInterface, JsonableInterfa
}
public function getDates()
{
return array(static::CREATED_AT, static::UPDATED_AT, static::DELETED_AT);
$defaults = array(static::CREATED_AT, static::UPDATED_AT, static::DELETED_AT);
return array_merge($this->dates, $defaults);
}
public function fromDateTime($value)
{
@ -6590,7 +6612,7 @@ class Store extends SymfonySession
}
protected function mergeNewFlashes(array $keys)
{
$values = array_unique(array_merge($this->get('flash.new'), $keys));
$values = array_unique(array_merge($this->get('flash.new', array()), $keys));
$this->put('flash.new', $values);
}
protected function removeFromOldFlashData(array $keys)

View file

@ -122,6 +122,7 @@ angular.module('ponyfm').controller "account-track", [
cover: track.cover_url
album_id: track.album_id
is_published: track.is_published
is_listed: track.is_listed
$scope.selectedAlbum = if track.album_id then albumsDb[track.album_id] else null
$scope.selectedSongs = {}

View file

@ -3,6 +3,12 @@ window.handleResize = () ->
$siteBody = $ '.site-body'
$siteBody.height windowHeight - $('header').height()
$('.dropdown-menu').each () ->
$this = $ this
newMaxHeight = windowHeight - $this.parent().offset().top - $this.parent().height() - 5
$this.css
'max-height': newMaxHeight
$('.stretch-to-bottom').each () ->
$this = $ this
newHeight = windowHeight - $this.offset().top

View file

@ -217,6 +217,7 @@ html body {
html .dropdown-menu {
.border-radius(0px);
padding: 0px;
overflow-y: auto;
> li {
margin: 0px;

View file

@ -18,7 +18,7 @@ header {
float: left;
width: (@pfm-sidebar-size - 25);
height: 64px;
line-height: 52px;
line-height: 42px;
background: #84528A;
color: #fff;
font-size: 24pt;
@ -27,12 +27,22 @@ header {
position: relative;
z-index: 600;
font-family: 'Josefin Sans', sans-serif;
position: relative;
&:hover {
background: darken(#84528A, 25%);
color: #fff;
text-decoration: none;
}
small {
font-family: 'Arial';
font-size: 8pt;
color: #eee;
position: absolute;
top: 30px;
left: 33px;
}
}
.user-details {

View file

@ -91,12 +91,15 @@
</div>
</div>
<div class="row-fluid">
<div class="span6 form-row">
<div class="span4 form-row">
<label for="is_explicit"><input ng-disabled="isSaving" ng-change="touchModel()" id="is_explicit" type="checkbox" ng-model="edit.is_explicit" /> Contains Explicit Content</label>
</div>
<div class="span6 form-row">
<div class="span4 form-row">
<label for="is_downloadable"><input ng-disabled="isSaving" ng-change="touchModel()" id="is_downloadable" type="checkbox" ng-model="edit.is_downloadable" /> Is Downloadable</label>
</div>
<div class="span4 form-row">
<label for="is_listed"><input ng-disabled="isSaving" ng-change="touchModel()" id="is_listed" type="checkbox" ng-model="edit.is_listed" /> Is Listed</label>
</div>
</div>
<div class="form-row">
<label class="strong">Choose a License:</label>

View file

@ -27,6 +27,7 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/kriswallsmith/assetic" />
<excludeFolder url="file://$MODULE_DIR$/vendor/laravel/framework" />
<excludeFolder url="file://$MODULE_DIR$/vendor/monolog/monolog" />
<excludeFolder url="file://$MODULE_DIR$/vendor/navruzm/lmongo" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nesbot/carbon" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nikic/php-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/patchwork/utf8" />
@ -75,6 +76,7 @@
<root url="file://$MODULE_DIR$/vendor/intouch/laravel-newrelic" />
<root url="file://$MODULE_DIR$/vendor/laravel/framework" />
<root url="file://$MODULE_DIR$/vendor/monolog/monolog" />
<root url="file://$MODULE_DIR$/vendor/navruzm/lmongo" />
<root url="file://$MODULE_DIR$/vendor/symfony/debug" />
<root url="file://$MODULE_DIR$/vendor/symfony/finder" />
<root url="file://$MODULE_DIR$/vendor/symfony/console" />
@ -115,6 +117,7 @@
<root url="file://$MODULE_DIR$/vendor/intouch/laravel-newrelic" />
<root url="file://$MODULE_DIR$/vendor/laravel/framework" />
<root url="file://$MODULE_DIR$/vendor/monolog/monolog" />
<root url="file://$MODULE_DIR$/vendor/navruzm/lmongo" />
<root url="file://$MODULE_DIR$/vendor/symfony/debug" />
<root url="file://$MODULE_DIR$/vendor/symfony/finder" />
<root url="file://$MODULE_DIR$/vendor/symfony/console" />