mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-11 07:46:07 -07:00
Some work on sync lists.
This commit is contained in:
parent
4830cc7d68
commit
69ffaf5292
7 changed files with 333 additions and 26 deletions
|
@ -238,3 +238,7 @@ function humanTime(seconds) {
|
|||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
String.prototype.toProperCase = function () {
|
||||
return this.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
|
||||
};
|
117
data/interfaces/default/js/tables/sync_table.js
Normal file
117
data/interfaces/default/js/tables/sync_table.js
Normal file
|
@ -0,0 +1,117 @@
|
|||
$('#sync_table').dataTable( {
|
||||
"responsive": {
|
||||
details: false
|
||||
},
|
||||
"processing": false,
|
||||
"serverSide": false,
|
||||
"ajax": {
|
||||
"url": "get_sync"
|
||||
},
|
||||
"sPaginationType": "bootstrap",
|
||||
"order": [ 0, 'desc'],
|
||||
"pageLength": 25,
|
||||
"stateSave": true,
|
||||
"language": {
|
||||
"search":"Search: ",
|
||||
"lengthMenu":"Show _MENU_ lines per page",
|
||||
"emptyTable": "No synced items",
|
||||
"info":"Showing _START_ to _END_ of _TOTAL_ lines",
|
||||
"infoEmpty":"Showing 0 to 0 of 0 lines",
|
||||
"infoFiltered":"(filtered from _MAX_ total lines)"},
|
||||
"columnDefs": [
|
||||
{
|
||||
"targets": [0],
|
||||
"data": "state",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData === 'pending') {
|
||||
$(td).addClass('currentlyWatching');
|
||||
$(td).html('Pending...');
|
||||
} else {
|
||||
$(td).html(cellData.toProperCase());
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"targets": [1],
|
||||
"data": "friendly_name",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
$(td).html('<a href="user?user=' + rowData['username'] + '">' + cellData + '</a>');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"targets": [2],
|
||||
"data": "title",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== '') {
|
||||
if (rowData['metadata_type'] !== 'track') {
|
||||
$(td).html('<a href="info?rating_key=' + rowData['rating_key'] + '">' + cellData + '</a>');
|
||||
} else {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"targets": [3],
|
||||
"data": "metadata_type",
|
||||
"render": function ( data, type, full ) {
|
||||
return data.toProperCase();
|
||||
}
|
||||
},
|
||||
{
|
||||
"targets": [4],
|
||||
"data": "device_name"
|
||||
},
|
||||
{
|
||||
"targets": [5],
|
||||
"data": "platform"
|
||||
},
|
||||
{
|
||||
"targets": [6],
|
||||
"data": "total_size",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData > 0 ) {
|
||||
megabytes = Math.round((cellData/1024)/1024, 0)
|
||||
$(td).html(megabytes + 'MB');
|
||||
} else {
|
||||
$(td).html(megabytes + '0MB');
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"targets": [7],
|
||||
"data": "item_count"
|
||||
},
|
||||
{
|
||||
"targets": [8],
|
||||
"data": "item_complete_count"
|
||||
},
|
||||
{
|
||||
"targets": [9],
|
||||
"data": "item_downloaded_count"
|
||||
},
|
||||
{
|
||||
"targets": [10],
|
||||
"data": null,
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (rowData['item_count'] > 0 ) {
|
||||
percent_complete = Math.round((rowData['item_downloaded_count']/rowData['item_count']*100),0);
|
||||
$(td).html('<span class="badge">' + percent_complete + '%</span>');
|
||||
} else {
|
||||
$(td).html('<span class="badge">0%</span>');
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"drawCallback": function (settings) {
|
||||
// Jump to top of page
|
||||
$('html,body').scrollTop(0);
|
||||
$('#ajaxMsg').addClass('success').fadeOut();
|
||||
},
|
||||
"preDrawCallback": function(settings) {
|
||||
$('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>Fetching rows...</div>");
|
||||
$('#ajaxMsg').addClass('success').fadeIn();
|
||||
}
|
||||
});
|
66
data/interfaces/default/sync.html
Normal file
66
data/interfaces/default/sync.html
Normal file
|
@ -0,0 +1,66 @@
|
|||
<%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">
|
||||
<style>
|
||||
td {word-wrap: break-word}
|
||||
</style>
|
||||
</%def>
|
||||
|
||||
<%def name="headerIncludes()">
|
||||
</%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-book"></i> Synced Items</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="sync_table" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th align='left' id="state">State</th>
|
||||
<th align='left' id="username">Username</th>
|
||||
<th align='left' id="title">Title</th>
|
||||
<th align='left' id="type">Type</th>
|
||||
<th align='left' id="device">Device</th>
|
||||
<th align='left' id="platform">Platform</th>
|
||||
<th align='left' id="size">Total Size</th>
|
||||
<th align='left' id="items">Total Items</th>
|
||||
<th align='left' id="converted">Converted</th>
|
||||
<th align='left' id="downloaded">Downloaded</th>
|
||||
<th align='left' id="percent_complete">Complete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
</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/tables/sync_table.js"></script>
|
||||
|
||||
</%def>
|
|
@ -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, helpers
|
||||
from plexpy import logger, helpers, plexwatch
|
||||
|
||||
from xml.dom import minidom
|
||||
from httplib import HTTPSConnection
|
||||
|
@ -283,3 +283,96 @@ class PlexTV(object):
|
|||
users_list.append(friend)
|
||||
|
||||
return users_list
|
||||
|
||||
def get_synced_items(self, machine_id=None):
|
||||
sync_list = self.get_plextv_sync_lists(machine_id)
|
||||
plex_watch = plexwatch.PlexWatch()
|
||||
|
||||
synced_items = []
|
||||
|
||||
try:
|
||||
xml_parse = minidom.parseString(sync_list)
|
||||
except Exception, e:
|
||||
logger.warn("Error parsing XML for Plex sync lists: %s" % e)
|
||||
except:
|
||||
logger.warn("Error parsing XML for Plex sync lists.")
|
||||
|
||||
xml_head = xml_parse.getElementsByTagName('SyncList')
|
||||
|
||||
if not xml_head:
|
||||
logger.warn("Error parsing XML for Plex sync lists.")
|
||||
else:
|
||||
for a in xml_head:
|
||||
client_id = self.get_xml_attr(a, 'id')
|
||||
|
||||
sync_device = a.getElementsByTagName('Device')
|
||||
for device in sync_device:
|
||||
device_name = self.get_xml_attr(device, 'name')
|
||||
device_product = self.get_xml_attr(device, 'product')
|
||||
device_product_version = self.get_xml_attr(device, 'productVersion')
|
||||
device_platform = self.get_xml_attr(device, 'platform')
|
||||
device_platform_version = self.get_xml_attr(device, 'platformVersion')
|
||||
device_type = self.get_xml_attr(device, 'device')
|
||||
device_model = self.get_xml_attr(device, 'model')
|
||||
device_last_seen = self.get_xml_attr(device, 'lastSeenAt')
|
||||
device_user_id = self.get_xml_attr(device, 'userID')
|
||||
device_username = plex_watch.get_user_details(user_id=device_user_id)['username']
|
||||
device_friendly_name = plex_watch.get_user_details(user_id=device_user_id)['friendly_name']
|
||||
|
||||
for synced in a.getElementsByTagName('SyncItems'):
|
||||
sync_item = synced.getElementsByTagName('SyncItem')
|
||||
for item in sync_item:
|
||||
sync_id = self.get_xml_attr(item, 'id')
|
||||
sync_version = self.get_xml_attr(item, 'version')
|
||||
sync_root_title = self.get_xml_attr(item, 'rootTitle')
|
||||
sync_title = self.get_xml_attr(item, 'title')
|
||||
sync_metadata_type = self.get_xml_attr(item, 'metadataType')
|
||||
sync_content_type = self.get_xml_attr(item, 'contentType')
|
||||
|
||||
for status in item.getElementsByTagName('Status'):
|
||||
status_failure_code = self.get_xml_attr(status, 'failureCode')
|
||||
status_failure = self.get_xml_attr(status, 'failure')
|
||||
status_state = self.get_xml_attr(status, 'state')
|
||||
status_item_count = self.get_xml_attr(status, 'itemsCount')
|
||||
status_item_complete_count = self.get_xml_attr(status, 'itemsCompleteCount')
|
||||
status_item_downloaded_count = self.get_xml_attr(status, 'itemsDownloadedCount')
|
||||
status_item_ready_count = self.get_xml_attr(status, 'itemsReadyCount')
|
||||
status_item_successful_count = self.get_xml_attr(status, 'itemsSuccessfulCount')
|
||||
status_total_size = self.get_xml_attr(status, 'totalSize')
|
||||
|
||||
for settings in item.getElementsByTagName('MediaSettings'):
|
||||
settings_audio_boost = self.get_xml_attr(settings, 'audioBoost')
|
||||
settings_music_bitrate = self.get_xml_attr(settings, 'musicBitrate')
|
||||
settings_photo_quality = self.get_xml_attr(settings, 'photoQuality')
|
||||
settings_photo_resolution = self.get_xml_attr(settings, 'photoResolution')
|
||||
settings_video_quality = self.get_xml_attr(settings, 'videoQuality')
|
||||
settings_video_resolution = self.get_xml_attr(settings, 'videoResolution')
|
||||
|
||||
rating_key = self.get_xml_attr(
|
||||
item.getElementsByTagName('Location')[0], 'uri').rpartition('%2F')[-1]
|
||||
|
||||
sync_details = {"device_name": device_name,
|
||||
"platform": device_platform,
|
||||
"username": device_username,
|
||||
"friendly_name": device_friendly_name,
|
||||
"user_id": device_user_id,
|
||||
"root_title": sync_root_title,
|
||||
"title": sync_title,
|
||||
"metadata_type": sync_metadata_type,
|
||||
"content_type": sync_content_type,
|
||||
"rating_key": rating_key,
|
||||
"state": status_state,
|
||||
"item_count": status_item_count,
|
||||
"item_complete_count": status_item_complete_count,
|
||||
"item_downloaded_count": status_item_downloaded_count,
|
||||
"music_bitrate": settings_music_bitrate,
|
||||
"photo_quality": settings_photo_quality,
|
||||
"video_quality": settings_video_quality,
|
||||
"total_size": status_total_size,
|
||||
"failure": status_failure,
|
||||
"sync_id": sync_id
|
||||
}
|
||||
|
||||
synced_items.append(sync_details)
|
||||
|
||||
return synced_items
|
|
@ -946,12 +946,16 @@ class PlexWatch(object):
|
|||
return None
|
||||
|
||||
def get_user_details(self, user=None, user_id=None):
|
||||
if user:
|
||||
try:
|
||||
myDB = db.DBConnection()
|
||||
if user:
|
||||
query = 'select user_id, username, friendly_name, email, thumb, ' \
|
||||
'is_home_user, is_allow_sync, is_restricted FROM plexpy_users WHERE username = ? LIMIT 1'
|
||||
result = myDB.select(query, args=[user])
|
||||
elif user_id:
|
||||
query = 'select user_id, username, friendly_name, email, thumb, ' \
|
||||
'is_home_user, is_allow_sync, is_restricted FROM plexpy_users WHERE user_id = ? LIMIT 1'
|
||||
result = myDB.select(query, args=[user_id])
|
||||
if result:
|
||||
for item in result:
|
||||
if not item['friendly_name']:
|
||||
|
|
|
@ -739,7 +739,7 @@ class PmsConnect(object):
|
|||
server_info = []
|
||||
for a in xml_head:
|
||||
output = {"name": self.get_xml_attr(a, 'name'),
|
||||
"machineIdentifier": self.get_xml_attr(a, 'machineIdentifier'),
|
||||
"machine_identifier": self.get_xml_attr(a, 'machineIdentifier'),
|
||||
"host": self.get_xml_attr(a, 'host'),
|
||||
"port": self.get_xml_attr(a, 'port'),
|
||||
"version": self.get_xml_attr(a, 'version')
|
||||
|
|
|
@ -97,6 +97,10 @@ class WebInterface(object):
|
|||
def graphs(self):
|
||||
return serve_template(templatename="graphs.html", title="Graphs")
|
||||
|
||||
@cherrypy.expose
|
||||
def sync(self):
|
||||
return serve_template(templatename="sync.html", title="Synced Items")
|
||||
|
||||
@cherrypy.expose
|
||||
def user(self, user=None):
|
||||
try:
|
||||
|
@ -891,3 +895,22 @@ class WebInterface(object):
|
|||
|
||||
logger.info("Users list refreshed.")
|
||||
raise cherrypy.HTTPRedirect("users")
|
||||
|
||||
@cherrypy.expose
|
||||
def get_sync(self, machine_id='', **kwargs):
|
||||
|
||||
pms_connect = pmsconnect.PmsConnect()
|
||||
server_info = pms_connect.get_servers_info()
|
||||
|
||||
plex_tv = plextv.PlexTV()
|
||||
if not machine_id:
|
||||
result = plex_tv.get_synced_items(machine_id=server_info[0]['machine_identifier'])
|
||||
else:
|
||||
result = plex_tv.get_synced_items(machine_id=machine_id)
|
||||
|
||||
if result:
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
output = {"data": result}
|
||||
return json.dumps(output)
|
||||
else:
|
||||
logger.warn('Unable to retrieve data.')
|
Loading…
Add table
Add a link
Reference in a new issue