From 1ff58a85dc2305d21c888fcad291a24644c3023d Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sun, 18 Oct 2015 10:27:18 -0700 Subject: [PATCH 01/84] Fix bug in "Last Watched" statistics *Fix if two users watched the same item, it would only show the most recent user. --- plexpy/datafactory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index f09ae30c..1ffdc2e5 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -564,7 +564,7 @@ class DataFactory(object): 'AND (session_history_metadata.media_type = "movie" ' \ 'OR session_history_metadata.media_type = "episode") ' \ 'AND percent_complete >= %s ' \ - 'GROUP BY session_history_metadata.full_title ' \ + 'GROUP BY session_history.id ' \ 'ORDER BY last_watch DESC ' \ 'LIMIT %s' % (time_range, notify_watched_percent, stats_count) result = monitor_db.select(query) From 2243cd1de9a12f7e4c38c9ef7bc24f595939b5e4 Mon Sep 17 00:00:00 2001 From: Jonathan Wong Date: Sun, 18 Oct 2015 11:50:01 -0700 Subject: [PATCH 02/84] Add filtering of media_type from history table --- data/interfaces/default/history.html | 55 ++++++++++++++++++++++------ data/interfaces/default/user.html | 42 ++++++++++++++++++--- plexpy/datatables.py | 13 ++++--- plexpy/webserve.py | 17 +++++---- 4 files changed, 98 insertions(+), 29 deletions(-) diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index 3f589390..050e82c4 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -76,19 +76,52 @@ % endif +% if data['type'] != 'library': + +% endif % if data['type'] != 'library' and data['rating']: -% if data['type'] == 'library': +% if data['media_type'] == 'library': -% elif data['type'] == 'show' or data['type'] == 'artist': +% elif data['media_type'] == 'show' or data['media_type'] == 'artist': -% elif data['type'] == 'season' or data['type'] == 'album': +% elif data['media_type'] == 'season' or data['media_type'] == 'album': -% elif data['type'] == 'episode' or data['type'] == 'track' or data['type'] == 'movie': +% elif data['media_type'] == 'episode' or data['media_type'] == 'track' or data['media_type'] == 'movie': -% if data['type'] == 'show' or data['type'] == 'season' or data['type'] == 'artist' or data['type'] == 'album': +% if data['media_type'] == 'show' or data['media_type'] == 'season' or data['media_type'] == 'artist' or data['media_type'] == 'album': % endif -% if data['type'] != 'library': +% if data['media_type'] != 'library': % endif -% if data['type'] != 'library' and data['rating']: +% if data['media_type'] != 'library' and data['rating']: diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 75873d0b..c15be036 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -515,7 +515,7 @@ available_notification_agents = notifiers.available_notification_agents() here to view usage information.


- +
  • + + +
  • 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 59/84] 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 60/84] 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 61/84] 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 62/84] 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 63/84] 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 64/84] 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 65/84] 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()
    • - +
    • - +
    • - +
    • - +
    • - +
    • - +
      • - +
      • - + +
      • +
      • + +
      • - +