mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-06 05:01:14 -07:00
New class to handle Datatables requests (very rough still)
Start cleaning up webserve
This commit is contained in:
parent
253a1efb3a
commit
7e238d6e62
6 changed files with 594 additions and 224 deletions
|
@ -34,11 +34,12 @@
|
|||
<th align='left' id="ip_address"><i class='fa fa-sort'></i> IP Address</th>
|
||||
<th align='left' id="title"><i class='fa fa-sort'></i> Title</th>
|
||||
<th align='left' id="started"><i class='fa fa-sort'></i> Started</th>
|
||||
<th align='left' id="paused"><i class='fa fa-sort'></i> Paused</th>
|
||||
<th align='left' id="paused_counter"><i class='fa fa-sort'></i> Paused</th>
|
||||
<th align='left' id="stopped"><i class='fa fa-sort'></i> Stopped</th>
|
||||
<th align='left' id="duration"><i class='fa fa-sort'></i> Duration</th>
|
||||
<th align='left' id="percent_complete"> Completed</th>
|
||||
<th align='left' id="rating_key"> RatingKey</th>
|
||||
<th align='left' id="xml"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -134,7 +135,8 @@
|
|||
{
|
||||
"targets": [0],
|
||||
"data":"id",
|
||||
"visible": false
|
||||
"visible": false,
|
||||
"searchable": false
|
||||
},
|
||||
{
|
||||
"targets": [1],
|
||||
|
@ -146,7 +148,8 @@
|
|||
} else {
|
||||
$(td).html(moment(cellData,"X").format("${date_format}"));
|
||||
}
|
||||
}
|
||||
},
|
||||
"searchable": false
|
||||
},
|
||||
{
|
||||
"targets": [2],
|
||||
|
@ -185,14 +188,16 @@
|
|||
"data":"started",
|
||||
"render": function ( data, type, full ) {
|
||||
return moment(data, "X").format("${time_format}");
|
||||
}
|
||||
},
|
||||
"searchable": false
|
||||
},
|
||||
{
|
||||
"targets": [7],
|
||||
"data":"paused",
|
||||
"data":"paused_counter",
|
||||
"render": function ( data, type, full ) {
|
||||
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
|
||||
}
|
||||
},
|
||||
"searchable": false
|
||||
},
|
||||
{
|
||||
"targets": [8],
|
||||
|
@ -203,7 +208,8 @@
|
|||
} else {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
},
|
||||
"searchable": false
|
||||
},
|
||||
{
|
||||
"targets": [9],
|
||||
|
@ -214,7 +220,8 @@
|
|||
} else {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
},
|
||||
"searchable": false
|
||||
},
|
||||
{
|
||||
"targets": [10],
|
||||
|
@ -226,12 +233,20 @@
|
|||
} else {
|
||||
return '<span class="badge">100%</span>';
|
||||
}
|
||||
}
|
||||
},
|
||||
"searchable": false
|
||||
},
|
||||
{
|
||||
"targets": [11],
|
||||
"data":"rating_key",
|
||||
"visible": false
|
||||
"visible": false,
|
||||
"searchable": false
|
||||
},
|
||||
{
|
||||
"targets": [12],
|
||||
"data":"xml",
|
||||
"searchable":false,
|
||||
"visible":false
|
||||
}
|
||||
],
|
||||
"drawCallback": function (settings) {
|
||||
|
|
152
data/interfaces/default/user.html
Normal file
152
data/interfaces/default/user.html
Normal file
|
@ -0,0 +1,152 @@
|
|||
<%inherit file="base.html"/>
|
||||
<%!
|
||||
from plexpy import helpers
|
||||
%>
|
||||
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="interfaces/default/css/plexwatch-tables.css">
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<div class="user-info-wrapper">
|
||||
<div class="user-info-poster-face">
|
||||
<img src="interfaces/default/images/gravatar-default-80x80.png">
|
||||
</div>
|
||||
<div class="user-info-username">
|
||||
Username
|
||||
</div>
|
||||
<div class="user-info-nav">
|
||||
<ul class="user-info-nav">
|
||||
<li class="active"><a href="#profile" data-toggle="tab">Profile</a></li>
|
||||
<li><a id="ip-tab-btn" href="#userAddresses" data-toggle="tab">IP Addresses</a></li>
|
||||
<li><a href="#userHistory" data-toggle="tab">History</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="profile">
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<div class="wellbg">
|
||||
<div class="wellheader">
|
||||
<div class="dashboard-wellheader">
|
||||
<h3>Global Stats</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div id="user-time-stats" class="user-overview-stats-wrapper">
|
||||
<div id="user-stats-spinner" class="spinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<div class="wellbg">
|
||||
<div class="wellheader">
|
||||
<div class="dashboard-wellheader">
|
||||
<h3>Platform Stats</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div id="user-platform-stats" class="user-platforms">
|
||||
<div id="user-platform-spinner" class="spinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<div class="wellbg">
|
||||
<div class="wellheader">
|
||||
<div class="dashboard-wellheader">
|
||||
<h3>Recently watched</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div id="user-recently-watched" class="dashboard-recent-media-row">
|
||||
<div id="user-watched-spinner" class="spinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="userAddresses">
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<div class="wellbg">
|
||||
<div class="wellheader">
|
||||
<div class="dashboard-wellheader">
|
||||
<h3>Public IP Addresses for <strong>
|
||||
Username
|
||||
</strong></h3>
|
||||
</div>
|
||||
</div>
|
||||
<table id="tableUserIpAddresses" class="display" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align="left"><i class="fa fa-sort"></i> Last seen</th>
|
||||
<th align="left"><i class="fa fa-sort"></i> IP Address</th>
|
||||
<th align="left"><i class="fa fa-sort"></i> Play Count</th>
|
||||
<th align="left"><i class="fa fa-sort"></i> Platform (Last Seen)</th>
|
||||
<th align="left"><i class="fa fa-sort"></i> Location</th>
|
||||
<th align="left"><i class="fa fa-sort"></i> Location</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="userHistory">
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<div class="wellbg">
|
||||
<div class="wellheader">
|
||||
<div class="dashboard-wellheader">
|
||||
<h3>Watching History for <strong>
|
||||
Username
|
||||
</strong></h3>
|
||||
</div>
|
||||
</div>
|
||||
Table
|
||||
<div id="info-modal" class="modal hide fade" tabindex="-1"
|
||||
role="dialog" aria-labelledby="info-modal" aria-hidden="true">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal"
|
||||
aria-hidden="true"><i class="fa fa-remove"></i></button>
|
||||
<h3 id="myModalLabel">Stream Info: <span id="modal-stream-info"></span></h3>
|
||||
</div>
|
||||
<div class="modal-body" id="modal-text"></div>
|
||||
<div class="modal-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer></footer>
|
||||
</%def>
|
||||
|
||||
<%def name="javascriptIncludes()">
|
||||
<script src="interfaces/default/js/jquery.dataTables.min.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>
|
||||
|
||||
</script>
|
||||
</%def>
|
|
@ -57,7 +57,7 @@
|
|||
"lengthMenu":"Show _MENU_ entries per page",
|
||||
"info":"Showing _START_ to _END_ of _TOTAL_ active users",
|
||||
"infoEmpty":"Showing 0 to 0 of 0 entries",
|
||||
"infoFiltered":"(filtered from _MAX_ total entries)",
|
||||
"infoFiltered":"",
|
||||
"emptyTable": "No data in table",
|
||||
},
|
||||
"destroy": true,
|
||||
|
@ -80,11 +80,11 @@
|
|||
"targets": [0],
|
||||
"data": null,
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (rowData['user_thumb'] === '') {
|
||||
//if (rowData['user_thumb'] === '') {
|
||||
$(td).html('<img src="interfaces/default/images/gravatar-default-80x80.png" alt="User Logo"/>');
|
||||
} else {
|
||||
$(td).html('<img src="' + rowData['user_thumb'] + '" alt="User Logo"/>');
|
||||
}
|
||||
//} else {
|
||||
// $(td).html('<img src="' + rowData['user_thumb'] + '" alt="User Logo"/>');
|
||||
//}
|
||||
},
|
||||
"orderable": false,
|
||||
"className": "users-poster-face",
|
||||
|
@ -103,7 +103,8 @@
|
|||
},
|
||||
{
|
||||
"targets": [3],
|
||||
"data": "ip_address"
|
||||
"data": "ip_address",
|
||||
"searchable": false
|
||||
},
|
||||
{
|
||||
"targets": [4],
|
||||
|
|
181
plexpy/datatables.py
Normal file
181
plexpy/datatables.py
Normal file
|
@ -0,0 +1,181 @@
|
|||
# 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/>.
|
||||
|
||||
# TODO: Implement with sqlite3 directly instead of using db class
|
||||
|
||||
from plexpy import logger, helpers, db
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class DataTables(object):
|
||||
"""
|
||||
Server side processing for Datatables
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.ssp_db = db.DBConnection()
|
||||
|
||||
def ssp_query(self, table_name,
|
||||
columns=[],
|
||||
start=0,
|
||||
length=0,
|
||||
order_column=0,
|
||||
order_dir='asc',
|
||||
search_value='',
|
||||
search_regex='',
|
||||
custom_where='',
|
||||
group_by='',
|
||||
kwargs=None):
|
||||
|
||||
parameters = self.process_kwargs(kwargs)
|
||||
|
||||
if group_by != '':
|
||||
grouping = True
|
||||
else:
|
||||
grouping = False
|
||||
|
||||
column_data = self.extract_columns(columns)
|
||||
where = self.construct_where(column_data, search_value, grouping, parameters)
|
||||
order = self.construct_order(column_data, order_column, order_dir, parameters)
|
||||
|
||||
# TODO: custom_where is ugly and causes issues with reported total results
|
||||
if custom_where != '':
|
||||
where += 'AND (' + custom_where + ')'
|
||||
|
||||
if grouping:
|
||||
query = 'SELECT * FROM (SELECT %s FROM %s GROUP BY %s) %s %s' \
|
||||
% (column_data['column_string'], table_name, group_by,
|
||||
where, order)
|
||||
else:
|
||||
query = 'SELECT %s FROM %s %s %s' \
|
||||
% (column_data['column_string'], table_name, where,
|
||||
order)
|
||||
|
||||
filtered = self.ssp_db.select(query)
|
||||
if search_value == '':
|
||||
totalcount = len(filtered)
|
||||
else:
|
||||
totalcount = self.ssp_db.select('SELECT COUNT(*) from %s' % table_name)[0][0]
|
||||
|
||||
# logger.debug(u"Query string: %s" % query)
|
||||
|
||||
result = filtered[start:(start + length)]
|
||||
output = {'result': result,
|
||||
'filteredCount': len(filtered),
|
||||
'totalCount': totalcount}
|
||||
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def construct_order(column_data, order_column, order_dir, parameters=None):
|
||||
order = ''
|
||||
if parameters:
|
||||
for parameter in parameters:
|
||||
if parameter['data'] != '':
|
||||
if int(order_column) == parameters.index(parameter):
|
||||
if parameter['data'] in column_data['column_named'] and parameter['orderable'] == 'true':
|
||||
order = 'ORDER BY %s COLLATE NOCASE %s' % (parameter['data'], order_dir)
|
||||
logger.debug(u"order string %s " % order)
|
||||
else:
|
||||
order = 'ORDER BY %s COLLATE NOCASE %s' % (column_data['column_named'][order_column], order_dir)
|
||||
logger.debug(u"order string (NO param received) %s " % order)
|
||||
return order
|
||||
|
||||
@staticmethod
|
||||
def construct_where(column_data, search_value='', grouping=False, parameters=None):
|
||||
if search_value != '':
|
||||
where = 'WHERE '
|
||||
if parameters:
|
||||
for column in column_data['column_named']:
|
||||
search_skip = False
|
||||
for parameter in parameters:
|
||||
if column in parameter['data']:
|
||||
if parameter['searchable'] == 'true':
|
||||
logger.debug(u"Column %s is searchable." % column)
|
||||
where += column + ' LIKE "%' + search_value + '%" OR '
|
||||
search_skip = True
|
||||
else:
|
||||
logger.debug(u"Column %s is NOT searchable." % column)
|
||||
search_skip = True
|
||||
|
||||
if not search_skip:
|
||||
where += column + ' LIKE "%' + search_value + '%" OR '
|
||||
else:
|
||||
for column in column_data['column_named']:
|
||||
where += column + ' LIKE "%' + search_value + '%" OR '
|
||||
|
||||
# TODO: This will break the query if all parameters are excluded
|
||||
where = where[:-4]
|
||||
|
||||
return where
|
||||
else:
|
||||
return ''
|
||||
|
||||
@staticmethod
|
||||
def extract_columns(columns=[]):
|
||||
columns_string = ''
|
||||
columns_literal = []
|
||||
columns_named = []
|
||||
|
||||
for column in columns:
|
||||
columns_string += column
|
||||
columns_string += ', '
|
||||
# TODO: make this case insensitive
|
||||
if ' as ' in column:
|
||||
columns_literal.append(column.rpartition(' as ')[0])
|
||||
columns_named.append(column.rpartition(' as ')[-1])
|
||||
else:
|
||||
columns_literal.append(column)
|
||||
columns_named.append(column)
|
||||
|
||||
columns_string = columns_string[:-2]
|
||||
|
||||
column_data = {'column_string': columns_string,
|
||||
'column_literal': columns_literal,
|
||||
'column_named': columns_named
|
||||
}
|
||||
|
||||
return column_data
|
||||
|
||||
# TODO: Fix this method. Should not break if kwarg list is not sorted.
|
||||
@staticmethod
|
||||
def process_kwargs(kwargs):
|
||||
|
||||
column_parameters = []
|
||||
|
||||
for kwarg in sorted(kwargs):
|
||||
if re.search(r"\[(\w+)\]", kwarg) and kwarg[:7] == 'columns':
|
||||
parameters = re.findall(r"\[(\w+)\]", kwarg)
|
||||
array_index = ''
|
||||
for parameter in parameters:
|
||||
pass_complete = False
|
||||
if parameter.isdigit():
|
||||
array_index = parameter
|
||||
if parameter == 'data':
|
||||
data = kwargs.get('columns[' + array_index + '][data]', "")
|
||||
if parameter == 'orderable':
|
||||
orderable = kwargs.get('columns[' + array_index + '][orderable]', "")
|
||||
if parameter == 'searchable':
|
||||
searchable = kwargs.get('columns[' + array_index + '][searchable]', "")
|
||||
pass_complete = True
|
||||
if pass_complete:
|
||||
row = {'index': int(array_index),
|
||||
'data': data,
|
||||
'searchable': searchable,
|
||||
'orderable': orderable}
|
||||
column_parameters.append(row)
|
||||
|
||||
return sorted(column_parameters, key=lambda i: i['index'])
|
218
plexpy/plexwatch.py
Normal file
218
plexpy/plexwatch.py
Normal file
|
@ -0,0 +1,218 @@
|
|||
# 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/>.
|
||||
|
||||
from plexpy import logger, helpers, request, datatables, config
|
||||
|
||||
from xml.dom import minidom
|
||||
import plexpy
|
||||
import json
|
||||
|
||||
|
||||
class PlexWatch(object):
|
||||
"""
|
||||
Retrieve and process data from the plexwatch database
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_history_table_name():
|
||||
|
||||
if plexpy.CONFIG.GROUPING_GLOBAL_HISTORY:
|
||||
return "grouped"
|
||||
else:
|
||||
return "processed"
|
||||
|
||||
@staticmethod
|
||||
def get_user_table_name():
|
||||
|
||||
if plexpy.CONFIG.GROUPING_USER_HISTORY:
|
||||
return "grouped"
|
||||
else:
|
||||
return "processed"
|
||||
|
||||
def get_user_list(self, start='', length='', kwargs=None):
|
||||
data_tables = datatables.DataTables()
|
||||
|
||||
start = int(start)
|
||||
length = int(length)
|
||||
filtered = []
|
||||
totalcount = 0
|
||||
search_value = ""
|
||||
search_regex = ""
|
||||
order_column = 1
|
||||
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 = ['user',
|
||||
'time',
|
||||
'ip_address',
|
||||
'COUNT(title) as plays']
|
||||
|
||||
query = data_tables.ssp_query(table_name=self.get_user_table_name(),
|
||||
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='',
|
||||
group_by='user',
|
||||
kwargs=kwargs)
|
||||
|
||||
users = query['result']
|
||||
|
||||
rows = []
|
||||
for item in users:
|
||||
row = {"plays": item['plays'],
|
||||
"time": item['time'],
|
||||
"user": item["user"],
|
||||
"ip_address": item["ip_address"]
|
||||
}
|
||||
|
||||
rows.append(row)
|
||||
|
||||
dict = {'recordsFiltered': query['filteredCount'],
|
||||
'recordsTotal': query['totalCount'],
|
||||
'data': rows,
|
||||
}
|
||||
|
||||
return dict
|
||||
|
||||
def get_history(self, start='', length='', kwargs=None):
|
||||
data_tables = datatables.DataTables()
|
||||
|
||||
start = int(start)
|
||||
length = int(length)
|
||||
filtered = []
|
||||
totalcount = 0
|
||||
search_value = ""
|
||||
search_regex = ""
|
||||
order_column = 1
|
||||
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 = ['id',
|
||||
'time as date',
|
||||
'user',
|
||||
'platform',
|
||||
'ip_address',
|
||||
'title',
|
||||
'time as started',
|
||||
'paused_counter',
|
||||
'stopped',
|
||||
'ratingKey as rating_key',
|
||||
'xml',
|
||||
'round((julianday(datetime(stopped, "unixepoch", "localtime")) - \
|
||||
julianday(datetime(time, "unixepoch", "localtime"))) * 86400) - \
|
||||
(case when paused_counter is null then 0 else paused_counter end) as duration'
|
||||
]
|
||||
|
||||
query = data_tables.ssp_query(table_name=self.get_history_table_name(),
|
||||
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='',
|
||||
group_by='',
|
||||
kwargs=kwargs)
|
||||
|
||||
history = query['result']
|
||||
|
||||
rows = []
|
||||
# NOTE: We are adding in a blank xml field in order enable the Datatables "searchable" parameter
|
||||
for item in history:
|
||||
row = {"id": item['id'],
|
||||
"date": item['date'],
|
||||
"user": item["user"],
|
||||
"platform": item["platform"],
|
||||
"ip_address": item["ip_address"],
|
||||
"title": item["title"],
|
||||
"started": item["started"],
|
||||
"paused_counter": item["paused_counter"],
|
||||
"stopped": item["stopped"],
|
||||
"rating_key": item["rating_key"],
|
||||
"duration": item["duration"],
|
||||
"percent_complete": 0,
|
||||
"xml": ""}
|
||||
|
||||
if item['paused_counter'] > 0:
|
||||
row['paused_counter'] = item['paused_counter']
|
||||
else:
|
||||
row['paused_counter'] = 0
|
||||
|
||||
if item['started']:
|
||||
if item['stopped'] > 0:
|
||||
stopped = item['stopped']
|
||||
else:
|
||||
stopped = 0
|
||||
if item['paused_counter'] > 0:
|
||||
paused_counter = item['paused_counter']
|
||||
else:
|
||||
paused_counter = 0
|
||||
|
||||
try:
|
||||
xml_parse = minidom.parseString(helpers.latinToAscii(item['xml']))
|
||||
except IOError, e:
|
||||
logger.warn("Error parsing XML in PlexWatch db: %s" % e)
|
||||
|
||||
xml_head = xml_parse.getElementsByTagName('opt')
|
||||
if not xml_head:
|
||||
logger.warn("Error parsing XML in PlexWatch db: %s" % e)
|
||||
|
||||
for s in xml_head:
|
||||
if s.getAttribute('duration') and s.getAttribute('viewOffset'):
|
||||
view_offset = helpers.cast_to_float(s.getAttribute('viewOffset'))
|
||||
duration = helpers.cast_to_float(s.getAttribute('duration'))
|
||||
if duration > 0:
|
||||
row['percent_complete'] = (view_offset / duration) * 100
|
||||
else:
|
||||
row['percent_complete'] = 0
|
||||
|
||||
rows.append(row)
|
||||
|
||||
dict = {'recordsFiltered': query['filteredCount'],
|
||||
'recordsTotal': query['totalCount'],
|
||||
'data': rows,
|
||||
}
|
||||
|
||||
return dict
|
|
@ -13,7 +13,7 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from plexpy import logger, db, helpers, notifiers, plextv, pmsconnect
|
||||
from plexpy import logger, db, helpers, notifiers, plextv, pmsconnect, plexwatch
|
||||
from plexpy.helpers import checked, radio, today, cleanName
|
||||
from xml.dom import minidom
|
||||
|
||||
|
@ -84,100 +84,18 @@ class WebInterface(object):
|
|||
def users(self):
|
||||
return serve_template(templatename="users.html", title="Users")
|
||||
|
||||
@cherrypy.expose
|
||||
def user(self):
|
||||
return serve_template(templatename="user.html", title="User")
|
||||
|
||||
@cherrypy.expose
|
||||
def get_user_list(self, start=0, length=100, **kwargs):
|
||||
start = int(start)
|
||||
length = int(length)
|
||||
filtered = []
|
||||
totalcount = 0
|
||||
search_value = ""
|
||||
search_regex = ""
|
||||
order_column = 1
|
||||
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]', "")
|
||||
|
||||
sortcolumn = 'user'
|
||||
if order_column == '2':
|
||||
sortcolumn = 'time'
|
||||
elif order_column == '3':
|
||||
sortcolumn = 'ip_address'
|
||||
elif order_column == '4':
|
||||
sortcolumn = 'plays'
|
||||
|
||||
myDB = db.DBConnection()
|
||||
db_table = db.DBConnection().get_history_table_name()
|
||||
|
||||
if search_value == "":
|
||||
query = 'SELECT COUNT(title) as plays, user, time, \
|
||||
SUM(time) as timeTotal, SUM(stopped) as stoppedTotal, \
|
||||
SUM(paused_counter) as paused_counterTotal, platform, \
|
||||
ip_address, xml \
|
||||
from %s GROUP by user ORDER by %s COLLATE NOCASE %s' % (db_table, sortcolumn, order_dir)
|
||||
filtered = myDB.select(query)
|
||||
totalcount = len(filtered)
|
||||
else:
|
||||
query = 'SELECT COUNT(title) as plays, user, time, \
|
||||
SUM(time) as timeTotal, SUM(stopped) as stoppedTotal, \
|
||||
SUM(paused_counter) as paused_counterTotal, platform, \
|
||||
ip_address, xml \
|
||||
from ' + db_table + ' WHERE user LIKE "%' + search_value + '%" \
|
||||
GROUP by user' + ' ORDER by %s COLLATE NOCASE %s' % (sortcolumn, order_dir)
|
||||
filtered = myDB.select(query)
|
||||
totalcount = myDB.select('SELECT COUNT(*) from %s' % db_table)[0][0]
|
||||
|
||||
users = filtered[start:(start + length)]
|
||||
rows = []
|
||||
for item in users:
|
||||
row = {"plays": item['plays'],
|
||||
"time": item['time'],
|
||||
"user": item["user"],
|
||||
"timeTotal": item["timeTotal"],
|
||||
"ip_address": item["ip_address"],
|
||||
"stoppedTotal": item["stoppedTotal"],
|
||||
"paused_counterTotal": item["paused_counterTotal"],
|
||||
"platform": item["platform"]
|
||||
}
|
||||
|
||||
try:
|
||||
xml_parse = minidom.parseString(helpers.latinToAscii(item['xml']))
|
||||
except IOError, e:
|
||||
logger.warn("Error parsing XML in PlexWatch db: %s" % e)
|
||||
|
||||
xml_head = xml_parse.getElementsByTagName('User')
|
||||
if not xml_head:
|
||||
logger.warn("Error parsing XML in PlexWatch db: %s" % e)
|
||||
|
||||
for s in xml_head:
|
||||
if s.getAttribute('thumb'):
|
||||
row['user_thumb'] = s.getAttribute('thumb')
|
||||
else:
|
||||
row['user_thumb'] = ""
|
||||
if s.getAttribute('id'):
|
||||
row['user_id'] = s.getAttribute('id')
|
||||
else:
|
||||
row['user_id'] = ""
|
||||
|
||||
rows.append(row)
|
||||
|
||||
dict = {'recordsFiltered': len(filtered),
|
||||
'recordsTotal': totalcount,
|
||||
'data': rows,
|
||||
}
|
||||
s = json.dumps(dict)
|
||||
plex_watch = plexwatch.PlexWatch()
|
||||
users = plex_watch.get_user_list(start, length, kwargs)
|
||||
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return s
|
||||
return json.dumps(users)
|
||||
|
||||
@cherrypy.expose
|
||||
def checkGithub(self):
|
||||
|
@ -415,127 +333,12 @@ class WebInterface(object):
|
|||
|
||||
@cherrypy.expose
|
||||
def getHistory_json(self, start=0, length=100, **kwargs):
|
||||
start = int(start)
|
||||
length = int(length)
|
||||
filtered = []
|
||||
totalcount = 0
|
||||
search_value = ""
|
||||
search_regex = ""
|
||||
order_column = 1
|
||||
order_dir = "desc"
|
||||
|
||||
if 'order[0][dir]' in kwargs:
|
||||
order_dir = kwargs.get('order[0][dir]', "desc")
|
||||
plex_watch = plexwatch.PlexWatch()
|
||||
history = plex_watch.get_history(start, length, kwargs)
|
||||
|
||||
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]', "")
|
||||
|
||||
myDB = db.DBConnection()
|
||||
db_table = db.DBConnection().get_history_table_name()
|
||||
|
||||
sortcolumn = 'time'
|
||||
sortbyhavepercent = False
|
||||
if order_column == '2':
|
||||
sortcolumn = 'user'
|
||||
if order_column == '3':
|
||||
sortcolumn = 'platform'
|
||||
elif order_column == '4':
|
||||
sortcolumn = 'ip_address'
|
||||
elif order_column == '5':
|
||||
sortcolumn = 'title'
|
||||
elif order_column == '6':
|
||||
sortcolumn = 'time'
|
||||
elif order_column == '7':
|
||||
sortcolumn = 'paused_counter'
|
||||
elif order_column == '8':
|
||||
sortcolumn = 'stopped'
|
||||
elif order_column == '9':
|
||||
sortcolumn = 'duration'
|
||||
|
||||
if search_value == "":
|
||||
query = 'SELECT id, time, user, platform, ip_address, title, time, paused_counter, stopped, ratingKey, xml, \
|
||||
round((julianday(datetime(stopped, "unixepoch", "localtime")) - \
|
||||
julianday(datetime(time, "unixepoch", "localtime"))) * 86400) - \
|
||||
(case when paused_counter is null then 0 else paused_counter end) as duration \
|
||||
from %s order by %s COLLATE NOCASE %s' % (db_table, sortcolumn, order_dir)
|
||||
filtered = myDB.select(query)
|
||||
totalcount = len(filtered)
|
||||
else:
|
||||
query = 'SELECT id, time, user, platform, ip_address, title, time, paused_counter, stopped, ratingKey, xml, \
|
||||
round((julianday(datetime(stopped, "unixepoch", "localtime")) - \
|
||||
julianday(datetime(time, "unixepoch", "localtime"))) * 86400) - \
|
||||
(case when paused_counter is null then 0 else paused_counter end) as duration \
|
||||
from ' + db_table + ' WHERE user LIKE "%' + search_value + '%" OR title LIKE "%' + search_value \
|
||||
+ '%"' + 'ORDER BY %s COLLATE NOCASE %s' % (sortcolumn, order_dir)
|
||||
filtered = myDB.select(query)
|
||||
totalcount = myDB.select('SELECT COUNT(*) from processed')[0][0]
|
||||
|
||||
history = filtered[start:(start + length)]
|
||||
rows = []
|
||||
for item in history:
|
||||
row = {"id": item['id'],
|
||||
"date": item['time'],
|
||||
"user": item["user"],
|
||||
"platform": item["platform"],
|
||||
"ip_address": item["ip_address"],
|
||||
"title": item["title"],
|
||||
"started": item["time"],
|
||||
"paused": item["paused_counter"],
|
||||
"stopped": item["stopped"],
|
||||
"rating_key": item["ratingKey"],
|
||||
"duration": item["duration"],
|
||||
"percent_complete": 0,
|
||||
}
|
||||
|
||||
if item['paused_counter'] > 0:
|
||||
row['paused'] = item['paused_counter']
|
||||
else:
|
||||
row['paused'] = 0
|
||||
|
||||
if item['time']:
|
||||
if item['stopped'] > 0:
|
||||
stopped = item['stopped']
|
||||
else:
|
||||
stopped = 0
|
||||
if item['paused_counter'] > 0:
|
||||
paused_counter = item['paused_counter']
|
||||
else:
|
||||
paused_counter = 0
|
||||
|
||||
try:
|
||||
xml_parse = minidom.parseString(helpers.latinToAscii(item['xml']))
|
||||
except IOError, e:
|
||||
logger.warn("Error parsing XML in PlexWatch db: %s" % e)
|
||||
|
||||
xml_head = xml_parse.getElementsByTagName('opt')
|
||||
if not xml_head:
|
||||
logger.warn("Error parsing XML in PlexWatch db: %s" % e)
|
||||
|
||||
for s in xml_head:
|
||||
if s.getAttribute('duration') and s.getAttribute('viewOffset'):
|
||||
view_offset = helpers.cast_to_float(s.getAttribute('viewOffset'))
|
||||
duration = helpers.cast_to_float(s.getAttribute('duration'))
|
||||
if duration > 0:
|
||||
row['percent_complete'] = (view_offset / duration) * 100
|
||||
else:
|
||||
row['percent_complete'] = 0
|
||||
|
||||
rows.append(row)
|
||||
|
||||
dict = {'recordsFiltered': len(filtered),
|
||||
'recordsTotal': totalcount,
|
||||
'data': rows,
|
||||
}
|
||||
s = json.dumps(dict)
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return s
|
||||
|
||||
return json.dumps(history)
|
||||
|
||||
@cherrypy.expose
|
||||
def getStreamDetails(self, id=0, **kwargs):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue