diff --git a/data/interfaces/default/js/script.js b/data/interfaces/default/js/script.js
index c031448f..b5d4dbde 100644
--- a/data/interfaces/default/js/script.js
+++ b/data/interfaces/default/js/script.js
@@ -237,4 +237,8 @@ function humanTime(seconds) {
text = '
/
' + Math.floor(moment.duration(((seconds % 86400) % 3600), 'seconds').asMinutes()) + '
mins
';
return text;
}
-}
\ No newline at end of file
+}
+
+String.prototype.toProperCase = function () {
+ return this.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
+};
\ No newline at end of file
diff --git a/data/interfaces/default/js/tables/sync_table.js b/data/interfaces/default/js/tables/sync_table.js
new file mode 100644
index 00000000..b824b4d9
--- /dev/null
+++ b/data/interfaces/default/js/tables/sync_table.js
@@ -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('' + cellData + '');
+ }
+ }
+ },
+ {
+ "targets": [2],
+ "data": "title",
+ "createdCell": function (td, cellData, rowData, row, col) {
+ if (cellData !== '') {
+ if (rowData['metadata_type'] !== 'track') {
+ $(td).html('' + cellData + '');
+ } 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('' + percent_complete + '%');
+ } else {
+ $(td).html('0%');
+ }
+ }
+ }
+ ],
+ "drawCallback": function (settings) {
+ // Jump to top of page
+ $('html,body').scrollTop(0);
+ $('#ajaxMsg').addClass('success').fadeOut();
+ },
+ "preDrawCallback": function(settings) {
+ $('#ajaxMsg').html("Fetching rows...
");
+ $('#ajaxMsg').addClass('success').fadeIn();
+ }
+});
diff --git a/data/interfaces/default/sync.html b/data/interfaces/default/sync.html
new file mode 100644
index 00000000..1b42ca78
--- /dev/null
+++ b/data/interfaces/default/sync.html
@@ -0,0 +1,66 @@
+<%inherit file="base.html"/>
+<%!
+from plexpy import helpers
+%>
+
+<%def name="headIncludes()">
+
+
+
+%def>
+
+<%def name="headerIncludes()">
+%def>
+
+<%def name="body()">
+
+
+
+
+
+
+
+
+ State |
+ Username |
+ Title |
+ Type |
+ Device |
+ Platform |
+ Total Size |
+ Total Items |
+ Converted |
+ Downloaded |
+ Complete |
+
+
+
+
+
+
+
+
+
+
+
+%def>
+
+<%def name="javascriptIncludes()">
+
+
+
+
+
+%def>
diff --git a/plexpy/plextv.py b/plexpy/plextv.py
index 1eb848a6..5adbb31a 100644
--- a/plexpy/plextv.py
+++ b/plexpy/plextv.py
@@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see .
-from plexpy import logger, helpers
+from plexpy import logger, helpers, plexwatch
from xml.dom import minidom
from httplib import HTTPSConnection
@@ -282,4 +282,97 @@ class PlexTV(object):
users_list.append(friend)
- return users_list
\ No newline at end of file
+ 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
\ No newline at end of file
diff --git a/plexpy/plexwatch.py b/plexpy/plexwatch.py
index 639d6ed8..fbe47565 100644
--- a/plexpy/plexwatch.py
+++ b/plexpy/plexwatch.py
@@ -946,33 +946,37 @@ class PlexWatch(object):
return None
def get_user_details(self, user=None, user_id=None):
- if user:
- try:
- myDB = db.DBConnection()
+ 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])
- if result:
- for item in result:
- if not item['friendly_name']:
- friendly_name = item['username']
- else:
- friendly_name = item['friendly_name']
+ 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']:
+ friendly_name = item['username']
+ else:
+ friendly_name = item['friendly_name']
- user_details = {"user_id": item['user_id'],
- "username": item['username'],
- "friendly_name": friendly_name,
- "email": item['email'],
- "thumb": item['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:
+ user_details = {"user_id": item['user_id'],
+ "username": item['username'],
+ "friendly_name": friendly_name,
+ "email": item['email'],
+ "thumb": item['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
diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py
index 64d599fe..90467cb7 100644
--- a/plexpy/pmsconnect.py
+++ b/plexpy/pmsconnect.py
@@ -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')
diff --git a/plexpy/webserve.py b/plexpy/webserve.py
index 56f0e39d..88662c84 100644
--- a/plexpy/webserve.py
+++ b/plexpy/webserve.py
@@ -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.')
\ No newline at end of file