From 18b328a387c1805e89ffbf56bc54b36ee29d6158 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sun, 15 Nov 2015 10:16:06 -0800 Subject: [PATCH 01/20] Fix private IP address for IPv6 --- data/interfaces/default/js/script.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/interfaces/default/js/script.js b/data/interfaces/default/js/script.js index 8fc15536..50348c46 100644 --- a/data/interfaces/default/js/script.js +++ b/data/interfaces/default/js/script.js @@ -232,7 +232,9 @@ function getPlatformImagePath(platformName) { function isPrivateIP(ip_address) { if (ip_address.indexOf(".") > -1) { - var parts = ip_address.split('.'); + // get IPv4 mapped address (xxx.xxx.xxx.xxx) from IPv6 addresss (::ffff:xxx.xxx.xxx.xxx) + var parts = ip_address.split(":"); + var parts = parts[parts.length - 1].split('.'); if (parts[0] === '10' || (parts[0] === '172' && (parseInt(parts[1], 10) >= 16 && parseInt(parts[1], 10) <= 31)) || (parts[0] === '192' && parts[1] === '168')) { From 1644bbd4b7feb8d56ba7f5d3e84c5685738fb433 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sun, 15 Nov 2015 10:26:03 -0800 Subject: [PATCH 02/20] Check for IPv4 mapped IPv6 address in PMS logs --- plexpy/activity_processor.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index db290c2d..2e582f5d 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -279,10 +279,17 @@ class ActivityProcessor(object): if ipv4: # The logged IP will always be the first match and we don't want localhost entries if ipv4[0] != '127.0.0.1': - logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s " - u"and machineIdentifier %s." - % (ipv4[0], rating_key, machine_id)) - return ipv4[0] + # check if IPv4 mapped IPv6 address (::ffff:xxx.xxx.xxx.xxx) + if '::ffff:' + ipv4[0] in line: + logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s " + u"and machineIdentifier %s." + % ('::ffff:' + ipv4[0], rating_key, machine_id)) + return '::ffff:' + ipv4[0] + else: + logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s " + u"and machineIdentifier %s." + % (ipv4[0], rating_key, machine_id)) + return ipv4[0] logger.debug(u"PlexPy ActivityProcessor :: Unable to find IP address on first pass. " u"Attempting fallback check in 5 seconds...") @@ -302,9 +309,14 @@ class ActivityProcessor(object): if ipv4: # The logged IP will always be the first match and we don't want localhost entries if ipv4[0] != '127.0.0.1': - logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s." % - (ipv4[0], rating_key)) - return ipv4[0] + if '::ffff:' + ipv4[0] in line: + logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s." % + ('::ffff:' + ipv4[0], rating_key)) + return '::ffff:' + ipv4[0] + else: + logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s." % + (ipv4[0], rating_key)) + return ipv4[0] logger.debug(u"PlexPy ActivityProcessor :: Unable to find IP address on fallback search. Not logging IP address.") From e9bc767c3b396d9b83c0a223a6f8ab605f43ebca Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sun, 15 Nov 2015 20:59:04 -0800 Subject: [PATCH 03/20] Fix username for database queries * Get the updated username from the users table instead of the one stored in the session_history table. --- plexpy/datafactory.py | 6 +++--- plexpy/graphs.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 38c4e5ce..948b8fa4 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -41,7 +41,7 @@ class DataFactory(object): 'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS paused_counter', 'session_history.user_id', 'session_history.user', - '(CASE WHEN users.friendly_name IS NULL THEN user ELSE users.friendly_name END) as friendly_name', + '(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) as friendly_name', 'platform', 'player', 'ip_address', @@ -437,7 +437,7 @@ class DataFactory(object): top_users = [] try: query = 'SELECT session_history.user, ' \ - '(case when users.friendly_name is null then session_history.user else ' \ + '(case when users.friendly_name is null then users.username else ' \ 'users.friendly_name end) as friendly_name,' \ 'COUNT(session_history.id) as total_plays, ' \ 'SUM(case when session_history.stopped > 0 ' \ @@ -536,7 +536,7 @@ class DataFactory(object): try: query = 'SELECT session_history_metadata.id, ' \ 'session_history.user, ' \ - '(case when users.friendly_name is null then session_history.user else ' \ + '(case when users.friendly_name is null then users.username else ' \ 'users.friendly_name end) as friendly_name,' \ 'users.user_id, ' \ 'users.custom_avatar_url as user_thumb, ' \ diff --git a/plexpy/graphs.py b/plexpy/graphs.py index e0a337cc..1240fad5 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -410,7 +410,7 @@ class Graphs(object): if y_axis == 'plays': query = 'SELECT ' \ - '(case when users.friendly_name is null then session_history.user else ' \ + '(case when users.friendly_name is null then users.username else ' \ 'users.friendly_name end) as friendly_name,' \ 'SUM(case when media_type = "episode" then 1 else 0 end) as tv_count, ' \ 'SUM(case when media_type = "movie" then 1 else 0 end) as movie_count, ' \ @@ -427,7 +427,7 @@ class Graphs(object): result = monitor_db.select(query) else: query = 'SELECT ' \ - '(case when users.friendly_name is null then session_history.user else ' \ + '(case when users.friendly_name is null then users.username else ' \ 'users.friendly_name end) as friendly_name,' \ 'SUM(case when media_type = "episode" and stopped > 0 then (stopped - started) ' \ ' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tv_duration, ' \ From f8b75eadc6749b58b85a71ee1317f2c237f90fca Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sun, 15 Nov 2015 21:35:53 -0800 Subject: [PATCH 04/20] Improved get server friendly name * Add server friendly name to page title --- data/interfaces/default/base.html | 2 +- data/interfaces/default/index.html | 12 ++---------- plexpy/notification_handler.py | 9 +-------- plexpy/pmsconnect.py | 16 +++++++++++++++- plexpy/webserve.py | 25 ++++++++++++++++++++++--- 5 files changed, 41 insertions(+), 23 deletions(-) diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index c471c3b3..0d66fac7 100644 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -7,7 +7,7 @@ from plexpy import version - PlexPy - ${title} + PlexPy - ${title} | ${server_name} diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index 5f8caeba..ca10ce39 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -100,20 +100,12 @@ function getLibraryStatsHeader() { $.ajax({ - "url": "get_servers_info", - type: "post", + url: 'get_server_friendly_name', cache: false, async: true, data: { }, complete: function (xhr, status) { - server_info = $.parseJSON(xhr.responseText); - var server_name = 'Server name not found'; - for (var i in server_info) { - if (server_info[i].machine_identifier == '${config['pms_identifier']}') { - server_name = server_info[i].name - break; - } - } + server_name = xhr.responseText; $('#library-statistics-header h3').append(' ' + server_name + '') } }); diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index f85887f4..2aa255f0 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -270,14 +270,7 @@ def build_notify_text(session=None, timeline=None, state=None): # Get the server name pms_connect = pmsconnect.PmsConnect() - server_name = pms_connect.get_server_pref(pref='FriendlyName') - # If friendly name is blank - if not server_name: - servers_info = pms_connect.get_servers_info() - for server in servers_info: - if server['machine_identifier'] == plexpy.CONFIG.PMS_IDENTIFIER: - server_name = server['name'] - break + server_name = pms_connect.get_server_friendly_name() # Get metadata feed for item if session: diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index a53e9f4b..12807983 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -1631,4 +1631,18 @@ class PmsConnect(object): 'children': parents} } - return key_list \ No newline at end of file + return key_list + + def get_server_friendly_name(self): + + server_name = self.get_server_pref(pref='FriendlyName') + + # If friendly name is blank + if not server_name: + servers_info = self.get_servers_info() + for server in servers_info: + if server['machine_identifier'] == plexpy.CONFIG.PMS_IDENTIFIER: + server_name = server['name'] + break + + return server_name \ No newline at end of file diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 4e0d4937..aae99de3 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -46,9 +46,13 @@ def serve_template(templatename, **kwargs): _hplookup = TemplateLookup(directories=[template_dir]) + # Get the server name + pms_connect = pmsconnect.PmsConnect() + server_name = pms_connect.get_server_friendly_name() + try: template = _hplookup.get_template(templatename) - return template.render(**kwargs) + return template.render(server_name=server_name, **kwargs) except: return exceptions.html_error_template().render() @@ -1137,10 +1141,25 @@ class WebInterface(object): logger.warn('Unable to retrieve data.') @cherrypy.expose - def get_server_prefs(self, **kwargs): + def get_server_friendly_name(self, **kwargs): pms_connect = pmsconnect.PmsConnect() - result = pms_connect.get_server_prefs(output_format='json') + result = pms_connect.get_server_friendly_name() + + if result: + cherrypy.response.headers['Content-type'] = 'application/json' + return result + else: + logger.warn('Unable to retrieve data.') + + @cherrypy.expose + def get_server_prefs(self, pref=None, **kwargs): + + if pref: + pms_connect = pmsconnect.PmsConnect() + result = pms_connect.get_server_pref(pref=pref) + else: + result = None if result: cherrypy.response.headers['Content-type'] = 'application/json' From da2e7635bdeaecdd98f6a1a6a920bb754f2be2e6 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sun, 15 Nov 2015 23:50:58 -0800 Subject: [PATCH 05/20] Fix notification tags removal for show and artist --- plexpy/notification_handler.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 2aa255f0..562f2d91 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -287,20 +287,22 @@ def build_notify_text(session=None, timeline=None, state=None): return [] # Check for exclusion tags - if metadata['media_type'] == 'episode': - # Regex pattern to remove the text in the tags we don't want - pattern = re.compile('[^>]+.|[^>]+.', re.IGNORECASE) - elif metadata['media_type'] == 'movie': + if metadata['media_type'] == 'movie': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('[^>]+.|[^>]+.', re.IGNORECASE) - elif metadata['media_type'] == 'track': + elif metadata['media_type'] == 'show' or metadata['media_type'] == 'episode': + # Regex pattern to remove the text in the tags we don't want + pattern = re.compile('[^>]+.|[^>]+.', re.IGNORECASE) + elif metadata['media_type'] == 'artist' or metadata['media_type'] == 'track': # Regex pattern to remove the text in the tags we don't want pattern = re.compile('[^>]+.|[^>]+.', re.IGNORECASE) else: pattern = None - if metadata['media_type'] == 'episode' or metadata['media_type'] == 'movie' or metadata['media_type'] == 'track' \ - and pattern: + if metadata['media_type'] == 'movie' \ + or metadata['media_type'] == 'show' or metadata['media_type'] == 'episode' \ + or metadata['media_type'] == 'artist' or metadata['media_type'] == 'track' \ + and pattern: # Remove the unwanted tags and strip any unmatch tags too. on_start_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT)) on_start_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT)) From 7c8fb58600323522abd34be1cf91ee5d8b111dc9 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sun, 15 Nov 2015 23:51:31 -0800 Subject: [PATCH 06/20] Change input to textarea for notification body --- data/interfaces/default/css/plexpy.css | 18 ++++++++++++++++++ data/interfaces/default/settings.html | 24 ++++++++++++++++-------- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 2571667a..e2e11185 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -341,6 +341,24 @@ input[type="color"], border-radius: 3px; transition: background-color .3s; } +textarea.form-control { + height: initial; + margin: 5px 0 5px 0; + color: #fff; + border: 0px solid #444; + background: #555; + padding: 6px 12px; + background-color: #555; + border-radius: 3px; + transition: background-color .3s; + resize: none; +} +textarea.form-control:focus { + outline: 0; + color: #555; + background-color: #fff; + transition: background-color .3s; +} .pagination > li > a, .pagination > li > span { position: relative; diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 76a8551f..6fc65627 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -533,7 +533,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
- +

Set a custom body.

@@ -550,7 +550,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
- +

Set a custom body.

@@ -567,7 +567,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
- +

Set a custom body.

@@ -584,7 +584,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
- +

Set a custom body.

@@ -601,7 +601,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
- +

Set a custom body.

@@ -618,7 +618,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
- +

Set a custom body.

@@ -637,7 +637,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
- +

Set a custom body.

@@ -1388,6 +1388,14 @@ $(document).ready(function() { e.preventDefault() }); + // auto resizing textarea for custom notification message body + $('textarea[data-autoresize]').each(function() { + var offset = this.offsetHeight - this.clientHeight; + var resizeTextarea = function(el) { + $(el).css('height', 'auto').css('height', el.scrollHeight + offset); + }; + $(this).on('focus keyup input', function() { resizeTextarea(this); }).removeAttr('data-autoresize'); + }); }); - + \ No newline at end of file From 12056ac2ba83416772e97b46619de34b74c260d5 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Mon, 16 Nov 2015 18:31:22 -0800 Subject: [PATCH 07/20] Get server name as a scheduled task --- data/interfaces/default/index.html | 16 +--------------- plexpy/__init__.py | 8 ++++++-- plexpy/config.py | 1 + plexpy/pmsconnect.py | 4 ++++ plexpy/webserve.py | 7 +++---- 5 files changed, 15 insertions(+), 21 deletions(-) diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index ca10ce39..1eb26c1d 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -33,7 +33,7 @@
-

Library Statistics

+

Library Statistics ${config['pms_name']}

Loading stats...
@@ -98,19 +98,6 @@ }); } - function getLibraryStatsHeader() { - $.ajax({ - url: 'get_server_friendly_name', - cache: false, - async: true, - data: { }, - complete: function (xhr, status) { - server_name = xhr.responseText; - $('#library-statistics-header h3').append(' ' + server_name + '') - } - }); - } - function getLibraryStats() { $.ajax({ url: 'library_stats', @@ -162,7 +149,6 @@ }); getHomeStats(); - getLibraryStatsHeader(); getLibraryStats(); diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 0c277473..3cf7d2a9 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -31,7 +31,7 @@ except ImportError: from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.interval import IntervalTrigger -from plexpy import versioncheck, logger, activity_pinger, plextv +from plexpy import versioncheck, logger, activity_pinger, plextv, pmsconnect import plexpy.config PROG_DIR = None @@ -169,6 +169,7 @@ def initialize(config_file): # Get the real PMS urls for SSL and remote access if CONFIG.PMS_TOKEN and CONFIG.PMS_IP and CONFIG.PMS_PORT: plextv.get_real_pms_url() + pmsconnect.PmsConnect().get_server_friendly_name() # Refresh the users list on startup if CONFIG.PMS_TOKEN and CONFIG.REFRESH_USERS_ON_STARTUP: @@ -280,7 +281,10 @@ def initialize_scheduler(): seconds = 0 if CONFIG.PMS_IP and CONFIG.PMS_TOKEN: - schedule_job(plextv.get_real_pms_url, 'Refresh Plex Server URLs', hours=12, minutes=0, seconds=0) + schedule_job(plextv.get_real_pms_url, 'Refresh Plex Server URLs', + hours=12, minutes=0, seconds=0) + schedule_job(pmsconnect.PmsConnect().get_server_friendly_name, 'Refresh Plex Server Name', + hours=12, minutes=0, seconds=0) # If we're not using websockets then fall back to polling if not CONFIG.MONITORING_USE_WEBSOCKET or POLLING_FAILOVER: diff --git a/plexpy/config.py b/plexpy/config.py index 88346b9a..dc274e00 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -26,6 +26,7 @@ _CONFIG_DEFINITIONS = { 'PMS_IP': (str, 'PMS', '127.0.0.1'), 'PMS_IS_REMOTE': (int, 'PMS', 0), 'PMS_LOGS_FOLDER': (str, 'PMS', ''), + 'PMS_NAME': (str, 'PMS', ''), 'PMS_PORT': (int, 'PMS', 32400), 'PMS_TOKEN': (str, 'PMS', ''), 'PMS_SSL': (int, 'General', 0), diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 12807983..5fcee3af 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -1645,4 +1645,8 @@ class PmsConnect(object): server_name = server['name'] break + if server_name != plexpy.CONFIG.PMS_NAME: + plexpy.CONFIG.__setattr__('PMS_NAME', server_name) + plexpy.CONFIG.write() + return server_name \ No newline at end of file diff --git a/plexpy/webserve.py b/plexpy/webserve.py index aae99de3..7baa4cbf 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -46,9 +46,7 @@ def serve_template(templatename, **kwargs): _hplookup = TemplateLookup(directories=[template_dir]) - # Get the server name - pms_connect = pmsconnect.PmsConnect() - server_name = pms_connect.get_server_friendly_name() + server_name = plexpy.CONFIG.PMS_NAME try: template = _hplookup.get_template(templatename) @@ -75,7 +73,8 @@ class WebInterface(object): "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, "home_stats_cards": plexpy.CONFIG.HOME_STATS_CARDS, "home_library_cards": plexpy.CONFIG.HOME_LIBRARY_CARDS, - "pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER + "pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER, + "pms_name": plexpy.CONFIG.PMS_NAME } return serve_template(templatename="index.html", title="Home", config=config) From 53044c75dd8de1d3f5d1164f3c9cb6ae1537557a Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Mon, 16 Nov 2015 21:17:47 -0800 Subject: [PATCH 08/20] Add setting for recently added notification delay --- data/interfaces/default/settings.html | 17 ++++++++++++++++- plexpy/config.py | 1 + plexpy/webserve.py | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 6fc65627..43375644 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -483,7 +483,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
-

Notification Tuning

+

Current Activity Notifications

@@ -502,12 +502,27 @@ available_notification_agents = sorted(notifiers.available_notification_agents()

Disable to prevent consecutive notifications (i.e. both watched & stopped notifications).

+ +
+

Recently Added Notifications

+
+

Enable to only get one notification for recently added Episodes or Tracks. Movies are unaffected.

+
+ +
+
+ +
+ +
+

Set the delay for recently added notifications to allow metadata to be processes. Minimum 60 seconds.

+

Custom Notification Messages

diff --git a/plexpy/config.py b/plexpy/config.py index dc274e00..4415f2cd 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -140,6 +140,7 @@ _CONFIG_DEFINITIONS = { 'NMA_ON_CREATED': (int, 'NMA', 0), 'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1), 'NOTIFY_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 0), + 'NOTIFY_RECENTLY_ADDED_DELAY': (int, 'Monitoring', 60), 'NOTIFY_WATCHED_PERCENT': (int, 'Monitoring', 85), 'NOTIFY_ON_START_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'), 'NOTIFY_ON_START_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) started playing {title}.'), diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 7baa4cbf..50c23876 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -446,6 +446,7 @@ class WebInterface(object): "pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE), "notify_consecutive": checked(plexpy.CONFIG.NOTIFY_CONSECUTIVE), "notify_recently_added_grandparent": checked(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT), + "notify_recently_added_delay": plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY, "notify_watched_percent": plexpy.CONFIG.NOTIFY_WATCHED_PERCENT, "notify_on_start_subject_text": plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT, "notify_on_start_body_text": plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT, From 69a3b5134fae328e62d4ddb1654ffc8c66bf8678 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Mon, 16 Nov 2015 21:19:04 -0800 Subject: [PATCH 09/20] Switch recently added to only use activity pinger * Fixed activity pinger logic for grouping notifications --- plexpy/__init__.py | 4 +-- plexpy/activity_handler.py | 2 +- plexpy/activity_pinger.py | 66 +++++++++++++++++++--------------- plexpy/notification_handler.py | 18 ++++------ plexpy/web_socket.py | 16 ++++----- 5 files changed, 56 insertions(+), 50 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 3cf7d2a9..ae6e2a02 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -285,13 +285,13 @@ def initialize_scheduler(): hours=12, minutes=0, seconds=0) schedule_job(pmsconnect.PmsConnect().get_server_friendly_name, 'Refresh Plex Server Name', hours=12, minutes=0, seconds=0) + schedule_job(activity_pinger.check_recently_added, 'Check for recently added items', + hours=0, minutes=0, seconds=seconds) # If we're not using websockets then fall back to polling if not CONFIG.MONITORING_USE_WEBSOCKET or POLLING_FAILOVER: schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions', hours=0, minutes=0, seconds=seconds) - schedule_job(activity_pinger.check_recently_added, 'Check for recently added items', - hours=0, minutes=0, seconds=seconds) # Refresh the users list if CONFIG.REFRESH_USERS_INTERVAL: diff --git a/plexpy/activity_handler.py b/plexpy/activity_handler.py index f726b3fe..967a2e21 100644 --- a/plexpy/activity_handler.py +++ b/plexpy/activity_handler.py @@ -218,7 +218,7 @@ class TimelineHandler(object): def __init__(self, timeline): self.timeline = timeline - logger.debug(timeline) + #logger.debug(timeline) def is_item(self): if 'itemID' in self.timeline: diff --git a/plexpy/activity_pinger.py b/plexpy/activity_pinger.py index 28fd781d..baa280d2 100644 --- a/plexpy/activity_pinger.py +++ b/plexpy/activity_pinger.py @@ -167,7 +167,10 @@ def check_active_sessions(ws_request=False): def check_recently_added(): with monitor_lock: - current_time = int(time.time()) + # add delay to allow for metadata processing + delay = plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY + time_threshold = int(time.time()) - delay + time_interval = plexpy.CONFIG.MONITORING_INTERVAL pms_connect = pmsconnect.PmsConnect() recently_added_list = pms_connect.get_recently_added_details(count='10') @@ -176,36 +179,43 @@ def check_recently_added(): recently_added = recently_added_list['recently_added'] for item in recently_added: - if int(item['added_at']) >= current_time - plexpy.CONFIG.MONITORING_INTERVAL: - if item['media_type'] == 'movie': - metadata_list = pms_connect.get_metadata_details(item['rating_key']) - if metadata_list: - metadata = [metadata_list['metadata']] - else: - logger.error(u"PlexPy Monitor :: Unable to retrieve metadata for rating_key %s" % str(item['rating_key'])) - - elif plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT: - metadata_list = pms_connect.get_metadata_details(item['parent_rating_key']) - if metadata_list: - metadata = [metadata_list['metadata']] - else: - logger.error(u"PlexPy Monitor :: Unable to retrieve metadata for parent_rating_key %s" % str(item['parent_rating_key'])) - + if item['media_type'] == 'movie': + metadata_list = pms_connect.get_metadata_details(item['rating_key']) + if metadata_list: + metadata = [metadata_list['metadata']] else: - metadata_list = pms_connect.get_metadata_children_details(item['rating_key']) - if metadata_list: - metadata = metadata_list['metadata'] - else: - logger.error(u"PlexPy Monitor :: Unable to retrieve children metadata for rating_key" % str(item['rating_key'])) + logger.error(u"PlexPy Monitor :: Unable to retrieve metadata for rating_key %s" \ + % str(item['rating_key'])) - if metadata: + else: + metadata_list = pms_connect.get_metadata_children_details(item['rating_key']) + if metadata_list: + metadata = metadata_list['metadata'] + else: + logger.error(u"PlexPy Monitor :: Unable to retrieve children metadata for rating_key %s" \ + % str(item['rating_key'])) + + if metadata: + if not plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT: for item in metadata: - if (plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT \ - and int(item['updated_at']) >= current_time - plexpy.CONFIG.MONITORING_INTERVAL) \ - or (not plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT \ - and int(item['added_at']) >= current_time - plexpy.CONFIG.MONITORING_INTERVAL): - logger.debug(u"PlexPy Monitor :: Library item %s has been added to Plex." % str(item['rating_key'])) - + if 0 < int(item['added_at']) - time_threshold <= time_interval: # Fire off notifications threading.Thread(target=notification_handler.notify_timeline, kwargs=dict(timeline_data=item, notify_action='created')).start() + + else: + item = max(metadata, key=lambda x:x['added_at']) + + if 0 < int(item['added_at']) - time_threshold <= time_interval: + if item['media_type'] == 'episode' or item['media_type'] == 'track': + metadata_list = pms_connect.get_metadata_details(item['grandparent_rating_key']) + + if metadata_list: + item = metadata_list['metadata'] + else: + logger.error(u"PlexPy Monitor :: Unable to retrieve grandparent metadata for grandparent_rating_key %s" \ + % str(item['rating_key'])) + + # Fire off notifications + threading.Thread(target=notification_handler.notify_timeline, + kwargs=dict(timeline_data=item, notify_action='created')).start() diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 562f2d91..4318e923 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -172,17 +172,13 @@ def notify_timeline(timeline_data=None, notify_action=None): if timeline_data and notify_action: for agent in notifiers.available_notification_agents(): if agent['on_created'] and notify_action == 'created': - if (plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT \ - and (timeline_data['media_type'] == 'movie' or timeline_data['media_type'] == 'show' or timeline_data['media_type'] == 'artist')) \ - or (not plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT \ - and (timeline_data['media_type'] == 'movie' or timeline_data['media_type'] == 'episode' or timeline_data['media_type'] == 'track')): - # Build and send notification - notify_strings = build_notify_text(timeline=timeline_data, state=notify_action) - notifiers.send_notification(config_id=agent['id'], - subject=notify_strings[0], - body=notify_strings[1]) - # Set the notification state in the db - set_notify_state(session=timeline_data, state=notify_action, agent_info=agent) + # Build and send notification + notify_strings = build_notify_text(timeline=timeline_data, state=notify_action) + notifiers.send_notification(config_id=agent['id'], + subject=notify_strings[0], + body=notify_strings[1]) + # Set the notification state in the db + set_notify_state(session=timeline_data, state=notify_action, agent_info=agent) else: logger.debug(u"PlexPy Notifier :: Notify timeline called but incomplete data received.") diff --git a/plexpy/web_socket.py b/plexpy/web_socket.py index 17603fe8..dc2a3f29 100644 --- a/plexpy/web_socket.py +++ b/plexpy/web_socket.py @@ -139,14 +139,14 @@ def process(opcode, data): activity = activity_handler.ActivityHandler(timeline=time_line[0]) activity.process() - if type == 'timeline': - try: - time_line = info.get('_children') - except: - logger.debug(u"PlexPy WebSocket :: Timeline event found but unable to get timeline data.") - return False + #if type == 'timeline': + # try: + # time_line = info.get('_children') + # except: + # logger.debug(u"PlexPy WebSocket :: Timeline event found but unable to get timeline data.") + # return False - activity = activity_handler.TimelineHandler(timeline=time_line[0]) - activity.process() + # activity = activity_handler.TimelineHandler(timeline=time_line[0]) + # activity.process() return True From 1983597cf1b6f655cc436a59d26fe54a10d278af Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Mon, 16 Nov 2015 22:51:21 -0800 Subject: [PATCH 10/20] Fix get_server_friendly_name --- plexpy/__init__.py | 4 ++-- plexpy/notification_handler.py | 4 +--- plexpy/pmsconnect.py | 37 +++++++++++++++++----------------- plexpy/webserve.py | 3 +-- 4 files changed, 22 insertions(+), 26 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index ae6e2a02..39c9a470 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -169,7 +169,7 @@ def initialize(config_file): # Get the real PMS urls for SSL and remote access if CONFIG.PMS_TOKEN and CONFIG.PMS_IP and CONFIG.PMS_PORT: plextv.get_real_pms_url() - pmsconnect.PmsConnect().get_server_friendly_name() + pmsconnect.get_server_friendly_name() # Refresh the users list on startup if CONFIG.PMS_TOKEN and CONFIG.REFRESH_USERS_ON_STARTUP: @@ -283,7 +283,7 @@ def initialize_scheduler(): if CONFIG.PMS_IP and CONFIG.PMS_TOKEN: schedule_job(plextv.get_real_pms_url, 'Refresh Plex Server URLs', hours=12, minutes=0, seconds=0) - schedule_job(pmsconnect.PmsConnect().get_server_friendly_name, 'Refresh Plex Server Name', + schedule_job(pmsconnect.get_server_friendly_name, 'Refresh Plex Server Name', hours=12, minutes=0, seconds=0) schedule_job(activity_pinger.check_recently_added, 'Check for recently added items', hours=0, minutes=0, seconds=seconds) diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 4318e923..fae6db6b 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -261,12 +261,10 @@ def set_notify_state(session, state, agent_info): def build_notify_text(session=None, timeline=None, state=None): - from plexpy import pmsconnect, helpers import re # Get the server name - pms_connect = pmsconnect.PmsConnect() - server_name = pms_connect.get_server_friendly_name() + server_name = plexpy.CONFIG.PMS_NAME # Get metadata feed for item if session: diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 5fcee3af..9ab4fd80 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -19,6 +19,23 @@ from urlparse import urlparse import plexpy import urllib2 +def get_server_friendly_name(): + logger.info("Requesting name from server...") + server_name = PmsConnect().get_server_pref(pref='FriendlyName') + + # If friendly name is blank + if not server_name: + servers_info = PmsConnect().get_servers_info() + for server in servers_info: + if server['machine_identifier'] == plexpy.CONFIG.PMS_IDENTIFIER: + server_name = server['name'] + break + + if server_name and server_name != plexpy.CONFIG.PMS_NAME: + plexpy.CONFIG.__setattr__('PMS_NAME', server_name) + plexpy.CONFIG.write() + + return server_name class PmsConnect(object): """ @@ -1631,22 +1648,4 @@ class PmsConnect(object): 'children': parents} } - return key_list - - def get_server_friendly_name(self): - - server_name = self.get_server_pref(pref='FriendlyName') - - # If friendly name is blank - if not server_name: - servers_info = self.get_servers_info() - for server in servers_info: - if server['machine_identifier'] == plexpy.CONFIG.PMS_IDENTIFIER: - server_name = server['name'] - break - - if server_name != plexpy.CONFIG.PMS_NAME: - plexpy.CONFIG.__setattr__('PMS_NAME', server_name) - plexpy.CONFIG.write() - - return server_name \ No newline at end of file + return key_list \ No newline at end of file diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 50c23876..249b1360 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -1143,8 +1143,7 @@ class WebInterface(object): @cherrypy.expose def get_server_friendly_name(self, **kwargs): - pms_connect = pmsconnect.PmsConnect() - result = pms_connect.get_server_friendly_name() + result = pmsconnect.get_server_friendly_name() if result: cherrypy.response.headers['Content-type'] = 'application/json' From b28ac1543ad2d5c8baac410e6d1c32eedc406aee Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Mon, 16 Nov 2015 23:19:37 -0800 Subject: [PATCH 11/20] Add notification for server down --- .../default/notification_triggers_modal.html | 7 +++ data/interfaces/default/settings.html | 17 +++++++ plexpy/config.py | 18 +++++++- plexpy/notification_handler.py | 44 +++++++++++++++++++ plexpy/notifiers.py | 42 ++++++++++++------ plexpy/webserve.py | 2 + 6 files changed, 115 insertions(+), 15 deletions(-) diff --git a/data/interfaces/default/notification_triggers_modal.html b/data/interfaces/default/notification_triggers_modal.html index bbe798fc..f6da7604 100644 --- a/data/interfaces/default/notification_triggers_modal.html +++ b/data/interfaces/default/notification_triggers_modal.html @@ -64,6 +64,13 @@ from plexpy import helpers

Trigger notification when a media item is added to the Plex Media Server.

+
+ +

Trigger notification when the Plex Media Server cannot be reached.

+
diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 43375644..477678d7 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -658,6 +658,23 @@ available_notification_agents = sorted(notifiers.available_notification_agents() +
  • + + +
  • diff --git a/plexpy/config.py b/plexpy/config.py index 4415f2cd..00b3dac5 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -46,6 +46,7 @@ _CONFIG_DEFINITIONS = { 'BOXCAR_ON_BUFFER': (int, 'Boxcar', 0), 'BOXCAR_ON_WATCHED': (int, 'Boxcar', 0), 'BOXCAR_ON_CREATED': (int, 'Boxcar', 0), + 'BOXCAR_ON_DOWN': (int, 'Boxcar', 0), 'BUFFER_THRESHOLD': (int, 'Monitoring', 3), 'BUFFER_WAIT': (int, 'Monitoring', 900), 'CACHE_DIR': (str, 'General', ''), @@ -71,6 +72,7 @@ _CONFIG_DEFINITIONS = { 'EMAIL_ON_BUFFER': (int, 'Email', 0), 'EMAIL_ON_WATCHED': (int, 'Email', 0), 'EMAIL_ON_CREATED': (int, 'Email', 0), + 'EMAIL_ON_DOWN': (int, 'Email', 0), 'ENABLE_HTTPS': (int, 'General', 0), 'FIRST_RUN_COMPLETE': (int, 'General', 0), 'FREEZE_DB': (int, 'General', 0), @@ -88,6 +90,7 @@ _CONFIG_DEFINITIONS = { 'GROWL_ON_BUFFER': (int, 'Growl', 0), 'GROWL_ON_WATCHED': (int, 'Growl', 0), 'GROWL_ON_CREATED': (int, 'Growl', 0), + 'GROWL_ON_DOWN': (int, 'Growl', 0), 'HOME_LIBRARY_CARDS': (str, 'General', 'library_statistics_first'), 'HOME_STATS_LENGTH': (int, 'General', 30), 'HOME_STATS_TYPE': (int, 'General', 0), @@ -113,6 +116,7 @@ _CONFIG_DEFINITIONS = { 'IFTTT_ON_BUFFER': (int, 'IFTTT', 0), 'IFTTT_ON_WATCHED': (int, 'IFTTT', 0), 'IFTTT_ON_CREATED': (int, 'IFTTT', 0), + 'IFTTT_ON_DOWN': (int, 'IFTTT', 0), 'JOURNAL_MODE': (str, 'Advanced', 'wal'), 'LAUNCH_BROWSER': (int, 'General', 1), 'LOG_DIR': (str, 'General', ''), @@ -138,6 +142,7 @@ _CONFIG_DEFINITIONS = { 'NMA_ON_BUFFER': (int, 'NMA', 0), 'NMA_ON_WATCHED': (int, 'NMA', 0), 'NMA_ON_CREATED': (int, 'NMA', 0), + 'NMA_ON_DOWN': (int, 'NMA', 0), 'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1), 'NOTIFY_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 0), 'NOTIFY_RECENTLY_ADDED_DELAY': (int, 'Monitoring', 60), @@ -156,6 +161,8 @@ _CONFIG_DEFINITIONS = { 'NOTIFY_ON_WATCHED_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) has watched {title}.'), 'NOTIFY_ON_CREATED_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'), 'NOTIFY_ON_CREATED_BODY_TEXT': (str, 'Monitoring', '{title} was recently added to Plex.'), + 'NOTIFY_ON_DOWN_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'), + 'NOTIFY_ON_DOWN_BODY_TEXT': (str, 'Monitoring', 'The Plex server is down.'), 'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/PlexPy'), 'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0), 'OSX_NOTIFY_ON_PLAY': (int, 'OSX_Notify', 0), @@ -165,6 +172,7 @@ _CONFIG_DEFINITIONS = { 'OSX_NOTIFY_ON_BUFFER': (int, 'OSX_Notify', 0), 'OSX_NOTIFY_ON_WATCHED': (int, 'OSX_Notify', 0), 'OSX_NOTIFY_ON_CREATED': (int, 'OSX_Notify', 0), + 'OSX_NOTIFY_ON_DOWN': (int, 'OSX_Notify', 0), 'PLEX_CLIENT_HOST': (str, 'Plex', ''), 'PLEX_ENABLED': (int, 'Plex', 0), 'PLEX_PASSWORD': (str, 'Plex', ''), @@ -176,6 +184,7 @@ _CONFIG_DEFINITIONS = { 'PLEX_ON_BUFFER': (int, 'Plex', 0), 'PLEX_ON_WATCHED': (int, 'Plex', 0), 'PLEX_ON_CREATED': (int, 'Plex', 0), + 'PLEX_ON_DOWN': (int, 'Plex', 0), 'PROWL_ENABLED': (int, 'Prowl', 0), 'PROWL_KEYS': (str, 'Prowl', ''), 'PROWL_PRIORITY': (int, 'Prowl', 0), @@ -186,6 +195,7 @@ _CONFIG_DEFINITIONS = { 'PROWL_ON_BUFFER': (int, 'Prowl', 0), 'PROWL_ON_WATCHED': (int, 'Prowl', 0), 'PROWL_ON_CREATED': (int, 'Prowl', 0), + 'PROWL_ON_DOWN': (int, 'Prowl', 0), 'PUSHALOT_APIKEY': (str, 'Pushalot', ''), 'PUSHALOT_ENABLED': (int, 'Pushalot', 0), 'PUSHALOT_ON_PLAY': (int, 'Pushalot', 0), @@ -195,6 +205,7 @@ _CONFIG_DEFINITIONS = { 'PUSHALOT_ON_BUFFER': (int, 'Pushalot', 0), 'PUSHALOT_ON_WATCHED': (int, 'Pushalot', 0), 'PUSHALOT_ON_CREATED': (int, 'Pushalot', 0), + 'PUSHALOT_ON_DOWN': (int, 'Pushalot', 0), 'PUSHBULLET_APIKEY': (str, 'PushBullet', ''), 'PUSHBULLET_DEVICEID': (str, 'PushBullet', ''), 'PUSHBULLET_CHANNEL_TAG': (str, 'PushBullet', ''), @@ -206,6 +217,7 @@ _CONFIG_DEFINITIONS = { 'PUSHBULLET_ON_BUFFER': (int, 'PushBullet', 0), 'PUSHBULLET_ON_WATCHED': (int, 'PushBullet', 0), 'PUSHBULLET_ON_CREATED': (int, 'PushBullet', 0), + 'PUSHBULLET_ON_DOWN': (int, 'PushBullet', 0), 'PUSHOVER_APITOKEN': (str, 'Pushover', ''), 'PUSHOVER_ENABLED': (int, 'Pushover', 0), 'PUSHOVER_KEYS': (str, 'Pushover', ''), @@ -218,6 +230,7 @@ _CONFIG_DEFINITIONS = { 'PUSHOVER_ON_BUFFER': (int, 'Pushover', 0), 'PUSHOVER_ON_WATCHED': (int, 'Pushover', 0), 'PUSHOVER_ON_CREATED': (int, 'Pushover', 0), + 'PUSHOVER_ON_DOWN': (int, 'Pushover', 0), 'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12), 'REFRESH_USERS_ON_STARTUP': (int, 'Monitoring', 1), 'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''), @@ -230,6 +243,7 @@ _CONFIG_DEFINITIONS = { 'TELEGRAM_ON_BUFFER': (int, 'Telegram', 0), 'TELEGRAM_ON_WATCHED': (int, 'Telegram', 0), 'TELEGRAM_ON_CREATED': (int, 'Telegram', 0), + 'TELEGRAM_ON_DOWN': (int, 'Telegram', 0), 'TV_NOTIFY_ENABLE': (int, 'Monitoring', 0), 'TV_NOTIFY_ON_START': (int, 'Monitoring', 1), 'TV_NOTIFY_ON_STOP': (int, 'Monitoring', 0), @@ -245,6 +259,7 @@ _CONFIG_DEFINITIONS = { 'TWITTER_ON_BUFFER': (int, 'Twitter', 0), 'TWITTER_ON_WATCHED': (int, 'Twitter', 0), 'TWITTER_ON_CREATED': (int, 'Twitter', 0), + 'TWITTER_ON_DOWN': (int, 'Twitter', 0), 'UPDATE_DB_INTERVAL': (int, 'General', 24), 'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1), 'VIDEO_LOGGING_ENABLE': (int, 'Monitoring', 1), @@ -258,7 +273,8 @@ _CONFIG_DEFINITIONS = { 'XBMC_ON_RESUME': (int, 'XBMC', 0), 'XBMC_ON_BUFFER': (int, 'XBMC', 0), 'XBMC_ON_WATCHED': (int, 'XBMC', 0), - 'XBMC_ON_CREATED': (int, 'XBMC', 0) + 'XBMC_ON_CREATED': (int, 'XBMC', 0), + 'XBMC_ON_DOWN': (int, 'XBMC', 0) } # pylint:disable=R0902 # it might be nice to refactor for fewer instance variables diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index fae6db6b..1e1768c9 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -179,6 +179,14 @@ def notify_timeline(timeline_data=None, notify_action=None): body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=timeline_data, state=notify_action, agent_info=agent) + elif notify_action == 'down': + for agent in notifiers.available_notification_agents(): + if agent['on_down'] and notify_action == 'down': + # Build and send notification + notify_strings = build_server_notify_text(state=notify_action) + notifiers.send_notification(config_id=agent['id'], + subject=notify_strings[0], + body=notify_strings[1]) else: logger.debug(u"PlexPy Notifier :: Notify timeline called but incomplete data received.") @@ -583,6 +591,42 @@ def build_notify_text(session=None, timeline=None, state=None): else: return None +def build_server_notify_text(state=None): + # Get the server name + server_name = plexpy.CONFIG.PMS_NAME + + on_down_subject = plexpy.CONFIG.NOTIFY_ON_DOWN_SUBJECT_TEXT + on_down_body = plexpy.CONFIG.NOTIFY_ON_DOWN_BODY_TEXT + + available_params = {'server_name': server_name} + + # Default text + subject_text = 'PlexPy (%s)' % server_name + + if state == 'down': + # Default body text + body_text = 'Unable to get a response from the server.' + + if on_down_subject and on_down_body: + try: + subject_text = unicode(on_down_subject).format(**available_params) + except LookupError, e: + logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) + except: + logger.error(u"PlexPy Notifier :: Unable to parse custom notification subject. Using fallback.") + + try: + body_text = unicode(on_down_body).format(**available_params) + except LookupError, e: + logger.error(u"PlexPy Notifier :: Unable to parse field %s in notification body. Using fallback." % e) + except: + logger.error(u"PlexPy Notifier :: Unable to parse custom notification body. Using fallback.") + + return [subject_text, body_text] + else: + return [subject_text, body_text] + else: + return None def strip_tag(data): import re diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 2589d388..863a597e 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -66,7 +66,8 @@ def available_notification_agents(): 'on_resume': plexpy.CONFIG.GROWL_ON_RESUME, 'on_buffer': plexpy.CONFIG.GROWL_ON_BUFFER, 'on_watched': plexpy.CONFIG.GROWL_ON_WATCHED, - 'on_created': plexpy.CONFIG.GROWL_ON_CREATED + 'on_created': plexpy.CONFIG.GROWL_ON_CREATED, + 'on_down': plexpy.CONFIG.GROWL_ON_DOWN }, {'name': 'Prowl', 'id': AGENT_IDS['Prowl'], @@ -79,7 +80,8 @@ def available_notification_agents(): 'on_resume': plexpy.CONFIG.PROWL_ON_RESUME, 'on_buffer': plexpy.CONFIG.PROWL_ON_BUFFER, 'on_watched': plexpy.CONFIG.PROWL_ON_WATCHED, - 'on_created': plexpy.CONFIG.PROWL_ON_CREATED + 'on_created': plexpy.CONFIG.PROWL_ON_CREATED, + 'on_down': plexpy.CONFIG.PROWL_ON_DOWN }, {'name': 'XBMC', 'id': AGENT_IDS['XBMC'], @@ -92,7 +94,8 @@ def available_notification_agents(): 'on_resume': plexpy.CONFIG.XBMC_ON_RESUME, 'on_buffer': plexpy.CONFIG.XBMC_ON_BUFFER, 'on_watched': plexpy.CONFIG.XBMC_ON_WATCHED, - 'on_created': plexpy.CONFIG.XBMC_ON_CREATED + 'on_created': plexpy.CONFIG.XBMC_ON_CREATED, + 'on_down': plexpy.CONFIG.XBMC_ON_DOWN }, {'name': 'Plex', 'id': AGENT_IDS['Plex'], @@ -105,7 +108,8 @@ def available_notification_agents(): 'on_resume': plexpy.CONFIG.PLEX_ON_RESUME, 'on_buffer': plexpy.CONFIG.PLEX_ON_BUFFER, 'on_watched': plexpy.CONFIG.PLEX_ON_WATCHED, - 'on_created': plexpy.CONFIG.PLEX_ON_CREATED + 'on_created': plexpy.CONFIG.PLEX_ON_CREATED, + 'on_down': plexpy.CONFIG.PLEX_ON_DOWN }, {'name': 'NotifyMyAndroid', 'id': AGENT_IDS['NMA'], @@ -118,7 +122,8 @@ def available_notification_agents(): 'on_resume': plexpy.CONFIG.NMA_ON_RESUME, 'on_buffer': plexpy.CONFIG.NMA_ON_BUFFER, 'on_watched': plexpy.CONFIG.NMA_ON_WATCHED, - 'on_created': plexpy.CONFIG.NMA_ON_CREATED + 'on_created': plexpy.CONFIG.NMA_ON_CREATED, + 'on_down': plexpy.CONFIG.NMA_ON_DOWN }, {'name': 'Pushalot', 'id': AGENT_IDS['Pushalot'], @@ -131,7 +136,8 @@ def available_notification_agents(): 'on_resume': plexpy.CONFIG.PUSHALOT_ON_RESUME, 'on_buffer': plexpy.CONFIG.PUSHALOT_ON_BUFFER, 'on_watched': plexpy.CONFIG.PUSHALOT_ON_WATCHED, - 'on_created': plexpy.CONFIG.PUSHALOT_ON_CREATED + 'on_created': plexpy.CONFIG.PUSHALOT_ON_CREATED, + 'on_down': plexpy.CONFIG.PUSHALOT_ON_DOWN }, {'name': 'Pushbullet', 'id': AGENT_IDS['Pushbullet'], @@ -144,7 +150,8 @@ def available_notification_agents(): 'on_resume': plexpy.CONFIG.PUSHBULLET_ON_RESUME, 'on_buffer': plexpy.CONFIG.PUSHBULLET_ON_BUFFER, 'on_watched': plexpy.CONFIG.PUSHBULLET_ON_WATCHED, - 'on_created': plexpy.CONFIG.PUSHBULLET_ON_CREATED + 'on_created': plexpy.CONFIG.PUSHBULLET_ON_CREATED, + 'on_down': plexpy.CONFIG.PUSHBULLET_ON_DOWN }, {'name': 'Pushover', 'id': AGENT_IDS['Pushover'], @@ -157,7 +164,8 @@ def available_notification_agents(): 'on_resume': plexpy.CONFIG.PUSHOVER_ON_RESUME, 'on_buffer': plexpy.CONFIG.PUSHOVER_ON_BUFFER, 'on_watched': plexpy.CONFIG.PUSHOVER_ON_WATCHED, - 'on_created': plexpy.CONFIG.PUSHOVER_ON_CREATED + 'on_created': plexpy.CONFIG.PUSHOVER_ON_CREATED, + 'on_down': plexpy.CONFIG.PUSHOVER_ON_DOWN }, {'name': 'Boxcar2', 'id': AGENT_IDS['Boxcar2'], @@ -170,7 +178,8 @@ def available_notification_agents(): 'on_resume': plexpy.CONFIG.BOXCAR_ON_RESUME, 'on_buffer': plexpy.CONFIG.BOXCAR_ON_BUFFER, 'on_watched': plexpy.CONFIG.BOXCAR_ON_WATCHED, - 'on_created': plexpy.CONFIG.BOXCAR_ON_CREATED + 'on_created': plexpy.CONFIG.BOXCAR_ON_CREATED, + 'on_down': plexpy.CONFIG.BOXCAR_ON_DOWN }, {'name': 'E-mail', 'id': AGENT_IDS['Email'], @@ -183,7 +192,8 @@ def available_notification_agents(): 'on_resume': plexpy.CONFIG.EMAIL_ON_RESUME, 'on_buffer': plexpy.CONFIG.EMAIL_ON_BUFFER, 'on_watched': plexpy.CONFIG.EMAIL_ON_WATCHED, - 'on_created': plexpy.CONFIG.EMAIL_ON_CREATED + 'on_created': plexpy.CONFIG.EMAIL_ON_CREATED, + 'on_down': plexpy.CONFIG.EMAIL_ON_DOWN }, {'name': 'Twitter', 'id': AGENT_IDS['Twitter'], @@ -196,7 +206,8 @@ def available_notification_agents(): 'on_resume': plexpy.CONFIG.TWITTER_ON_RESUME, 'on_buffer': plexpy.CONFIG.TWITTER_ON_BUFFER, 'on_watched': plexpy.CONFIG.TWITTER_ON_WATCHED, - 'on_created': plexpy.CONFIG.TWITTER_ON_CREATED + 'on_created': plexpy.CONFIG.TWITTER_ON_CREATED, + 'on_down': plexpy.CONFIG.TWITTER_ON_DOWN }, {'name': 'IFTTT', 'id': AGENT_IDS['IFTTT'], @@ -209,7 +220,8 @@ def available_notification_agents(): 'on_resume': plexpy.CONFIG.IFTTT_ON_RESUME, 'on_buffer': plexpy.CONFIG.IFTTT_ON_BUFFER, 'on_watched': plexpy.CONFIG.IFTTT_ON_WATCHED, - 'on_created': plexpy.CONFIG.IFTTT_ON_CREATED + 'on_created': plexpy.CONFIG.IFTTT_ON_CREATED, + 'on_down': plexpy.CONFIG.IFTTT_ON_DOWN }, {'name': 'Telegram', 'id': AGENT_IDS['Telegram'], @@ -222,7 +234,8 @@ def available_notification_agents(): 'on_resume': plexpy.CONFIG.TELEGRAM_ON_RESUME, 'on_buffer': plexpy.CONFIG.TELEGRAM_ON_BUFFER, 'on_watched': plexpy.CONFIG.TELEGRAM_ON_WATCHED, - 'on_created': plexpy.CONFIG.TELEGRAM_ON_CREATED + 'on_created': plexpy.CONFIG.TELEGRAM_ON_CREATED, + 'on_down': plexpy.CONFIG.TELEGRAM_ON_DOWN } ] @@ -240,7 +253,8 @@ def available_notification_agents(): 'on_resume': plexpy.CONFIG.OSX_NOTIFY_ON_RESUME, 'on_buffer': plexpy.CONFIG.OSX_NOTIFY_ON_BUFFER, 'on_watched': plexpy.CONFIG.OSX_NOTIFY_ON_WATCHED, - 'on_created': plexpy.CONFIG.OSX_NOTIFY_ON_CREATED + 'on_created': plexpy.CONFIG.OSX_NOTIFY_ON_CREATED, + 'on_down': plexpy.CONFIG.OSX_NOTIFY_ON_DOWN }) return agents diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 249b1360..97bf78cf 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -462,6 +462,8 @@ class WebInterface(object): "notify_on_watched_body_text": plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT, "notify_on_created_subject_text": plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT, "notify_on_created_body_text": plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT, + "notify_on_down_subject_text": plexpy.CONFIG.NOTIFY_ON_DOWN_SUBJECT_TEXT, + "notify_on_down_body_text": plexpy.CONFIG.NOTIFY_ON_DOWN_BODY_TEXT, "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, "home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE), "home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT, From 4d87666a42e72449fca9748f101a8667b146d1da Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Mon, 16 Nov 2015 23:19:59 -0800 Subject: [PATCH 12/20] Schedule job to check if server down --- plexpy/__init__.py | 2 ++ plexpy/activity_pinger.py | 20 ++++++++++++++++++++ plexpy/pmsconnect.py | 15 ++++++++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 39c9a470..9e3e25c6 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -287,6 +287,8 @@ def initialize_scheduler(): hours=12, minutes=0, seconds=0) schedule_job(activity_pinger.check_recently_added, 'Check for recently added items', hours=0, minutes=0, seconds=seconds) + schedule_job(activity_pinger.check_server_response, 'Check for server response', + hours=0, minutes=0, seconds=seconds) # If we're not using websockets then fall back to polling if not CONFIG.MONITORING_USE_WEBSOCKET or POLLING_FAILOVER: diff --git a/plexpy/activity_pinger.py b/plexpy/activity_pinger.py index baa280d2..9db9dd32 100644 --- a/plexpy/activity_pinger.py +++ b/plexpy/activity_pinger.py @@ -20,6 +20,7 @@ import plexpy import time monitor_lock = threading.Lock() +ping_count = 0 def check_active_sessions(ws_request=False): @@ -219,3 +220,22 @@ def check_recently_added(): # Fire off notifications threading.Thread(target=notification_handler.notify_timeline, kwargs=dict(timeline_data=item, notify_action='created')).start() + + +def check_server_response(): + + with monitor_lock: + pms_connect = pmsconnect.PmsConnect() + response = pms_connect.get_server_response() + global ping_count + + if not response: + ping_count += 1 + logger.warn(u"PlexPy Monitor :: Unable to get a response from the server, ping attempt %s." % str(ping_count)) + + if ping_count == 3: + # Fire off notifications + threading.Thread(target=notification_handler.notify_timeline, + kwargs=dict(notify_action='down')).start() + else: + ping_count = 0 \ No newline at end of file diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 9ab4fd80..e647b34c 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -1648,4 +1648,17 @@ class PmsConnect(object): 'children': parents} } - return key_list \ No newline at end of file + return key_list + + """ + Check for a server response. + + Output: bool + """ + def get_server_response(self): + response = self.get_server_list() + + if not response: + return False + else: + return True From 707c30b0afcce5f23be66c389815b894f525f67a Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Mon, 16 Nov 2015 23:28:43 -0800 Subject: [PATCH 13/20] Fix missing pmsconnect --- plexpy/notification_handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 1e1768c9..5282bb3c 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -269,6 +269,7 @@ def set_notify_state(session, state, agent_info): def build_notify_text(session=None, timeline=None, state=None): + from plexpy import pmsconnect import re # Get the server name @@ -280,6 +281,7 @@ def build_notify_text(session=None, timeline=None, state=None): elif timeline: rating_key = timeline['rating_key'] + pms_connect = pmsconnect.PmsConnect() metadata_list = pms_connect.get_metadata_details(rating_key=rating_key) if metadata_list: From 1c4df69e616745896d11a2c962ee4f48e86c1d30 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Mon, 16 Nov 2015 23:52:03 -0800 Subject: [PATCH 14/20] Toggle bell icon for on_down --- data/interfaces/default/settings.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 477678d7..2ae60de4 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -692,7 +692,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents() % for agent in available_notification_agents:
  • - % if agent['on_play'] or agent['on_stop'] or agent['on_pause'] or agent['on_resume'] or agent['on_buffer'] or agent['on_watched'] or agent['on_created']: + % if agent['on_play'] or agent['on_stop'] or agent['on_pause'] or agent['on_resume'] or agent['on_buffer'] or agent['on_watched'] or agent['on_created'] or agent['on_down']: % else: From c3ea35806ef3c470af66f9dac8b10060bf5e5e90 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Tue, 17 Nov 2015 06:57:02 -0800 Subject: [PATCH 15/20] Move check for server down to check_active_sessions --- plexpy/__init__.py | 2 -- plexpy/activity_pinger.py | 34 ++++++++++++++-------------------- plexpy/notification_handler.py | 2 +- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 9e3e25c6..39c9a470 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -287,8 +287,6 @@ def initialize_scheduler(): hours=12, minutes=0, seconds=0) schedule_job(activity_pinger.check_recently_added, 'Check for recently added items', hours=0, minutes=0, seconds=seconds) - schedule_job(activity_pinger.check_server_response, 'Check for server response', - hours=0, minutes=0, seconds=seconds) # If we're not using websockets then fall back to polling if not CONFIG.MONITORING_USE_WEBSOCKET or POLLING_FAILOVER: diff --git a/plexpy/activity_pinger.py b/plexpy/activity_pinger.py index 9db9dd32..c1701769 100644 --- a/plexpy/activity_pinger.py +++ b/plexpy/activity_pinger.py @@ -163,6 +163,19 @@ def check_active_sessions(ws_request=False): monitor_process.write_session(session) else: logger.debug(u"PlexPy Monitor :: Unable to read session list.") + response = pms_connect.get_server_response() + global ping_count + + if not response: + ping_count += 1 + logger.warn(u"PlexPy Monitor :: Unable to get a response from the server, ping attempt %s." % str(ping_count)) + + if ping_count == 3: + # Fire off notifications + threading.Thread(target=notification_handler.notify_timeline, + kwargs=dict(notify_action='down')).start() + else: + ping_count = 0 def check_recently_added(): @@ -219,23 +232,4 @@ def check_recently_added(): # Fire off notifications threading.Thread(target=notification_handler.notify_timeline, - kwargs=dict(timeline_data=item, notify_action='created')).start() - - -def check_server_response(): - - with monitor_lock: - pms_connect = pmsconnect.PmsConnect() - response = pms_connect.get_server_response() - global ping_count - - if not response: - ping_count += 1 - logger.warn(u"PlexPy Monitor :: Unable to get a response from the server, ping attempt %s." % str(ping_count)) - - if ping_count == 3: - # Fire off notifications - threading.Thread(target=notification_handler.notify_timeline, - kwargs=dict(notify_action='down')).start() - else: - ping_count = 0 \ No newline at end of file + kwargs=dict(timeline_data=item, notify_action='created')).start() \ No newline at end of file diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 5282bb3c..c9db601a 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -179,7 +179,7 @@ def notify_timeline(timeline_data=None, notify_action=None): body=notify_strings[1]) # Set the notification state in the db set_notify_state(session=timeline_data, state=notify_action, agent_info=agent) - elif notify_action == 'down': + elif not timeline_data and notify_action: for agent in notifiers.available_notification_agents(): if agent['on_down'] and notify_action == 'down': # Build and send notification From 4fffbf8a0cefacc129062bfda9b97b1d4496aad6 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Tue, 17 Nov 2015 19:31:13 -0800 Subject: [PATCH 16/20] Add server uptime --- data/interfaces/default/settings.html | 4 ++++ plexpy/activity_pinger.py | 4 +++- plexpy/helpers.py | 27 ++++++++++++++++++++++++- plexpy/notification_handler.py | 29 ++++++++++++++++++++++++--- plexpy/plextv.py | 19 ++++++++++++++++++ 5 files changed, 78 insertions(+), 5 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 2ae60de4..3d78c3e4 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -902,6 +902,10 @@ available_notification_agents = sorted(notifiers.available_notification_agents() {server_name} The name of your Plex Server. + + {server_uptime} + The uptime (in days, hours, mins, secs) of your Plex Server. + {user} The username of the person streaming. diff --git a/plexpy/activity_pinger.py b/plexpy/activity_pinger.py index c1701769..d01b49c1 100644 --- a/plexpy/activity_pinger.py +++ b/plexpy/activity_pinger.py @@ -26,6 +26,8 @@ ping_count = 0 def check_active_sessions(ws_request=False): with monitor_lock: + global ping_count + pms_connect = pmsconnect.PmsConnect() session_list = pms_connect.get_current_activity() monitor_db = database.MonitorDatabase() @@ -33,6 +35,7 @@ def check_active_sessions(ws_request=False): # logger.debug(u"PlexPy Monitor :: Checking for active streams.") if session_list: + ping_count = 0 media_container = session_list['sessions'] # Check our temp table for what we must do with the new streams @@ -164,7 +167,6 @@ def check_active_sessions(ws_request=False): else: logger.debug(u"PlexPy Monitor :: Unable to read session list.") response = pms_connect.get_server_response() - global ping_count if not response: ping_count += 1 diff --git a/plexpy/helpers.py b/plexpy/helpers.py index f81cbd84..fbefaaa1 100644 --- a/plexpy/helpers.py +++ b/plexpy/helpers.py @@ -1,4 +1,4 @@ -# This file is part of PlexPy. +# 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 @@ -144,6 +144,31 @@ def now(): now = datetime.datetime.now() return now.strftime("%Y-%m-%d %H:%M:%S") +def human_duration(s): + + hd = '' + + if str(s).isdigit(): + d = int(s / 84600) + h = int((s % 84600) / 3600) + m = int(((s % 84600) % 3600) / 60) + s = int(((s % 84600) % 3600) % 60) + + hd_list = [] + if d > 0: + hd_list.append(str(d) + ' days') + if h > 0: + hd_list.append(str(h) + ' hrs') + if m > 0: + hd_list.append(str(m) + ' mins') + if s > 0: + hd_list.append(str(s) + ' secs') + + hd = ' '.join(hd_list) + + return hd + else: + return hd def get_age(date): diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index c9db601a..09a3a417 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.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, config, notifiers, database, helpers +from plexpy import logger, config, notifiers, database, helpers, plextv, pmsconnect import plexpy import time @@ -269,12 +269,22 @@ def set_notify_state(session, state, agent_info): def build_notify_text(session=None, timeline=None, state=None): - from plexpy import pmsconnect import re # Get the server name server_name = plexpy.CONFIG.PMS_NAME + # Get the server uptime + plex_tv = plextv.PlexTV() + server_times = plex_tv.get_server_times() + + if server_times: + updated_at = server_times[0]['updated_at'] + server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_float(updated_at))) + else: + logger.error(u"PlexPy Notifier :: Unable to retrieve server uptime.") + server_uptime = 'N/A' + # Get metadata feed for item if session: rating_key = session['rating_key'] @@ -388,6 +398,7 @@ def build_notify_text(session=None, timeline=None, state=None): progress_percent = helpers.get_percent(view_offset, duration) available_params = {'server_name': server_name, + 'server_uptime': server_uptime, 'user': user, 'platform': platform, 'player': player, @@ -597,10 +608,22 @@ def build_server_notify_text(state=None): # Get the server name server_name = plexpy.CONFIG.PMS_NAME + # Get the server uptime + plex_tv = plextv.PlexTV() + server_times = plex_tv.get_server_times() + + if server_times: + updated_at = server_times[0]['updated_at'] + server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_float(updated_at))) + else: + logger.error(u"PlexPy Notifier :: Unable to retrieve server uptime.") + server_uptime = 'N/A' + on_down_subject = plexpy.CONFIG.NOTIFY_ON_DOWN_SUBJECT_TEXT on_down_body = plexpy.CONFIG.NOTIFY_ON_DOWN_BODY_TEXT - available_params = {'server_name': server_name} + available_params = {'server_name': server_name, + 'server_uptime': server_uptime} # Default text subject_text = 'PlexPy (%s)' % server_name diff --git a/plexpy/plextv.py b/plexpy/plextv.py index 03372fdc..4610121a 100644 --- a/plexpy/plextv.py +++ b/plexpy/plextv.py @@ -444,3 +444,22 @@ class PlexTV(object): return clean_servers return json.dumps(clean_servers, indent=4) + + def get_server_times(self): + servers = self.get_plextv_server_list(output_format='xml') + server_times = [] + + try: + xml_head = servers.getElementsByTagName('Server') + except: + logger.warn("Error parsing XML for Plex servers.") + return [] + + for a in xml_head: + if helpers.get_xml_attr(a, 'machineIdentifier') == plexpy.CONFIG.PMS_IDENTIFIER: + server_times.append({"created_at": helpers.get_xml_attr(a, 'createdAt'), + "updated_at": helpers.get_xml_attr(a, 'updatedAt') + }) + break + + return server_times From ca91adbd53888d8d686e4a4d8640c72a8dbab261 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Tue, 17 Nov 2015 21:54:54 -0800 Subject: [PATCH 17/20] Add check for Plex external port down --- plexpy/__init__.py | 2 ++ plexpy/activity_pinger.py | 76 +++++++++++++++++++++++++++------------ plexpy/plextv.py | 19 ++++++++++ 3 files changed, 75 insertions(+), 22 deletions(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 39c9a470..9e3e25c6 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -287,6 +287,8 @@ def initialize_scheduler(): hours=12, minutes=0, seconds=0) schedule_job(activity_pinger.check_recently_added, 'Check for recently added items', hours=0, minutes=0, seconds=seconds) + schedule_job(activity_pinger.check_server_response, 'Check for server response', + hours=0, minutes=0, seconds=seconds) # If we're not using websockets then fall back to polling if not CONFIG.MONITORING_USE_WEBSOCKET or POLLING_FAILOVER: diff --git a/plexpy/activity_pinger.py b/plexpy/activity_pinger.py index d01b49c1..9f4b6a0e 100644 --- a/plexpy/activity_pinger.py +++ b/plexpy/activity_pinger.py @@ -13,21 +13,21 @@ # You should have received a copy of the GNU General Public License # along with PlexPy. If not, see . -from plexpy import logger, pmsconnect, notification_handler, database, helpers, activity_processor +from plexpy import logger, pmsconnect, plextv, notification_handler, database, helpers, activity_processor import threading import plexpy import time +import urllib2 monitor_lock = threading.Lock() -ping_count = 0 +ext_ping_count = 0 +int_ping_count = 0 def check_active_sessions(ws_request=False): with monitor_lock: - global ping_count - pms_connect = pmsconnect.PmsConnect() session_list = pms_connect.get_current_activity() monitor_db = database.MonitorDatabase() @@ -35,7 +35,6 @@ def check_active_sessions(ws_request=False): # logger.debug(u"PlexPy Monitor :: Checking for active streams.") if session_list: - ping_count = 0 media_container = session_list['sessions'] # Check our temp table for what we must do with the new streams @@ -166,19 +165,6 @@ def check_active_sessions(ws_request=False): monitor_process.write_session(session) else: logger.debug(u"PlexPy Monitor :: Unable to read session list.") - response = pms_connect.get_server_response() - - if not response: - ping_count += 1 - logger.warn(u"PlexPy Monitor :: Unable to get a response from the server, ping attempt %s." % str(ping_count)) - - if ping_count == 3: - # Fire off notifications - threading.Thread(target=notification_handler.notify_timeline, - kwargs=dict(notify_action='down')).start() - else: - ping_count = 0 - def check_recently_added(): @@ -201,7 +187,7 @@ def check_recently_added(): metadata = [metadata_list['metadata']] else: logger.error(u"PlexPy Monitor :: Unable to retrieve metadata for rating_key %s" \ - % str(item['rating_key'])) + % str(item['rating_key'])) else: metadata_list = pms_connect.get_metadata_children_details(item['rating_key']) @@ -209,7 +195,7 @@ def check_recently_added(): metadata = metadata_list['metadata'] else: logger.error(u"PlexPy Monitor :: Unable to retrieve children metadata for rating_key %s" \ - % str(item['rating_key'])) + % str(item['rating_key'])) if metadata: if not plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT: @@ -230,8 +216,54 @@ def check_recently_added(): item = metadata_list['metadata'] else: logger.error(u"PlexPy Monitor :: Unable to retrieve grandparent metadata for grandparent_rating_key %s" \ - % str(item['rating_key'])) + % str(item['rating_key'])) # Fire off notifications threading.Thread(target=notification_handler.notify_timeline, - kwargs=dict(timeline_data=item, notify_action='created')).start() \ No newline at end of file + kwargs=dict(timeline_data=item, notify_action='created')).start() + +def check_server_response(): + + with monitor_lock: + plex_tv = plextv.PlexTV() + external_response = plex_tv.get_server_response() + pms_connect = pmsconnect.PmsConnect() + internal_response = pms_connect.get_server_response() + + global ext_ping_count + global int_ping_count + + if not external_response: + ext_ping_count += 1 + logger.warn(u"PlexPy Monitor :: Plex remote access port mapping failed, ping attempt %s." \ + % str(ext_ping_count)) + else: + host = external_response[0]['host'] + port = external_response[0]['port'] + + try: + http_response = urllib2.urlopen('http://' + host + ':' + port) + except urllib2.HTTPError, e: + ext_ping_count = 0 + except urllib2.URLError, e: + ext_ping_count += 1 + logger.warn(u"PlexPy Monitor :: Unable to get an external response from the server, ping attempt %s." \ + % str(ext_ping_count)) + else: + ext_ping_count = 0 + + if not internal_response: + int_ping_count += 1 + logger.warn(u"PlexPy Monitor :: Unable to get an internal response from the server, ping attempt %s." \ + % str(int_ping_count)) + else: + int_ping_count = 0 + + if ext_ping_count == 3: + # Fire off notifications + threading.Thread(target=notification_handler.notify_timeline, + kwargs=dict(notify_action='extdown')).start() + if int_ping_count == 3: + # Fire off notifications + threading.Thread(target=notification_handler.notify_timeline, + kwargs=dict(notify_action='intdown')).start() \ No newline at end of file diff --git a/plexpy/plextv.py b/plexpy/plextv.py index 4610121a..76c55d69 100644 --- a/plexpy/plextv.py +++ b/plexpy/plextv.py @@ -463,3 +463,22 @@ class PlexTV(object): break return server_times + + def get_server_response(self): + response = self.get_plextv_server_list(output_format='xml') + + server_url = [] + + try: + xml_head = response.getElementsByTagName('Server') + except: + return False + + for a in xml_head: + if helpers.get_xml_attr(a, 'machineIdentifier') == plexpy.CONFIG.PMS_IDENTIFIER: + server_url.append({"host": helpers.get_xml_attr(a, 'host'), + "port": helpers.get_xml_attr(a, 'port') + }) + break + + return server_url \ No newline at end of file From 223e2b2b323f5cc1ac5495637dfc39f086cde5a9 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Tue, 17 Nov 2015 21:55:07 -0800 Subject: [PATCH 18/20] Add notification for Plex external port down --- .../default/notification_triggers_modal.html | 13 +++-- data/interfaces/default/settings.html | 43 ++++++++++++----- plexpy/config.py | 48 ++++++++++++------- plexpy/notification_handler.py | 46 ++++++++++++++---- plexpy/notifiers.py | 42 ++++++++++------ plexpy/webserve.py | 6 ++- 6 files changed, 142 insertions(+), 56 deletions(-) diff --git a/data/interfaces/default/notification_triggers_modal.html b/data/interfaces/default/notification_triggers_modal.html index f6da7604..8b752907 100644 --- a/data/interfaces/default/notification_triggers_modal.html +++ b/data/interfaces/default/notification_triggers_modal.html @@ -66,10 +66,17 @@ from plexpy import helpers
    -

    Trigger notification when the Plex Media Server cannot be reached.

    +

    Trigger notification when the Plex Media Server cannot be reached externally.

    +
    +
    + +

    Trigger notification when the Plex Media Server cannot be reached internally.

    diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 3d78c3e4..47cda611 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -538,7 +538,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
    • - +
    • - +
    • - +
    • - +
    • - +
    • - +
      • - +
      • - + +
      • +
      • + +
      • - +