This commit is contained in:
herby2212 2024-04-25 12:43:32 -07:00 committed by GitHub
commit c3f13c3a13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1253 additions and 13 deletions

View file

@ -44,6 +44,7 @@
<li role="presentation"><a id="nav-tabs-plays" href="#tabs-plays" aria-controls="tabs-plays" data-toggle="tab" role="tab">Media Type</a></li>
<li role="presentation"><a id="nav-tabs-stream" href="#tabs-stream" aria-controls="tabs-stream" data-toggle="tab" role="tab">Stream Type</a></li>
<li role="presentation"><a id="nav-tabs-total" href="#tabs-total" aria-controls="tabs-total" data-toggle="tab" role="tab">Play Totals</a></li>
<li role="presentation"><a id="nav-tabs-library-statistics" href="#tabs-library-statistics" aria-controls="tabs-library-statistics" data-toggle="tab" role="tab">Library Statistics</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane" id="tabs-plays">
@ -225,6 +226,68 @@
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="tabs-library-statistics">
<div id="section-library-graphs">
<div class="row">
<div class="col-md-12">
<h4><i class="fa fa-history"></i> Daily addition by media type <small>Last <span class="days">30</span> days</small></h4>
<p class="help-block">
The total addition count of shows, seasons, episodes and movies per day.
</p>
<div class="graphs-instance">
<div class="watch-chart" id="graph_additions_by_day">
<div class="graphs-load"><i class="fa fa-refresh fa-spin"></i> Loading chart...</div>
<br>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<h4><i class="fa fa-signal"></i> Library Growth <small>Last <span class="days">30</span> days</small></h4>
<p class="help-block">
The overall library growth by the library stats per day.
</p>
<div class="graphs-instance">
<div class="watch-chart" id="library_growth_by_day">
<div class="graphs-load"><i class="fa fa-refresh fa-spin"></i> Loading chart...</div>
<br>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<h4><i class="fa fa-television"></i> Addition count by media type <small>Last <span class="days">30</span> days</small></h4>
<p class="help-block">
The combined total of tv, movies, and music added to the server.
</p>
<div class="graphs-instance">
<div class="watch-chart" id="graph_additions_by_media_type" style="float: left;">
<div class="graphs-load">
<i class="fa fa-refresh fa-spin"></i> Loading chart...
</div>
<br>
</div>
</div>
</div>
<div class="col-md-6">
<h4><i class="fa fa-television"></i> Addition count by source resolution <small>Last <span class="days">30</span> days</small></h4>
<p class="help-block">
The combined total of tv, movies, and music added to the server by source resolution.
</p>
<div class="graphs-instance">
<div class="watch-chart" id="graph_additions_by_source_resolution" style="float: left;">
<div class="graphs-load">
<i class="fa fa-refresh fa-spin"></i> Loading chart...
</div>
<br>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@ -246,6 +309,8 @@
<script>
var selected_user_id = null;
var _Charts = [];
var _enableChartSync = false;
// Modal popup dialog
function selectHandler(selectedDate, selectedSeries) {
@ -304,15 +369,154 @@
return data_series.map(function(s) {
var obj = Object.assign({}, s);
obj.visible = (chart_visibility[s.name] !== false);
if(chart_name == "graph_additions_by_day" || chart_name == "library_growth_by_day") {
if(obj.name == "Episodes") {
obj.yAxis = 1;
} else if(obj.name == "Tracks") {
obj.yAxis = 2;
}
obj.softThreshold = true;
}
return obj
});
}
var syncLinks = {
'library_growth_by_day': 'graph_additions_by_day',
'graph_additions_by_day': 'library_growth_by_day'
}
function syncGraphs(instance, chart_name, series_name, browserEvent) {
var otherChart = _Charts.find(c => c['renderTo'].id == syncLinks[chart_name]);
var sisterSeries = otherChart.series.find(s => s['name'] == series_name);
// Chart Visibility in storage - needs to be done before setting axis visibility
setGraphVisibility(syncLinks[chart_name], otherChart.series, series_name);
// Axis visibility
if (sisterSeries) {
if (instance.visible) {
sisterSeries.hide();
} else {
sisterSeries.show();
}
}
// Axis name visibility
if (series_name == "Episodes") {
document.getElementsByClassName("highcharts-yaxis-title")[0].innerHTML = (instance.visible) ? "" : "Episodes";
document.getElementsByClassName("highcharts-yaxis-title")[2].innerHTML = (instance.visible) ? "" : "Episodes";
} else if(series_name == "Tracks") {
document.getElementsByClassName("highcharts-yaxis-title")[1].innerHTML = (instance.visible) ? "" : "Tracks";
document.getElementsByClassName("highcharts-yaxis-title")[3].innerHTML = (instance.visible) ? "" : "Tracks";
}
// Tooltip
if(browserEvent) {
//syncTooltip(browserEvent);
}
}
// Sync selected day (tooltip position)
// https://jsfiddle.net/BlackLabel/5wq9sdbp
['mousemove', 'touchmove', 'touchstart', 'mouseleave'].forEach(function (eventType) {
document.getElementById('section-library-graphs').addEventListener(
eventType,
function(e) {
if(_enableChartSync) {
syncTooltip(e);
}
}
);
});
function syncTooltip(e) {
var chart, otherChart,
point, points,
i, event;
for (i = 0; i < Highcharts.charts.length; i = i + 1) {
chart = _Charts[i];
if(chart == undefined || chart.options.chart.type != "line" || !syncLinks.hasOwnProperty(chart.renderTo.id)) {
continue;
}
// Find coordinates within the chart
event = chart.pointer.normalize(e);
const isMouseLeave = e.type == "mouseleave";
otherChart = _Charts.find(c => c['renderTo'].id == syncLinks[chart.renderTo.id]);
// Get the hovered points
points = [];
chart.series.forEach(function (series, idx) {
var _point = series.searchPoint(event, true);
if(isMouseLeave) {
if(_point) {
_point.setState('');
points.push(_point);
}
} else {
if(_point && _point.series.visible == true) {
points.push(_point);
}
}
});
if(points.length && !points.includes(undefined)) {
number = 0;
if(isMouseLeave) {
chart.tooltip.hide();
chart.xAxis[0].hideCrosshair();
} else {
chart.tooltip.refresh(points);
chart.xAxis[0].drawCrosshair(e, points[0]);
//chart.xAxis[0].drawCrosshair(e, points[points.length]);
pointsChart2 = []
}
otherChart.series.forEach(function(_series, idx) {
if(_series.visible) {
try {
var _point = _series.points[points[number].x];
if(isMouseLeave) {
_point.setState('');
} else {
pointsChart2.push(_point);
}
number++;
} catch {
// Graph render issue. Normalizes by itself so no additional measures required.
// No effect on the user.
}
}
});
if(isMouseLeave) {
otherChart.tooltip.hide();
otherChart.xAxis[0].hideCrosshair();
} else {
otherChart.tooltip.refresh(pointsChart2);
otherChart.xAxis[0].drawCrosshair(e, pointsChart2[0]);
}
}
/**
* If a matching chart is found the other will be retrieved through the syncLink so no additional iteration is required.
**/
break;
}
}
function setGraphVisibility(chart_name, data_series, series_name) {
var chart_key = 'HighCharts_' + chart_name;
var chart_visibility = data_series.map(function(s) {
return {name: s.name, visible: (s.name === series_name) ? !s.visible : s.visible}
var _visible = (s.name === series_name) ? !s.visible : s.visible;
return {
name: s.name, visible: _visible
}
});
setLocalStorage(chart_key, JSON.stringify(chart_visibility));
@ -320,6 +524,12 @@
function getGraphColors(data_series) {
var colors = {
'Artists': '#461d1d',
'Albums': '#9b4141',
'Tracks': '#f06464',
'Shows': '#ff3300',
'Seasons': '#ff6600',
'Episodes': '#ff9933',
'TV': '#E5A00D',
'Movies': '#FFFFFF',
'Music': '#F06464',
@ -336,6 +546,10 @@
return series_colors;
}
</script>
<script src="${http_root}js/graphs/library_additions_by_media_type.js${cache_param}"></script>
<script src="${http_root}js/graphs/library_growth_by_day.js${cache_param}"></script>
<script src="${http_root}js/graphs/library_additions_by_source_resolution.js${cache_param}"></script>
<script src="${http_root}js/graphs/library_additions_by_day.js${cache_param}"></script>
<script src="${http_root}js/graphs/plays_by_day.js${cache_param}"></script>
<script src="${http_root}js/graphs/plays_by_dayofweek.js${cache_param}"></script>
<script src="${http_root}js/graphs/plays_by_hourofday.js${cache_param}"></script>
@ -368,6 +582,8 @@
case '#tabs-3':
current_tab = '#tabs-total'
break
case '#tabs-4':
current_tab = '#tabs-library-statistics'
default:
break
}
@ -436,6 +652,8 @@
function loadGraphsTab1(time_range, yaxis) {
$('#days-selection').show();
$('#months-selection').hide();
$('#user-selection').show();
$('#yaxis-selection').show();
setGraphFormat(yaxis);
@ -512,7 +730,7 @@
$.ajax({
url: "get_plays_by_top_10_users",
type: 'get',
data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id },
data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id },
dataType: "json",
success: function(data) {
if (yaxis === 'duration') { dataSecondsToHours(data); }
@ -522,13 +740,15 @@
var hc_plays_by_user = new Highcharts.Chart(hc_plays_by_user_options);
}
});
$('#nav-tabs-plays').tab('show');
}
function loadGraphsTab2(time_range, yaxis) {
$('#days-selection').show();
$('#months-selection').hide();
$('#user-selection').show();
$('#yaxis-selection').show();
setGraphFormat(yaxis);
@ -649,6 +869,8 @@
function loadGraphsTab3(time_range, yaxis) {
$('#days-selection').hide();
$('#months-selection').show();
$('#user-selection').show();
$('#yaxis-selection').show();
setGraphFormat(yaxis);
@ -670,6 +892,132 @@
$('#nav-tabs-total').tab('show');
}
function loadGraphsTab4(time_range, yaxis) {
$('#days-selection').show();
$('#months-selection').hide();
$('#user-selection').hide();
$('#yaxis-selection').hide();
// Fixed as graph uses own measurement 'addition count' with formatting of 'plays'
setGraphFormat("plays", "ddd D MMM YY");
var _graph_1_call = $.ajax({
url: "get_additions_by_date",
type: 'get',
data: { time_range: time_range },
dataType: "json",
success: function(data) {
var dateArray = [];
$.each(data.categories, function (i, day) {
dateArray.push(moment(day, 'YYYY-MM-DD').valueOf());
// Highlight the weekend
if ((moment(day, 'YYYY-MM-DD').format('ddd') === 'Sat') ||
(moment(day, 'YYYY-MM-DD').format('ddd') === 'Sun')) {
hc_library_additions_by_day_options.xAxis.plotBands.push({
from: i-0.5,
to: i+0.5,
color: 'rgba(80,80,80,0.3)'
});
}
});
hc_library_additions_by_day_options.yAxis.min = 0;
hc_library_additions_by_day_options.xAxis.categories = dateArray;
hc_library_additions_by_day_options.series = getGraphVisibility(hc_library_additions_by_day_options.chart.renderTo, data.series);
hc_library_additions_by_day_options.colors = getGraphColors(data.series);
const hc_library_additions_by_day = new Highcharts.Chart(hc_library_additions_by_day_options);
}
});
var _graph_2_call = $.ajax({
url: "get_additions_by_date",
type: 'get',
data: { time_range: time_range, growth: 1 },
dataType: "json",
success: function(data) {
var dateArray = [];
$.each(data.categories, function (i, day) {
dateArray.push(moment(day, 'YYYY-MM-DD').valueOf());
// Highlight the weekend
if ((moment(day, 'YYYY-MM-DD').format('ddd') === 'Sat') ||
(moment(day, 'YYYY-MM-DD').format('ddd') === 'Sun')) {
hc_library_growth_by_day_options.xAxis.plotBands.push({
from: i-0.5,
to: i+0.5,
color: 'rgba(80,80,80,0.3)'
});
}
});
hc_library_growth_by_day_options.yAxis.min = 0;
hc_library_growth_by_day_options.xAxis.categories = dateArray;
hc_library_growth_by_day_options.series = getGraphVisibility(hc_library_growth_by_day_options.chart.renderTo, data.series);
hc_library_growth_by_day_options.colors = getGraphColors(data.series);
const hc_library_growth_by_day = new Highcharts.Chart(hc_library_growth_by_day_options);
}
});
$.ajax({
url: "get_additions_by_media_type",
type: 'get',
data: { time_range: time_range },
dataType: "json",
success: function(data) {
hc_library_additions_by_media_type_options.xAxis.categories = data.categories;
hc_library_additions_by_media_type_options.series = getGraphVisibility(hc_library_additions_by_media_type_options.chart.renderTo, data.series);
hc_library_additions_by_media_type_options.colors = getGraphColors(data.series);
const hc_library_additions_by_media_type = new Highcharts.Chart(hc_library_additions_by_media_type_options);
}
});
$.ajax({
url: "get_additions_by_resolution",
type: 'get',
data: { time_range: time_range },
dataType: "json",
success: function(data) {
hc_library_additions_by_source_resolution_options.xAxis.categories = data.categories;
hc_library_additions_by_source_resolution_options.series = getGraphVisibility(hc_library_additions_by_source_resolution_options.chart.renderTo, data.series);
hc_library_additions_by_source_resolution_options.colors = getGraphColors(data.series);
const hc_library_additions_by_source_resolution = new Highcharts.Chart(hc_library_additions_by_source_resolution_options);
}
});
$.when(_graph_1_call, _graph_2_call).then(function(a1, a2) {
// Define charts.
if(Highcharts.charts.includes(undefined)) {
_Charts = Highcharts.charts.filter(chart => chart !== undefined);
} else {
_Charts = Highcharts.charts;
}
// Both graphs loaded successful. Prepare sync.
chart = _Charts.find(c => c['renderTo'].id == "graph_additions_by_day");
otherChart = _Charts.find(c => c['renderTo'].id == syncLinks["graph_additions_by_day"]);
/**
* Sync different series to fix possible misconfiguration/data that leads
* to a series having different states in the two additions_by_day graphs.
**/
chart.series.forEach(function(series) {
_othSeries = otherChart.series.find(s => s['name'] == series.name);
if(!series.visible == _othSeries.visible) {
syncGraphs(series, syncLinks["graph_additions_by_day"], series.name);
}
});
// Enable sync.
_enableChartSync = true;
});
$('#nav-tabs-library-statistics').tab('show')
}
// Set initial state
if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); }
if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); }
if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); }
if (current_tab === '#tabs-library-statistics') { loadGraphsTab4(current_day_range, yaxis); }
// Tab1 opened
$('#nav-tabs-plays').on('shown.bs.tab', function (e) {
e.preventDefault();
@ -694,6 +1042,14 @@
loadGraphsTab3(current_month_range, yaxis);
});
// Tab4 opened
$('#nav-tabs-library-statistics').on('shown.bs.tab', function (e) {
e.preventDefault();
current_tab = $(this).attr('href');
setLocalStorage('graph_tab', current_tab.replace('#',''));
loadGraphsTab4(current_day_range, yaxis);
});
// Date range changed
$('#graph-days').tooltip({ container: 'body', placement: 'top', html: true });
$('#graph-days').on('change', function() {
@ -702,6 +1058,7 @@
setLocalStorage('graph_days', current_day_range);
if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); }
if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); }
if (current_tab === '#tabs-library-statistics') { loadGraphsTab4(current_day_range, yaxis); }
$('.days').text(current_day_range);
});
@ -732,6 +1089,7 @@
if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); }
if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); }
if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); }
//GraphTab4 not needed as no user relevant graph is included here -> may change in the future?
});
// Y-axis changed
@ -741,14 +1099,15 @@
if (current_tab === '#tabs-plays') { loadGraphsTab1(current_day_range, yaxis); }
if (current_tab === '#tabs-stream') { loadGraphsTab2(current_day_range, yaxis); }
if (current_tab === '#tabs-total') { loadGraphsTab3(current_month_range, yaxis); }
//GraphTab4 not needed as Addition Count is used for the Y-Axis
});
function setGraphFormat(type) {
function setGraphFormat(type, tooltipFormat) {
if (type === 'plays') {
yaxis_format = function() { return this.value; };
tooltip_format = function() {
if (moment(this.x, 'X').isValid() && (this.x > 946684800)) {
var s = '<b>'+ moment(this.x).format('ddd MMM D') +'</b>';
var s = tooltipFormat ? '<b>'+ moment(this.x).format(tooltipFormat) +'</b>' : '<b>'+ moment(this.x).format('ddd MMM D') +'</b>';
} else {
var s = '<b>'+ this.x +'</b>';
}
@ -800,6 +1159,8 @@
}
hc_plays_by_day_options.xAxis.plotBands = [];
hc_library_additions_by_day_options.xAxis.plotBands = [];
hc_library_growth_by_day_options.xAxis.plotBands = [];
hc_plays_by_stream_type_options.xAxis.plotBands = [];
hc_concurrent_streams_by_stream_type_options.xAxis.plotBands = [];
@ -812,6 +1173,14 @@
hc_plays_by_source_resolution_options.yAxis.labels.formatter = yaxis_format;
hc_plays_by_stream_resolution_options.yAxis.labels.formatter = yaxis_format;
hc_plays_by_platform_by_stream_type_options.yAxis.labels.formatter = yaxis_format;
hc_library_additions_by_day_options.yAxis[0].labels.formatter = yaxis_format;
hc_library_additions_by_day_options.yAxis[1].labels.formatter = yaxis_format;
hc_library_additions_by_day_options.yAxis[2].labels.formatter = yaxis_format;
hc_library_growth_by_day_options.yAxis[0].labels.formatter = yaxis_format;
hc_library_growth_by_day_options.yAxis[1].labels.formatter = yaxis_format;
hc_library_growth_by_day_options.yAxis[2].labels.formatter = yaxis_format;
hc_library_additions_by_media_type_options.yAxis.labels.formatter = yaxis_format;
hc_library_additions_by_source_resolution_options.yAxis.labels.formatter = yaxis_format;
hc_plays_by_user_by_stream_type_options.yAxis.labels.formatter = yaxis_format;
hc_plays_by_month_options.yAxis.labels.formatter = yaxis_format;
@ -819,6 +1188,10 @@
hc_plays_by_dayofweek_options.tooltip.formatter = tooltip_format;
hc_plays_by_hourofday_options.tooltip.formatter = tooltip_format;
hc_plays_by_platform_options.tooltip.formatter = tooltip_format;
hc_library_additions_by_media_type_options.tooltip.formatter = tooltip_format;
hc_library_additions_by_source_resolution_options.tooltip.formatter = tooltip_format;
hc_library_additions_by_day_options.tooltip.formatter = tooltip_format;
hc_library_growth_by_day_options.tooltip.formatter = tooltip_format;
hc_plays_by_user_options.tooltip.formatter = tooltip_format;
hc_plays_by_stream_type_options.tooltip.formatter = tooltip_format;
hc_plays_by_source_resolution_options.tooltip.formatter = tooltip_format;

View file

@ -0,0 +1,86 @@
var hc_library_additions_by_day_options = {
chart: {
type: 'line',
backgroundColor: 'rgba(0,0,0,0)',
renderTo: 'graph_additions_by_day'
},
title: {
text: ''
},
legend: {
enabled: true,
itemStyle: {
font: '9pt "Open Sans", sans-serif',
color: '#A0A0A0'
},
itemHoverStyle: {
color: '#FFF'
},
itemHiddenStyle: {
color: '#444'
}
},
credits: {
enabled: false
},
plotOptions: {
series: {
allowPointSelect: false,
threshold: 0,
events: {
legendItemClick: function(event) {
syncGraphs(this, this.chart.renderTo.id, this.name, event.browserEvent);
setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
}
}
}
},
xAxis: {
type: 'datetime',
labels: {
formatter: function() {
return moment(this.value).format("YY MMM D");
},
style: {
color: '#aaa'
}
},
categories: [{}],
plotBands: []
},
yAxis: [{
title: {
text: null
},
labels: {
style: {
color: '#aaa'
}
}
}, {
title: {
text: 'Episodes'
},
labels: {
style: {
color: '#aaa'
}
},
opposite: true
}, {
title: {
text: 'Tracks'
},
labels: {
style: {
color: '#aaa'
}
},
opposite: true
}],
tooltip: {
shared: true,
crosshairs: true
},
series: [{}]
};

View file

@ -0,0 +1,73 @@
var hc_library_additions_by_media_type_options = {
chart: {
type: 'column',
backgroundColor: 'rgba(0,0,0,0)',
renderTo: 'graph_additions_by_media_type'
},
title: {
text: ''
},
legend: {
enabled: true,
itemStyle: {
font: '9pt "Open Sans", sans-serif',
color: '#A0A0A0'
},
itemHoverStyle: {
color: '#FFF'
},
itemHiddenStyle: {
color: '#444'
}
},
credits: {
enabled: false
},
xAxis: {
categories: [{}],
labels: {
style: {
color: '#aaa'
}
}
},
yAxis: {
title: {
text: null
},
labels: {
style: {
color: '#aaa'
}
},
stackLabels: {
enabled: false,
style: {
color: '#fff'
}
}
},
plotOptions: {
column: {
borderWidth: 0,
stacking: 'normal',
dataLabels: {
enabled: false,
style: {
color: '#000'
}
}
},
series: {
events: {
legendItemClick: function () {
setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
}
}
}
},
tooltip: {
shared: true
},
series: [{}]
};

View file

@ -0,0 +1,73 @@
var hc_library_additions_by_source_resolution_options = {
chart: {
type: 'column',
backgroundColor: 'rgba(0,0,0,0)',
renderTo: 'graph_additions_by_source_resolution'
},
title: {
text: ''
},
legend: {
enabled: true,
itemStyle: {
font: '9pt "Open Sans", sans-serif',
color: '#A0A0A0'
},
itemHoverStyle: {
color: '#FFF'
},
itemHiddenStyle: {
color: '#444'
}
},
credits: {
enabled: false
},
xAxis: {
categories: [{}],
labels: {
style: {
color: '#aaa'
}
}
},
yAxis: {
title: {
text: null
},
labels: {
style: {
color: '#aaa'
}
},
stackLabels: {
enabled: false,
style: {
color: '#fff'
}
}
},
plotOptions: {
column: {
borderWidth: 0,
stacking: 'normal',
dataLabels: {
enabled: false,
style: {
color: '#000'
}
}
},
series: {
events: {
legendItemClick: function () {
setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
}
}
}
},
tooltip: {
shared: true
},
series: [{}]
};

View file

@ -0,0 +1,86 @@
var hc_library_growth_by_day_options = {
chart: {
type: 'line',
backgroundColor: 'rgba(0,0,0,0)',
renderTo: 'library_growth_by_day'
},
title: {
text: ''
},
legend: {
enabled: true,
itemStyle: {
font: '9pt "Open Sans", sans-serif',
color: '#A0A0A0'
},
itemHoverStyle: {
color: '#FFF'
},
itemHiddenStyle: {
color: '#444'
}
},
credits: {
enabled: false
},
plotOptions: {
series: {
allowPointSelect: false,
threshold: 0,
events: {
legendItemClick: function(event) {
syncGraphs(this, this.chart.renderTo.id, this.name, event.browserEvent);
setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
}
}
}
},
xAxis: {
type: 'datetime',
labels: {
formatter: function() {
return moment(this.value).format("YY MMM D");
},
style: {
color: '#aaa'
}
},
categories: [{}],
plotBands: []
},
yAxis: [{
title: {
text: null
},
labels: {
style: {
color: '#aaa'
}
}
}, {
title: {
text: 'Episodes'
},
labels: {
style: {
color: '#aaa'
}
},
opposite: true
}, {
title: {
text: 'Tracks'
},
labels: {
style: {
color: '#aaa'
}
},
opposite: true
}],
tooltip: {
shared: true,
crosshairs: true
},
series: [{}]
};

View file

@ -976,6 +976,25 @@
<p class="help-block">Refresh the libraries list when Tautulli starts.</p>
</div>
<div class="form-group advanced-setting">
<label for="refresh_library_stats_data_interval">Library Statistics Data Refresh Interval</label>
<div class="row">
<div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="refresh_library_stats_data_interval" name="refresh_library_stats_data_interval" value="${config['refresh_library_stats_data_interval']}" size="5" data-parsley-range="[6,24]" data-parsley-trigger="change" data-parsley-errors-container="#refresh_library_stats_data_interval_error" required>
</div>
<div id="refresh_library_stats_data_interval_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
<p class="help-block">The interval (in hours) Tautulli will request an update of all media items from your Plex Media Server.</p>
<p class="help-block">Minimum 6, maximum 24, default 12.</p>
<p class="help-block">This process, depending on your library sizes, can take multiple minutes up to half an hour.</p>
</div>
<div class="checkbox advanced-setting">
<label>
<input type="checkbox" id="refresh_library_stats_data_on_startup" name="refresh_library_stats_data_on_startup" value="1" ${config['refresh_library_stats_data_on_startup']}> Refresh Library Statistics Data on Startup
</label>
<p class="help-block">Refresh the library statistics data when Tautulli starts.</p>
</div>
<div class="padded-header">
<h3>Plex.tv Authentication</h3>
</div>

View file

@ -485,11 +485,15 @@ def initialize_scheduler():
# Refresh the users list and libraries list
user_hours = CONFIG.REFRESH_USERS_INTERVAL if 1 <= CONFIG.REFRESH_USERS_INTERVAL <= 24 else 12
library_hours = CONFIG.REFRESH_LIBRARIES_INTERVAL if 1 <= CONFIG.REFRESH_LIBRARIES_INTERVAL <= 24 else 12
library_stats_data_hours = CONFIG.REFRESH_LIBRARY_STATS_DATA_INTERVAL if 6 <= CONFIG.REFRESH_LIBRARY_STATS_DATA_INTERVAL <= 24 else 12
schedule_job(users.refresh_users, 'Refresh users list',
hours=user_hours, minutes=0, seconds=0)
schedule_job(libraries.refresh_libraries, 'Refresh libraries list',
hours=library_hours, minutes=0, seconds=0)
schedule_job(libraries.refresh_library_statistics, 'Refresh libraries statistics data',
hours=library_stats_data_hours, minutes=0, seconds=0)
schedule_job(activity_pinger.connect_server, 'Check for server response',
hours=0, minutes=0, seconds=0)
@ -509,6 +513,9 @@ def initialize_scheduler():
schedule_job(libraries.refresh_libraries, 'Refresh libraries list',
hours=0, minutes=0, seconds=0)
schedule_job(libraries.refresh_library_statistics, 'Refresh libraries statistics data',
hours=0, minutes=0, seconds=0)
# Schedule job to reconnect server
schedule_job(activity_pinger.connect_server, 'Check for server response',
hours=0, minutes=0, seconds=30, args=(False,))
@ -611,6 +618,9 @@ def startup_refresh():
if CONFIG.PMS_IP and CONFIG.PMS_TOKEN and CONFIG.REFRESH_LIBRARIES_ON_STARTUP:
libraries.refresh_libraries()
# Refresh the library stats data on startup
if CONFIG.PMS_IP and CONFIG.PMS_TOKEN and CONFIG.REFRESH_LIBRARY_STATS_DATA_ON_STARTUP:
libraries.refresh_library_statistics()
def sig_handler(signum=None, frame=None):
if signum is not None:
@ -821,6 +831,14 @@ def dbcheck():
"media_info TEXT)"
)
# library_stats_items table :: This table keeps record of all added items
c_db.execute(
'CREATE TABLE IF NOT EXISTS library_stats_items (id INTEGER PRIMARY KEY AUTOINCREMENT, '
'added_at INTEGER, updated_at INTEGER, last_viewed_at INTEGER, pms_identifier TEXT, section_id INTEGER, '
'library_name TEXT, rating_key INTEGER, parent_rating_key INTEGER, grandparent_rating_key INTEGER, '
'media_type TEXT, media_info TEXT, user_ratings TEXT)'
)
# mobile_devices table :: This table keeps record of devices linked with the mobile app
c_db.execute(
"CREATE TABLE IF NOT EXISTS mobile_devices (id INTEGER PRIMARY KEY AUTOINCREMENT, "
@ -2716,6 +2734,20 @@ def dbcheck():
"ON session_history_media_info (transcode_decision)"
)
# Create library_stats_items table indices
c_db.execute(
'CREATE INDEX IF NOT EXISTS "idx_library_stats_items_media_type" '
'ON "library_stats_items" ("media_type")'
)
c_db.execute(
'CREATE INDEX IF NOT EXISTS "idx_library_stats_items_added_at" '
'ON "library_stats_items" ("added_at")'
)
c_db.execute(
'CREATE INDEX IF NOT EXISTS "idx_library_stats_items_rating_key" '
'ON "library_stats_items" ("rating_key")'
)
# Create lookup table indices
c_db.execute(
"CREATE UNIQUE INDEX IF NOT EXISTS idx_tvmaze_lookup "

View file

@ -188,6 +188,8 @@ _CONFIG_DEFINITIONS = {
'PLEXPY_AUTO_UPDATE': (int, 'General', 0),
'REFRESH_LIBRARIES_INTERVAL': (int, 'Monitoring', 12),
'REFRESH_LIBRARIES_ON_STARTUP': (int, 'Monitoring', 1),
'REFRESH_LIBRARY_STATS_DATA_INTERVAL': (int, 'Monitoring', 12),
'REFRESH_LIBRARY_STATS_DATA_ON_STARTUP': (int, 'Monitoring', 1),
'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12),
'REFRESH_USERS_ON_STARTUP': (int, 'Monitoring', 1),
'SESSION_DB_WRITE_ATTEMPTS': (int, 'Advanced', 5),
@ -299,6 +301,7 @@ SETTINGS = [
'PMS_VERSION',
'PMS_WEB_URL',
'REFRESH_LIBRARIES_INTERVAL',
'REFRESH_LIBRARY_STATS_DATA_INTERVAL',
'REFRESH_USERS_INTERVAL',
'SHOW_ADVANCED_SETTINGS',
'TIME_FORMAT',
@ -338,6 +341,7 @@ CHECKED_SETTINGS = [
'PLEXPY_AUTO_UPDATE',
'PMS_URL_MANUAL',
'REFRESH_LIBRARIES_ON_STARTUP',
'REFRESH_LIBRARY_STATS_DATA_ON_STARTUP',
'REFRESH_USERS_ON_STARTUP',
'SYS_TRAY_ICON',
'THEMOVIEDB_LOOKUP',
@ -709,3 +713,8 @@ class Config(object):
self.ANON_REDIRECT_DYNAMIC = 1
self.CONFIG_VERSION = 22
if self.CONFIG_VERSION == 22:
self.REFRESH_LIBRARY_STATS_DATA_INTERVAL = 12
self.REFRESH_LIBRARY_STATS_DATA_ON_STARTUP = 1
self.CONFIG_VERSION = 23

View file

@ -2473,3 +2473,52 @@ class DataFactory(object):
return False
return True
def get_library_stats_item(self, rating_key=''):
monitor_db = database.MonitorDatabase()
if rating_key:
try:
query = 'SELECT * FROM library_stats_items WHERE rating_key = ?'
result = monitor_db.select(query=query, args=[rating_key])
except Exception as e:
logger.warn("Tautulli DataFactory :: Unable to execute database query for get_library_stats_item: %s." % e)
return []
else:
return []
return result
def set_library_stats_item(self, rating_key='', created_at=None):
monitor_db = database.MonitorDatabase()
pms_connect = pmsconnect.PmsConnect()
metadata = pms_connect.get_metadata_details(rating_key=rating_key, skip_cache=True, media_info=True)
keys = {'rating_key': metadata['rating_key']}
_addedAt = metadata['added_at']
# Catch media items which have a timestamp when their corresponding library did not existed
added_at = _addedAt if _addedAt > created_at else created_at
values = {'added_at': added_at,
'updated_at': metadata['updated_at'],
'last_viewed_at': metadata['last_viewed_at'],
'section_id': metadata['section_id'],
'library_name': metadata['library_name'],
'parent_rating_key': metadata['parent_rating_key'],
'grandparent_rating_key': metadata['grandparent_rating_key'],
'media_type': metadata['media_type'],
'media_info': json.dumps(metadata['media_info']),
# TODO json array with ratings from all users
'user_ratings': ''
}
try:
monitor_db.upsert(table_name='library_stats_items', key_dict=keys, value_dict=values)
except Exception as e:
logger.warn("Tautulli DataFactory :: Unable to execute database query for set_library_stats_item: %s." % e)
return False
return True

View file

@ -387,6 +387,333 @@ class Graphs(object):
'series': series_output}
return output
def get_total_additions_per_day(self, time_range='30', growth=False):
monitor_db = database.MonitorDatabase()
time_range = helpers.cast_to_int(time_range) or 30
timestamp = helpers.timestamp() - time_range * 24 * 60 * 60
join_statement = ' AS lsi JOIN library_sections AS ls ON ' \
'lsi.section_id = ls.section_id AND lsi.library_name = ls.section_name ' \
'AND ls.is_active = 1 AND ls.deleted_section = 0 '
try:
if growth:
query = 'SELECT ' \
'0 AS date_added, ' \
'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "show" THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "season" THEN 1 ELSE 0 END) AS season_count, ' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS episode_count, ' \
'SUM(CASE WHEN media_type = "artist" THEN 1 ELSE 0 END) AS artist_count, ' \
'SUM(CASE WHEN media_type = "album" THEN 1 ELSE 0 END) AS album_count, ' \
'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS track_count ' \
'FROM library_stats_items %s' \
'WHERE added_at < %s ' \
'UNION ALL ' \
'SELECT ' \
'date(added_at, "unixepoch", "localtime") AS date_added, ' \
'COUNT(DISTINCT CASE WHEN media_type = "movie" THEN rating_key ELSE NULL END) AS movie_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "show" THEN grandparent_rating_key ELSE NULL END) AS tv_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "season" THEN parent_rating_key ELSE NULL END) AS season_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "episode" THEN rating_key ELSE NULL END) AS episode_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "artist" THEN grandparent_rating_key ELSE NULL END) AS artist_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "album" THEN parent_rating_key ELSE NULL END) AS album_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "track" THEN rating_key ELSE NULL END) AS track_count ' \
'FROM library_stats_items %s' \
'WHERE added_at >= %s ' \
'GROUP BY date_added ' \
'ORDER BY date_added' % (join_statement, timestamp,
join_statement, timestamp)
result = monitor_db.select(query)
else:
query = 'SELECT ' \
'date(added_at, "unixepoch", "localtime") AS date_added, ' \
'COUNT(DISTINCT CASE WHEN media_type = "movie" THEN rating_key ELSE NULL END) AS movie_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "show" THEN grandparent_rating_key ELSE NULL END) AS tv_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "season" THEN parent_rating_key ELSE NULL END) AS season_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "episode" THEN rating_key ELSE NULL END) AS episode_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "artist" THEN grandparent_rating_key ELSE NULL END) AS artist_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "album" THEN parent_rating_key ELSE NULL END) AS album_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "track" THEN rating_key ELSE NULL END) AS track_count ' \
'FROM library_stats_items %s' \
'WHERE added_at >= %s ' \
'GROUP BY date_added ' \
'ORDER BY date_added' % (join_statement, timestamp)
result = monitor_db.select(query)
except Exception as e:
logger.warn("Tautulli Graphs :: Unable to execute database query for get_total_additions_per_day: %s." % e)
return None
# create our date range as some days may not have any data
# but we still want to display them
base = datetime.date.today()
date_list = [base - datetime.timedelta(days=x) for x in range(0, int(time_range))]
categories = []
series_1 = []
series_2 = []
series_3 = []
series_4 = []
series_5 = []
series_6 = []
series_7 = []
if growth:
base_value_1 = result[0]['movie_count'] if result[0]['movie_count'] else 0
base_value_2 = result[0]['tv_count'] if result[0]['tv_count'] else 0
base_value_3 = result[0]['season_count'] if result[0]['season_count'] else 0
base_value_4 = result[0]['episode_count'] if result[0]['episode_count'] else 0
base_value_5 = result[0]['artist_count'] if result[0]['artist_count'] else 0
base_value_6 = result[0]['album_count'] if result[0]['album_count'] else 0
base_value_7 = result[0]['track_count'] if result[0]['track_count'] else 0
for date_item in sorted(date_list):
date_string = date_item.strftime('%Y-%m-%d')
categories.append(date_string)
series_1_value = 0
series_2_value = 0
series_3_value = 0
series_4_value = 0
series_5_value = 0
series_6_value = 0
series_7_value = 0
for item in result:
if date_string == item['date_added']:
series_1_value = item['movie_count'] if item['movie_count'] else 0
series_2_value = item['tv_count'] if item['tv_count'] else 0
series_3_value = item['season_count'] if item['season_count'] else 0
series_4_value = item['episode_count'] if item['episode_count'] else 0
series_5_value = item['artist_count'] if item['artist_count'] else 0
series_6_value = item['album_count'] if item['album_count'] else 0
series_7_value = item['track_count'] if item['track_count'] else 0
continue
series_1.append(series_1_value)
series_2.append(series_2_value)
series_3.append(series_3_value)
series_4.append(series_4_value)
series_5.append(series_5_value)
series_6.append(series_6_value)
series_7.append(series_7_value)
if growth:
for idx, day in enumerate(series_1):
series_1[idx] = base_value_1 + day
base_value_1 += day
for idx, day in enumerate(series_2):
series_2[idx] = base_value_2 + day
base_value_2 += day
for idx, day in enumerate(series_3):
series_3[idx] = base_value_3 + day
base_value_3 += day
for idx, day in enumerate(series_4):
series_4[idx] = base_value_4 + day
base_value_4 += day
for idx, day in enumerate(series_5):
series_5[idx] = base_value_5 + day
base_value_5 += day
for idx, day in enumerate(series_6):
series_6[idx] = base_value_6 + day
base_value_6 += day
for idx, day in enumerate(series_7):
series_7[idx] = base_value_7 + day
base_value_7 += day
series_1_output = {'name': 'Movies',
'data': series_1}
series_2_output = {'name': 'Shows',
'data': series_2}
series_3_output = {'name': 'Seasons',
'data': series_3}
series_4_output = {'name': 'Episodes',
'data': series_4}
series_5_output = {'name': 'Artists',
'data': series_5}
series_6_output = {'name': 'Albums',
'data': series_6}
series_7_output = {'name': 'Tracks',
'data': series_7}
series_output = []
if libraries.has_library_type('movie'):
series_output.append(series_1_output)
if libraries.has_library_type('show'):
series_output.append(series_2_output)
series_output.append(series_3_output)
series_output.append(series_4_output)
if libraries.has_library_type('artist'):
series_output.append(series_5_output)
series_output.append(series_6_output)
series_output.append(series_7_output)
output = {'categories': categories,
'series': series_output}
return output
def get_total_additions_by_media_type(self, time_range='30'):
monitor_db = database.MonitorDatabase()
time_range = helpers.cast_to_int(time_range) or 30
timestamp = helpers.timestamp() - time_range * 24 * 60 * 60
try:
query = 'SELECT ' \
'SUM(CASE WHEN media_type = "movie" THEN 1 ELSE 0 END) AS movie_count, ' \
'SUM(CASE WHEN media_type = "show" THEN 1 ELSE 0 END) AS tv_count, ' \
'SUM(CASE WHEN media_type = "season" THEN 1 ELSE 0 END) AS season_count, ' \
'SUM(CASE WHEN media_type = "episode" THEN 1 ELSE 0 END) AS episode_count, ' \
'SUM(CASE WHEN media_type = "artist" THEN 1 ELSE 0 END) AS artist_count, ' \
'SUM(CASE WHEN media_type = "album" THEN 1 ELSE 0 END) AS album_count, ' \
'SUM(CASE WHEN media_type = "track" THEN 1 ELSE 0 END) AS track_count ' \
'FROM library_stats_items AS lsi JOIN library_sections AS ls ON ' \
'lsi.section_id = ls.section_id AND lsi.library_name = ls.section_name ' \
'AND ls.is_active = 1 AND ls.deleted_section = 0 ' \
'WHERE added_at >= %s' % timestamp
result = monitor_db.select(query)
except Exception as e:
logger.warn("Tautulli Graphs :: Unable to execute database query for get_total_additions_by_media_type: %s." % e)
return None
categories = ["Movies", "TV", "Music"]
_catCount = len(categories)
series_1 = [None] * _catCount
series_2 = [None] * _catCount
series_3 = [None] * _catCount
series_4 = [None] * _catCount
series_5 = [None] * _catCount
series_6 = [None] * _catCount
series_7 = [None] * _catCount
content = result[0]
for idx, item in enumerate(categories):
if idx == 0:
series_1[idx] = content['movie_count']
elif idx == 1:
series_2[idx] = content['tv_count']
series_3[idx] = content['season_count']
series_4[idx] = content['episode_count']
else:
series_5[idx] = content['artist_count']
series_6[idx] = content['album_count']
series_7[idx] = content['track_count']
series_1_output = {'name': 'Movies',
'data': series_1}
series_2_output = {'name': 'Shows',
'data': series_2}
series_3_output = {'name': 'Seasons',
'data': series_3}
series_4_output = {'name': 'Episodes',
'data': series_4}
series_5_output = {'name': 'Artists',
'data': series_5}
series_6_output = {'name': 'Albums',
'data': series_6}
series_7_output = {'name': 'Tracks',
'data': series_7}
series_output = []
if libraries.has_library_type('movie'):
series_output.append(series_1_output)
if libraries.has_library_type('show'):
series_output.append(series_2_output)
series_output.append(series_3_output)
series_output.append(series_4_output)
if libraries.has_library_type('artist'):
series_output.append(series_5_output)
series_output.append(series_6_output)
series_output.append(series_7_output)
output = {'categories': categories,
'series': series_output}
return output
def get_total_additions_by_resolution(self, time_range='30'):
monitor_db = database.MonitorDatabase()
time_range = helpers.cast_to_int(time_range) or 30
timestamp = helpers.timestamp() - time_range * 24 * 60 * 60
resolution = '(CASE WHEN media_info LIKE \'%"video_resolution": "4K"%\' THEN "1_4K" ' \
'WHEN media_info LIKE \'%"video_resolution": "1080"%\' THEN "2_1080" ' \
'WHEN media_info LIKE \'%"video_resolution": "720"%\' THEN "3_720" ' \
'WHEN media_info LIKE \'%"video_resolution": "576"%\' THEN "4_576" ' \
'WHEN media_info LIKE \'%"video_resolution": "480"%\' THEN "5_480" ' \
'WHEN media_info LIKE \'%"video_resolution": "sd"%\' THEN "6_SD" ELSE "7_Unknown" END) AS resolution '
join_statement = ' AS lsi JOIN library_sections AS ls ON ' \
'lsi.section_id = ls.section_id AND lsi.library_name = ls.section_name ' \
'AND ls.is_active = 1 AND ls.deleted_section = 0 '
try:
#Change queries for show and episode so they also get a resolution like before? -> Cool for the user,
# but it doesn't make real sense as show/seasons itself have no resolution
query = 'SELECT ' \
'%s, ' \
'COUNT(DISTINCT CASE WHEN media_type = "movie" THEN rating_key ELSE NULL END) AS movie_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "show" THEN grandparent_rating_key ELSE NULL END) AS tv_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "season" THEN parent_rating_key ELSE NULL END) AS season_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "episode" THEN rating_key ELSE NULL END) AS episode_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "artist" THEN grandparent_rating_key ELSE NULL END) AS artist_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "album" THEN parent_rating_key ELSE NULL END) AS album_count, ' \
'COUNT(DISTINCT CASE WHEN media_type = "track" THEN rating_key ELSE NULL END) AS track_count ' \
'FROM library_stats_items %s' \
'WHERE added_at >= %s AND (media_type = "movie" OR media_type = "episode") ' \
'GROUP BY resolution ' \
'ORDER BY resolution' % (resolution, join_statement, timestamp)
result = monitor_db.select(query)
except Exception as e:
logger.warn("Tautulli Graphs :: Unable to execute database query for get_total_additions_by_resolution: %s." % e)
return None
categories = []
series_1 = []
series_2 = []
series_3 = []
series_4 = []
for idx, item in enumerate(result):
#remove sorting indicators (like 1_%)
categories.append(item['resolution'][2:])
series_1.append(item['movie_count'])
series_2.append(item['tv_count'])
series_3.append(item['season_count'])
series_4.append(item['episode_count'])
series_1_output = {'name': 'Movies',
'data': series_1}
series_2_output = {'name': 'Shows',
'data': series_2}
series_3_output = {'name': 'Seasons',
'data': series_3}
series_4_output = {'name': 'Episodes',
'data': series_4}
series_output = []
if libraries.has_library_type('movie'):
series_output.append(series_1_output)
if libraries.has_library_type('show'):
series_output.append(series_2_output)
series_output.append(series_3_output)
series_output.append(series_4_output)
output = {'categories': categories,
'series': series_output}
return output
def get_total_plays_per_month(self, time_range='12', y_axis='plays', user_id=None, grouping=None):
monitor_db = database.MonitorDatabase()

View file

@ -35,6 +35,7 @@ if plexpy.PYTHON2:
import pmsconnect
import session
import users
import datafactory
from plex import Plex
else:
from plexpy import common
@ -47,6 +48,7 @@ else:
from plexpy import session
from plexpy import users
from plexpy.plex import Plex
from plexpy import datafactory
def refresh_libraries():
@ -109,6 +111,59 @@ def refresh_libraries():
logger.warn("Tautulli Libraries :: Unable to refresh libraries list.")
return False
def refresh_library_statistics():
logger.info("Tautulli Library Statistics :: Requesting library statistics data refresh...")
server_id = plexpy.CONFIG.PMS_IDENTIFIER
if not server_id:
logger.error("Tautulli Library Statistics :: No PMS identifier, cannot refresh data. Verify server in settings.")
return
library_sections = pmsconnect.PmsConnect().get_library_details()
if library_sections:
ratingKeys = {}
_pms = pmsconnect.PmsConnect()
_datafactory = datafactory.DataFactory()
for section in library_sections:
if section['created_at'] and section['is_active']:
section_type = section['section_type']
# Push Data to library_sections table
# Placed here as statistics should represent current library status (be in sync)
# initial run: 16min for 16000 item (movies + shows + seasons + episodes + track + album + artist)
# update run: 8min -,-
_resultSet = []
_resultSet.append(_pms.get_library_children_details(section_id=section['section_id'], section_type=section['section_type'], get_media_info=False))
# Add additional library contents for easier filtering at graph queries
if section_type == 'show':
_resultSet.append(_pms.get_library_children_details(section_id=section['section_id'], section_type='season', get_media_info=False))
_resultSet.append(_pms.get_library_children_details(section_id=section['section_id'], section_type='episode', get_media_info=False))
if section_type == 'artist':
_resultSet.append(_pms.get_library_children_details(section_id=section['section_id'], section_type='album', get_media_info=False))
_resultSet.append(_pms.get_library_children_details(section_id=section['section_id'], section_type='track', get_media_info=False))
for result in _resultSet:
for item in result['children_list']:
if item['rating_key'] not in ratingKeys:
ratingKeys[item['rating_key']] = section['created_at']
elif not section['created_at']:
logger.warn("Tautulli Library Statistics :: Library " + library['section_name'] + " skipped, because of no created_at timestamp!")
ratingKeys = sorted(ratingKeys.items())
for key, createdAt in ratingKeys:
_datafactory.set_library_stats_item(rating_key=key, created_at=createdAt)
logger.info("Tautulli Library Statistics :: Data refreshed.")
return True
else:
logger.warn("Tautulli Library Statistics :: Unable to refresh data.")
return False
def add_live_tv_library(refresh=False):
monitor_db = database.MonitorDatabase()

View file

@ -2766,6 +2766,7 @@ class PmsConnect(object):
libraries_output = {'section_id': helpers.get_xml_attr(result, 'key'),
'section_type': helpers.get_xml_attr(result, 'type'),
'section_name': helpers.get_xml_attr(result, 'title'),
'created_at': helpers.get_xml_attr(result, 'createdAt'),
'agent': helpers.get_xml_attr(result, 'agent'),
'thumb': helpers.get_xml_attr(result, 'thumb'),
'art': helpers.get_xml_attr(result, 'art')
@ -2944,6 +2945,7 @@ class PmsConnect(object):
'agent': library['agent'],
'thumb': library['thumb'],
'art': library['art'],
'created_at': library['created_at'],
'count': children_list['library_count'],
'is_active': 1
}

View file

@ -2505,6 +2505,48 @@ class WebInterface(object):
else:
logger.warn("Unable to retrieve data for get_plays_by_top_10_users.")
return result
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth()
#called additions instead of adds so it isn't blocked by adblockers...
def get_additions_by_media_type(self, time_range='30'):
graph = graphs.Graphs()
result = graph.get_total_additions_by_media_type(time_range=time_range)
if result:
return result
else:
logger.warn("Unable to retrieve data for get_additions_by_media_type.")
return result
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth()
#called additions instead of adds so it isn't blocked by adblockers...
def get_additions_by_resolution(self, time_range='30'):
graph = graphs.Graphs()
result = graph.get_total_additions_by_resolution(time_range=time_range)
if result:
return result
else:
logger.warn("Unable to retrieve data for get_additions_by_resolution.")
return result
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth()
#called additions instead of adds so it isn't blocked by adblockers...
def get_additions_by_date(self, time_range='30', growth=False):
graph = graphs.Graphs()
result = graph.get_total_additions_per_day(time_range=time_range, growth=growth)
if result:
return result
else:
logger.warn("Unable to retrieve data for get_additions_by_date.")
return result
@cherrypy.expose
@cherrypy.tools.json_out()
@ -3248,14 +3290,28 @@ class WebInterface(object):
first_run = True
server_changed = True
if not first_run:
for checked_config in config.CHECKED_SETTINGS:
checked_config = checked_config.lower()
if checked_config not in kwargs:
# checked items should be zero or one. if they were not sent then the item was not checked
kwargs[checked_config] = 0
else:
kwargs[checked_config] = 1
checked_configs = [
"launch_browser", "launch_startup", "enable_https", "https_create_cert",
"api_enabled", "freeze_db", "check_github",
"group_history_tables",
"pms_url_manual", "week_start_monday",
"refresh_libraries_on_startup", "refresh_users_on_startup",
"notify_consecutive", "notify_recently_added_upgrade",
"notify_group_recently_added_grandparent", "notify_group_recently_added_parent",
"notify_new_device_initial_only",
"notify_server_update_repeat", "notify_plexpy_update_repeat",
"monitor_pms_updates", "get_file_sizes", "log_blacklist",
"allow_guest_access", "cache_images", "http_proxy", "notify_concurrent_by_ip",
"history_table_activity", "plexpy_auto_update",
"themoviedb_lookup", "tvmaze_lookup", "musicbrainz_lookup", "http_plex_admin",
"newsletter_self_hosted", "newsletter_inline_styles", "sys_tray_icon"
]
for checked_config in checked_configs:
if checked_config not in kwargs:
# checked items should be zero or one. if they were not sent then the item was not checked
kwargs[checked_config] = 0
else:
kwargs[checked_config] = 1
# If http password exists in config, do not overwrite when blank value received
if kwargs.get('http_password') == ' ':