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 name="headerIncludes()"> + + +<%def name="body()"> +
+
+
+
+
+

Synced Items

+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + +
StateUsernameTitleTypeDevicePlatformTotal SizeTotal ItemsConvertedDownloadedComplete
+
+
+
+
+ + + + +<%def name="javascriptIncludes()"> + + + + + + 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