mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-07 13:41:15 -07:00
Lots of changes which will break things!
You can no longer use your PlexWatch db with PlexPy, import tool is included though. Removed the need for separate PlexWatch database. Created PlexWatch import tool (settings -> Monitoring) Lots of re-writes to most pages.
This commit is contained in:
parent
2eb7aff20a
commit
4cde833f9d
17 changed files with 1257 additions and 645 deletions
|
@ -39,7 +39,7 @@
|
||||||
<!-- Nav tabs -->
|
<!-- Nav tabs -->
|
||||||
<ul class="nav nav-pills" role="tablist">
|
<ul class="nav nav-pills" role="tablist">
|
||||||
<li role="presentation" class="active"><a href="#tabs-1" aria-controls="tabs-1" role="tab" data-toggle="tab">Web Interface</a></li>
|
<li role="presentation" class="active"><a href="#tabs-1" aria-controls="tabs-1" role="tab" data-toggle="tab">Web Interface</a></li>
|
||||||
<li role="presentation"><a href="#tabs-2" aria-controls="tabs-2" role="tab" data-toggle="tab">Plex & PlexWatch</a></li>
|
<li role="presentation"><a href="#tabs-2" aria-controls="tabs-2" role="tab" data-toggle="tab">Plex Settings</a></li>
|
||||||
<li role="presentation"><a href="#tabs-3" aria-controls="tabs-3" role="tab" data-toggle="tab">Monitoring</a></li>
|
<li role="presentation"><a href="#tabs-3" aria-controls="tabs-3" role="tab" data-toggle="tab">Monitoring</a></li>
|
||||||
<li role="presentation"><a href="#tabs-5" aria-controls="tabs-5" role="tab" data-toggle="tab">Notification Agents</a></li>
|
<li role="presentation"><a href="#tabs-5" aria-controls="tabs-5" role="tab" data-toggle="tab">Notification Agents</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -182,22 +182,10 @@
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<div class="span4">
|
<div class="span4">
|
||||||
<div class="wellheader">
|
|
||||||
<h3>PlexWatch</h3>
|
|
||||||
</div>
|
|
||||||
<fieldset>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="plexwatch_database">PlexWatch Database</label>
|
|
||||||
<input type="text" id="plexwatch_database" name="plexwatch_database" value="${config['plexwatch_database']}" size="30" data-parsley-trigger="change" required>
|
|
||||||
<p class="help-block">Full path and file name of your PlexWatch database.</p>
|
|
||||||
</div>
|
|
||||||
<div class="checkbox">
|
|
||||||
<input type="checkbox" id="grouping_global_history" name="grouping_global_history" value="1" ${config['grouping_global_history']}> Enable Grouping
|
|
||||||
<p class="help-block">Show grouped history data. <a target="_blank" href="https://github.com/ljunkie/plexWatch#list-watched-shows-option---nogrouping-vs-default">More info.</a></p>
|
|
||||||
</div>
|
|
||||||
<div class="wellheader">
|
<div class="wellheader">
|
||||||
<h3>Extra Settings</h3>
|
<h3>Extra Settings</h3>
|
||||||
</div>
|
</div>
|
||||||
|
<fieldset>
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<input type="checkbox" id="pms_use_bif" name="pms_use_bif" value="1" ${config['pms_use_bif']}> Use BIF thumbs
|
<input type="checkbox" id="pms_use_bif" name="pms_use_bif" value="1" ${config['pms_use_bif']}> Use BIF thumbs
|
||||||
<p class="help-block">If you have media indexing enabled on your server, use these on the activity pane.</p>
|
<p class="help-block">If you have media indexing enabled on your server, use these on the activity pane.</p>
|
||||||
|
@ -221,7 +209,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div role="tabpanel" class="tab-pane" id="tabs-3">
|
<div role="tabpanel" class="tab-pane" id="tabs-3">
|
||||||
<div class="alert">History and IP logging are experimental features which shouldn't be enabled unless you are doing development on the project.</div>
|
<div class="alert">These features are currently experimental, please report any bugs on the Github project page <a href="https://www.github.com/drzoidberg33/plexpy/issues" target="_blank">Here</a></div>
|
||||||
<div class="wellbg" style="padding: 0px 0px 0px 20px;">
|
<div class="wellbg" style="padding: 0px 0px 0px 20px;">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row-fluid">
|
<div class="row-fluid">
|
||||||
|
@ -251,6 +239,12 @@
|
||||||
<p class="help-block">Refresh the user list when PlexPy starts.</p>
|
<p class="help-block">Refresh the user list when PlexPy starts.</p>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<div class="wellheader">
|
||||||
|
<h3>PlexWatch Import Tool</h3>
|
||||||
|
</div>
|
||||||
|
<fieldset>
|
||||||
|
<p class="help-block"><a href="#plexwatch-import-modal" id="toggle-plexwatch-import-modal" data-toggle="modal">Click here to Import an existing Plexwatch database.</a></p>
|
||||||
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<div class="span4">
|
<div class="span4">
|
||||||
<div class="wellheader">
|
<div class="wellheader">
|
||||||
|
@ -811,6 +805,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="plexwatch-import-modal" class="modal hide fade" tabindex="-1" role="dialog"
|
||||||
|
aria-labelledby="plexwatch-import-modal" aria-hidden="true">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</%def>
|
</%def>
|
||||||
|
@ -862,6 +859,18 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load PlexWatch import modal
|
||||||
|
$("#toggle-plexwatch-import-modal").click(function() {
|
||||||
|
$.ajax({
|
||||||
|
url: 'plexwatch_import',
|
||||||
|
cache: false,
|
||||||
|
async: true,
|
||||||
|
complete: function(xhr, status) {
|
||||||
|
$("#plexwatch-import-modal").html(xhr.responseText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function openExtrasDialog() {
|
function openExtrasDialog() {
|
||||||
$("#dialog").dialog({ close: function(){
|
$("#dialog").dialog({ close: function(){
|
||||||
var elem = '<input type="button" data-success="Changes saved successfully">';
|
var elem = '<input type="button" data-success="Changes saved successfully">';
|
||||||
|
|
|
@ -38,10 +38,12 @@ from plexpy import helpers
|
||||||
<th class="desktop" align='left' id="paused_counter">Paused</th>
|
<th class="desktop" align='left' id="paused_counter">Paused</th>
|
||||||
<th class="desktop" align='left' id="stopped">Stopped</th>
|
<th class="desktop" align='left' id="stopped">Stopped</th>
|
||||||
<th class="desktop" align='left' id="duration">Duration</th>
|
<th class="desktop" align='left' id="duration">Duration</th>
|
||||||
<th class="desktop" align='left' id="percent_complete">Completed</th>
|
<th class="desktop" align='left' id="percent_complete"></th>
|
||||||
|
<th class="never" align='left' id="grandparent_rating_key">grandparentRatingKey</th>
|
||||||
<th class="never" align='left' id="rating_key">RatingKey</th>
|
<th class="never" align='left' id="rating_key">RatingKey</th>
|
||||||
<th class="never" align='left' id="xml"></th>
|
|
||||||
<th class="never" align='left' id="user"></th>
|
<th class="never" align='left' id="user"></th>
|
||||||
|
<th class="never" align='left' id="media_type"></th>
|
||||||
|
<th class="never" align='left' id="video_decision"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
<%inherit file="base.html"/>
|
|
||||||
<%!
|
|
||||||
from plexpy import helpers
|
|
||||||
%>
|
|
||||||
|
|
||||||
<%def name="headIncludes()">
|
|
||||||
<link rel="stylesheet" href="interfaces/default/css/plexwatch-tables.css">
|
|
||||||
<link rel="stylesheet" href="interfaces/default/css/dataTables.responsive.css">
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="body()">
|
|
||||||
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row-fluid">
|
|
||||||
<div class="span12">
|
|
||||||
<div class="wellheader-bg">
|
|
||||||
<div class="dashboard-wellheader-no-chevron">
|
|
||||||
<h2><i class="fa fa-history"></i> History</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class='container-fluid'>
|
|
||||||
<div class='row-fluid'>
|
|
||||||
<div class='span12'>
|
|
||||||
<div class='table-card-back'>
|
|
||||||
<table class="display" id="history_table" width="100%">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="never" align='left' id="id">ID</th>
|
|
||||||
<th class="all" align='left' id="time">Time</th>
|
|
||||||
<th class="all" align='left' id="friendly_name">User</th>
|
|
||||||
<th class="desktop" align='left' id="platform">Platform</th>
|
|
||||||
<th class="desktop" align='left' id="ip_address">IP Address</th>
|
|
||||||
<th class="min-tablet" align='left' id="title">Title</th>
|
|
||||||
<th class="min-tablet" align='left' id="started">Started</th>
|
|
||||||
<th class="desktop" align='left' id="paused_counter">Paused</th>
|
|
||||||
<th class="desktop" align='left' id="stopped">Stopped</th>
|
|
||||||
<th class="desktop" align='left' id="duration">Duration</th>
|
|
||||||
<th class="desktop" align='left' id="percent_complete"></th>
|
|
||||||
<th class="never" align='left' id="grandparent_rating_key">grandparentRatingKey</th>
|
|
||||||
<th class="never" align='left' id="rating_key">RatingKey</th>
|
|
||||||
<th class="never" align='left' id="user"></th>
|
|
||||||
<th class="never" align='left' id="media_type"></th>
|
|
||||||
<th class="never" align='left' id="video_decision"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div id="info-modal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="info-modal"
|
|
||||||
aria-hidden="true">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
|
|
||||||
<%def name="javascriptIncludes()">
|
|
||||||
|
|
||||||
<script src="interfaces/default/js/jquery.dataTables.min.js"></script>
|
|
||||||
<script src="interfaces/default/js/dataTables.responsive.js"></script>
|
|
||||||
<script src="interfaces/default/js/jquery.dataTables.bootstrap.pagination.integration.js"></script>
|
|
||||||
<script src="interfaces/default/js/moment-with-locale.js"></script>
|
|
||||||
<script src="interfaces/default/js/tables/history_table_new.js"></script>
|
|
||||||
<script>
|
|
||||||
$(document).ready(function() {
|
|
||||||
history_table_options.ajax = {
|
|
||||||
"url": "get_history_new"
|
|
||||||
}
|
|
||||||
|
|
||||||
history_table = $('#history_table').DataTable(history_table_options);
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</%def>
|
|
|
@ -183,10 +183,12 @@ from plexpy import helpers
|
||||||
<th class="desktop" align='left' id="paused_counter">Paused</th>
|
<th class="desktop" align='left' id="paused_counter">Paused</th>
|
||||||
<th class="desktop" align='left' id="stopped">Stopped</th>
|
<th class="desktop" align='left' id="stopped">Stopped</th>
|
||||||
<th class="desktop" align='left' id="duration">Duration</th>
|
<th class="desktop" align='left' id="duration">Duration</th>
|
||||||
<th class="desktop" align='left' id="percent_complete">Completed</th>
|
<th class="desktop" align='left' id="percent_complete"></th>
|
||||||
<th class="never" align='left' id="rating_key">rating_key</th>
|
<th class="never" align='left' id="grandparent_rating_key">grandparentRatingKey</th>
|
||||||
<th class="never" align='left' id="xml"></th>
|
<th class="never" align='left' id="rating_key">RatingKey</th>
|
||||||
<th class="never" align='left' id="user"></th>
|
<th class="never" align='left' id="user"></th>
|
||||||
|
<th class="never" align='left' id="media_type"></th>
|
||||||
|
<th class="never" align='left' id="video_decision"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
|
@ -68,7 +68,7 @@ history_table_options = {
|
||||||
"data":"platform",
|
"data":"platform",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
if (cellData !== '') {
|
if (cellData !== '') {
|
||||||
$(td).html('<a href="#info-modal" data-toggle="modal"><span data-toggle="tooltip" data-placement="left" title="Stream Info" id="stream-info" class="badge badge-inverse"><i class="fa fa-info"></i></span></a> '+cellData);
|
$(td).html('<a href="#info-modal" data-toggle="modal"><span data-toggle="tooltip" data-placement="left" title="Stream Info" id="stream-info"><i class="fa fa-lg fa-info-circle"></i></span></a> '+cellData);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"className": "modal-control no-wrap"
|
"className": "modal-control no-wrap"
|
||||||
|
@ -89,9 +89,19 @@ history_table_options = {
|
||||||
"name":"title",
|
"name":"title",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
if (cellData !== '') {
|
if (cellData !== '') {
|
||||||
|
if (rowData['media_type'] === 'movie' || rowData['media_type'] === 'episode') {
|
||||||
|
var transcode_dec = '';
|
||||||
|
if (rowData['video_decision'] === 'transcode') {
|
||||||
|
transcode_dec = '<i class="fa fa-server"></i> ';
|
||||||
|
}
|
||||||
|
$(td).html('<div><div style="float: left;"><a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a></div><div style="float: right; text-align: right; padding-right: 5px;">' + transcode_dec + '<i class="fa fa-video-camera"></i></div></div>');
|
||||||
|
} else if (rowData['media_type'] === 'track') {
|
||||||
|
$(td).html('<div><div style="float: left;">' + cellData + '</div><div style="float: right; text-align: right; padding-right: 5px;"><i class="fa fa-music"></i></div></div>');
|
||||||
|
} else {
|
||||||
$(td).html('<a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a>');
|
$(td).html('<a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targets": [6],
|
"targets": [6],
|
||||||
|
@ -140,36 +150,53 @@ history_table_options = {
|
||||||
{
|
{
|
||||||
"targets": [10],
|
"targets": [10],
|
||||||
"data":"percent_complete",
|
"data":"percent_complete",
|
||||||
"orderable": false,
|
|
||||||
"render": function ( data, type, full ) {
|
"render": function ( data, type, full ) {
|
||||||
if (data < 95) {
|
if (data > 80) {
|
||||||
return '<span class="badge">'+Math.round(data)+'%</span>';
|
return '<i class="fa fa-lg fa-circle"></i>'
|
||||||
|
//return '<span class="badge">'+Math.round(data)+'%</span>';
|
||||||
|
} else if (data > 40) {
|
||||||
|
return '<i class="fa fa-lg fa-adjust"></i>'
|
||||||
|
//return '<span class="badge">100%</span>';
|
||||||
} else {
|
} else {
|
||||||
return '<span class="badge">100%</span>';
|
return '<i class="fa fa-lg fa-circle-o"></i>'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"searchable": false,
|
"searchable": false,
|
||||||
|
"orderable": true,
|
||||||
"className": "no-wrap"
|
"className": "no-wrap"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targets": [11],
|
"targets": [11],
|
||||||
"data":"rating_key",
|
"data":"grandparent_rating_key",
|
||||||
"visible": false,
|
"visible": false,
|
||||||
"searchable": false
|
"searchable": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targets": [12],
|
"targets": [12],
|
||||||
"data":"xml",
|
"data":"rating_key",
|
||||||
|
"visible": false,
|
||||||
|
"searchable": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"targets": [13],
|
||||||
|
"data":"media_type",
|
||||||
"searchable":false,
|
"searchable":false,
|
||||||
"visible":false
|
"visible":false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"targets": [13],
|
"targets": [14],
|
||||||
"data":"user",
|
"data":"user",
|
||||||
"searchable":false,
|
"searchable":false,
|
||||||
"visible":false
|
"visible":false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"targets": [15],
|
||||||
|
"data":"video_decision",
|
||||||
|
"searchable":false,
|
||||||
|
"visible":false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
],
|
],
|
||||||
"drawCallback": function (settings) {
|
"drawCallback": function (settings) {
|
||||||
// Jump to top of page
|
// Jump to top of page
|
||||||
|
|
|
@ -1,233 +0,0 @@
|
||||||
var date_format = 'YYYY-MM-DD hh:mm';
|
|
||||||
var time_format = 'hh:mm a';
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: 'get_date_formats',
|
|
||||||
type: 'GET',
|
|
||||||
success: function(data) {
|
|
||||||
date_format = data.date_format;
|
|
||||||
time_format = data.time_format;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
history_table_options = {
|
|
||||||
"destroy": true,
|
|
||||||
"responsive": {
|
|
||||||
details: false
|
|
||||||
},
|
|
||||||
"language": {
|
|
||||||
"search": "Search: ",
|
|
||||||
"lengthMenu":"Show _MENU_ entries per page",
|
|
||||||
"info":"Showing _START_ to _END_ of _TOTAL_ history items",
|
|
||||||
"infoEmpty":"Showing 0 to 0 of 0 entries",
|
|
||||||
"infoFiltered":"(filtered from _MAX_ total entries)",
|
|
||||||
"emptyTable": "No data in table",
|
|
||||||
},
|
|
||||||
"stateSave": false,
|
|
||||||
"sPaginationType": "bootstrap",
|
|
||||||
"processing": false,
|
|
||||||
"serverSide": true,
|
|
||||||
"pageLength": 25,
|
|
||||||
"order": [ 1, 'desc'],
|
|
||||||
"columnDefs": [
|
|
||||||
{
|
|
||||||
"targets": [0],
|
|
||||||
"data":"id",
|
|
||||||
"visible": false,
|
|
||||||
"searchable": false,
|
|
||||||
"className": "no-wrap"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [1],
|
|
||||||
"data":"date",
|
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
|
||||||
if (rowData['stopped'] === null) {
|
|
||||||
$(td).addClass('currentlyWatching');
|
|
||||||
$(td).html('Currently watching...');
|
|
||||||
} else {
|
|
||||||
$(td).html(moment(cellData,"X").format(date_format));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"searchable": false,
|
|
||||||
"className": "no-wrap"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [2],
|
|
||||||
"data":"friendly_name",
|
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
|
||||||
if (cellData !== '') {
|
|
||||||
$(td).html('<a href="user?user=' + rowData['user'] + '">' + cellData + '</a>');
|
|
||||||
} else {
|
|
||||||
$(td).html(cellData);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"className": "no-wrap"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [3],
|
|
||||||
"data":"platform",
|
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
|
||||||
if (cellData !== '') {
|
|
||||||
$(td).html('<a href="#info-modal" data-toggle="modal"><span data-toggle="tooltip" data-placement="left" title="Stream Info" id="stream-info"><i class="fa fa-lg fa-info-circle"></i></span></a> '+cellData);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"className": "modal-control no-wrap"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [4],
|
|
||||||
"data":"ip_address",
|
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
|
||||||
if ((cellData == '') || (cellData == '0')) {
|
|
||||||
$(td).html('n/a');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"className": "no-wrap"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [5],
|
|
||||||
"data":"title",
|
|
||||||
"name":"title",
|
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
|
||||||
if (cellData !== '') {
|
|
||||||
if (rowData['media_type'] === 'movie' || rowData['media_type'] === 'episode') {
|
|
||||||
var transcode_dec = '';
|
|
||||||
if (rowData['video_decision'] === 'transcode') {
|
|
||||||
transcode_dec = '<i class="fa fa-server"></i> ';
|
|
||||||
}
|
|
||||||
$(td).html('<div><div style="float: left;"><a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a></div><div style="float: right; text-align: right; padding-right: 5px;">' + transcode_dec + '<i class="fa fa-video-camera"></i></div></div>');
|
|
||||||
} else if (rowData['media_type'] === 'track') {
|
|
||||||
$(td).html('<div><div style="float: left;">' + cellData + '</div><div style="float: right; text-align: right; padding-right: 5px;"><i class="fa fa-music"></i></div></div>');
|
|
||||||
} else {
|
|
||||||
$(td).html('<a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [6],
|
|
||||||
"data":"started",
|
|
||||||
"render": function ( data, type, full ) {
|
|
||||||
return moment(data, "X").format(time_format);
|
|
||||||
},
|
|
||||||
"searchable": false,
|
|
||||||
"className": "no-wrap"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [7],
|
|
||||||
"data":"paused_counter",
|
|
||||||
"render": function ( data, type, full ) {
|
|
||||||
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
|
|
||||||
},
|
|
||||||
"searchable": false,
|
|
||||||
"className": "no-wrap"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [8],
|
|
||||||
"data":"stopped",
|
|
||||||
"render": function ( data, type, full ) {
|
|
||||||
if (data !== null) {
|
|
||||||
return moment(data, "X").format(time_format);
|
|
||||||
} else {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"searchable": false,
|
|
||||||
"className": "no-wrap"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [9],
|
|
||||||
"data":"duration",
|
|
||||||
"render": function ( data, type, full ) {
|
|
||||||
if (data !== null) {
|
|
||||||
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
|
|
||||||
} else {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"searchable": false,
|
|
||||||
"className": "no-wrap"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [10],
|
|
||||||
"data":"percent_complete",
|
|
||||||
"render": function ( data, type, full ) {
|
|
||||||
if (data > 80) {
|
|
||||||
return '<i class="fa fa-lg fa-circle"></i>'
|
|
||||||
//return '<span class="badge">'+Math.round(data)+'%</span>';
|
|
||||||
} else if (data > 40) {
|
|
||||||
return '<i class="fa fa-lg fa-adjust"></i>'
|
|
||||||
//return '<span class="badge">100%</span>';
|
|
||||||
} else {
|
|
||||||
return '<i class="fa fa-lg fa-circle-o"></i>'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"searchable": false,
|
|
||||||
"orderable": true,
|
|
||||||
"className": "no-wrap"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [11],
|
|
||||||
"data":"grandparent_rating_key",
|
|
||||||
"visible": false,
|
|
||||||
"searchable": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [12],
|
|
||||||
"data":"rating_key",
|
|
||||||
"visible": false,
|
|
||||||
"searchable": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [13],
|
|
||||||
"data":"media_type",
|
|
||||||
"searchable":false,
|
|
||||||
"visible":false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [14],
|
|
||||||
"data":"user",
|
|
||||||
"searchable":false,
|
|
||||||
"visible":false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [15],
|
|
||||||
"data":"video_decision",
|
|
||||||
"searchable":false,
|
|
||||||
"visible":false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
],
|
|
||||||
"drawCallback": function (settings) {
|
|
||||||
// Jump to top of page
|
|
||||||
// $('html,body').scrollTop(0);
|
|
||||||
$('#ajaxMsg').addClass('success').fadeOut();
|
|
||||||
},
|
|
||||||
"preDrawCallback": function(settings) {
|
|
||||||
$('#ajaxMsg').html("<div class='msg'><i class='fa fa-refresh fa-spin'></i> Fetching rows...</div>");
|
|
||||||
$('#ajaxMsg').addClass('success').fadeIn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#history_table').on('mouseenter', 'td.modal-control span', function () {
|
|
||||||
$(this).tooltip();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#history_table').on('click', 'td.modal-control', function () {
|
|
||||||
var tr = $(this).parents('tr');
|
|
||||||
var row = history_table.row( tr );
|
|
||||||
var rowData = row.data();
|
|
||||||
|
|
||||||
function showStreamDetails() {
|
|
||||||
$.ajax({
|
|
||||||
url: 'get_stream_data',
|
|
||||||
data: {row_id: rowData['id'], user: rowData['friendly_name']},
|
|
||||||
cache: false,
|
|
||||||
async: true,
|
|
||||||
complete: function(xhr, status) {
|
|
||||||
$("#info-modal").html(xhr.responseText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
showStreamDetails();
|
|
||||||
});
|
|
50
data/interfaces/default/plexwatch_import.html
Normal file
50
data/interfaces/default/plexwatch_import.html
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
|
||||||
|
<h3>Import PlexWatch Database</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="modal-text">
|
||||||
|
<div class="card-back">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="db_location">Database Location</label>
|
||||||
|
<input type="text" id="db_location" name="db_location" value="" size="30" required>
|
||||||
|
<p class="help-block">Enter the path and file name for the PlexWatch database you wish to import.</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="table_name">Table Name</label>
|
||||||
|
<select id="table_name" name="table_name">
|
||||||
|
<option value="processed">processed</option>
|
||||||
|
<option value="processed">grouped</option>
|
||||||
|
</select>
|
||||||
|
<p class="help-block">The table name from which you wish to import.</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="import_ignore_interval">Ignore Interval</label>
|
||||||
|
<input type="text" id="import_ignore_interval" name="import_ignore_interval" value="120" size="30" required>
|
||||||
|
<p class="help-block">Enter the minimum duration (in seconds) an item must have been active for. Set to 0 to import all.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div>
|
||||||
|
<input type="button" id="import_db" class="btn btn-primary" value="Save">
|
||||||
|
<span id="status-message"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
// Send database path to import script
|
||||||
|
$("#import_db").click(function() {
|
||||||
|
var database_path = $("#db_location").val();
|
||||||
|
var table_name = $("#table_name").val();
|
||||||
|
var import_ignore_interval = $("#import_ignore_interval").val();
|
||||||
|
$.ajax({
|
||||||
|
url: 'get_plexwatch_export_data',
|
||||||
|
data: {database_path: database_path, table_name:table_name, import_ignore_interval:import_ignore_interval},
|
||||||
|
cache: false,
|
||||||
|
async: true,
|
||||||
|
success: function(data) {
|
||||||
|
$("#status-message").html(data);
|
||||||
|
$("#db_location").val('')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -14,12 +14,12 @@ media_type Returns the type of session. Either 'episode' or 'mo
|
||||||
title Returns the name of the episode or movie.
|
title Returns the name of the episode or movie.
|
||||||
user Returns the name of the user.
|
user Returns the name of the user.
|
||||||
transcode_video_dec Returns the video transcode decision. Either 'transcode', 'copy' or 'direct play'.
|
transcode_video_dec Returns the video transcode decision. Either 'transcode', 'copy' or 'direct play'.
|
||||||
transcode_video_codec Returns the name of the video codec for the stream.
|
transcode_video_codec Returns the name of the video codec for any transcode session.
|
||||||
transcode_height Returns the value of the video height for the stream.
|
transcode_height Returns the value of the video height for any transcode session.
|
||||||
transcode_width Returns the value of the video width for the stream.
|
transcode_width Returns the value of the video width for any transcode session.
|
||||||
transcode_audio_dec Returns the audio transcode decision. Either 'transcode', 'copy' or 'direct play'.
|
transcode_audio_dec Returns the audio transcode decision. Either 'transcode', 'copy' or 'direct play'.
|
||||||
transcode_audio_codec Returns the name of the audio codec for the stream.
|
transcode_audio_codec Returns the name of the audio codec for any transcode session.
|
||||||
transcode_audio_channels Returns the number of audio channels for the stream.
|
transcode_audio_channels Returns the number of audio channels for any transcode session.
|
||||||
container Returns the type of container for the original media.
|
container Returns the type of container for the original media.
|
||||||
height Returns the value of the video height for the original media.
|
height Returns the value of the video height for the original media.
|
||||||
bitrate Returns the value of the video bitrate for the original media.
|
bitrate Returns the value of the video bitrate for the original media.
|
||||||
|
@ -50,17 +50,35 @@ DOCUMENTATION :: END
|
||||||
<h4>Stream Details</h4>
|
<h4>Stream Details</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<h5>Video</h5>
|
<h5>Video</h5>
|
||||||
|
% if data['transcode_video_dec'] != 'direct play':
|
||||||
<li>Stream Type: <strong>${data['transcode_video_dec']}</strong></li>
|
<li>Stream Type: <strong>${data['transcode_video_dec']}</strong></li>
|
||||||
<li>Video Resolution: <strong>${data['transcode_height']}p</strong></li>
|
<li>Video Resolution: <strong>${data['transcode_height']}p</strong></li>
|
||||||
<li>Video Codec: <strong>${data['transcode_video_codec']}</strong></li>
|
<li>Video Codec: <strong>${data['transcode_video_codec']}</strong></li>
|
||||||
<li>Video Width: <strong>${data['transcode_width']}</strong></li>
|
<li>Video Width: <strong>${data['transcode_width']}</strong></li>
|
||||||
<li>Video Height: <strong>${data['transcode_height']}</span></strong></li>
|
<li>Video Height: <strong>${data['transcode_height']}</span></strong></li>
|
||||||
|
% else:
|
||||||
|
<li>Stream Type: <strong>${data['transcode_video_dec']}</strong></li>
|
||||||
|
% if data['video_resolution'] != 'sd':
|
||||||
|
<li>Video Resolution: <strong>${data['video_resolution']}p</strong></li>
|
||||||
|
% else:
|
||||||
|
<li>Video Resolution: <strong>${data['video_resolution']}</strong></li>
|
||||||
|
% endif
|
||||||
|
<li>Video Codec: <strong>${data['video_codec']}</strong></li>
|
||||||
|
<li>Video Width: <strong>${data['width']}</strong></li>
|
||||||
|
<li>Video Height: <strong>${data['height']}</span></strong></li>
|
||||||
|
% endif
|
||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
<h5>Audio</h5>
|
<h5>Audio</h5>
|
||||||
|
% if data['transcode_audio_dec'] != 'direct play':
|
||||||
<li>Stream Type: <strong>${data['transcode_audio_dec']}</strong></li>
|
<li>Stream Type: <strong>${data['transcode_audio_dec']}</strong></li>
|
||||||
<li>Audio Codec: <strong>${data['transcode_audio_codec']}</strong></li>
|
<li>Audio Codec: <strong>${data['transcode_audio_codec']}</strong></li>
|
||||||
<li>Audio Channels: <strong>${data['transcode_audio_channels']}</strong></li>
|
<li>Audio Channels: <strong>${data['transcode_audio_channels']}</strong></li>
|
||||||
|
% else:
|
||||||
|
<li>Stream Type: <strong>${data['transcode_audio_dec']}</strong></li>
|
||||||
|
<li>Audio Codec: <strong>${data['audio_codec']}</strong></li>
|
||||||
|
<li>Audio Channels: <strong>${data['audio_channels']}</strong></li>
|
||||||
|
% endif
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="span4">
|
<div class="span4">
|
||||||
|
|
|
@ -185,17 +185,19 @@ from plexpy import helpers
|
||||||
<th class="never" align='left' id="id">ID</th>
|
<th class="never" align='left' id="id">ID</th>
|
||||||
<th class="all" align='left' id="time">Time</th>
|
<th class="all" align='left' id="time">Time</th>
|
||||||
<th class="never" align='left' id="friendly_name">User</th>
|
<th class="never" align='left' id="friendly_name">User</th>
|
||||||
<th class="min-tablet" align='left' id="platform">Platform</th>
|
<th class="desktop" align='left' id="platform">Platform</th>
|
||||||
<th class="desktop" align='left' id="ip_address">IP Address</th>
|
<th class="desktop" align='left' id="ip_address">IP Address</th>
|
||||||
<th class="min-tablet" align='left' id="title">Title</th>
|
<th class="min-tablet" align='left' id="title">Title</th>
|
||||||
<th class="desktop" align='left' id="started">Started</th>
|
<th class="min-tablet" align='left' id="started">Started</th>
|
||||||
<th class="desktop" align='left' id="paused_counter">Paused</th>
|
<th class="desktop" align='left' id="paused_counter">Paused</th>
|
||||||
<th class="desktop" align='left' id="stopped">Stopped</th>
|
<th class="desktop" align='left' id="stopped">Stopped</th>
|
||||||
<th class="desktop" align='left' id="duration">Duration</th>
|
<th class="desktop" align='left' id="duration">Duration</th>
|
||||||
<th class="desktop" align='left' id="percent_complete">Completed</th>
|
<th class="desktop" align='left' id="percent_complete"></th>
|
||||||
|
<th class="never" align='left' id="grandparent_rating_key">grandparentRatingKey</th>
|
||||||
<th class="never" align='left' id="rating_key">RatingKey</th>
|
<th class="never" align='left' id="rating_key">RatingKey</th>
|
||||||
<th class="never" align='left' id="xml"></th>
|
|
||||||
<th class="never" align='left' id="user"></th>
|
<th class="never" align='left' id="user"></th>
|
||||||
|
<th class="never" align='left' id="media_type"></th>
|
||||||
|
<th class="never" align='left' id="video_decision"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
<%inherit file="base.html"/>
|
|
||||||
<%!
|
|
||||||
from plexpy import helpers
|
|
||||||
%>
|
|
||||||
|
|
||||||
<%def name="headIncludes()">
|
|
||||||
<link rel="stylesheet" href="interfaces/default/css/plexwatch-tables.css">
|
|
||||||
<link rel="stylesheet" href="interfaces/default/css/dataTables.responsive.css">
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="body()">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row-fluid">
|
|
||||||
<div class="span12">
|
|
||||||
<div class="wellheader-bg">
|
|
||||||
<div class="dashboard-wellheader-no-chevron">
|
|
||||||
<div class="span9"><h2><i class="fa fa-group"></i> Active Users</h2></div>
|
|
||||||
<div class="span3">
|
|
||||||
<div class="pull-right">
|
|
||||||
<h5><a href="refresh_users_list"><i class="fa fa-refresh"></i> Refresh users</a></h5>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class='container-fluid'>
|
|
||||||
<div class='row-fluid'>
|
|
||||||
<div class='span12'>
|
|
||||||
<div class='table-card-back'>
|
|
||||||
<table id="users_list_table" class="display" width="100%">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th class="all" align="right" id="avatar"></th>
|
|
||||||
<th class="all" align="left" id="friendly_name">User</th>
|
|
||||||
<th class="min-tablet" align="left" id="last_seen">Last Seen</th>
|
|
||||||
<th class="min-tablet" align="left" id="last_known_ip">Last Known IP</th>
|
|
||||||
<th class="min-tablet" align="left" id="total_plays">Total Plays</th>
|
|
||||||
<th class="never" align="left" id="user"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<footer></footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="javascriptIncludes()">
|
|
||||||
<script src="interfaces/default/js/jquery.dataTables.min.js"></script>
|
|
||||||
<script src="interfaces/default/js/dataTables.responsive.js"></script>
|
|
||||||
<script src="interfaces/default/js/jquery.dataTables.bootstrap.pagination.integration.js"></script>
|
|
||||||
<script src="interfaces/default/js/moment-with-locale.js"></script>
|
|
||||||
<script src="interfaces/default/js/tables/users.js"></script>
|
|
||||||
<script>
|
|
||||||
users_list_table_options.ajax = {
|
|
||||||
"url": "get_user_list_new"
|
|
||||||
}
|
|
||||||
|
|
||||||
var users_list_table = $('#users_list_table').DataTable(users_list_table_options);
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</%def>
|
|
|
@ -331,40 +331,6 @@ def sig_handler(signum=None, frame=None):
|
||||||
|
|
||||||
|
|
||||||
def dbcheck():
|
def dbcheck():
|
||||||
conn = sqlite3.connect(plexpy.CONFIG.PLEXWATCH_DATABASE)
|
|
||||||
c = conn.cursor()
|
|
||||||
c.execute(
|
|
||||||
'CREATE TABLE IF NOT EXISTS plexpy_users (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
|
||||||
'user_id INTEGER DEFAULT NULL UNIQUE, username TEXT NOT NULL UNIQUE, '
|
|
||||||
'friendly_name TEXT, thumb TEXT, email TEXT, is_home_user INTEGER DEFAULT NULL, '
|
|
||||||
'is_allow_sync INTEGER DEFAULT NULL, is_restricted INTEGER DEFAULT NULL)'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Upgrade plexpy_users table from earlier versions
|
|
||||||
try:
|
|
||||||
c.execute('SELECT user_id from plexpy_users')
|
|
||||||
except sqlite3.OperationalError:
|
|
||||||
logger.debug(u"Altering database. Updating database table plexpy_users.")
|
|
||||||
c.execute(
|
|
||||||
'CREATE TABLE tmp_table (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
|
||||||
'user_id INTEGER DEFAULT NULL UNIQUE, username TEXT NOT NULL UNIQUE, '
|
|
||||||
'friendly_name TEXT, thumb TEXT, email TEXT, is_home_user INTEGER DEFAULT NULL, '
|
|
||||||
'is_allow_sync INTEGER DEFAULT NULL, is_restricted INTEGER DEFAULT NULL)'
|
|
||||||
)
|
|
||||||
c.execute(
|
|
||||||
'INSERT INTO tmp_table SELECT id, NULL, username, friendly_name, NULL, NULL, NULL, NULL, NULL '
|
|
||||||
'FROM plexpy_users'
|
|
||||||
)
|
|
||||||
c.execute(
|
|
||||||
'DROP TABLE plexpy_users'
|
|
||||||
)
|
|
||||||
c.execute(
|
|
||||||
'ALTER TABLE tmp_table RENAME TO plexpy_users'
|
|
||||||
)
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
c.close()
|
|
||||||
|
|
||||||
conn_db = sqlite3.connect(DB_FILE)
|
conn_db = sqlite3.connect(DB_FILE)
|
||||||
c_db = conn_db.cursor()
|
c_db = conn_db.cursor()
|
||||||
|
|
||||||
|
@ -388,13 +354,13 @@ def dbcheck():
|
||||||
'CREATE TABLE IF NOT EXISTS session_history (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
'CREATE TABLE IF NOT EXISTS session_history (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||||
'started INTEGER, stopped INTEGER, rating_key INTEGER, user_id INTEGER, user TEXT, '
|
'started INTEGER, stopped INTEGER, rating_key INTEGER, user_id INTEGER, user TEXT, '
|
||||||
'ip_address TEXT, paused_counter INTEGER DEFAULT 0, player TEXT, platform TEXT, machine_id TEXT, '
|
'ip_address TEXT, paused_counter INTEGER DEFAULT 0, player TEXT, platform TEXT, machine_id TEXT, '
|
||||||
'parent_rating_key INTEGER, grandparent_rating_key INTEGER, media_type TEXT, view_offset INTEGER)'
|
'parent_rating_key INTEGER, grandparent_rating_key INTEGER, media_type TEXT, view_offset INTEGER DEFAULT 0)'
|
||||||
)
|
)
|
||||||
|
|
||||||
# session_history_media_info table :: This is a table which logs each session's media info
|
# session_history_media_info table :: This is a table which logs each session's media info
|
||||||
c_db.execute(
|
c_db.execute(
|
||||||
'CREATE TABLE IF NOT EXISTS session_history_media_info (id INTEGER PRIMARY KEY, '
|
'CREATE TABLE IF NOT EXISTS session_history_media_info (id INTEGER PRIMARY KEY, '
|
||||||
'rating_key INTEGER, video_decision TEXT, audio_decision TEXT, duration INTEGER, width INTEGER, '
|
'rating_key INTEGER, video_decision TEXT, audio_decision TEXT, duration INTEGER DEFAULT 0, width INTEGER, '
|
||||||
'height INTEGER, container TEXT, video_codec TEXT, audio_codec TEXT, bitrate INTEGER, video_resolution TEXT, '
|
'height INTEGER, container TEXT, video_codec TEXT, audio_codec TEXT, bitrate INTEGER, video_resolution TEXT, '
|
||||||
'video_framerate TEXT, aspect_ratio TEXT, audio_channels INTEGER, transcode_protocol TEXT, '
|
'video_framerate TEXT, aspect_ratio TEXT, audio_channels INTEGER, transcode_protocol TEXT, '
|
||||||
'transcode_container TEXT, transcode_video_codec TEXT, transcode_audio_codec TEXT, '
|
'transcode_container TEXT, transcode_video_codec TEXT, transcode_audio_codec TEXT, '
|
||||||
|
@ -408,7 +374,7 @@ def dbcheck():
|
||||||
'title TEXT, parent_title TEXT, grandparent_title TEXT, full_title TEXT, media_index INTEGER, '
|
'title TEXT, parent_title TEXT, grandparent_title TEXT, full_title TEXT, media_index INTEGER, '
|
||||||
'parent_media_index INTEGER, thumb TEXT, parent_thumb TEXT, grandparent_thumb TEXT, art TEXT, media_type TEXT, '
|
'parent_media_index INTEGER, thumb TEXT, parent_thumb TEXT, grandparent_thumb TEXT, art TEXT, media_type TEXT, '
|
||||||
'year INTEGER, originally_available_at TEXT, added_at INTEGER, updated_at INTEGER, last_viewed_at INTEGER, '
|
'year INTEGER, originally_available_at TEXT, added_at INTEGER, updated_at INTEGER, last_viewed_at INTEGER, '
|
||||||
'content_rating TEXT, summary TEXT, rating TEXT, duration INTEGER, guid TEXT, '
|
'content_rating TEXT, summary TEXT, rating TEXT, duration INTEGER DEFAULT 0, guid TEXT, '
|
||||||
'directors TEXT, writers TEXT, actors TEXT, genres TEXT, studio TEXT)'
|
'directors TEXT, writers TEXT, actors TEXT, genres TEXT, studio TEXT)'
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
|
|
|
@ -146,16 +146,16 @@ class DataFactory(object):
|
||||||
t1 + '.started as date',
|
t1 + '.started as date',
|
||||||
'(CASE WHEN users.friendly_name IS NULL THEN ' + t1 +
|
'(CASE WHEN users.friendly_name IS NULL THEN ' + t1 +
|
||||||
'.user ELSE users.friendly_name END) as friendly_name',
|
'.user ELSE users.friendly_name END) as friendly_name',
|
||||||
t1 + '.player',
|
t1 + '.player as platform',
|
||||||
t1 + '.ip_address',
|
t1 + '.ip_address',
|
||||||
t2 + '.full_title',
|
t2 + '.full_title as title',
|
||||||
t1 + '.started',
|
t1 + '.started',
|
||||||
t1 + '.paused_counter',
|
t1 + '.paused_counter',
|
||||||
t1 + '.stopped',
|
t1 + '.stopped',
|
||||||
'round((julianday(datetime(' + t1 + '.stopped, "unixepoch", "localtime")) - \
|
'round((julianday(datetime(' + t1 + '.stopped, "unixepoch", "localtime")) - \
|
||||||
julianday(datetime(' + t1 + '.started, "unixepoch", "localtime"))) * 86400) - \
|
julianday(datetime(' + t1 + '.started, "unixepoch", "localtime"))) * 86400) - \
|
||||||
(CASE WHEN ' + t1 + '.paused_counter IS NULL THEN 0 ELSE ' + t1 + '.paused_counter END) as duration',
|
(CASE WHEN ' + t1 + '.paused_counter IS NULL THEN 0 ELSE ' + t1 + '.paused_counter END) as duration',
|
||||||
'((CASE WHEN ' + t1 + '.view_offset IS NULL THEN 0.0 ELSE ' + t1 + '.view_offset * 1.0 END) / \
|
'((CASE WHEN ' + t1 + '.view_offset IS NULL THEN 0.1 ELSE ' + t1 + '.view_offset * 1.0 END) / \
|
||||||
(CASE WHEN ' + t2 + '.duration IS NULL THEN 1.0 ELSE ' + t2 + '.duration * 1.0 END) * 100) as percent_complete',
|
(CASE WHEN ' + t2 + '.duration IS NULL THEN 1.0 ELSE ' + t2 + '.duration * 1.0 END) * 100) as percent_complete',
|
||||||
t1 + '.grandparent_rating_key as grandparent_rating_key',
|
t1 + '.grandparent_rating_key as grandparent_rating_key',
|
||||||
t1 + '.rating_key as rating_key',
|
t1 + '.rating_key as rating_key',
|
||||||
|
@ -194,14 +194,14 @@ class DataFactory(object):
|
||||||
row = {"id": item['id'],
|
row = {"id": item['id'],
|
||||||
"date": item['date'],
|
"date": item['date'],
|
||||||
"friendly_name": item['friendly_name'],
|
"friendly_name": item['friendly_name'],
|
||||||
"platform": item["player"],
|
"platform": item["platform"],
|
||||||
"ip_address": item["ip_address"],
|
"ip_address": item["ip_address"],
|
||||||
"title": item["full_title"],
|
"title": item["title"],
|
||||||
"started": item["started"],
|
"started": item["started"],
|
||||||
"paused_counter": item["paused_counter"],
|
"paused_counter": item["paused_counter"],
|
||||||
"stopped": item["stopped"],
|
"stopped": item["stopped"],
|
||||||
"duration": item["duration"],
|
"duration": item["duration"],
|
||||||
"percent_complete": round(item["percent_complete"], 0),
|
"percent_complete": item["percent_complete"],
|
||||||
"grandparent_rating_key": item["grandparent_rating_key"],
|
"grandparent_rating_key": item["grandparent_rating_key"],
|
||||||
"rating_key": item["rating_key"],
|
"rating_key": item["rating_key"],
|
||||||
"user": item["user"],
|
"user": item["user"],
|
||||||
|
@ -233,6 +233,79 @@ class DataFactory(object):
|
||||||
|
|
||||||
return dict
|
return dict
|
||||||
|
|
||||||
|
def get_user_unique_ips(self, start='', length='', kwargs=None, custom_where=''):
|
||||||
|
data_tables = datatables_new.DataTables()
|
||||||
|
|
||||||
|
start = int(start)
|
||||||
|
length = int(length)
|
||||||
|
filtered = []
|
||||||
|
totalcount = 0
|
||||||
|
search_value = ""
|
||||||
|
search_regex = ""
|
||||||
|
order_column = 0
|
||||||
|
order_dir = "desc"
|
||||||
|
|
||||||
|
if 'order[0][dir]' in kwargs:
|
||||||
|
order_dir = kwargs.get('order[0][dir]', "desc")
|
||||||
|
|
||||||
|
if 'order[0][column]' in kwargs:
|
||||||
|
order_column = kwargs.get('order[0][column]', 1)
|
||||||
|
|
||||||
|
if 'search[value]' in kwargs:
|
||||||
|
search_value = kwargs.get('search[value]', "")
|
||||||
|
|
||||||
|
if 'search[regex]' in kwargs:
|
||||||
|
search_regex = kwargs.get('search[regex]', "")
|
||||||
|
|
||||||
|
columns = ['session_history.started as last_seen',
|
||||||
|
'session_history.ip_address as ip_address',
|
||||||
|
'COUNT(session_history.ip_address) as play_count',
|
||||||
|
'session_history.player as platform',
|
||||||
|
'session_history_metadata.full_title as last_watched',
|
||||||
|
'session_history.user as user'
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
query = data_tables.ssp_query(table_name='session_history',
|
||||||
|
columns=columns,
|
||||||
|
start=start,
|
||||||
|
length=length,
|
||||||
|
order_column=int(order_column),
|
||||||
|
order_dir=order_dir,
|
||||||
|
search_value=search_value,
|
||||||
|
search_regex=search_regex,
|
||||||
|
custom_where=custom_where,
|
||||||
|
group_by='session_history.ip_address',
|
||||||
|
join_type=['JOIN'],
|
||||||
|
join_table=['session_history_metadata'],
|
||||||
|
join_evals=[['session_history.id', 'session_history_metadata.id']],
|
||||||
|
kwargs=kwargs)
|
||||||
|
except:
|
||||||
|
logger.warn("Unable to execute database query.")
|
||||||
|
return {'recordsFiltered': 0,
|
||||||
|
'recordsTotal': 0,
|
||||||
|
'data': 'null'},
|
||||||
|
|
||||||
|
results = query['result']
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for item in results:
|
||||||
|
row = {"last_seen": item['last_seen'],
|
||||||
|
"ip_address": item['ip_address'],
|
||||||
|
"play_count": item['play_count'],
|
||||||
|
"platform": item['platform'],
|
||||||
|
"last_watched": item['last_watched']
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.append(row)
|
||||||
|
|
||||||
|
dict = {'recordsFiltered': query['filteredCount'],
|
||||||
|
'recordsTotal': query['totalCount'],
|
||||||
|
'data': rows,
|
||||||
|
}
|
||||||
|
|
||||||
|
return dict
|
||||||
|
|
||||||
def set_user_friendly_name(self, user=None, friendly_name=None):
|
def set_user_friendly_name(self, user=None, friendly_name=None):
|
||||||
if user:
|
if user:
|
||||||
if friendly_name.strip() == '':
|
if friendly_name.strip() == '':
|
||||||
|
@ -261,3 +334,539 @@ class DataFactory(object):
|
||||||
return user
|
return user
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_user_id(self, user=None):
|
||||||
|
if user:
|
||||||
|
try:
|
||||||
|
monitor_db = monitor.MonitorDatabase()
|
||||||
|
query = 'select user_id FROM users WHERE username = ?'
|
||||||
|
result = monitor_db.select_single(query, args=[user])
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_user_details(self, user=None, user_id=None):
|
||||||
|
try:
|
||||||
|
monitor_db = monitor.MonitorDatabase()
|
||||||
|
|
||||||
|
if user:
|
||||||
|
query = 'SELECT user_id, username, friendly_name, email, ' \
|
||||||
|
'thumb, is_home_user, is_allow_sync, is_restricted ' \
|
||||||
|
'FROM users ' \
|
||||||
|
'WHERE username = ? ' \
|
||||||
|
'UNION ALL ' \
|
||||||
|
'SELECT null, user, null, null, null, null, null, null ' \
|
||||||
|
'FROM session_history ' \
|
||||||
|
'WHERE user = ? ' \
|
||||||
|
'GROUP BY user ' \
|
||||||
|
'LIMIT 1'
|
||||||
|
result = monitor_db.select(query, args=[user, user])
|
||||||
|
elif user_id:
|
||||||
|
query = 'SELECT user_id, username, friendly_name, email, thumb, ' \
|
||||||
|
'is_home_user, is_allow_sync, is_restricted FROM users WHERE user_id = ? LIMIT 1'
|
||||||
|
result = monitor_db.select(query, args=[user_id])
|
||||||
|
if result:
|
||||||
|
for item in result:
|
||||||
|
if not item['friendly_name']:
|
||||||
|
friendly_name = item['username']
|
||||||
|
else:
|
||||||
|
friendly_name = item['friendly_name']
|
||||||
|
if not item['thumb'] or item['thumb'] == '':
|
||||||
|
user_thumb = common.DEFAULT_USER_THUMB
|
||||||
|
else:
|
||||||
|
user_thumb = item['thumb']
|
||||||
|
|
||||||
|
user_details = {"user_id": item['user_id'],
|
||||||
|
"username": item['username'],
|
||||||
|
"friendly_name": friendly_name,
|
||||||
|
"email": item['email'],
|
||||||
|
"thumb": user_thumb,
|
||||||
|
"is_home_user": item['is_home_user'],
|
||||||
|
"is_allow_sync": item['is_allow_sync'],
|
||||||
|
"is_restricted": item['is_restricted']
|
||||||
|
}
|
||||||
|
return user_details
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_home_stats(self, time_range='30'):
|
||||||
|
monitor_db = monitor.MonitorDatabase()
|
||||||
|
|
||||||
|
if not time_range.isdigit():
|
||||||
|
time_range = '30'
|
||||||
|
|
||||||
|
stats_queries = ["top_tv", "popular_tv", "top_users", "top_platforms"]
|
||||||
|
home_stats = []
|
||||||
|
|
||||||
|
for stat in stats_queries:
|
||||||
|
if 'top_tv' in stat:
|
||||||
|
top_tv = []
|
||||||
|
try:
|
||||||
|
query = 'SELECT session_history_metadata.grandparent_title, ' \
|
||||||
|
'COUNT(session_history_metadata.grandparent_title) as total_plays, ' \
|
||||||
|
'session_history_metadata.grandparent_rating_key, ' \
|
||||||
|
'MAX(session_history.started) as last_watch,' \
|
||||||
|
'session_history_metadata.grandparent_thumb ' \
|
||||||
|
'FROM session_history_metadata ' \
|
||||||
|
'JOIN session_history on session_history_metadata.id = session_history.id ' \
|
||||||
|
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||||
|
'>= datetime("now", "-%s days", "localtime") ' \
|
||||||
|
'AND session_history_metadata.media_type = "episode" ' \
|
||||||
|
'GROUP BY session_history_metadata.grandparent_title ' \
|
||||||
|
'ORDER BY total_plays DESC LIMIT 10' % time_range
|
||||||
|
result = monitor_db.select(query)
|
||||||
|
except:
|
||||||
|
logger.warn("Unable to execute database query.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
for item in result:
|
||||||
|
row = {'title': item[0],
|
||||||
|
'total_plays': item[1],
|
||||||
|
'users_watched': '',
|
||||||
|
'rating_key': item[2],
|
||||||
|
'last_play': item[3],
|
||||||
|
'grandparent_thumb': item[4],
|
||||||
|
'thumb': '',
|
||||||
|
'user': '',
|
||||||
|
'friendly_name': '',
|
||||||
|
'platform_type': '',
|
||||||
|
'platform': ''
|
||||||
|
}
|
||||||
|
top_tv.append(row)
|
||||||
|
|
||||||
|
home_stats.append({'stat_id': stat,
|
||||||
|
'rows': top_tv})
|
||||||
|
|
||||||
|
elif 'popular_tv' in stat:
|
||||||
|
popular_tv = []
|
||||||
|
try:
|
||||||
|
query = 'SELECT session_history_metadata.grandparent_title, ' \
|
||||||
|
'COUNT(DISTINCT session_history.user_id) as users_watched, ' \
|
||||||
|
'session_history_metadata.grandparent_rating_key, ' \
|
||||||
|
'MAX(session_history.started) as last_watch, ' \
|
||||||
|
'COUNT(session_history.id) as total_plays, ' \
|
||||||
|
'session_history_metadata.grandparent_thumb ' \
|
||||||
|
'FROM session_history_metadata ' \
|
||||||
|
'JOIN session_history ON session_history_metadata.id = session_history.id ' \
|
||||||
|
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||||
|
'>= datetime("now", "-%s days", "localtime") ' \
|
||||||
|
'AND session_history_metadata.media_type = "episode" ' \
|
||||||
|
'GROUP BY session_history_metadata.grandparent_title ' \
|
||||||
|
'ORDER BY users_watched DESC, total_plays DESC ' \
|
||||||
|
'LIMIT 10' % time_range
|
||||||
|
result = monitor_db.select(query)
|
||||||
|
except:
|
||||||
|
logger.warn("Unable to execute database query.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
for item in result:
|
||||||
|
row = {'title': item[0],
|
||||||
|
'users_watched': item[1],
|
||||||
|
'rating_key': item[2],
|
||||||
|
'last_play': item[3],
|
||||||
|
'total_plays': item[4],
|
||||||
|
'grandparent_thumb': item[5],
|
||||||
|
'thumb': '',
|
||||||
|
'user': '',
|
||||||
|
'friendly_name': '',
|
||||||
|
'platform_type': '',
|
||||||
|
'platform': ''
|
||||||
|
}
|
||||||
|
popular_tv.append(row)
|
||||||
|
|
||||||
|
home_stats.append({'stat_id': stat,
|
||||||
|
'rows': popular_tv})
|
||||||
|
|
||||||
|
elif 'top_users' in stat:
|
||||||
|
top_users = []
|
||||||
|
try:
|
||||||
|
query = 'SELECT session_history.user, ' \
|
||||||
|
'(case when users.friendly_name is null then session_history.user else ' \
|
||||||
|
'users.friendly_name end) as friendly_name,' \
|
||||||
|
'COUNT(session_history.id) as total_plays, ' \
|
||||||
|
'MAX(session_history.started) as last_watch, ' \
|
||||||
|
'users.thumb ' \
|
||||||
|
'FROM session_history ' \
|
||||||
|
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id ' \
|
||||||
|
'LEFT OUTER JOIN users ON session_history.user_id = users.user_id ' \
|
||||||
|
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
|
||||||
|
'datetime("now", "-%s days", "localtime") '\
|
||||||
|
'GROUP BY session_history.user_id ' \
|
||||||
|
'ORDER BY total_plays DESC LIMIT 10' % time_range
|
||||||
|
result = monitor_db.select(query)
|
||||||
|
except:
|
||||||
|
logger.warn("Unable to execute database query.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
for item in result:
|
||||||
|
if not item[4] or item[4] == '':
|
||||||
|
user_thumb = common.DEFAULT_USER_THUMB
|
||||||
|
else:
|
||||||
|
user_thumb = item[4]
|
||||||
|
|
||||||
|
row = {'user': item[0],
|
||||||
|
'friendly_name': item[1],
|
||||||
|
'total_plays': item[2],
|
||||||
|
'last_play': item[3],
|
||||||
|
'thumb': user_thumb,
|
||||||
|
'grandparent_thumb': '',
|
||||||
|
'users_watched': '',
|
||||||
|
'rating_key': '',
|
||||||
|
'title': '',
|
||||||
|
'platform_type': '',
|
||||||
|
'platform': ''
|
||||||
|
}
|
||||||
|
top_users.append(row)
|
||||||
|
|
||||||
|
home_stats.append({'stat_id': stat,
|
||||||
|
'rows': top_users})
|
||||||
|
|
||||||
|
elif 'top_platforms' in stat:
|
||||||
|
top_platform = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
query = 'SELECT session_history.platform, ' \
|
||||||
|
'COUNT(session_history.id) as total_plays, ' \
|
||||||
|
'MAX(session_history.started) as last_watch ' \
|
||||||
|
'FROM session_history ' \
|
||||||
|
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") ' \
|
||||||
|
'>= datetime("now", "-%s days", "localtime") ' \
|
||||||
|
'GROUP BY session_history.platform ' \
|
||||||
|
'ORDER BY total_plays DESC' % time_range
|
||||||
|
result = monitor_db.select(query)
|
||||||
|
except:
|
||||||
|
logger.warn("Unable to execute database query.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
for item in result:
|
||||||
|
row = {'platform': item[0],
|
||||||
|
'total_plays': item[1],
|
||||||
|
'last_play': item[2],
|
||||||
|
'platform_type': item[0],
|
||||||
|
'title': '',
|
||||||
|
'thumb': '',
|
||||||
|
'grandparent_thumb': '',
|
||||||
|
'users_watched': '',
|
||||||
|
'rating_key': '',
|
||||||
|
'user': '',
|
||||||
|
'friendly_name': ''
|
||||||
|
}
|
||||||
|
top_platform.append(row)
|
||||||
|
|
||||||
|
home_stats.append({'stat_id': stat,
|
||||||
|
'rows': top_platform})
|
||||||
|
|
||||||
|
return home_stats
|
||||||
|
|
||||||
|
def get_stream_details(self, row_id=None):
|
||||||
|
monitor_db = monitor.MonitorDatabase()
|
||||||
|
|
||||||
|
if row_id:
|
||||||
|
query = 'SELECT container, bitrate, video_resolution, width, height, aspect_ratio, video_framerate, ' \
|
||||||
|
'video_codec, audio_codec, audio_channels, video_decision, transcode_video_codec, transcode_height, ' \
|
||||||
|
'transcode_width, audio_decision, transcode_audio_codec, transcode_audio_channels, media_type, ' \
|
||||||
|
'title, grandparent_title ' \
|
||||||
|
'from session_history_media_info ' \
|
||||||
|
'join session_history_metadata on session_history_media_info.id = session_history_metadata.id ' \
|
||||||
|
'where session_history_media_info.id = ?'
|
||||||
|
result = monitor_db.select(query, args=[row_id])
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
print result
|
||||||
|
stream_output = {}
|
||||||
|
|
||||||
|
for item in result:
|
||||||
|
stream_output = {'container': item[0],
|
||||||
|
'bitrate': item[1],
|
||||||
|
'video_resolution': item[2],
|
||||||
|
'width': item[3],
|
||||||
|
'height': item[4],
|
||||||
|
'aspect_ratio': item[5],
|
||||||
|
'video_framerate': item[6],
|
||||||
|
'video_codec': item[7],
|
||||||
|
'audio_codec': item[8],
|
||||||
|
'audio_channels': item[9],
|
||||||
|
'transcode_video_dec': item[10],
|
||||||
|
'transcode_video_codec': item[11],
|
||||||
|
'transcode_height': item[12],
|
||||||
|
'transcode_width': item[13],
|
||||||
|
'transcode_audio_dec': item[14],
|
||||||
|
'transcode_audio_codec': item[15],
|
||||||
|
'transcode_audio_channels': item[16],
|
||||||
|
'media_type': item[17],
|
||||||
|
'title': item[18],
|
||||||
|
'grandparent_title': item[19]
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream_output
|
||||||
|
|
||||||
|
def get_recently_watched(self, user=None, limit='10'):
|
||||||
|
monitor_db = monitor.MonitorDatabase()
|
||||||
|
recently_watched = []
|
||||||
|
|
||||||
|
if not limit.isdigit():
|
||||||
|
limit = '10'
|
||||||
|
|
||||||
|
try:
|
||||||
|
if user:
|
||||||
|
query = 'SELECT session_history.media_type, session_history.rating_key, title, thumb, parent_thumb, ' \
|
||||||
|
'media_index, parent_media_index, year, started, user ' \
|
||||||
|
'FROM session_history_metadata ' \
|
||||||
|
'JOIN session_history ON session_history_metadata.id = session_history.id ' \
|
||||||
|
'WHERE user = ? ORDER BY started DESC LIMIT ?'
|
||||||
|
result = monitor_db.select(query, args=[user, limit])
|
||||||
|
else:
|
||||||
|
query = 'SELECT session_history.media_type, session_history.rating_key, title, thumb, parent_thumb, ' \
|
||||||
|
'media_index, parent_media_index, year, started, user ' \
|
||||||
|
'FROM session_history_metadata ' \
|
||||||
|
'JOIN session_history ON session_history_metadata.id = session_history.id ' \
|
||||||
|
'ORDER BY started DESC LIMIT ?'
|
||||||
|
result = monitor_db.select(query, args=[limit])
|
||||||
|
except:
|
||||||
|
logger.warn("Unable to execute database query.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
for row in result:
|
||||||
|
if row[0] == 'episode':
|
||||||
|
thumb = row[4]
|
||||||
|
else:
|
||||||
|
thumb = row[3]
|
||||||
|
|
||||||
|
recent_output = {'type': row[0],
|
||||||
|
'rating_key': row[1],
|
||||||
|
'title': row[2],
|
||||||
|
'thumb': thumb,
|
||||||
|
'index': row[5],
|
||||||
|
'parentIndex': row[6],
|
||||||
|
'year': row[7],
|
||||||
|
'time': row[8],
|
||||||
|
'user': row[9]
|
||||||
|
}
|
||||||
|
recently_watched.append(recent_output)
|
||||||
|
|
||||||
|
return recently_watched
|
||||||
|
|
||||||
|
def get_user_watch_time_stats(self, user=None):
|
||||||
|
monitor_db = monitor.MonitorDatabase()
|
||||||
|
|
||||||
|
time_queries = [1, 7, 30, 0]
|
||||||
|
user_watch_time_stats = []
|
||||||
|
|
||||||
|
for days in time_queries:
|
||||||
|
if days > 0:
|
||||||
|
query = 'SELECT (SUM(stopped - started) - ' \
|
||||||
|
'SUM(CASE WHEN paused_counter is null THEN 0 ELSE paused_counter END)) as total_time, ' \
|
||||||
|
'COUNT(id) AS total_plays ' \
|
||||||
|
'FROM session_history ' \
|
||||||
|
'WHERE datetime(stopped, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") ' \
|
||||||
|
'AND user = ?' % days
|
||||||
|
result = monitor_db.select(query, args=[user])
|
||||||
|
else:
|
||||||
|
query = 'SELECT (SUM(stopped - started) - ' \
|
||||||
|
'SUM(CASE WHEN paused_counter is null THEN 0 ELSE paused_counter END)) as total_time, ' \
|
||||||
|
'COUNT(id) AS total_plays ' \
|
||||||
|
'FROM session_history ' \
|
||||||
|
'WHERE user = ?'
|
||||||
|
result = monitor_db.select(query, args=[user])
|
||||||
|
|
||||||
|
for item in result:
|
||||||
|
if item[0]:
|
||||||
|
total_time = item[0]
|
||||||
|
total_plays = item[1]
|
||||||
|
else:
|
||||||
|
total_time = 0
|
||||||
|
total_plays = 0
|
||||||
|
|
||||||
|
row = {'query_days': days,
|
||||||
|
'total_time': total_time,
|
||||||
|
'total_plays': total_plays
|
||||||
|
}
|
||||||
|
|
||||||
|
user_watch_time_stats.append(row)
|
||||||
|
|
||||||
|
return user_watch_time_stats
|
||||||
|
|
||||||
|
def get_user_platform_stats(self, user=None):
|
||||||
|
monitor_db = monitor.MonitorDatabase()
|
||||||
|
|
||||||
|
platform_stats = []
|
||||||
|
result_id = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
query = 'SELECT player, COUNT(player) as player_count, platform ' \
|
||||||
|
'FROM session_history ' \
|
||||||
|
'WHERE user = ? ' \
|
||||||
|
'GROUP BY player ' \
|
||||||
|
'ORDER BY player_count DESC'
|
||||||
|
result = monitor_db.select(query, args=[user])
|
||||||
|
except:
|
||||||
|
logger.warn("Unable to execute database query.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
for item in result:
|
||||||
|
row = {'platform_name': item[0],
|
||||||
|
'platform_type': item[2],
|
||||||
|
'total_plays': item[1],
|
||||||
|
'result_id': result_id
|
||||||
|
}
|
||||||
|
platform_stats.append(row)
|
||||||
|
result_id += 1
|
||||||
|
|
||||||
|
return platform_stats
|
||||||
|
|
||||||
|
def get_total_plays_per_day(self, time_range='30'):
|
||||||
|
monitor_db = monitor.MonitorDatabase()
|
||||||
|
|
||||||
|
if not time_range.isdigit():
|
||||||
|
time_range = '30'
|
||||||
|
|
||||||
|
try:
|
||||||
|
query = 'SELECT date(started, "unixepoch", "localtime") as date_played, ' \
|
||||||
|
'SUM(case when media_type = "episode" then 1 else 0 end) as tv_count, ' \
|
||||||
|
'SUM(case when media_type = "movie" then 1 else 0 end) as movie_count ' \
|
||||||
|
'FROM session_history ' \
|
||||||
|
'WHERE datetime(stopped, "unixepoch", "localtime") >= datetime("now", "-%s days", "localtime") ' \
|
||||||
|
'GROUP BY date_played ' \
|
||||||
|
'ORDER BY started ASC' % time_range
|
||||||
|
|
||||||
|
result = monitor_db.select(query)
|
||||||
|
except:
|
||||||
|
logger.warn("Unable to execute database query.")
|
||||||
|
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 = []
|
||||||
|
|
||||||
|
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
|
||||||
|
for item in result:
|
||||||
|
if date_string == item[0]:
|
||||||
|
series_1_value = item[1]
|
||||||
|
series_2_value = item[2]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
series_1_value = 0
|
||||||
|
series_2_value = 0
|
||||||
|
|
||||||
|
series_1.append(series_1_value)
|
||||||
|
series_2.append(series_2_value)
|
||||||
|
|
||||||
|
series_1_output = {'name': 'TV',
|
||||||
|
'data': series_1}
|
||||||
|
series_2_output = {'name': 'Movies',
|
||||||
|
'data': series_2}
|
||||||
|
|
||||||
|
output = {'categories': categories,
|
||||||
|
'series': [series_1_output, series_2_output]}
|
||||||
|
return output
|
||||||
|
|
||||||
|
def get_total_plays_per_dayofweek(self, time_range='30'):
|
||||||
|
monitor_db = monitor.MonitorDatabase()
|
||||||
|
|
||||||
|
if not time_range.isdigit():
|
||||||
|
time_range = '30'
|
||||||
|
|
||||||
|
query = 'SELECT strftime("%w", datetime(started, "unixepoch", "localtime")) as daynumber, ' \
|
||||||
|
'case cast (strftime("%w", datetime(started, "unixepoch", "localtime")) as integer) ' \
|
||||||
|
'when 0 then "Sunday" ' \
|
||||||
|
'when 1 then "Monday" ' \
|
||||||
|
'when 2 then "Tuesday" ' \
|
||||||
|
'when 3 then "Wednesday" ' \
|
||||||
|
'when 4 then "Thursday" ' \
|
||||||
|
'when 5 then "Friday" ' \
|
||||||
|
'else "Saturday" end as dayofweek, ' \
|
||||||
|
'COUNT(id) as total_plays ' \
|
||||||
|
'from session_history ' \
|
||||||
|
'WHERE datetime(stopped, "unixepoch", "localtime") >= ' \
|
||||||
|
'datetime("now", "-' + time_range + ' days", "localtime") ' \
|
||||||
|
'GROUP BY dayofweek ' \
|
||||||
|
'ORDER BY daynumber'
|
||||||
|
|
||||||
|
result = monitor_db.select(query)
|
||||||
|
|
||||||
|
days_list = ['Sunday', 'Monday', 'Tuesday', 'Wednesday',
|
||||||
|
'Thursday', 'Friday', 'Saturday']
|
||||||
|
|
||||||
|
categories = []
|
||||||
|
series_1 = []
|
||||||
|
|
||||||
|
for day_item in days_list:
|
||||||
|
categories.append(day_item)
|
||||||
|
series_1_value = 0
|
||||||
|
for item in result:
|
||||||
|
if day_item == item[1]:
|
||||||
|
series_1_value = item[2]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
series_1_value = 0
|
||||||
|
|
||||||
|
series_1.append(series_1_value)
|
||||||
|
|
||||||
|
series_1_output = {'name': 'Total plays',
|
||||||
|
'data': series_1}
|
||||||
|
|
||||||
|
output = {'categories': categories,
|
||||||
|
'series': [series_1_output]}
|
||||||
|
return output
|
||||||
|
|
||||||
|
def get_total_plays_per_hourofday(self, time_range='30'):
|
||||||
|
monitor_db = monitor.MonitorDatabase()
|
||||||
|
|
||||||
|
if not time_range.isdigit():
|
||||||
|
time_range = '30'
|
||||||
|
|
||||||
|
query = 'select strftime("%H", datetime(started, "unixepoch", "localtime")) as hourofday, ' \
|
||||||
|
'COUNT(id) ' \
|
||||||
|
'FROM session_history ' \
|
||||||
|
'WHERE datetime(stopped, "unixepoch", "localtime") >= ' \
|
||||||
|
'datetime("now", "-' + time_range + ' days", "localtime") ' \
|
||||||
|
'GROUP BY hourofday ' \
|
||||||
|
'ORDER BY hourofday'
|
||||||
|
|
||||||
|
result = monitor_db.select(query)
|
||||||
|
|
||||||
|
hours_list = ['00','01','02','03','04','05',
|
||||||
|
'06','07','08','09','10','11',
|
||||||
|
'12','13','14','15','16','17',
|
||||||
|
'18','19','20','21','22','23']
|
||||||
|
|
||||||
|
categories = []
|
||||||
|
series_1 = []
|
||||||
|
|
||||||
|
for hour_item in hours_list:
|
||||||
|
categories.append(hour_item)
|
||||||
|
series_1_value = 0
|
||||||
|
for item in result:
|
||||||
|
if hour_item == item[0]:
|
||||||
|
series_1_value = item[1]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
series_1_value = 0
|
||||||
|
|
||||||
|
series_1.append(series_1_value)
|
||||||
|
|
||||||
|
series_1_output = {'name': 'Total plays',
|
||||||
|
'data': series_1}
|
||||||
|
|
||||||
|
output = {'categories': categories,
|
||||||
|
'series': [series_1_output]}
|
||||||
|
return output
|
|
@ -163,7 +163,7 @@ class MonitorDatabase(object):
|
||||||
self.connection.execute("PRAGMA cache_size=-%s" % (get_cache_size() * 1024))
|
self.connection.execute("PRAGMA cache_size=-%s" % (get_cache_size() * 1024))
|
||||||
self.connection.row_factory = sqlite3.Row
|
self.connection.row_factory = sqlite3.Row
|
||||||
|
|
||||||
def action(self, query, args=None):
|
def action(self, query, args=None, return_last_id=False):
|
||||||
|
|
||||||
if query is None:
|
if query is None:
|
||||||
return
|
return
|
||||||
|
@ -302,56 +302,74 @@ class MonitorProcessing(object):
|
||||||
# If it's our first write then time stamp it.
|
# If it's our first write then time stamp it.
|
||||||
self.db.upsert('sessions', timestamp, keys)
|
self.db.upsert('sessions', timestamp, keys)
|
||||||
|
|
||||||
def write_session_history(self, session=None):
|
def write_session_history(self, session=None, import_metadata=None, is_import=False, import_ignore_interval=0):
|
||||||
|
|
||||||
if session:
|
if session:
|
||||||
logging_enabled = False
|
logging_enabled = False
|
||||||
|
|
||||||
|
if is_import:
|
||||||
|
if str(session['stopped']).isdigit():
|
||||||
|
stopped = session['stopped']
|
||||||
|
else:
|
||||||
|
stopped = int(time.time())
|
||||||
|
else:
|
||||||
|
stopped = int(time.time())
|
||||||
|
|
||||||
if plexpy.CONFIG.VIDEO_LOGGING_ENABLE and \
|
if plexpy.CONFIG.VIDEO_LOGGING_ENABLE and \
|
||||||
(session['media_type'] == 'movie' or session['media_type'] == 'episode'):
|
(session['media_type'] == 'movie' or session['media_type'] == 'episode'):
|
||||||
logging_enabled = True
|
logging_enabled = True
|
||||||
|
elif plexpy.CONFIG.MUSIC_LOGGING_ENABLE and \
|
||||||
if plexpy.CONFIG.MUSIC_LOGGING_ENABLE and \
|
|
||||||
session['media_type'] == 'track':
|
session['media_type'] == 'track':
|
||||||
logging_enabled = True
|
logging_enabled = True
|
||||||
|
else:
|
||||||
|
logger.debug(u"PlexPy Monitor :: ratingKey %s not logged. Does not meet logging criteria. "
|
||||||
|
u"Media type is '%s'" % (session['rating_key'], session['media_type']))
|
||||||
|
|
||||||
if plexpy.CONFIG.LOGGING_IGNORE_INTERVAL:
|
if plexpy.CONFIG.LOGGING_IGNORE_INTERVAL and not is_import:
|
||||||
if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \
|
if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \
|
||||||
(int(time.time()) - session['started'] < plexpy.CONFIG.LOGGING_IGNORE_INTERVAL):
|
(int(stopped) - session['started'] < int(plexpy.CONFIG.LOGGING_IGNORE_INTERVAL)):
|
||||||
logging_enabled = False
|
logging_enabled = False
|
||||||
logger.debug(u"PlexPy Monitor :: Item played for %s seconds which is less than %s seconds, "
|
logger.debug(u"PlexPy Monitor :: Play duration for ratingKey %s is %s secs which is less than %s "
|
||||||
u"so we're not logging it." %
|
u"seconds, so we're not logging it." %
|
||||||
(str(int(time.time()) - session['started']), plexpy.CONFIG.LOGGING_IGNORE_INTERVAL))
|
(session['rating_key'], str(int(stopped) - session['started']),
|
||||||
|
plexpy.CONFIG.LOGGING_IGNORE_INTERVAL))
|
||||||
|
elif is_import and import_ignore_interval:
|
||||||
|
if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \
|
||||||
|
(int(stopped) - session['started'] < int(import_ignore_interval)):
|
||||||
|
logging_enabled = False
|
||||||
|
logger.debug(u"PlexPy Monitor :: Play duration for ratingKey %s is %s secs which is less than %s "
|
||||||
|
u"seconds, so we're not logging it." %
|
||||||
|
(session['rating_key'], str(int(stopped) - session['started']),
|
||||||
|
import_ignore_interval))
|
||||||
|
|
||||||
if logging_enabled:
|
if logging_enabled:
|
||||||
logger.debug(u"PlexPy Monitor :: Attempting to write to session_history table...")
|
# logger.debug(u"PlexPy Monitor :: Attempting to write to session_history table...")
|
||||||
query = 'INSERT INTO session_history (started, stopped, rating_key, parent_rating_key, ' \
|
query = 'INSERT INTO session_history (started, stopped, rating_key, parent_rating_key, ' \
|
||||||
'grandparent_rating_key, media_type, user_id, user, ip_address, paused_counter, player, ' \
|
'grandparent_rating_key, media_type, user_id, user, ip_address, paused_counter, player, ' \
|
||||||
'platform, machine_id, view_offset) VALUES ' \
|
'platform, machine_id, view_offset) VALUES ' \
|
||||||
'(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
'(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
||||||
|
|
||||||
args = [session['started'], int(time.time()), session['rating_key'], session['parent_rating_key'],
|
args = [session['started'], stopped, session['rating_key'], session['parent_rating_key'],
|
||||||
session['grandparent_rating_key'], session['media_type'], session['user_id'], session['user'],
|
session['grandparent_rating_key'], session['media_type'], session['user_id'], session['user'],
|
||||||
session['ip_address'], session['paused_counter'], session['player'], session['platform'],
|
session['ip_address'], session['paused_counter'], session['player'], session['platform'],
|
||||||
session['machine_id'], session['view_offset']]
|
session['machine_id'], session['view_offset']]
|
||||||
|
|
||||||
logger.debug(u"PlexPy Monitor :: Writing session_history transaction...")
|
# logger.debug(u"PlexPy Monitor :: Writing session_history transaction...")
|
||||||
self.db.action(query=query, args=args)
|
self.db.action(query=query, args=args)
|
||||||
|
|
||||||
# Get the id for the last transaction
|
# logger.debug(u"PlexPy Monitor :: Successfully written history item, last id for session_history is %s"
|
||||||
last_id = self.db.select_single('SELECT max(id) FROM session_history')
|
# % last_id)
|
||||||
logger.debug(u"PlexPy Monitor :: Successfully written history item, last id for session_history is %s"
|
|
||||||
% last_id)
|
|
||||||
|
|
||||||
logger.debug(u"PlexPy Monitor :: Attempting to write to session_history_media_info table...")
|
# Write the session_history_media_info table
|
||||||
|
# logger.debug(u"PlexPy Monitor :: Attempting to write to session_history_media_info table...")
|
||||||
query = 'INSERT INTO session_history_media_info (id, rating_key, video_decision, audio_decision, ' \
|
query = 'INSERT INTO session_history_media_info (id, rating_key, video_decision, audio_decision, ' \
|
||||||
'duration, width, height, container, video_codec, audio_codec, bitrate, video_resolution, ' \
|
'duration, width, height, container, video_codec, audio_codec, bitrate, video_resolution, ' \
|
||||||
'video_framerate, aspect_ratio, audio_channels, transcode_protocol, transcode_container, ' \
|
'video_framerate, aspect_ratio, audio_channels, transcode_protocol, transcode_container, ' \
|
||||||
'transcode_video_codec, transcode_audio_codec, transcode_audio_channels, transcode_width, ' \
|
'transcode_video_codec, transcode_audio_codec, transcode_audio_channels, transcode_width, ' \
|
||||||
'transcode_height) VALUES ' \
|
'transcode_height) VALUES ' \
|
||||||
'(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
'(last_insert_rowid(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
||||||
|
|
||||||
args = [last_id, session['rating_key'], session['video_decision'], session['audio_decision'],
|
args = [session['rating_key'], session['video_decision'], session['audio_decision'],
|
||||||
session['duration'], session['width'], session['height'], session['container'],
|
session['duration'], session['width'], session['height'], session['container'],
|
||||||
session['video_codec'], session['audio_codec'], session['bitrate'],
|
session['video_codec'], session['audio_codec'], session['bitrate'],
|
||||||
session['video_resolution'], session['video_framerate'], session['aspect_ratio'],
|
session['video_resolution'], session['video_framerate'], session['aspect_ratio'],
|
||||||
|
@ -359,15 +377,18 @@ class MonitorProcessing(object):
|
||||||
session['transcode_video_codec'], session['transcode_audio_codec'],
|
session['transcode_video_codec'], session['transcode_audio_codec'],
|
||||||
session['transcode_audio_channels'], session['transcode_width'], session['transcode_height']]
|
session['transcode_audio_channels'], session['transcode_width'], session['transcode_height']]
|
||||||
|
|
||||||
logger.debug(u"PlexPy Monitor :: Writing session_history_media_info transaction...")
|
# logger.debug(u"PlexPy Monitor :: Writing session_history_media_info transaction...")
|
||||||
self.db.action(query=query, args=args)
|
self.db.action(query=query, args=args)
|
||||||
|
|
||||||
|
if not is_import:
|
||||||
logger.debug(u"PlexPy Monitor :: Fetching metadata for item ratingKey %s" % session['rating_key'])
|
logger.debug(u"PlexPy Monitor :: Fetching metadata for item ratingKey %s" % session['rating_key'])
|
||||||
pms_connect = pmsconnect.PmsConnect()
|
pms_connect = pmsconnect.PmsConnect()
|
||||||
result = pms_connect.get_metadata_details(rating_key=str(session['rating_key']))
|
result = pms_connect.get_metadata_details(rating_key=str(session['rating_key']))
|
||||||
|
|
||||||
metadata = result['metadata']
|
metadata = result['metadata']
|
||||||
|
else:
|
||||||
|
metadata = import_metadata
|
||||||
|
|
||||||
|
# Write the session_history_metadata table
|
||||||
directors = ";".join(metadata['directors'])
|
directors = ";".join(metadata['directors'])
|
||||||
writers = ";".join(metadata['writers'])
|
writers = ";".join(metadata['writers'])
|
||||||
actors = ";".join(metadata['actors'])
|
actors = ";".join(metadata['actors'])
|
||||||
|
@ -381,15 +402,16 @@ class MonitorProcessing(object):
|
||||||
else:
|
else:
|
||||||
full_title = metadata['title']
|
full_title = metadata['title']
|
||||||
|
|
||||||
logger.debug(u"PlexPy Monitor :: Attempting to write to session_history_metadata table...")
|
# logger.debug(u"PlexPy Monitor :: Attempting to write to session_history_metadata table...")
|
||||||
query = 'INSERT INTO session_history_metadata (id, rating_key, parent_rating_key, ' \
|
query = 'INSERT INTO session_history_metadata (id, rating_key, parent_rating_key, ' \
|
||||||
'grandparent_rating_key, title, parent_title, grandparent_title, full_title, media_index, ' \
|
'grandparent_rating_key, title, parent_title, grandparent_title, full_title, media_index, ' \
|
||||||
'parent_media_index, thumb, parent_thumb, grandparent_thumb, art, media_type, year, ' \
|
'parent_media_index, thumb, parent_thumb, grandparent_thumb, art, media_type, year, ' \
|
||||||
'originally_available_at, added_at, updated_at, last_viewed_at, content_rating, summary, ' \
|
'originally_available_at, added_at, updated_at, last_viewed_at, content_rating, summary, ' \
|
||||||
'rating, duration, guid, directors, writers, actors, genres, studio) VALUES ' \
|
'rating, duration, guid, directors, writers, actors, genres, studio) VALUES ' \
|
||||||
'(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
'(last_insert_rowid(), ' \
|
||||||
|
'?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
||||||
|
|
||||||
args = [last_id, session['rating_key'], session['parent_rating_key'], session['grandparent_rating_key'],
|
args = [session['rating_key'], session['parent_rating_key'], session['grandparent_rating_key'],
|
||||||
session['title'], session['parent_title'], session['grandparent_title'], full_title,
|
session['title'], session['parent_title'], session['grandparent_title'], full_title,
|
||||||
metadata['index'], metadata['parent_index'], metadata['thumb'], metadata['parent_thumb'],
|
metadata['index'], metadata['parent_index'], metadata['thumb'], metadata['parent_thumb'],
|
||||||
metadata['grandparent_thumb'], metadata['art'], session['media_type'], metadata['year'],
|
metadata['grandparent_thumb'], metadata['art'], session['media_type'], metadata['year'],
|
||||||
|
@ -397,7 +419,7 @@ class MonitorProcessing(object):
|
||||||
metadata['last_viewed_at'], metadata['content_rating'], metadata['summary'], metadata['rating'],
|
metadata['last_viewed_at'], metadata['content_rating'], metadata['summary'], metadata['rating'],
|
||||||
metadata['duration'], metadata['guid'], directors, writers, actors, genres, metadata['studio']]
|
metadata['duration'], metadata['guid'], directors, writers, actors, genres, metadata['studio']]
|
||||||
|
|
||||||
logger.debug(u"PlexPy Monitor :: Writing session_history_metadata transaction...")
|
# logger.debug(u"PlexPy Monitor :: Writing session_history_metadata transaction...")
|
||||||
self.db.action(query=query, args=args)
|
self.db.action(query=query, args=args)
|
||||||
|
|
||||||
def find_session_ip(self, rating_key=None, machine_id=None):
|
def find_session_ip(self, rating_key=None, machine_id=None):
|
||||||
|
|
|
@ -23,7 +23,6 @@ import plexpy
|
||||||
def refresh_users():
|
def refresh_users():
|
||||||
logger.info("Requesting users list refresh...")
|
logger.info("Requesting users list refresh...")
|
||||||
result = PlexTV().get_full_users_list()
|
result = PlexTV().get_full_users_list()
|
||||||
pw_db = db.DBConnection()
|
|
||||||
monitor_db = monitor.MonitorDatabase()
|
monitor_db = monitor.MonitorDatabase()
|
||||||
|
|
||||||
if len(result) > 0:
|
if len(result) > 0:
|
||||||
|
@ -38,7 +37,6 @@ def refresh_users():
|
||||||
"is_restricted": item['is_restricted']
|
"is_restricted": item['is_restricted']
|
||||||
}
|
}
|
||||||
|
|
||||||
pw_db.upsert('plexpy_users', new_value_dict, control_value_dict)
|
|
||||||
monitor_db.upsert('users', new_value_dict, control_value_dict)
|
monitor_db.upsert('users', new_value_dict, control_value_dict)
|
||||||
|
|
||||||
logger.info("Users list refreshed.")
|
logger.info("Users list refreshed.")
|
||||||
|
|
|
@ -972,6 +972,21 @@ class PlexWatch(object):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_user_id(self, user=None):
|
||||||
|
if user:
|
||||||
|
try:
|
||||||
|
myDB = db.DBConnection()
|
||||||
|
query = 'select user_id FROM plexpy_users WHERE username = ?'
|
||||||
|
result = myDB.select_single(query, args=[user])
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def get_user_details(self, user=None, user_id=None):
|
def get_user_details(self, user=None, user_id=None):
|
||||||
try:
|
try:
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
|
|
368
plexpy/plexwatch_import.py
Normal file
368
plexpy/plexwatch_import.py
Normal file
|
@ -0,0 +1,368 @@
|
||||||
|
# This file is part of PlexPy.
|
||||||
|
#
|
||||||
|
# PlexPy is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# PlexPy 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
from plexpy import logger, helpers, monitor, datafactory, plextv
|
||||||
|
from xml.dom import minidom
|
||||||
|
|
||||||
|
import plexpy
|
||||||
|
|
||||||
|
def extract_plexwatch_xml(xml=None):
|
||||||
|
output = {}
|
||||||
|
clean_xml = helpers.latinToAscii(xml)
|
||||||
|
try:
|
||||||
|
xml_parse = minidom.parseString(clean_xml)
|
||||||
|
except:
|
||||||
|
logger.warn("Error parsing XML for Plexwatch database.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
xml_head = xml_parse.getElementsByTagName('opt')
|
||||||
|
if not xml_head:
|
||||||
|
logger.warn("Error parsing XML for Plexwatch database.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
for a in xml_head:
|
||||||
|
added_at = helpers.get_xml_attr(a, 'addedAt')
|
||||||
|
art = helpers.get_xml_attr(a, 'art')
|
||||||
|
duration = helpers.get_xml_attr(a, 'duration')
|
||||||
|
grandparent_thumb = helpers.get_xml_attr(a, 'grandparentThumb')
|
||||||
|
grandparent_title = helpers.get_xml_attr(a, 'grandparentTitle')
|
||||||
|
guid = helpers.get_xml_attr(a, 'guid')
|
||||||
|
media_index = helpers.get_xml_attr(a, 'index')
|
||||||
|
originally_available_at = helpers.get_xml_attr(a, 'originallyAvailableAt')
|
||||||
|
last_viewed_at = helpers.get_xml_attr(a, 'lastViewedAt')
|
||||||
|
parent_media_index = helpers.get_xml_attr(a, 'parentIndex')
|
||||||
|
parent_thumb = helpers.get_xml_attr(a, 'parent_thumb')
|
||||||
|
rating = helpers.get_xml_attr(a, 'rating')
|
||||||
|
thumb = helpers.get_xml_attr(a, 'thumb')
|
||||||
|
media_type = helpers.get_xml_attr(a, 'type')
|
||||||
|
updated_at = helpers.get_xml_attr(a, 'updatedAt')
|
||||||
|
view_offset = helpers.get_xml_attr(a, 'viewOffset')
|
||||||
|
year = helpers.get_xml_attr(a, 'year')
|
||||||
|
parent_title = helpers.get_xml_attr(a, 'parentTitle')
|
||||||
|
studio = helpers.get_xml_attr(a, 'studio')
|
||||||
|
title = helpers.get_xml_attr(a, 'title')
|
||||||
|
|
||||||
|
directors = []
|
||||||
|
if a.getElementsByTagName('Director'):
|
||||||
|
director_elem = a.getElementsByTagName('Director')
|
||||||
|
for b in director_elem:
|
||||||
|
directors.append(helpers.get_xml_attr(b, 'tag'))
|
||||||
|
|
||||||
|
aspect_ratio = ''
|
||||||
|
audio_channels = None
|
||||||
|
audio_codec = ''
|
||||||
|
bitrate = None
|
||||||
|
container = ''
|
||||||
|
height = None
|
||||||
|
video_codec = ''
|
||||||
|
video_framerate = ''
|
||||||
|
video_resolution = ''
|
||||||
|
width = None
|
||||||
|
|
||||||
|
if a.getElementsByTagName('Media'):
|
||||||
|
media_elem = a.getElementsByTagName('Media')
|
||||||
|
for c in media_elem:
|
||||||
|
aspect_ratio = helpers.get_xml_attr(c, 'aspectRatio')
|
||||||
|
audio_channels = helpers.get_xml_attr(c, 'audioChannels')
|
||||||
|
audio_codec = helpers.get_xml_attr(c, 'audioCodec')
|
||||||
|
bitrate = helpers.get_xml_attr(c, 'bitrate')
|
||||||
|
container = helpers.get_xml_attr(c, 'container')
|
||||||
|
height = helpers.get_xml_attr(c, 'height')
|
||||||
|
video_codec = helpers.get_xml_attr(c, 'videoCodec')
|
||||||
|
video_framerate = helpers.get_xml_attr(c, 'videoFrameRate')
|
||||||
|
video_resolution = helpers.get_xml_attr(c, 'videoResolution')
|
||||||
|
width = helpers.get_xml_attr(c, 'width')
|
||||||
|
|
||||||
|
machine_id = ''
|
||||||
|
platform = ''
|
||||||
|
player = ''
|
||||||
|
|
||||||
|
if a.getElementsByTagName('Player'):
|
||||||
|
player_elem = a.getElementsByTagName('Player')
|
||||||
|
for d in player_elem:
|
||||||
|
machine_id = helpers.get_xml_attr(d, 'machineIdentifier')
|
||||||
|
platform = helpers.get_xml_attr(d, 'platform')
|
||||||
|
player = helpers.get_xml_attr(d, 'title')
|
||||||
|
|
||||||
|
transcode_audio_channels = None
|
||||||
|
transcode_audio_codec = ''
|
||||||
|
audio_decision = 'direct play'
|
||||||
|
transcode_container = ''
|
||||||
|
transcode_height = None
|
||||||
|
transcode_protocol = ''
|
||||||
|
transcode_video_codec = ''
|
||||||
|
video_decision = 'direct play'
|
||||||
|
transcode_width = None
|
||||||
|
|
||||||
|
if a.getElementsByTagName('TranscodeSession'):
|
||||||
|
transcode_elem = a.getElementsByTagName('TranscodeSession')
|
||||||
|
for e in transcode_elem:
|
||||||
|
transcode_audio_channels = helpers.get_xml_attr(e, 'audioChannels')
|
||||||
|
transcode_audio_codec = helpers.get_xml_attr(e, 'audioCodec')
|
||||||
|
audio_decision = helpers.get_xml_attr(e, 'audioDecision')
|
||||||
|
transcode_container = helpers.get_xml_attr(e, 'container')
|
||||||
|
transcode_height = helpers.get_xml_attr(e, 'height')
|
||||||
|
transcode_protocol = helpers.get_xml_attr(e, 'protocol')
|
||||||
|
transcode_video_codec = helpers.get_xml_attr(e, 'videoCodec')
|
||||||
|
video_decision = helpers.get_xml_attr(e, 'videoDecision')
|
||||||
|
transcode_width = helpers.get_xml_attr(e, 'width')
|
||||||
|
|
||||||
|
user_id = None
|
||||||
|
|
||||||
|
if a.getElementsByTagName('User'):
|
||||||
|
user_elem = a.getElementsByTagName('User')
|
||||||
|
for f in user_elem:
|
||||||
|
user_id = helpers.get_xml_attr(f, 'id')
|
||||||
|
|
||||||
|
writers = []
|
||||||
|
if a.getElementsByTagName('Writer'):
|
||||||
|
writer_elem = a.getElementsByTagName('Writer')
|
||||||
|
for g in writer_elem:
|
||||||
|
writers.append(helpers.get_xml_attr(g, 'tag'))
|
||||||
|
|
||||||
|
actors = []
|
||||||
|
if a.getElementsByTagName('Role'):
|
||||||
|
actor_elem = a.getElementsByTagName('Role')
|
||||||
|
for h in actor_elem:
|
||||||
|
actors.append(helpers.get_xml_attr(h, 'tag'))
|
||||||
|
|
||||||
|
genres = []
|
||||||
|
if a.getElementsByTagName('Genre'):
|
||||||
|
genre_elem = a.getElementsByTagName('Genre')
|
||||||
|
for i in genre_elem:
|
||||||
|
genres.append(helpers.get_xml_attr(i, 'tag'))
|
||||||
|
|
||||||
|
output = {'added_at': added_at,
|
||||||
|
'art': art,
|
||||||
|
'duration': duration,
|
||||||
|
'grandparent_thumb': grandparent_thumb,
|
||||||
|
'grandparent_title': grandparent_title,
|
||||||
|
'parent_title': parent_title,
|
||||||
|
'title': title,
|
||||||
|
'guid': guid,
|
||||||
|
'media_index': media_index,
|
||||||
|
'originally_available_at': originally_available_at,
|
||||||
|
'last_viewed_at': last_viewed_at,
|
||||||
|
'parent_media_index': parent_media_index,
|
||||||
|
'parent_thumb': parent_thumb,
|
||||||
|
'rating': rating,
|
||||||
|
'thumb': thumb,
|
||||||
|
'media_type': media_type,
|
||||||
|
'updated_at': updated_at,
|
||||||
|
'view_offset': view_offset,
|
||||||
|
'year': year,
|
||||||
|
'directors': directors,
|
||||||
|
'aspect_ratio': aspect_ratio,
|
||||||
|
'audio_channels': audio_channels,
|
||||||
|
'audio_codec': audio_codec,
|
||||||
|
'bitrate': bitrate,
|
||||||
|
'container': container,
|
||||||
|
'height': height,
|
||||||
|
'video_codec': video_codec,
|
||||||
|
'video_framerate': video_framerate,
|
||||||
|
'video_resolution': video_resolution,
|
||||||
|
'width': width,
|
||||||
|
'machine_id': machine_id,
|
||||||
|
'platform': platform,
|
||||||
|
'player': player,
|
||||||
|
'transcode_audio_channels': transcode_audio_channels,
|
||||||
|
'transcode_audio_codec': transcode_audio_codec,
|
||||||
|
'audio_decision': audio_decision,
|
||||||
|
'transcode_container': transcode_container,
|
||||||
|
'transcode_height': transcode_height,
|
||||||
|
'transcode_protocol': transcode_protocol,
|
||||||
|
'transcode_video_codec': transcode_video_codec,
|
||||||
|
'video_decision': video_decision,
|
||||||
|
'transcode_width': transcode_width,
|
||||||
|
'user_id': user_id,
|
||||||
|
'writers': writers,
|
||||||
|
'actors': actors,
|
||||||
|
'genres': genres,
|
||||||
|
'studio': studio
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def validate_database(database=None, table_name=None):
|
||||||
|
try:
|
||||||
|
connection = sqlite3.connect(database, timeout=20)
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
logger.error('PlexPy Importer :: Invalid database specified.')
|
||||||
|
return 'Invalid database specified.'
|
||||||
|
except ValueError:
|
||||||
|
logger.error('PlexPy Importer :: Invalid database specified.')
|
||||||
|
return 'Invalid database specified.'
|
||||||
|
|
||||||
|
try:
|
||||||
|
connection.execute('SELECT ratingKey from %s' % table_name)
|
||||||
|
connection.close()
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
logger.error('PlexPy Importer :: Invalid database specified.')
|
||||||
|
return 'Invalid database specified.'
|
||||||
|
|
||||||
|
return 'success'
|
||||||
|
|
||||||
|
def import_from_plexwatch(database=None, table_name=None, import_ignore_interval=0):
|
||||||
|
|
||||||
|
try:
|
||||||
|
connection = sqlite3.connect(database, timeout=20)
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
logger.error('PlexPy Importer :: Invalid filename.')
|
||||||
|
return None
|
||||||
|
except ValueError:
|
||||||
|
logger.error('PlexPy Importer :: Invalid filename.')
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
connection.execute('SELECT ratingKey from %s' % table_name)
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
logger.error('PlexPy Importer :: Database specified does not contain the required fields.')
|
||||||
|
return None
|
||||||
|
|
||||||
|
logger.debug(u"PlexPy Importer :: PlexWatch data import in progress...")
|
||||||
|
|
||||||
|
logger.debug(u"PlexPy Importer :: Disabling monitoring while import in progress.")
|
||||||
|
plexpy.schedule_job(monitor.check_active_sessions, 'Check for active sessions', hours=0, minutes=0, seconds=0)
|
||||||
|
|
||||||
|
monitor_processing = monitor.MonitorProcessing()
|
||||||
|
data_factory = datafactory.DataFactory()
|
||||||
|
|
||||||
|
# Get the latest friends list so we can pull user id's
|
||||||
|
try:
|
||||||
|
plextv.refresh_users()
|
||||||
|
except:
|
||||||
|
logger.debug(u"PlexPy Importer :: Unable to refresh the users list. Aborting import.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
query = 'SELECT time AS started, ' \
|
||||||
|
'stopped, ' \
|
||||||
|
'ratingKey AS rating_key, ' \
|
||||||
|
'null AS user_id, ' \
|
||||||
|
'user, ' \
|
||||||
|
'ip_address, ' \
|
||||||
|
'paused_counter, ' \
|
||||||
|
'platform AS player, ' \
|
||||||
|
'null AS platform, ' \
|
||||||
|
'null as machine_id, ' \
|
||||||
|
'parentRatingKey as parent_rating_key, ' \
|
||||||
|
'grandparentRatingKey as grandparent_rating_key, ' \
|
||||||
|
'null AS media_type, ' \
|
||||||
|
'null AS view_offset, ' \
|
||||||
|
'xml, ' \
|
||||||
|
'rating as content_rating,' \
|
||||||
|
'summary,' \
|
||||||
|
'title AS full_title,' \
|
||||||
|
'orig_title AS title, ' \
|
||||||
|
'orig_title_ep AS grandparent_title ' \
|
||||||
|
'FROM ' + table_name + ' ORDER BY id'
|
||||||
|
|
||||||
|
result = connection.execute(query)
|
||||||
|
|
||||||
|
for row in result:
|
||||||
|
# Extract the xml from the Plexwatch db xml field.
|
||||||
|
extracted_xml = extract_plexwatch_xml(row[14])
|
||||||
|
|
||||||
|
# If the user_id no longer exists in the friends list, pull it from the xml.
|
||||||
|
if data_factory.get_user_id(user=row[4]):
|
||||||
|
user_id = data_factory.get_user_id(user=row[4])
|
||||||
|
else:
|
||||||
|
user_id = extracted_xml['user_id']
|
||||||
|
|
||||||
|
session_history = {'started': row[0],
|
||||||
|
'stopped': row[1],
|
||||||
|
'rating_key': row[2],
|
||||||
|
'title': extracted_xml['title'],
|
||||||
|
'parent_title': extracted_xml['parent_title'],
|
||||||
|
'grandparent_title': extracted_xml['grandparent_title'],
|
||||||
|
'user_id': user_id,
|
||||||
|
'user': row[4],
|
||||||
|
'ip_address': row[5],
|
||||||
|
'paused_counter': row[6],
|
||||||
|
'player': row[7],
|
||||||
|
'platform': extracted_xml['platform'],
|
||||||
|
'machine_id': extracted_xml['machine_id'],
|
||||||
|
'parent_rating_key': row[10],
|
||||||
|
'grandparent_rating_key': row[11],
|
||||||
|
'media_type': extracted_xml['media_type'],
|
||||||
|
'view_offset': extracted_xml['view_offset'],
|
||||||
|
'video_decision': extracted_xml['video_decision'],
|
||||||
|
'audio_decision': extracted_xml['audio_decision'],
|
||||||
|
'duration': extracted_xml['duration'],
|
||||||
|
'width': extracted_xml['width'],
|
||||||
|
'height': extracted_xml['height'],
|
||||||
|
'container': extracted_xml['container'],
|
||||||
|
'video_codec': extracted_xml['video_codec'],
|
||||||
|
'audio_codec': extracted_xml['audio_codec'],
|
||||||
|
'bitrate': extracted_xml['bitrate'],
|
||||||
|
'video_resolution': extracted_xml['video_resolution'],
|
||||||
|
'video_framerate': extracted_xml['video_framerate'],
|
||||||
|
'aspect_ratio': extracted_xml['aspect_ratio'],
|
||||||
|
'audio_channels': extracted_xml['audio_channels'],
|
||||||
|
'transcode_protocol': extracted_xml['transcode_protocol'],
|
||||||
|
'transcode_container': extracted_xml['transcode_container'],
|
||||||
|
'transcode_video_codec': extracted_xml['transcode_video_codec'],
|
||||||
|
'transcode_audio_codec': extracted_xml['transcode_audio_codec'],
|
||||||
|
'transcode_audio_channels': extracted_xml['transcode_audio_channels'],
|
||||||
|
'transcode_width': extracted_xml['transcode_width'],
|
||||||
|
'transcode_height': extracted_xml['transcode_height']
|
||||||
|
}
|
||||||
|
|
||||||
|
session_history_metadata = {'rating_key': row[2],
|
||||||
|
'parent_rating_key': row[10],
|
||||||
|
'grandparent_rating_key': row[11],
|
||||||
|
'title': extracted_xml['title'],
|
||||||
|
'parent_title': extracted_xml['parent_title'],
|
||||||
|
'grandparent_title': extracted_xml['grandparent_title'],
|
||||||
|
'index': extracted_xml['media_index'],
|
||||||
|
'parent_index': extracted_xml['parent_media_index'],
|
||||||
|
'thumb': extracted_xml['thumb'],
|
||||||
|
'parent_thumb': extracted_xml['parent_thumb'],
|
||||||
|
'grandparent_thumb': extracted_xml['grandparent_thumb'],
|
||||||
|
'art': extracted_xml['art'],
|
||||||
|
'media_type': extracted_xml['media_type'],
|
||||||
|
'year': extracted_xml['year'],
|
||||||
|
'originally_available_at': extracted_xml['originally_available_at'],
|
||||||
|
'added_at': extracted_xml['added_at'],
|
||||||
|
'updated_at': extracted_xml['updated_at'],
|
||||||
|
'last_viewed_at': extracted_xml['last_viewed_at'],
|
||||||
|
'content_rating': row[15],
|
||||||
|
'summary': row[16],
|
||||||
|
'rating': extracted_xml['rating'],
|
||||||
|
'duration': extracted_xml['duration'],
|
||||||
|
'guid': extracted_xml['guid'],
|
||||||
|
'directors': extracted_xml['directors'],
|
||||||
|
'writers': extracted_xml['writers'],
|
||||||
|
'actors': extracted_xml['actors'],
|
||||||
|
'genres': extracted_xml['genres'],
|
||||||
|
'studio': extracted_xml['studio'],
|
||||||
|
'full_title': row[17]
|
||||||
|
}
|
||||||
|
|
||||||
|
# On older versions of PMS, "clip" items were still classified as "movie" and had bad ratingKey values
|
||||||
|
# Just make sure that the ratingKey is indeed an integer
|
||||||
|
if str(row[2]).isdigit():
|
||||||
|
monitor_processing.write_session_history(session=session_history,
|
||||||
|
import_metadata=session_history_metadata,
|
||||||
|
is_import=True,
|
||||||
|
import_ignore_interval=import_ignore_interval)
|
||||||
|
else:
|
||||||
|
logger.debug(u"PlexPy Importer :: Item has bad rating_key: %s" % str(row[2]))
|
||||||
|
|
||||||
|
logger.debug(u"PlexPy Importer :: PlexWatch data import complete.")
|
||||||
|
|
||||||
|
logger.debug(u"PlexPy Importer :: Re-enabling monitoring.")
|
||||||
|
plexpy.initialize_scheduler()
|
|
@ -13,13 +13,14 @@
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from plexpy import logger, notifiers, plextv, pmsconnect, plexwatch, db, common, log_reader, datafactory, monitor
|
from plexpy import logger, notifiers, plextv, pmsconnect, plexwatch, db, common, log_reader, datafactory
|
||||||
from plexpy.helpers import checked, radio
|
from plexpy.helpers import checked, radio
|
||||||
|
|
||||||
from mako.lookup import TemplateLookup
|
from mako.lookup import TemplateLookup
|
||||||
from mako import exceptions
|
from mako import exceptions
|
||||||
|
|
||||||
import plexpy
|
import plexpy
|
||||||
|
import threading
|
||||||
import cherrypy
|
import cherrypy
|
||||||
import hashlib
|
import hashlib
|
||||||
import random
|
import random
|
||||||
|
@ -60,9 +61,6 @@ class WebInterface(object):
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def home(self):
|
def home(self):
|
||||||
if plexpy.CONFIG.PLEXWATCH_DATABASE == '':
|
|
||||||
raise cherrypy.HTTPRedirect("config")
|
|
||||||
else:
|
|
||||||
return serve_template(templatename="index.html", title="Home")
|
return serve_template(templatename="index.html", title="Home")
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
|
@ -83,28 +81,27 @@ class WebInterface(object):
|
||||||
return json.dumps(formats)
|
return json.dumps(formats)
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def home_stats(self, time_range='30', **kwargs):
|
def home_stats_old(self, time_range='30', **kwargs):
|
||||||
plex_watch = plexwatch.PlexWatch()
|
plex_watch = plexwatch.PlexWatch()
|
||||||
stats_data = plex_watch.get_home_stats(time_range)
|
stats_data = plex_watch.get_home_stats(time_range)
|
||||||
|
|
||||||
return serve_template(templatename="home_stats.html", title="Stats", data=stats_data)
|
return serve_template(templatename="home_stats.html", title="Stats", data=stats_data)
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
def home_stats(self, time_range='30', **kwargs):
|
||||||
|
data_factory = datafactory.DataFactory()
|
||||||
|
stats_data = data_factory.get_home_stats(time_range=time_range)
|
||||||
|
|
||||||
|
return serve_template(templatename="home_stats.html", title="Stats", data=stats_data)
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def history(self):
|
def history(self):
|
||||||
return serve_template(templatename="history.html", title="History")
|
return serve_template(templatename="history.html", title="History")
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
def history_new(self):
|
|
||||||
return serve_template(templatename="history_new.html", title="History")
|
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def users(self):
|
def users(self):
|
||||||
return serve_template(templatename="users.html", title="Users")
|
return serve_template(templatename="users.html", title="Users")
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
def users_new(self):
|
|
||||||
return serve_template(templatename="users_new.html", title="Users")
|
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def graphs(self):
|
def graphs(self):
|
||||||
return serve_template(templatename="graphs.html", title="Graphs")
|
return serve_template(templatename="graphs.html", title="Graphs")
|
||||||
|
@ -116,8 +113,8 @@ class WebInterface(object):
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def user(self, user=None):
|
def user(self, user=None):
|
||||||
try:
|
try:
|
||||||
plex_watch = plexwatch.PlexWatch()
|
data_factory = datafactory.DataFactory()
|
||||||
user_details = plex_watch.get_user_details(user)
|
user_details = data_factory.get_user_details(user=user)
|
||||||
except:
|
except:
|
||||||
logger.warn("Unable to retrieve friendly name for user %s " % user)
|
logger.warn("Unable to retrieve friendly name for user %s " % user)
|
||||||
|
|
||||||
|
@ -127,9 +124,9 @@ class WebInterface(object):
|
||||||
def edit_user_dialog(self, user=None, **kwargs):
|
def edit_user_dialog(self, user=None, **kwargs):
|
||||||
if user:
|
if user:
|
||||||
try:
|
try:
|
||||||
plex_watch = plexwatch.PlexWatch()
|
data_factory = datafactory.DataFactory()
|
||||||
result = {'user': user,
|
result = {'user': user,
|
||||||
'friendly_name': plex_watch.get_user_friendly_name(user)
|
'friendly_name': data_factory.get_user_friendly_name(user)
|
||||||
}
|
}
|
||||||
status_message = ""
|
status_message = ""
|
||||||
except:
|
except:
|
||||||
|
@ -146,10 +143,6 @@ class WebInterface(object):
|
||||||
def edit_user(self, user=None, friendly_name=None, **kwargs):
|
def edit_user(self, user=None, friendly_name=None, **kwargs):
|
||||||
if user:
|
if user:
|
||||||
try:
|
try:
|
||||||
plex_watch = plexwatch.PlexWatch()
|
|
||||||
plex_watch.set_user_friendly_name(user, friendly_name)
|
|
||||||
|
|
||||||
# For the new database too
|
|
||||||
data_factory = datafactory.DataFactory()
|
data_factory = datafactory.DataFactory()
|
||||||
data_factory.set_user_friendly_name(user, friendly_name)
|
data_factory.set_user_friendly_name(user, friendly_name)
|
||||||
|
|
||||||
|
@ -162,23 +155,14 @@ class WebInterface(object):
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_stream_data(self, row_id=None, user=None, **kwargs):
|
def get_stream_data(self, row_id=None, user=None, **kwargs):
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
data_factory = datafactory.DataFactory()
|
||||||
stream_data = plex_watch.get_stream_details(row_id)
|
stream_data = data_factory.get_stream_details(row_id)
|
||||||
|
|
||||||
return serve_template(templatename="stream_data.html", title="Stream Data", data=stream_data, user=user)
|
return serve_template(templatename="stream_data.html", title="Stream Data", data=stream_data, user=user)
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_user_list(self, start=0, length=100, **kwargs):
|
def get_user_list(self, start=0, length=100, **kwargs):
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
|
||||||
users = plex_watch.get_user_list(start, length, kwargs)
|
|
||||||
|
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
|
||||||
return json.dumps(users)
|
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
def get_user_list_new(self, start=0, length=100, **kwargs):
|
|
||||||
|
|
||||||
data_factory = datafactory.DataFactory()
|
data_factory = datafactory.DataFactory()
|
||||||
users = data_factory.get_user_list(start, length, kwargs)
|
users = data_factory.get_user_list(start, length, kwargs)
|
||||||
|
|
||||||
|
@ -430,25 +414,6 @@ class WebInterface(object):
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_history(self, start=0, length=100, custom_where='', **kwargs):
|
def get_history(self, start=0, length=100, custom_where='', **kwargs):
|
||||||
|
|
||||||
if 'user' in kwargs:
|
|
||||||
user = kwargs.get('user', "")
|
|
||||||
custom_where = 'user = "%s"' % user
|
|
||||||
if 'rating_key' in kwargs:
|
|
||||||
rating_key = kwargs.get('rating_key', "")
|
|
||||||
custom_where = 'rating_key = %s' % rating_key
|
|
||||||
if 'grandparent_rating_key' in kwargs:
|
|
||||||
rating_key = kwargs.get('grandparent_rating_key', "")
|
|
||||||
custom_where = 'grandparent_rating_key = %s' % rating_key
|
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
|
||||||
history = plex_watch.get_history(start, length, kwargs, custom_where)
|
|
||||||
|
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
|
||||||
return json.dumps(history)
|
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
def get_history_new(self, start=0, length=100, custom_where='', **kwargs):
|
|
||||||
|
|
||||||
if 'user' in kwargs:
|
if 'user' in kwargs:
|
||||||
user = kwargs.get('user', "")
|
user = kwargs.get('user', "")
|
||||||
custom_where = 'user = "%s"' % user
|
custom_where = 'user = "%s"' % user
|
||||||
|
@ -466,19 +431,11 @@ class WebInterface(object):
|
||||||
return json.dumps(history)
|
return json.dumps(history)
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def clear_all_history_new(self, **kwargs):
|
def clear_all_history(self, **kwargs):
|
||||||
|
from plexpy import monitor
|
||||||
|
|
||||||
monitor.clear_history_tables()
|
threading.Thread(target=monitor.clear_history_tables).start()
|
||||||
raise cherrypy.HTTPRedirect("history_new")
|
raise cherrypy.HTTPRedirect("config")
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
def get_stream_details(self, rating_key=0, **kwargs):
|
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
|
||||||
stream_details = plex_watch.get_stream_details(rating_key)
|
|
||||||
|
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
|
||||||
return json.dumps(stream_details)
|
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
|
@ -655,8 +612,8 @@ class WebInterface(object):
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_user_recently_watched(self, user=None, limit='10', **kwargs):
|
def get_user_recently_watched(self, user=None, limit='10', **kwargs):
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
data_factory = datafactory.DataFactory()
|
||||||
result = plex_watch.get_recently_watched(user, limit)
|
result = data_factory.get_recently_watched(user=user, limit=limit)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return serve_template(templatename="user_recently_watched.html", data=result,
|
return serve_template(templatename="user_recently_watched.html", data=result,
|
||||||
|
@ -669,8 +626,8 @@ class WebInterface(object):
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_user_watch_time_stats(self, user=None, **kwargs):
|
def get_user_watch_time_stats(self, user=None, **kwargs):
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
data_factory = datafactory.DataFactory()
|
||||||
result = plex_watch.get_user_watch_time_stats(user)
|
result = data_factory.get_user_watch_time_stats(user=user)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return serve_template(templatename="user_watch_time_stats.html", data=result, title="Watch Stats")
|
return serve_template(templatename="user_watch_time_stats.html", data=result, title="Watch Stats")
|
||||||
|
@ -681,8 +638,8 @@ class WebInterface(object):
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_user_platform_stats(self, user=None, **kwargs):
|
def get_user_platform_stats(self, user=None, **kwargs):
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
data_factory = datafactory.DataFactory()
|
||||||
result = plex_watch.get_user_platform_stats(user)
|
result = data_factory.get_user_platform_stats(user=user)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return serve_template(templatename="user_platform_stats.html", data=result,
|
return serve_template(templatename="user_platform_stats.html", data=result,
|
||||||
|
@ -751,18 +708,6 @@ class WebInterface(object):
|
||||||
else:
|
else:
|
||||||
logger.warn('Unable to retrieve data.')
|
logger.warn('Unable to retrieve data.')
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
def get_stream(self, row_id='', **kwargs):
|
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
|
||||||
result = plex_watch.get_stream_details('122')
|
|
||||||
|
|
||||||
if result:
|
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
|
||||||
return result
|
|
||||||
else:
|
|
||||||
logger.warn('Unable to retrieve data.')
|
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_user_ips(self, start=0, length=100, custom_where='', **kwargs):
|
def get_user_ips(self, start=0, length=100, custom_where='', **kwargs):
|
||||||
|
|
||||||
|
@ -770,77 +715,20 @@ class WebInterface(object):
|
||||||
user = kwargs.get('user', "")
|
user = kwargs.get('user', "")
|
||||||
custom_where = 'user = "%s"' % user
|
custom_where = 'user = "%s"' % user
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
data_factory = datafactory.DataFactory()
|
||||||
history = plex_watch.get_user_unique_ips(start, length, kwargs, custom_where)
|
history = data_factory.get_user_unique_ips(start=start,
|
||||||
|
length=length,
|
||||||
|
kwargs=kwargs,
|
||||||
|
custom_where=custom_where)
|
||||||
|
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
return json.dumps(history)
|
return json.dumps(history)
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
def get_watched(self, user=None, limit='10', **kwargs):
|
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
|
||||||
result = plex_watch.get_recently_watched(user, limit)
|
|
||||||
|
|
||||||
if result:
|
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
|
||||||
return json.dumps(result)
|
|
||||||
else:
|
|
||||||
logger.warn('Unable to retrieve data.')
|
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
def get_time_stats(self, user=None, **kwargs):
|
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
|
||||||
result = plex_watch.get_user_watch_time_stats(user)
|
|
||||||
|
|
||||||
if result:
|
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
|
||||||
return json.dumps(result)
|
|
||||||
else:
|
|
||||||
logger.warn('Unable to retrieve data.')
|
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
def get_platform_stats(self, user=None, **kwargs):
|
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
|
||||||
result = plex_watch.get_user_platform_stats(user)
|
|
||||||
|
|
||||||
if result:
|
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
|
||||||
return json.dumps(result)
|
|
||||||
else:
|
|
||||||
logger.warn('Unable to retrieve data.')
|
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
def get_user_gravatar_image(self, user=None, **kwargs):
|
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
|
||||||
result = plex_watch.get_user_gravatar_image(user)
|
|
||||||
|
|
||||||
if result:
|
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
|
||||||
return json.dumps(result)
|
|
||||||
else:
|
|
||||||
logger.warn('Unable to retrieve data.')
|
|
||||||
|
|
||||||
@cherrypy.expose
|
|
||||||
def get_home_stats(self, time_range='30', **kwargs):
|
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
|
||||||
result = plex_watch.get_home_stats(time_range)
|
|
||||||
|
|
||||||
if result:
|
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
|
||||||
return json.dumps(result)
|
|
||||||
else:
|
|
||||||
logger.warn('Unable to retrieve data.')
|
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_plays_by_date(self, time_range='30', **kwargs):
|
def get_plays_by_date(self, time_range='30', **kwargs):
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
data_factory = datafactory.DataFactory()
|
||||||
result = plex_watch.get_total_plays_per_day(time_range)
|
result = data_factory.get_total_plays_per_day(time_range=time_range)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
|
@ -851,8 +739,8 @@ class WebInterface(object):
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_plays_by_dayofweek(self, time_range='30', **kwargs):
|
def get_plays_by_dayofweek(self, time_range='30', **kwargs):
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
data_factory = datafactory.DataFactory()
|
||||||
result = plex_watch.get_total_plays_per_dayofweek(time_range)
|
result = data_factory.get_total_plays_per_dayofweek(time_range=time_range)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
|
@ -863,8 +751,8 @@ class WebInterface(object):
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
def get_plays_by_hourofday(self, time_range='30', **kwargs):
|
def get_plays_by_hourofday(self, time_range='30', **kwargs):
|
||||||
|
|
||||||
plex_watch = plexwatch.PlexWatch()
|
data_factory = datafactory.DataFactory()
|
||||||
result = plex_watch.get_total_plays_per_hourofday(time_range)
|
result = data_factory.get_total_plays_per_hourofday(time_range=time_range)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
|
@ -1040,3 +928,22 @@ class WebInterface(object):
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
logger.warn('Unable to retrieve data.')
|
logger.warn('Unable to retrieve data.')
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
def get_plexwatch_export_data(self, database_path=None, table_name=None, import_ignore_interval=0, **kwargs):
|
||||||
|
from plexpy import plexwatch_import
|
||||||
|
|
||||||
|
db_check_msg = plexwatch_import.validate_database(database=database_path,
|
||||||
|
table_name=table_name)
|
||||||
|
if db_check_msg == 'success':
|
||||||
|
threading.Thread(target=plexwatch_import.import_from_plexwatch,
|
||||||
|
kwargs={'database': database_path,
|
||||||
|
'table_name': table_name,
|
||||||
|
'import_ignore_interval': import_ignore_interval}).start()
|
||||||
|
return 'Import has started. Check the PlexPy logs to monitor any problems.'
|
||||||
|
else:
|
||||||
|
return db_check_msg
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
def plexwatch_import(self, **kwargs):
|
||||||
|
return serve_template(templatename="plexwatch_import.html", title="Import PlexWatch Database")
|
Loading…
Add table
Add a link
Reference in a new issue