From 3436175223549b789994f8f024e7f406a1b1f7fa Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 29 Aug 2015 12:15:11 -0700 Subject: [PATCH 1/5] Initial implementation of server statistics --- data/interfaces/default/css/plexpy.css | 31 ++++ data/interfaces/default/index.html | 66 ++++++-- data/interfaces/default/server_stats.html | 75 +++++++++ plexpy/pmsconnect.py | 177 ++++++++++++++++++++++ plexpy/webserve.py | 10 +- 5 files changed, 348 insertions(+), 11 deletions(-) create mode 100644 data/interfaces/default/server_stats.html diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index d5a9cc85..678d6ba1 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1572,6 +1572,30 @@ a .season-episodes-card-overlay:hover { overflow: hidden; text-overflow: ellipsis; } +.home-platforms-instance-name2 { + position: absolute; + top: 34px; + left: 210px; +} +.home-platforms-instance-name2 h5 { + font-size: 14px; + line-height: 16px; + margin: 15px 0 2px 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.home-platforms-instance-name2 h3 { + font-size: 30px; + font-weight: bold; + color: #F9AA03; + line-height: 22px; + position: relative; + top: 5px; + margin: 0 5px 0 0; + padding-top: 6px; + float: left; +} .home-platforms-instance-playcount { float: left; position: relative; @@ -1633,6 +1657,13 @@ a .season-episodes-card-overlay:hover { -moz-box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1); } +.home-platforms-instance-poster .home-platforms-server-thumb { + background-position: center; + background-size: cover; + height: 80px; + width: 80px; + margin-top: 20px; +} .home-platforms-instance-box { background-position: center; background-size: cover; diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index 9c1c361f..5efc6f8b 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -19,7 +19,7 @@
-

Statistics Last ${config['home_stats_length']} days

+

Watch Statistics Last ${config['home_stats_length']} days

Loading stats...
@@ -27,6 +27,17 @@
+
+
+
+

Server Statistics

+
+
+
Loading stats...
+
+
+
+
@@ -45,17 +56,18 @@ diff --git a/data/interfaces/default/server_stats.html b/data/interfaces/default/server_stats.html new file mode 100644 index 00000000..780f84a2 --- /dev/null +++ b/data/interfaces/default/server_stats.html @@ -0,0 +1,75 @@ +<%doc> +USAGE DOCUMENTATION :: PLEASE LEAVE THIS AT THE TOP OF THIS FILE + +For Mako templating syntax documentation please visit: http://docs.makotemplates.org/en/latest/ + +Filename: server_stats.html +Version: 0.1 +Variable names: data [array] + +data[array_index] :: Usable parameters + +data['type'] Returns the type of the library. Either 'movie', 'show', 'photo', or 'artist'. +data['rows'] Returns an array containing stat data + +data[array_index]['rows'] :: Usable parameters + +title Returns the title of the library. +thumb Returns the thumb of the library. +count Returns the number of items in the library. +count_type Returns the sorting type for the library + +== Only if 'type' is 'show' +episode_count Return the number of episodes in the library. +episode_count_type Return the sorting type for the episodes. + +== Only if 'type' is 'artist' +album_count Return the number of episodes in the library. +album_count_type Return the sorting type for the episodes. + +DOCUMENTATION :: END + + +% if data: +
    + % for library in data: +
    +
  • +
    +
    +

    ${library['rows']['title']}

    +
    ${library['rows']['count_type']}
    +
    +
    +

    ${library['rows']['count']}

    +
    + % if library['type'] == 'show': +
    +
    ${library['rows']['episode_count_type']}
    +

    ${library['rows']['episode_count']}

    +
    + % endif + % if library['type'] == 'artist': +
    +
    ${library['rows']['album_count_type']}
    +

    ${library['rows']['album_count']}

    +
    + % endif +
    + % if library['rows']['thumb']: +
    +
    +
    + % else: +
    +
    +
    + % endif +
  • +
    + % endfor +
+% else: +
Unable to retrieve data from database. Please check your settings. +

+% endif \ No newline at end of file diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index f23eb32d..4b59c79a 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -171,6 +171,38 @@ class PmsConnect(object): return request + """ + Return list of libraries on server. + + Optional parameters: output_format { dict, json } + + Output: array + """ + def get_libraries_list(self, output_format=''): + uri = '/library/sections' + request = self.request_handler.make_request(uri=uri, + proto=self.protocol, + request_type='GET', + output_format=output_format) + + return request + + """ + Return list of items in library on server. + + Optional parameters: output_format { dict, json } + + Output: array + """ + def get_library_list(self, section_key='', list_type='all', count='0', sort_type='', output_format=''): + uri = '/library/sections/' + section_key + '/' + list_type +'?X-Plex-Container-Start=0&X-Plex-Container-Size=' + count + sort_type + request = self.request_handler.make_request(uri=uri, + proto=self.protocol, + request_type='GET', + output_format=output_format) + + return request + """ Return sync item details. @@ -984,6 +1016,151 @@ class PmsConnect(object): logger.debug(u"Server preferences queried but no parameter received.") return None + """ + Return processed and validated server libraries list. + + Output: array + """ + def get_server_children(self): + libraries_data = self.get_libraries_list(output_format='xml') + + try: + xml_head = libraries_data.getElementsByTagName('MediaContainer') + except: + logger.warn("Unable to parse XML for get_libraries_list.") + return [] + + libraries_list = [] + + for a in xml_head: + if a.getAttribute('size'): + if a.getAttribute('size') == '0': + logger.debug(u"No libraries data.") + libraries_list = {'libraries_count': '0', + 'libraries_list': [] + } + return libraries_list + + if a.getElementsByTagName('Directory'): + result_data = a.getElementsByTagName('Directory') + for result in result_data: + libraries_output = {'key': helpers.get_xml_attr(result, 'key'), + 'type': helpers.get_xml_attr(result, 'type'), + 'title': helpers.get_xml_attr(result, 'title'), + 'thumb': helpers.get_xml_attr(result, 'thumb') + } + libraries_list.append(libraries_output) + + output = {'libraries_count': helpers.get_xml_attr(xml_head[0], 'size'), + 'title': helpers.get_xml_attr(xml_head[0], 'title1'), + 'libraries_list': libraries_list + } + + return output + + """ + Return processed and validated server library items list. + + Parameters required: library_type { movie, show, episode, artist } + section_key { unique library key } + + Output: array + """ + def get_library_children(self, library_type='', section_key='', list_type='all', sort_type = ''): + + # Currently only grab the library with 1 items so 'size' is not 0 + count = '1' + + if library_type == 'movie': + sort_type = '&type=1' + elif library_type == 'show': + sort_type = '&type=2' + elif library_type == 'episode': + sort_type = '&type=4' + elif library_type == 'album': + list_type = 'albums' + + library_data = self.get_library_list(section_key, list_type, count, sort_type, output_format='xml') + + try: + xml_head = library_data.getElementsByTagName('MediaContainer') + except: + logger.warn("Unable to parse XML for get_library_children.") + return [] + + library_list = [] + + for a in xml_head: + if a.getAttribute('size'): + if a.getAttribute('size') == '0': + logger.debug(u"No library data.") + library_list = {'library_count': '0', + 'library_list': [] + } + return library_list + + if a.getElementsByTagName('Directory'): + result_data = a.getElementsByTagName('Directory') + for result in result_data: + library_output = {'key': helpers.get_xml_attr(result, 'key'), + 'type': helpers.get_xml_attr(result, 'type'), + 'title': helpers.get_xml_attr(result, 'title'), + 'thumb': helpers.get_xml_attr(result, 'thumb') + } + library_list.append(library_output) + + output = {'library_count': helpers.get_xml_attr(xml_head[0], 'totalSize'), + 'count_type': helpers.get_xml_attr(xml_head[0], 'title2'), + 'library_list': library_list + } + + return output + + """ + Return processed and validated server statistics. + + Output: array + """ + def get_server_stats(self): + server_info = self.get_servers_info() + server_libraries = self.get_server_children() + + server_stats = [] + + if server_libraries['libraries_count'] != '0': + libraries_list = server_libraries['libraries_list'] + + for library in libraries_list: + library_type = library['type'] + section_key = library['key'] + library_list = self.get_library_children(library_type, section_key) + + if library_list['library_count'] != '0': + library_stats = {'title': library['title'], + 'thumb': library['thumb'], + 'count': library_list['library_count'], + 'count_type': library_list['count_type'] + } + + if library_type == 'show': + episode_list = self.get_library_children(library_type='episode', section_key=section_key) + episode_stats = {'episode_count': episode_list['library_count'], + 'episode_count_type': 'All Episodes' + } + library_stats.update(episode_stats) + + if library_type == 'artist': + album_list = self.get_library_children(library_type='album', section_key=section_key) + album_stats = {'album_count': album_list['library_count'], + 'album_count_type': 'All Albums' + } + library_stats.update(album_stats) + + server_stats.append({'type': library_type, + 'rows': library_stats}) + + return server_stats + """ Return image data as array. Array contains the image content type and image binary diff --git a/plexpy/webserve.py b/plexpy/webserve.py index f2b6067b..1346698f 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -67,7 +67,8 @@ class WebInterface(object): config = { "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, "home_stats_type": plexpy.CONFIG.HOME_STATS_TYPE, - "home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT + "home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT, + "pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER, } return serve_template(templatename="index.html", title="Home", config=config) @@ -126,6 +127,13 @@ class WebInterface(object): return serve_template(templatename="home_stats.html", title="Stats", data=stats_data) + @cherrypy.expose + def server_stats(self, **kwargs): + pms_connect = pmsconnect.PmsConnect() + stats_data = pms_connect.get_server_stats() + + return serve_template(templatename="server_stats.html", title="Server Stats", data=stats_data) + @cherrypy.expose def history(self): return serve_template(templatename="history.html", title="History") From 0245668907d9af7d8117b3fb92e74cdf57ee3664 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 29 Aug 2015 13:15:29 -0700 Subject: [PATCH 2/5] Add "items" text --- data/interfaces/default/css/plexpy.css | 8 ++++++++ data/interfaces/default/server_stats.html | 3 +++ 2 files changed, 11 insertions(+) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 678d6ba1..da38a26d 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1596,6 +1596,14 @@ a .season-episodes-card-overlay:hover { padding-top: 6px; float: left; } +.home-platforms-instance-name2 p { + color: #aaa; + font-size: 12px; + float: left; + position: relative; + top: 21px; + left: 0px; +} .home-platforms-instance-playcount { float: left; position: relative; diff --git a/data/interfaces/default/server_stats.html b/data/interfaces/default/server_stats.html index 780f84a2..14c0ccd3 100644 --- a/data/interfaces/default/server_stats.html +++ b/data/interfaces/default/server_stats.html @@ -42,17 +42,20 @@ DOCUMENTATION :: END

${library['rows']['count']}

+

items

% if library['type'] == 'show':
${library['rows']['episode_count_type']}

${library['rows']['episode_count']}

+

items

% endif % if library['type'] == 'artist':
${library['rows']['album_count_type']}

${library['rows']['album_count']}

+

items

% endif
From 6ff902e6531f85cbde38bbbdb2bea43f95d79f40 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 29 Aug 2015 13:16:03 -0700 Subject: [PATCH 3/5] Fix playcounts 1px off --- data/interfaces/default/css/plexpy.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index da38a26d..20c9b452 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1433,7 +1433,7 @@ a .season-episodes-card-overlay:hover { font-size: 12px; float: left; position: relative; - top: 14px; + top: 15px; left: 0px; } .user-overview-stats-instance h3 strong{ @@ -1516,7 +1516,7 @@ a .season-episodes-card-overlay:hover { font-size: 12px; float: left; position: relative; - top: 14px; + top: 15px; left: 0px; } .home-platforms { @@ -1625,7 +1625,7 @@ a .season-episodes-card-overlay:hover { font-size: 12px; float: left; position: relative; - top: 14px; + top: 15px; left: 0px; margin-right: 5px; } From 82fc314b35685e061acc6ab802df401ec2cfefbe Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 29 Aug 2015 13:20:21 -0700 Subject: [PATCH 4/5] Another 1px off fix --- data/interfaces/default/css/plexpy.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 20c9b452..a55aacec 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1638,7 +1638,7 @@ a .season-episodes-card-overlay:hover { .home-platforms-instance-last-user h5 { font-size: 12px; position: relative; - margin: 0 0 3px 0; + margin: 0 0 2px 0; float: left; } .home-platforms-instance-last-user p { From d44bd2f35b33f458a1321fb4522cb7049e47be3b Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sat, 29 Aug 2015 13:24:44 -0700 Subject: [PATCH 5/5] Shift secondary count right to account for 4 digit items --- data/interfaces/default/css/plexpy.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index a55aacec..6cfc445a 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -1575,7 +1575,7 @@ a .season-episodes-card-overlay:hover { .home-platforms-instance-name2 { position: absolute; top: 34px; - left: 210px; + left: 215px; } .home-platforms-instance-name2 h5 { font-size: 14px;