Created new "internal tracking" station type, add support for metadata tracking of stations outside network totals.

This commit is contained in:
Buster Neece 2014-06-18 04:17:56 -05:00
parent d5872deff8
commit 6efb5e4dfb
16 changed files with 299 additions and 140 deletions

7
.gitignore vendored
View File

@ -16,6 +16,10 @@ app/models/Proxy/*.php
.vagrant
.idea
# Local development files.
app/.env
util/test.php
# Auto-generated content.
/vendor/
web/static/api/*
@ -248,5 +252,4 @@ pip-log.txt
*.mo
#Mr Developer
.mr.developer.cfg
app/.env
.mr.developer.cfg

View File

@ -14,7 +14,11 @@ class AnalyticsManager
// Force all times to be UTC before continuing.
date_default_timezone_set('UTC');
$short_names = Station::getShortNameLookup();
$stations = Station::fetchAll();
$short_names = array();
foreach($stations as $station)
$short_names[$station->getShortName()] = $station;
$current_date = date('Y-m-d');
// Interval of seconds to use for "minute"-level statistics.

View File

@ -12,9 +12,13 @@ class Debug
self::$echo_debug = $new_value;
}
static function showErrors()
static function showErrors($include_notices = FALSE)
{
error_reporting(E_ALL & ~E_STRICT);
if ($include_notices)
error_reporting(E_ALL & ~E_STRICT);
else
error_reporting(E_ALL & ~E_STRICT & ~E_NOTICE);
ini_set('display_errors', 1);
}

View File

@ -55,32 +55,38 @@ class NowPlaying
{
set_time_limit(60);
$nowplaying = self::loadNowPlaying();
$np_file_lines = array();
$text_lines = array();
foreach($nowplaying as $shortcode => $station_info)
{
switch($station_info['category'])
{
case "audio":
$text_line = array(
$station_info['id'],
$station_info['name'],
$station_info['listeners'],
$station_info['title'],
$station_info['artist'],
);
$text_lines[] = implode('|', $text_line);
case "video":
$np_file_lines[$shortcode] = $station_info;
break;
}
}
// Generate PVL now playing file.
$pvl_file_path = self::getFilePath('nowplaying');
$nowplaying = self::loadNowPlaying();
$nowplaying_feed = json_encode($nowplaying, JSON_UNESCAPED_SLASHES);
$nowplaying_feed = json_encode($np_file_lines, JSON_UNESCAPED_SLASHES);
@file_put_contents($pvl_file_path, $nowplaying_feed);
// Write shorter delimited file.
$text_file_path = self::getFilePath('nowplaying', 'txt');
$text_lines = array();
foreach($nowplaying as $station_shortcode => $station_info)
{
if ($station_info['category'] == 'audio')
{
$text_line = array(
$station_info['id'],
$station_info['name'],
$station_info['listeners'],
$station_info['title'],
$station_info['artist'],
);
$text_lines[] = implode('|', $text_line);
}
}
$nowplaying_text = implode("<>", $text_lines);
@file_put_contents($text_file_path, $nowplaying_text);

View File

@ -55,12 +55,24 @@ class AdapterAbstract
}
/* Fetch a remote URL. */
protected function getUrl($url = null, $cache_time = 0)
protected function getUrl($c_opts = null, $cache_time = 0)
{
if ($url === null)
$url = $this->url;
// Compose cURL configuration array.
if (is_null($c_opts))
$c_opts = array();
elseif (!is_array($c_opts))
$c_opts = array('url' => $c_opts);
$cache_name = 'nowplaying_url_'.substr(md5($url), 0, 10);
$c_defaults = array(
'url' => $this->url,
'method' => 'GET',
'useragent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2) Gecko/20070219 Firefox/2.0.0.2',
);
$c_opts = array_merge($c_defaults, $c_opts);
\PVL\Debug::print_r($c_opts);
$cache_name = 'nowplaying_url_'.substr(md5($c_opts['url']), 0, 10);
if ($cache_time > 0)
{
$return_raw = \DF\Cache::load($cache_name);
@ -70,13 +82,38 @@ class AdapterAbstract
$curl_start = time();
$postfields = false;
if (!empty($c_opts['params']))
{
if (strtoupper($c_opts['method']) == 'POST')
$postfields = $c_opts['params'];
else
$url = $url.'?'.http_build_query($c_opts['params']);
}
// Start cURL request.
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
$curl = curl_init($c_opts['url']);
// Handle POST support.
if (strtoupper($c_opts['method']) == 'POST')
curl_setopt($curl, CURLOPT_POST, true);
if (!empty($c_opts['referer']))
curl_setopt($curl, CURLOPT_REFERER, $c_opts['referer']);
if ($postfields)
curl_setopt($curl, CURLOPT_POSTFIELDS, $postfields);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 10);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2) Gecko/20070219 Firefox/2.0.0.2');
curl_setopt($curl, CURLOPT_USERAGENT, $c_opts['useragent']);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, TRUE);
curl_setopt($curl, CURLOPT_MAXREDIRS, 3);
// Set custom HTTP headers.
if (!empty($c_opts['headers']))
curl_setopt($curl, CURLOPT_HTTPHEADER, $c_opts['headers']);
$return_raw = \PVL\Utilities::curl_exec_utf8($curl);
// End cURL request.
@ -85,6 +122,7 @@ class AdapterAbstract
$curl_time = $curl_end - $curl_start;
\PVL\Debug::log("Curl processed in ".$curl_time." second(s).");
\PVL\Debug::log("Curl return: ".$return_raw);
$error = curl_error($curl);
if ($error)

View File

@ -0,0 +1,48 @@
<?php
namespace PVL\NowPlayingAdapter;
use \Entity\Station;
class EverfreeRadio extends AdapterAbstract
{
/* Process a nowplaying record. */
protected function _process($np)
{
$return_raw = $this->getUrl(array(
'method' => 'POST',
'params' => 'payload%5Baction%5D=radio-info',
'referer' => 'http://everfree.net/',
'headers' => array(
'Accept: application/json, text/javascript, */*; q=0.01',
'Accept-Encoding: gzip, deflate',
'Accept-Language: en-US,en;q=0.5',
'Cache-Control: no-cache',
'Connection: keep-alive',
'Content-Type: application/x-www-form-urlencoded; charset=UTF-8',
'Pragma: no-cache',
'Referer: http://everfree.net/',
'X-Requested-With: XMLHttpRequest',
),
));
if (!$return_raw)
return false;
$return = @json_decode($return_raw, true);
if ($return['data'])
{
$np['listeners'] = (int)$return['data']['listeners'];
$np['text'] = $return['data']['title'];
list($artist, $title) = explode(" - ", $np['text'], 2);
$np['title'] = $title;
$np['artist'] = $artist;
return $np;
}
return false;
}
}

View File

@ -30,7 +30,8 @@ class ScheduleManager
);
$schedule_stations[0] = NULL;
$stations = $em->createQuery('SELECT s FROM Entity\Station s WHERE s.is_active = 1')
$stations = $em->createQuery('SELECT s FROM Entity\Station s WHERE s.category IN (:types) AND s.is_active = 1')
->setParameter('types', array('audio', 'video'))
->execute();
foreach($stations as $station)

View File

@ -61,8 +61,6 @@ class Analytics extends \DF\Doctrine\Entity
$this->number_avg = (int)(array_sum($number_set) / count($number_set));
}
/**
* Static Functions
*/

View File

@ -261,7 +261,7 @@ class Station extends \DF\Doctrine\Entity
public static function fetchAll()
{
$em = self::getEntityManager();
return $em->createQuery('SELECT s FROM '.__CLASS__.' s WHERE s.is_active=1 ORDER BY s.category ASC, s.weight ASC')->execute();
return $em->createQuery('SELECT s FROM '.__CLASS__.' s WHERE s.is_active = 1 ORDER BY s.category ASC, s.weight ASC')->execute();
}
public static function fetchArray($cached = true)
@ -271,7 +271,9 @@ class Station extends \DF\Doctrine\Entity
if (!$stations || !$cached)
{
$em = self::getEntityManager();
$stations = $em->createQuery('SELECT s FROM '.__CLASS__.' s WHERE s.is_active=1 ORDER BY s.category ASC, s.weight ASC')->getArrayResult();
$stations = $em->createQuery('SELECT s FROM '.__CLASS__.' s WHERE s.is_active = 1 AND s.category IN (:types) ORDER BY s.category ASC, s.weight ASC')
->setParameter('types', array('audio', 'video'))
->getArrayResult();
foreach($stations as &$station)
{
@ -313,11 +315,6 @@ class Station extends \DF\Doctrine\Entity
public static function getCategories()
{
return array(
'event' => array(
'name' => 'Special Event Coverage',
'icon' => 'icon-calendar',
'stations' => array(),
),
'audio' => array(
'name' => 'Radio Stations',
'icon' => 'icon-music',
@ -328,6 +325,16 @@ class Station extends \DF\Doctrine\Entity
'icon' => 'icon-facetime-video',
'stations' => array(),
),
'event' => array(
'name' => 'Special Event Coverage',
'icon' => 'icon-calendar',
'stations' => array(),
),
'internal' => array(
'name' => 'Internal Tracking Station',
'icon' => 'icon-cog',
'stations' => array(),
),
);
}
@ -339,10 +346,6 @@ class Station extends \DF\Doctrine\Entity
foreach($stations as $station)
$categories[$station['category']]['stations'][] = $station;
$special_event = Settings::getSetting('special_event', 0);
if (!$special_event)
unset($categories['event']);
return $categories;
}

View File

@ -39,9 +39,13 @@ class Statistic extends \DF\Doctrine\Entity
$total_overall = 0;
$total_stations = array();
$active_shortcodes = Station::getShortNameLookup();
foreach($nowplaying as $short_code => $info)
{
$total_overall += (int)$info['listeners'];
if (isset($active_shortcodes[$short_code]))
$total_overall += (int)$info['listeners'];
$total_stations[$short_code] = (int)$info['listeners'];
}

View File

@ -1,4 +1,10 @@
<?php
<?php
$cat_raw = \Entity\Station::getCategories();
$cat_select = array();
foreach($cat_raw as $cat_id => $cat_info)
$cat_select[$cat_id] = $cat_info['name'];
return array(
'method' => 'post',
'enctype' => 'multipart/form-data',
@ -58,14 +64,7 @@ return array(
'category' => array('radio', array(
'label' => 'Station Category',
'multiOptions' => array(
'audio' => 'Radio Station',
'video' => 'Video Stream',
/*
'podcast' => 'Podcast',
'event' => 'Special Event',
*/
),
'multiOptions' => $cat_select,
'required' => true,
)),

View File

@ -11,23 +11,42 @@ class Admin_IndexController extends \DF\Controller\Action
*/
public function indexAction()
{
$this->view->stations = \Entity\Station::fetchAll();
$stations = \Entity\Station::fetchAll();
$this->view->stations = $stations;
$internal_stations = array();
foreach($stations as $station)
{
if ($station->category == "internal")
$internal_stations[$station->id] = $station;
}
// Statistics by day.
$daily_stats = $this->em->createQuery('SELECT a FROM Entity\Analytics a WHERE a.type = :type ORDER BY a.timestamp ASC')
->setParameter('type', 'day')
->getArrayResult();
$pvl_ranges = array();
$pvl_averages = array();
$station_averages = array();
$network_data = array(
'PVL Network' => array(
'ranges' => array(),
'averages' => array(),
),
);
foreach($daily_stats as $stat)
{
if (!$stat['station_id'])
{
$pvl_ranges[] = array($stat['timestamp']*1000, $stat['number_min'], $stat['number_max']);
$pvl_averages[] = array($stat['timestamp']*1000, $stat['number_avg']);
$network_name = 'PVL Network';
$network_data[$network_name]['ranges'][] = array($stat['timestamp']*1000, $stat['number_min'], $stat['number_max']);
$network_data[$network_name]['averages'][] = array($stat['timestamp']*1000, $stat['number_avg']);
}
elseif (isset($internal_stations[$stat['station_id']]))
{
$network_name = $internal_stations[$stat['station_id']]['name'];
$network_data[$network_name]['ranges'][] = array($stat['timestamp']*1000, $stat['number_min'], $stat['number_max']);
$network_data[$network_name]['averages'][] = array($stat['timestamp']*1000, $stat['number_avg']);
}
else
{
@ -35,23 +54,47 @@ class Admin_IndexController extends \DF\Controller\Action
}
}
$this->view->pvl_ranges = json_encode($pvl_ranges);
$this->view->pvl_averages = json_encode($pvl_averages);
$network_metrics = array();
foreach($network_data as $network_name => $data_charts)
{
if (isset($data_charts['ranges']))
{
$metric_row = new \stdClass;
$metric_row->name = $network_name.' Listener Range';
$metric_row->type = 'arearange';
$metric_row->data = $data_charts['ranges'];
$network_metrics[] = $metric_row;
}
if (isset($data_charts['averages']))
{
$metric_row = new \stdClass;
$metric_row->name = $network_name.' Daily Average';
$metric_row->type = 'spline';
$metric_row->data = $data_charts['averages'];
$network_metrics[] = $metric_row;
}
}
$stations = \Entity\Station::fetchArray();
$station_metrics = array();
foreach($stations as $station)
{
$station_id = $station['id'];
$series_obj = new \stdClass;
$series_obj->name = $station['name'];
$series_obj->type = 'spline';
$series_obj->data = $station_averages[$station_id];
$station_metrics[] = $series_obj;
if (isset($station_averages[$station_id]))
{
$series_obj = new \stdClass;
$series_obj->name = $station['name'];
$series_obj->type = 'spline';
$series_obj->data = $station_averages[$station_id];
$station_metrics[] = $series_obj;
}
}
$this->view->network_metrics = json_encode($network_metrics);
$this->view->station_metrics = json_encode($station_metrics);
}
}

View File

@ -91,15 +91,7 @@ $(function () {
xDateFormat: '%Y-%m-%d'
},
series: [{
name: 'Listener Range',
type: 'arearange',
data: <?=$this->pvl_ranges ?>
}, {
name: 'Average Listeners',
type: 'spline',
data: <?=$this->pvl_averages ?>
}]
series: <?=$this->network_metrics ?>
});
$('#station_listeners_by_day').highcharts({

View File

@ -15,61 +15,33 @@ $this->headTitle('Manage Stations');
<h2>Current Active Stations</h2>
<div class="row-fluid">
<? foreach($this->categories as $cat_name => $stations): ?>
<div class="span6">
<h3><?=ucfirst($cat_name) ?></h3>
<table class="table table-striped table-bordered table-condensed">
<colgroup>
<col width="30%" />
<col width="10%" />
<col width="60%" />
</colgroup>
<thead>
<tr>
<th>Actions</th>
<th>Image</th>
<th>Station</th>
</tr>
</thead>
<tbody>
<? foreach($stations as $record): ?>
<tr class="input">
<td class="center">
<div class="btn-group">
<?=$this->button(array(
'type' => 'small',
'href' => $this->routeFromHere(array('action' => 'edit', 'id' => $record['id'])),
'text' => 'Edit',
)) ?>
<?=$this->button(array(
'type' => 'small',
'class' => 'danger confirm-delete',
'href' => $this->routeFromHere(array('action' => 'delete', 'id' => $record['id'])),
'text' => 'Delete',
)) ?>
</div>
</td>
<td class="center"><img src="<?=\DF\Url::content($record['image_url']) ?>" style="max-width: 70px;"></td>
<td>
<div><big><?=$record['name'] ?></big></div>
<div>Stream type: <?=$record['type'] ?></div>
<? if ($record['twitter_url']): ?>
<div class="text-success"><i class="icon-check-sign"></i> Has Twitter</div>
<? else: ?>
<div class="text-error"><i class="icon-exclamation-sign"></i> Needs Twitter</div>
<? endif; ?>
<? if ($record['gcal_url']): ?>
<div class="text-success"><i class="icon-check-sign"></i> Has Calendar</div>
<? else: ?>
<div class="text-error"><i class="icon-exclamation-sign"></i> Needs Calendar</div>
<? endif; ?>
</td>
</tr>
<? endforeach; ?>
</tbody>
</table>
<div class="span4">
<h3>Radio Stations</h3>
<?
$this->stations = (array)$this->categories['audio'];
echo $this->renderHere('list');
?>
</div>
<div class="span4">
<h3>Video Streams</h3>
<?
$this->stations = (array)$this->categories['video'];
echo $this->renderHere('list');
?>
</div>
<div class="span4">
<h3>Special Events</h3>
<?
$this->stations = (array)$this->categories['event'];
echo $this->renderHere('list');
?>
<h3>Internal Tracking</h3>
<?
$this->stations = (array)$this->categories['internal'];
echo $this->renderHere('list');
?>
</div>
<? endforeach; ?>
</div>
<h2>Stations Pending Review</h2>

View File

@ -1,7 +1,50 @@
<?php
/**
* Created by PhpStorm.
* User: Buster
* Date: 6/17/14
* Time: 2:14 AM
*/
<table class="table table-striped table-bordered table-condensed">
<colgroup>
<col width="30%" />
<col width="15%" />
<col width="55%" />
</colgroup>
<thead>
<tr>
<th>Actions</th>
<th>Image</th>
<th>Station</th>
</tr>
</thead>
<tbody>
<? foreach($this->stations as $record): ?>
<tr class="input">
<td class="center">
<div class="btn-group">
<?=$this->button(array(
'type' => 'small',
'href' => $this->routeFromHere(array('action' => 'edit', 'id' => $record['id'])),
'text' => 'Edit',
)) ?>
<?=$this->button(array(
'type' => 'small',
'class' => 'danger confirm-delete',
'href' => $this->routeFromHere(array('action' => 'delete', 'id' => $record['id'])),
'text' => 'Delete',
)) ?>
</div>
</td>
<td class="center"><img src="<?=\DF\Url::content($record['image_url']) ?>" style="max-width: 70px;"></td>
<td>
<div><big><?=$record['name'] ?></big></div>
<div>Stream type: <?=$record['type'] ?></div>
<? if ($record['twitter_url']): ?>
<div class="text-success"><i class="icon-check-sign"></i> Has Twitter</div>
<? else: ?>
<div class="text-error"><i class="icon-exclamation-sign"></i> Needs Twitter</div>
<? endif; ?>
<? if ($record['gcal_url']): ?>
<div class="text-success"><i class="icon-check-sign"></i> Has Calendar</div>
<? else: ?>
<div class="text-error"><i class="icon-exclamation-sign"></i> Needs Calendar</div>
<? endif; ?>
</td>
</tr>
<? endforeach; ?>
</tbody>
</table>

View File

@ -143,7 +143,8 @@ class IndexController extends \DF\Controller\Action
}
else
{
$stations_raw = $this->em->createQuery('SELECT s FROM Entity\Station s WHERE s.is_active=1 ORDER BY s.weight ASC')
$stations_raw = $this->em->createQuery('SELECT s FROM Entity\Station s WHERE s.category IN (:types) AND s.is_active = 1 ORDER BY s.weight ASC')
->setParameter('types', array('audio', 'video'))
->getArrayResult();
}