From 9ef389d335e9077b6467172d0e049d8b8c8994c2 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Mon, 22 Feb 2016 21:20:05 -0800 Subject: [PATCH 01/48] Actually allow HTML tags for Pushover --- plexpy/notification_handler.py | 120 +++++++++++++++++++++++---------- 1 file changed, 85 insertions(+), 35 deletions(-) diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 62c83e43..0ceb4692 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -50,7 +50,10 @@ def notify(stream_data=None, notify_action=None): for agent in notifiers.available_notification_agents(): if agent['on_play'] and notify_action == 'play': # Build and send notification - notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) + notify_strings, metadata = build_notify_text(session=stream_data, + notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -68,7 +71,10 @@ def notify(stream_data=None, notify_action=None): elif agent['on_stop'] and notify_action == 'stop' \ and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < plexpy.CONFIG.NOTIFY_WATCHED_PERCENT): # Build and send notification - notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) + notify_strings, metadata = build_notify_text(session=stream_data, + notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -86,7 +92,10 @@ def notify(stream_data=None, notify_action=None): elif agent['on_pause'] and notify_action == 'pause' \ and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99): # Build and send notification - notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) + notify_strings, metadata = build_notify_text(session=stream_data, + notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -104,7 +113,10 @@ def notify(stream_data=None, notify_action=None): elif agent['on_resume'] and notify_action == 'resume' \ and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99): # Build and send notification - notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) + notify_strings, metadata = build_notify_text(session=stream_data, + notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -121,7 +133,10 @@ def notify(stream_data=None, notify_action=None): elif agent['on_buffer'] and notify_action == 'buffer': # Build and send notification - notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) + notify_strings, metadata = build_notify_text(session=stream_data, + notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -143,7 +158,10 @@ def notify(stream_data=None, notify_action=None): # If there is nothing in the notify_log for our agent id but it is enabled we should notify if not any(d['agent_id'] == agent['id'] and d['notify_action'] == notify_action for d in notify_states): # Build and send notification - notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) + notify_strings, metadata = build_notify_text(session=stream_data, + notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -163,7 +181,10 @@ def notify(stream_data=None, notify_action=None): for agent in notifiers.available_notification_agents(): if agent['on_play'] and notify_action == 'play': # Build and send notification - notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) + notify_strings, metadata = build_notify_text(session=stream_data, + notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -180,7 +201,10 @@ def notify(stream_data=None, notify_action=None): elif agent['on_stop'] and notify_action == 'stop': # Build and send notification - notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) + notify_strings, metadata = build_notify_text(session=stream_data, + notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -197,7 +221,10 @@ def notify(stream_data=None, notify_action=None): elif agent['on_pause'] and notify_action == 'pause': # Build and send notification - notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) + notify_strings, metadata = build_notify_text(session=stream_data, + notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -214,7 +241,10 @@ def notify(stream_data=None, notify_action=None): elif agent['on_resume'] and notify_action == 'resume': # Build and send notification - notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) + notify_strings, metadata = build_notify_text(session=stream_data, + notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -231,7 +261,10 @@ def notify(stream_data=None, notify_action=None): elif agent['on_buffer'] and notify_action == 'buffer': # Build and send notification - notify_strings, metadata = build_notify_text(session=stream_data, notify_action=notify_action) + notify_strings, metadata = build_notify_text(session=stream_data, + notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -260,7 +293,10 @@ def notify_timeline(timeline_data=None, notify_action=None): for agent in notifiers.available_notification_agents(): if agent['on_created'] and notify_action == 'created': # Build and send notification - notify_strings, metadata = build_notify_text(timeline=timeline_data, notify_action=notify_action) + notify_strings, metadata = build_notify_text(timeline=timeline_data, + notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -279,7 +315,9 @@ def notify_timeline(timeline_data=None, notify_action=None): for agent in notifiers.available_notification_agents(): if agent['on_extdown'] and notify_action == 'extdown': # Build and send notification - notify_strings = build_server_notify_text(notify_action=notify_action) + notify_strings = build_server_notify_text(notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -295,7 +333,9 @@ def notify_timeline(timeline_data=None, notify_action=None): if agent['on_intdown'] and notify_action == 'intdown': # Build and send notification - notify_strings = build_server_notify_text(notify_action=notify_action) + notify_strings = build_server_notify_text(notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -311,7 +351,9 @@ def notify_timeline(timeline_data=None, notify_action=None): if agent['on_extup'] and notify_action == 'extup': # Build and send notification - notify_strings = build_server_notify_text(notify_action=notify_action) + notify_strings = build_server_notify_text(notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -327,7 +369,9 @@ def notify_timeline(timeline_data=None, notify_action=None): if agent['on_intup'] and notify_action == 'intup': # Build and send notification - notify_strings = build_server_notify_text(notify_action=notify_action) + notify_strings = build_server_notify_text(notify_action=notify_action, + agent_id=agent['id']) + notifiers.send_notification(agent_id=agent['id'], subject=notify_strings[0], body=notify_strings[1], @@ -395,7 +439,7 @@ def set_notify_state(session, notify_action, agent_info, notify_strings, metadat logger.error(u"PlexPy NotificationHandler :: Unable to set notify state.") -def build_notify_text(session=None, timeline=None, notify_action=None): +def build_notify_text(session=None, timeline=None, notify_action=None, agent_id=None): # Get time formats date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','') time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','') @@ -450,21 +494,21 @@ def build_notify_text(session=None, timeline=None, notify_action=None): 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)) - on_stop_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT)) - on_stop_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT)) - on_pause_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT)) - on_pause_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT)) - on_resume_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT)) - on_resume_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT)) - on_buffer_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT)) - on_buffer_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT)) - on_watched_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT)) - on_watched_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT)) - on_created_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT)) - on_created_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT)) - script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT)) + on_start_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT), agent_id) + on_start_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT), agent_id) + on_stop_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT), agent_id) + on_stop_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT), agent_id) + on_pause_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT), agent_id) + on_pause_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT), agent_id) + on_resume_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT), agent_id) + on_resume_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT), agent_id) + on_buffer_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT), agent_id) + on_buffer_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT), agent_id) + on_watched_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT), agent_id) + on_watched_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT), agent_id) + on_created_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT), agent_id) + on_created_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT), agent_id) + script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT), agent_id) else: on_start_subject = plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT on_start_body = plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT @@ -854,7 +898,7 @@ def build_notify_text(session=None, timeline=None, notify_action=None): return None -def build_server_notify_text(notify_action=None): +def build_server_notify_text(notify_action=None, agent_id=None): # Get time formats date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','') time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','') @@ -1000,6 +1044,12 @@ def build_server_notify_text(notify_action=None): return None -def strip_tag(data): - p = re.compile(r'<.*?>') +def strip_tag(data, agent_id): + # Allow tags b, i, u, a[href], font[color] for Pushover + if agent_id == 7: + p = re.compile(r'<(?!/?(b>|i>|u>)|(a\shref=\"[^\"\'\s]+\"|/a>|font\scolor=\"[^\"\'\s]+\"|/font>)).*?>', + re.IGNORECASE | re.DOTALL) + else: + p = re.compile(r'<.*?>', re.IGNORECASE | re.DOTALL) + return p.sub('', data) \ No newline at end of file From eedd0d9c07c3da3987ee11d9683bdc22706f5673 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 23 Feb 2016 18:29:33 -0800 Subject: [PATCH 02/48] Use subprocess.Popen on windows to restart PlexPy * See python bug: https://bugs.python.org/issue19066 --- plexpy/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 82203de5..9e92bc62 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -890,7 +890,13 @@ def shutdown(restart=False, update=False): if '--nolaunch' not in args: args += ['--nolaunch'] logger.info('Restarting PlexPy with %s', args) - os.execv(exe, args) + + # os.execv fails with spaced names on Windows + # https://bugs.python.org/issue19066 + if os.name == 'nt': + subprocess.Popen(args, cwd=os.getcwd()) + else: + os.execv(exe, args) os._exit(0) From 1920c9b7e38cad50b764fc60c6e03fc35f66a52d Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 23 Feb 2016 19:05:20 -0800 Subject: [PATCH 03/48] Reconnect websocket on server change --- plexpy/web_socket.py | 34 +++++++++++++++++++++++++--------- plexpy/webserve.py | 4 +++- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/plexpy/web_socket.py b/plexpy/web_socket.py index 2de97dcf..76fdfe3d 100644 --- a/plexpy/web_socket.py +++ b/plexpy/web_socket.py @@ -25,6 +25,7 @@ import websocket name = 'websocket' opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY) +ws_reconnect = False def start_thread(): @@ -34,6 +35,11 @@ def start_thread(): threading.Thread(target=run).start() +def reconnect(): + global ws_reconnect + ws_reconnect = True + + def run(): from websocket import create_connection @@ -51,19 +57,21 @@ def run(): if plexpy.CONFIG.PMS_TOKEN: uri += '?X-Plex-Token=' + plexpy.CONFIG.PMS_TOKEN + global ws_reconnect + ws_reconnect = False ws_connected = False reconnects = 0 # Try an open the websocket connection - if it fails after 15 retries fallback to polling while not ws_connected and reconnects <= 15: try: - logger.info(u'PlexPy WebSocket :: Opening%s websocket, connection attempt %s.' % (secure, str(reconnects + 1))) + logger.info(u"PlexPy WebSocket :: Opening%s websocket, connection attempt %s." % (secure, str(reconnects + 1))) ws = create_connection(uri) reconnects = 0 ws_connected = True - logger.info(u'PlexPy WebSocket :: Ready') + logger.info(u"PlexPy WebSocket :: Ready") except IOError, e: - logger.error(u'PlexPy WebSocket :: %s.' % e) + logger.error(u"PlexPy WebSocket :: %s." % e) reconnects += 1 time.sleep(5) @@ -81,22 +89,30 @@ def run(): if reconnects > 1: time.sleep(5) - logger.warn(u'PlexPy WebSocket :: Connection has closed, reconnecting...') + logger.warn(u"PlexPy WebSocket :: Connection has closed, reconnecting...") try: ws = create_connection(uri) except IOError, e: - logger.info(u'PlexPy WebSocket :: %s.' % e) + logger.info(u"PlexPy WebSocket :: %s." % e) else: + ws.shutdown() ws_connected = False break - if not ws_connected: - logger.error(u'PlexPy WebSocket :: Connection unavailable, falling back to polling.') + # Check if we recieved a restart notification and close websocket connection cleanly + if ws_reconnect: + logger.info(u"PlexPy WebSocket :: Reconnecting websocket...") + ws.shutdown() + ws_connected = False + start_thread() + + if not ws_connected and not ws_reconnect: + logger.error(u"PlexPy WebSocket :: Connection unavailable, falling back to polling.") plexpy.POLLING_FAILOVER = True plexpy.initialize_scheduler() - logger.debug(u'PlexPy WebSocket :: Leaving thread.') + logger.debug(u"PlexPy WebSocket :: Leaving thread.") def receive(ws): @@ -124,7 +140,7 @@ def process(opcode, data): try: info = json.loads(data) except Exception as ex: - logger.warn(u'PlexPy WebSocket :: Error decoding message from websocket: %s' % ex) + logger.warn(u"PlexPy WebSocket :: Error decoding message from websocket: %s" % ex) logger.debug(data) return False diff --git a/plexpy/webserve.py b/plexpy/webserve.py index d50dcf5a..2e120524 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -13,7 +13,8 @@ # You should have received a copy of the GNU General Public License # along with PlexPy. If not, see . -from plexpy import logger, notifiers, plextv, pmsconnect, common, log_reader, datafactory, graphs, users, libraries, database +from plexpy import logger, notifiers, plextv, pmsconnect, common, log_reader, \ + datafactory, graphs, users, libraries, database, web_socket from plexpy.helpers import checked, addtoapi, get_ip, create_https_certificates from mako.lookup import TemplateLookup @@ -1327,6 +1328,7 @@ class WebInterface(object): if server_changed: plextv.get_real_pms_url() pmsconnect.get_server_friendly_name() + web_socket.reconnect() # Reconfigure scheduler if intervals changed if reschedule: From 23fa64d28985582b091be54380a4f72a0956d811 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 24 Feb 2016 21:40:19 -0800 Subject: [PATCH 04/48] Change colour of grouped recently added note on checkbox toggle --- data/interfaces/default/settings.html | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index da399148..23b27301 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -749,8 +749,14 @@ available_notification_agents = sorted(notifiers.available_notification_agents() -

Enable to only get one TV Show or Artist notification for a batch of recently added Episodes or Tracks. Movies are unaffected.
- Note: No Season/Episode or Album/Track metadata will be available.

+

+ Enable to only get one TV Show or Artist notification for a batch of recently added Episodes or Tracks. Movies are unaffected.
+ % if config['notify_recently_added_grandparent'] == 'Checked': + Note: No Season/Episode or Album/Track metadata will be available. + % else: + Note: No Season/Episode or Album/Track metadata will be available. + % endif +

@@ -2055,6 +2061,10 @@ $(document).ready(function() { } getSchedulerTable(); + $("#notify_recently_added_grandparent").change(function () { + var c = this.checked ? '#eb8600' : '#737373'; + $('#notify_recently_added_grandparent_note').css('color', c); + }); }); \ No newline at end of file From 05a410b327c3032e59b45772077fb77b0c4244f2 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Wed, 24 Feb 2016 21:40:29 -0800 Subject: [PATCH 05/48] Catch blank view_offset or duration in history table query --- plexpy/datafactory.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 2857417e..1dc5a1a4 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -58,9 +58,9 @@ class DataFactory(object): 'session_history_metadata.thumb', 'session_history_metadata.parent_thumb', 'session_history_metadata.grandparent_thumb', - 'MAX((CASE WHEN view_offset IS NULL THEN 0.1 ELSE view_offset * 1.0 END) / \ - (CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 \ - ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete', + 'MAX((CASE WHEN (view_offset IS NULL OR view_offset = "") THEN 0.1 ELSE view_offset * 1.0 END) / \ + (CASE WHEN (session_history_metadata.duration IS NULL OR session_history_metadata.duration = "") \ + THEN 1.0 ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete', 'session_history_media_info.video_decision', 'session_history_media_info.audio_decision', 'COUNT(*) AS group_count', From 13438e3e25b21e263ea56ef3c64f50d895cae964 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Thu, 25 Feb 2016 09:57:05 -0800 Subject: [PATCH 06/48] Anonymize more URLs --- data/interfaces/default/base.html | 3 ++- data/interfaces/default/ip_address_modal.html | 3 ++- plexpy/notifiers.py | 15 ++++++++++----- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/data/interfaces/default/base.html b/data/interfaces/default/base.html index 0d52c444..e21f5646 100644 --- a/data/interfaces/default/base.html +++ b/data/interfaces/default/base.html @@ -1,6 +1,7 @@ <% import plexpy from plexpy import version +from plexpy.helpers import anon_url %> @@ -142,7 +143,7 @@ from plexpy import version % elif plexpy.CONFIG.CHECK_GITHUB and plexpy.CURRENT_VERSION != plexpy.LATEST_VERSION and plexpy.COMMITS_BEHIND > 0 and plexpy.INSTALL_TYPE != 'win': diff --git a/data/interfaces/default/ip_address_modal.html b/data/interfaces/default/ip_address_modal.html index 9e444f0c..e25b3991 100644 --- a/data/interfaces/default/ip_address_modal.html +++ b/data/interfaces/default/ip_address_modal.html @@ -32,7 +32,8 @@
diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index afcdf741..ccf5bf52 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -1266,7 +1266,7 @@ class TwitterNotifier(object): def return_config_options(self): config_option = [{'label': 'Instructions', - 'description': 'Step 1: Visit \ + 'description': 'Step 1: Visit \ Twitter Apps to Create New App. A vaild "Website" is not required.
\ Step 2: Go to Keys and Access Tokens and click \ Create my access token.
\ @@ -1625,7 +1625,8 @@ class IFTTT(object): config_option = [{'label': 'Ifttt Maker Channel Key', 'value': self.apikey, 'name': 'ifttt_key', - 'description': 'Your Ifttt key. You can get a key from here.', + 'description': 'Your Ifttt key. You can get a key from' + ' here.', 'input_type': 'text' }, {'label': 'Ifttt Event', @@ -1699,13 +1700,17 @@ class TELEGRAM(object): config_option = [{'label': 'Telegram Bot Token', 'value': self.bot_token, 'name': 'telegram_bot_token', - 'description': 'Your Telegram bot token. Contact @BotFather on Telegram to get one.', + 'description': 'Your Telegram bot token. ' + 'Contact @BotFather' + ' on Telegram to get one.', 'input_type': 'text' }, {'label': 'Telegram Chat ID, Group ID, or Channel Username', 'value': self.chat_id, 'name': 'telegram_chat_id', - 'description': 'Your Telegram Chat ID, Group ID, or @channelusername. Contact @myidbot on Telegram to get an ID.', + 'description': 'Your Telegram Chat ID, Group ID, or @channelusername. ' + 'Contact @myidbot' + ' on Telegram to get an ID.', 'input_type': 'text' }, {'label': 'Include Subject Line', @@ -2186,7 +2191,7 @@ class FacebookNotifier(object): def return_config_options(self): config_option = [{'label': 'Instructions', 'description': 'Facebook notifications are currently experimental!

\ - Step 1: Visit \ + Step 1: Visit \ Facebook Developers to add a new app using basic setup.
\ Step 2: Go to Settings > Basic and fill in a \ Contact Email.
\ From ddb0f198a9e69f0b0e08444761a376cd708f400d Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Thu, 25 Feb 2016 21:53:40 -0800 Subject: [PATCH 07/48] Add tooltip to current activity progress bars --- data/interfaces/default/current_activity.html | 8 +++++--- data/interfaces/default/index.html | 16 ++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/data/interfaces/default/current_activity.html b/data/interfaces/default/current_activity.html index 47be0dcd..13c2ef30 100644 --- a/data/interfaces/default/current_activity.html +++ b/data/interfaces/default/current_activity.html @@ -217,8 +217,8 @@ DOCUMENTATION :: END % endif
-
${a['transcode_progress']}%
-
${a['progress_percent']}%
+
${a['transcode_progress']}%
+
${a['progress_percent']}%
-
Loading stats...
+
Loading stats...

@@ -36,19 +36,27 @@

Library Statistics ${config['pms_name']}

-
Loading stats...
+
Loading stats...

% endif -
+
+

Recently Added

-
-
Looking for new items...
+
+
Looking for new items...

@@ -116,31 +124,17 @@ getLibraryStats(); function recentlyAdded() { - var widthVal = $('body').find(".container-fluid").width(); - var tmp = (widthVal-20) / 182; - - if (tmp > 0) { - containerSize = parseInt(tmp); - } else { - containerSize = 1; - } - $.ajax({ url: 'get_recently_added', type: "GET", async: true, - data: { count : containerSize }, + data: { count : 50 }, complete: function(xhr, status) { $("#recentlyAdded").html(xhr.responseText); } }); } - $(document).ready(function () { - recentlyAdded(); - $(window).resize(function() { - recentlyAdded(); - }); - }); + recentlyAdded(); var date_format = 'YYYY-MM-DD'; var time_format = 'hh:mm a'; @@ -152,6 +146,30 @@ time_format = data.time_format; } }); + + var leftTotal = 0; + $(".paginate").click(function (e) { + e.preventDefault(); + var scroller = $("#recently-added-row-scroller"); + var containerWidth = $("body").find(".container-fluid").width(); + var scrollAmount = $(this).data("id") * parseInt((containerWidth - 15) / 175) * 175; + var leftMax = -(parseInt(scroller.width()) + scrollAmount); + + leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax); + scroller.animate({ left: leftTotal }, 250); + + if (leftTotal == 0) { + $("#recently-added-page-left").addClass("disabled").blur(); + } else { + $("#recently-added-page-left").removeClass("disabled"); + } + + if (leftTotal == leftMax) { + $("#recently-added-page-right").addClass("disabled").blur(); + } else { + $("#recently-added-page-right").removeClass("disabled"); + } + }); diff --git a/data/interfaces/default/recently_added.html b/data/interfaces/default/recently_added.html index bba1601b..10e786a7 100644 --- a/data/interfaces/default/recently_added.html +++ b/data/interfaces/default/recently_added.html @@ -32,56 +32,58 @@ DOCUMENTATION :: END % if data != None:
% else:
There was an error communicating with your Plex Server. Please check your settings. From b5e9ff3b4ecb2fbf670b42cae155080b85987416 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 27 Feb 2016 00:00:11 -0800 Subject: [PATCH 12/48] Add scrolling recently watched and added to user and library pages --- data/interfaces/default/css/plexpy.css | 3 +- data/interfaces/default/library.html | 86 ++++++++++---- .../default/library_recently_added.html | 106 +++++++++--------- data/interfaces/default/user.html | 44 ++++++-- .../default/user_recently_watched.html | 90 +++++++-------- 5 files changed, 200 insertions(+), 129 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index c519aac6..8e43f251 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -2772,7 +2772,8 @@ a.no-highlight:hover { .nav-header > li > a:hover { background-color: transparent; } -#recently-added-row-scroller { +#recently-added-row-scroller, +#recently-watched-row-scroller { width: 8750px; position: relative; } \ No newline at end of file diff --git a/data/interfaces/default/library.html b/data/interfaces/default/library.html index 3333b53c..95026667 100644 --- a/data/interfaces/default/library.html +++ b/data/interfaces/default/library.html @@ -118,6 +118,14 @@ DOCUMENTATION :: END
+
Recently Watched
@@ -135,6 +143,14 @@ DOCUMENTATION :: END
+
Recently Added
@@ -460,22 +476,13 @@ DOCUMENTATION :: END }); function recentlyWatched() { - var widthVal = $("#library-recently-watched").width(); - var tmp = (widthVal-25) / 175; - - if (tmp > 0) { - var containerSize = parseInt(tmp); - } else { - var containerSize = 1; - } - // Populate recently watched $.ajax({ url: 'get_library_recently_watched', async: true, data: { section_id: section_id, - limit: containerSize + limit: 50 }, complete: function(xhr, status) { $("#library-recently-watched").html(xhr.responseText); @@ -484,22 +491,13 @@ DOCUMENTATION :: END } function recentlyAdded() { - var widthVal = $("#library-recently-added").width(); - var tmp = (widthVal-25) / 175; - - if (tmp > 0) { - var containerSize = parseInt(tmp); - } else { - var containerSize = 1; - } - // Populate recently added $.ajax({ url: 'get_library_recently_added', async: true, data: { section_id: section_id, - limit: containerSize + limit: 50 }, complete: function(xhr, status) { $("#library-recently-added").html(xhr.responseText); @@ -515,6 +513,54 @@ DOCUMENTATION :: END }); $('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 }); + + var leftTotalWatched = 0; + $(".paginate-watched").click(function (e) { + e.preventDefault(); + var scroller = $("#recently-watched-row-scroller"); + var containerWidth = $("#library-recently-watched").width(); + var scrollAmount = $(this).data("id") * parseInt((containerWidth - 15) / 175) * 175; + var leftMax = -(parseInt(scroller.width()) + scrollAmount); + + leftTotalWatched = Math.max(Math.min(leftTotalWatched + scrollAmount, 0), leftMax); + scroller.animate({ left: leftTotalWatched }, 250); + + if (leftTotalWatched == 0) { + $("#recently-watched-page-left").addClass("disabled").blur(); + } else { + $("#recently-watched-page-left").removeClass("disabled"); + } + + if (leftTotalWatched == leftMax) { + $("#recently-watched-page-right").addClass("disabled").blur(); + } else { + $("#recently-watched-page-right").removeClass("disabled"); + } + }); + + var leftTotalAdded = 0; + $(".paginate-added").click(function (e) { + e.preventDefault(); + var scroller = $("#recently-added-row-scroller"); + var containerWidth = $("#library-recently-added").width(); + var scrollAmount = $(this).data("id") * parseInt((containerWidth - 15) / 175) * 175; + var leftMax = -(parseInt(scroller.width()) + scrollAmount); + + leftTotalAdded = Math.max(Math.min(leftTotalAdded + scrollAmount, 0), leftMax); + scroller.animate({ left: leftTotalAdded }, 250); + + if (leftTotalAdded == 0) { + $("#recently-added-page-left").addClass("disabled").blur(); + } else { + $("#recently-added-page-left").removeClass("disabled"); + } + + if (leftTotalAdded == leftMax) { + $("#recently-added-page-right").addClass("disabled").blur(); + } else { + $("#recently-added-page-right").removeClass("disabled"); + } + }); }); % endif diff --git a/data/interfaces/default/library_recently_added.html b/data/interfaces/default/library_recently_added.html index 69eea34f..12bd495f 100644 --- a/data/interfaces/default/library_recently_added.html +++ b/data/interfaces/default/library_recently_added.html @@ -32,63 +32,65 @@ DOCUMENTATION :: END % if data:
- + + % endif + + % endfor + +
% else:
No stats to show. diff --git a/data/interfaces/default/user.html b/data/interfaces/default/user.html index d8fb0bd5..7f4b6ce7 100644 --- a/data/interfaces/default/user.html +++ b/data/interfaces/default/user.html @@ -112,6 +112,14 @@ from plexpy import helpers
+
Recently Watched
@@ -471,22 +479,13 @@ from plexpy import helpers }); function recentlyWatched() { - var widthVal = $("#user-recently-watched").width(); - var tmp = (widthVal-25) / 175; - - if (tmp > 0) { - var containerSize = parseInt(tmp); - } else { - var containerSize = 1; - } - // Populate recently watched $.ajax({ url: 'get_user_recently_watched', async: true, data: { user_id: user_id, - limit: containerSize + limit: 50 }, complete: function(xhr, status) { $("#user-recently-watched").html(xhr.responseText); @@ -495,8 +494,29 @@ from plexpy import helpers } recentlyWatched(); - $(window).resize(function() { - recentlyWatched(); + + var leftTotal = 0; + $(".paginate").click(function (e) { + e.preventDefault(); + var scroller = $("#recently-watched-row-scroller"); + var containerWidth = $("#user-recently-watched").width(); + var scrollAmount = $(this).data("id") * parseInt((containerWidth - 15) / 175) * 175; + var leftMax = -(parseInt(scroller.width()) + scrollAmount); + + leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax); + scroller.animate({ left: leftTotal }, 250); + + if (leftTotal == 0) { + $("#recently-watched-page-left").addClass("disabled").blur(); + } else { + $("#recently-watched-page-left").removeClass("disabled"); + } + + if (leftTotal == leftMax) { + $("#recently-watched-page-right").addClass("disabled").blur(); + } else { + $("#recently-watched-page-right").removeClass("disabled"); + } }); }); diff --git a/data/interfaces/default/user_recently_watched.html b/data/interfaces/default/user_recently_watched.html index d13652b9..23734bcf 100644 --- a/data/interfaces/default/user_recently_watched.html +++ b/data/interfaces/default/user_recently_watched.html @@ -28,56 +28,58 @@ DOCUMENTATION :: END % if data:
% else:
No stats to show.

From 5e15884d8fe8c3a529d81a3d3524ac6c745e2e0b Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 27 Feb 2016 01:00:12 -0800 Subject: [PATCH 13/48] Fix scrollers when items don't fill up the row --- data/interfaces/default/css/plexpy.css | 5 ++-- data/interfaces/default/index.html | 22 ++++++++++++-- data/interfaces/default/library.html | 41 +++++++++++++++++++++----- data/interfaces/default/user.html | 20 +++++++++++-- 4 files changed, 73 insertions(+), 15 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 8e43f251..9cb75630 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -2764,7 +2764,9 @@ a.no-highlight:hover { float: left; } .btn-gray.disabled, -.btn-gray.disabled:hover { +.btn-gray.disabled:focus, +.btn-gray.disabled:hover, +.btn-gray.disabled:active { color: #323232; cursor: default; } @@ -2774,6 +2776,5 @@ a.no-highlight:hover { } #recently-added-row-scroller, #recently-watched-row-scroller { - width: 8750px; position: relative; } \ No newline at end of file diff --git a/data/interfaces/default/index.html b/data/interfaces/default/index.html index 71958e47..a430bfcc 100644 --- a/data/interfaces/default/index.html +++ b/data/interfaces/default/index.html @@ -50,7 +50,7 @@
  • - +
  • Recently Added

    @@ -131,6 +131,7 @@ data: { count : 50 }, complete: function(xhr, status) { $("#recentlyAdded").html(xhr.responseText); + highlightAddedScrollerButton(); } }); } @@ -147,14 +148,29 @@ } }); + function highlightAddedScrollerButton() { + var scroller = $("#recently-added-row-scroller"); + var numElems = scroller.find("li").length; + scroller.width(numElems * 175); + if (scroller.width() > $("body").find(".container-fluid").width()) { + $("#recently-added-page-right").removeClass("disabled"); + } else { + $("#recently-added-page-right").addClass("disabled"); + } + } + + $(window).resize(function () { + highlightAddedScrollerButton(); + }); + var leftTotal = 0; $(".paginate").click(function (e) { e.preventDefault(); var scroller = $("#recently-added-row-scroller"); var containerWidth = $("body").find(".container-fluid").width(); var scrollAmount = $(this).data("id") * parseInt((containerWidth - 15) / 175) * 175; - var leftMax = -(parseInt(scroller.width()) + scrollAmount); - + var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0); + leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax); scroller.animate({ left: leftTotal }, 250); diff --git a/data/interfaces/default/library.html b/data/interfaces/default/library.html index 95026667..0293f96d 100644 --- a/data/interfaces/default/library.html +++ b/data/interfaces/default/library.html @@ -123,7 +123,7 @@ DOCUMENTATION :: END
  • - +
  • @@ -148,7 +148,7 @@ DOCUMENTATION :: END
  • - +
  • @@ -486,6 +486,7 @@ DOCUMENTATION :: END }, complete: function(xhr, status) { $("#library-recently-watched").html(xhr.responseText); + highlightWatchedScrollerButton(); } }); } @@ -501,15 +502,39 @@ DOCUMENTATION :: END }, complete: function(xhr, status) { $("#library-recently-added").html(xhr.responseText); + highlightAddedScrollerButton(); } }); } recentlyWatched(); recentlyAdded(); + + function highlightWatchedScrollerButton() { + var scroller = $("#recently-watched-row-scroller"); + var numElems = scroller.find("li").length; + scroller.width(numElems * 175); + if (scroller.width() > $("#library-recently-watched").width()) { + $("#recently-watched-page-right").removeClass("disabled"); + } else { + $("#recently-watched-page-right").addClass("disabled"); + } + } + + function highlightAddedScrollerButton() { + var scroller = $("#recently-added-row-scroller"); + var numElems = scroller.find("li").length; + scroller.width(numElems * 175); + if (scroller.width() > $("#library-recently-added").width()) { + $("#recently-added-page-right").removeClass("disabled"); + } else { + $("#recently-added-page-right").addClass("disabled"); + } + } + $(window).resize(function() { - recentlyWatched(); - recentlyAdded(); + highlightWatchedScrollerButton(); + highlightAddedScrollerButton(); }); $('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 }); @@ -519,8 +544,8 @@ DOCUMENTATION :: END e.preventDefault(); var scroller = $("#recently-watched-row-scroller"); var containerWidth = $("#library-recently-watched").width(); - var scrollAmount = $(this).data("id") * parseInt((containerWidth - 15) / 175) * 175; - var leftMax = -(parseInt(scroller.width()) + scrollAmount); + var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175; + var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0); leftTotalWatched = Math.max(Math.min(leftTotalWatched + scrollAmount, 0), leftMax); scroller.animate({ left: leftTotalWatched }, 250); @@ -543,8 +568,8 @@ DOCUMENTATION :: END e.preventDefault(); var scroller = $("#recently-added-row-scroller"); var containerWidth = $("#library-recently-added").width(); - var scrollAmount = $(this).data("id") * parseInt((containerWidth - 15) / 175) * 175; - var leftMax = -(parseInt(scroller.width()) + scrollAmount); + var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175; + var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0); leftTotalAdded = Math.max(Math.min(leftTotalAdded + scrollAmount, 0), leftMax); scroller.animate({ left: leftTotalAdded }, 250); diff --git a/data/interfaces/default/user.html b/data/interfaces/default/user.html index 7f4b6ce7..49a878e6 100644 --- a/data/interfaces/default/user.html +++ b/data/interfaces/default/user.html @@ -489,19 +489,35 @@ from plexpy import helpers }, complete: function(xhr, status) { $("#user-recently-watched").html(xhr.responseText); + highlightWatchedScrollerButton(); } }); } recentlyWatched(); + function highlightWatchedScrollerButton() { + var scroller = $("#recently-watched-row-scroller"); + var numElems = scroller.find("li").length; + scroller.width(numElems * 175); + if (scroller.width() > $("#user-recently-watched").width()) { + $("#recently-watched-page-right").removeClass("disabled"); + } else { + $("#recently-watched-page-right").addClass("disabled"); + } + } + + $(window).resize(function() { + highlightWatchedScrollerButton(); + }); + var leftTotal = 0; $(".paginate").click(function (e) { e.preventDefault(); var scroller = $("#recently-watched-row-scroller"); var containerWidth = $("#user-recently-watched").width(); - var scrollAmount = $(this).data("id") * parseInt((containerWidth - 15) / 175) * 175; - var leftMax = -(parseInt(scroller.width()) + scrollAmount); + var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175; + var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0); leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax); scroller.animate({ left: leftTotal }, 250); From fa8c5e0982e228a3b84b568ffaad5a64dceaff67 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 27 Feb 2016 01:18:40 -0800 Subject: [PATCH 14/48] Only use user_id in current activity link to user page --- data/interfaces/default/current_activity.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/data/interfaces/default/current_activity.html b/data/interfaces/default/current_activity.html index 13c2ef30..4d93b7f7 100644 --- a/data/interfaces/default/current_activity.html +++ b/data/interfaces/default/current_activity.html @@ -261,11 +261,7 @@ DOCUMENTATION :: END % endif
    From 464d2a541d50c1bf043937d483d84527421ad530 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 27 Feb 2016 13:39:21 -0800 Subject: [PATCH 15/48] Give tables unique ids to save state indivdually --- data/interfaces/default/info.html | 32 +++++++++---------- .../default/js/tables/media_info_table.js | 2 +- data/interfaces/default/library.html | 12 +++---- data/interfaces/default/user.html | 18 +++++------ 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 9a025348..2ddd7420 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -348,21 +348,21 @@ DOCUMENTATION :: END
    - +
    - - - - - - - - - - - - + + + + + + + + + + + + @@ -458,11 +458,11 @@ DOCUMENTATION :: END % if source == 'history': % endif diff --git a/data/interfaces/default/js/tables/media_info_table.js b/data/interfaces/default/js/tables/media_info_table.js index 87615398..3934e4db 100644 --- a/data/interfaces/default/js/tables/media_info_table.js +++ b/data/interfaces/default/js/tables/media_info_table.js @@ -23,7 +23,7 @@ media_info_table_options = { "emptyTable": "No data in table" }, "pagingType": "bootstrap", - "stateSave": false, + "stateSave": true, "processing": false, "serverSide": true, "pageLength": 25, diff --git a/data/interfaces/default/library.html b/data/interfaces/default/library.html index 0293f96d..08bb0a18 100644 --- a/data/interfaces/default/library.html +++ b/data/interfaces/default/library.html @@ -186,7 +186,7 @@ DOCUMENTATION :: END
    -
    DeleteTimeUserIP AddressPlatformPlayerTitleStartedPausedStoppedDurationDeleteTimeUserIP AddressPlatformPlayerTitleStartedPausedStoppedDuration
    +
    @@ -245,7 +245,7 @@ DOCUMENTATION :: END
    -
    Delete
    +
    @@ -380,12 +380,12 @@ DOCUMENTATION :: END }; } } - history_table = $('#history_table').DataTable(history_table_options); + history_table = $('#history_table-SID-${data["section_id"]}').DataTable(history_table_options); var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] }); $(colvis.button()).appendTo('#button-bar-history'); - clearSearchButton('history_table', history_table); + clearSearchButton('history_table-SID-${data["section_id"]}', history_table); } function loadMediaInfoTable() { @@ -401,12 +401,12 @@ DOCUMENTATION :: END }; } } - media_info_table = $('#media_info_table').DataTable(media_info_table_options); + media_info_table = $('#media_info_table-SID-${data["section_id"]}').DataTable(media_info_table_options); var colvis = new $.fn.dataTable.ColVis(media_info_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark' }); $(colvis.button()).appendTo('#button-bar-media-info'); - clearSearchButton('media_info_table', media_info_table); + clearSearchButton('media_info_table-SID-${data["section_id"]}', media_info_table); } $( "#history-tab-btn" ).one( "click", function() { diff --git a/data/interfaces/default/user.html b/data/interfaces/default/user.html index 49a878e6..ad0c8711 100644 --- a/data/interfaces/default/user.html +++ b/data/interfaces/default/user.html @@ -148,7 +148,7 @@ from plexpy import helpers
    -
    Added At
    +
    @@ -186,7 +186,7 @@ from plexpy import helpers
    -
    Last Seen
    +
    @@ -226,7 +226,7 @@ from plexpy import helpers
    -
    Delete
    +
    @@ -356,13 +356,13 @@ from plexpy import helpers }; } } - history_table = $('#history_table').DataTable(history_table_options); + history_table = $('#history_table-UID-${data["user_id"]}').DataTable(history_table_options); history_table.column(2).visible(false); var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: ' Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] }); $(colvis.button()).appendTo('#button-bar-history'); - clearSearchButton('history_table', history_table); + clearSearchButton('history_table-UID-${data["user_id"]}', history_table); $('#history_table_filter').prepend('
    \
    -

    Set the complete folder path where your Plex Server logs are, shortcuts are not recognized.
    +

    Set the complete folder path where your Plex Server logs are, shortcuts are not recognized.
    Click here for help. This is required if you enable IP logging (for PMS 0.9.12 and below).

    @@ -1905,6 +1905,11 @@ $(document).ready(function() { $.get("/osxnotifyregister", { 'app': osx_notify_app }, function (data) { showMsg("
    " + data + "
    ", false, true, 3000); }); }) + pms_version = false; + pms_logs_debug = false; + pms_logs = false; + + // Checks to see if PMS server version is >= 0.9.14 with automaatically logged IP addresses $.ajax({ url: 'get_server_identity', async: true, @@ -1914,41 +1919,54 @@ $(document).ready(function() { $("#debugLogCheck").html("IP address is automatically logged for PMS version 0.9.14 and above."); $("#ip_logging_enable").attr("disabled", true); $("#ip_logging_enable").attr("checked", true); + pms_version = true; + checkLogsPath(); } else { + // Check to see if debug logs are enabled on the PMS. $.ajax({ url: 'get_server_pref', data: { pref: 'logDebug' }, async: true, success: function(data) { - if (data !== 'true') { - $("#debugLogCheck").html("Debug logging must be enabled on your Plex Server. Click here for help."); - $("#ip_logging_enable").attr("disabled", true); - $("#ip_logging_enable").attr("checked", false); - } + pms_logs_debug = (data == 'true' ? true : false); + // Check to see if our logs folder is set before allowing IP logging to be enabled. + checkLogsPath(); } }); - - // Check to see if our logs folder is set before allowing IP logging to be enabled. - checkLogsPath(); - - $("#pms_logs_folder").change(function() { - checkLogsPath(); - }); - - function checkLogsPath() { - if ($("#pms_logs_folder").val() == '') { - $("#debugLogCheck").html("You must first define your Plex Server Logs folder path under the Plex Media Server tab."); - $("#ip_logging_enable").attr("disabled", true); - $("#ip_logging_enable").attr("checked", false); - } else { - $("#ip_logging_enable").attr("disabled", false); - $("#debugLogCheck").html(""); - } - } } } }); + $("#pms_logs_folder").change(function() { + checkLogsPath(); + }); + + function checkLogsPath() { + if ($("#pms_logs_folder").val().startsWith("%") || $("#pms_logs_folder").val().startsWith("~")) { + $("#pms-logs-shortcut").css("color", "#eb8600"); + pms_logs = false + } else { + $("#pms-logs-shortcut").css("color", "#737373"); + pms_logs = ($("#pms_logs_folder").val() == '' ? false : true); + } + + // Toggle IP logging checkbox depending on debug logs, and logs path + if (!(pms_version)) { + if (pms_logs_debug && pms_logs) { + $("#ip_logging_enable").attr("disabled", false); + $("#debugLogCheck").html(""); + } else if (!(pms_logs_debug)) { + $("#debugLogCheck").html("Debug logging must be enabled on your Plex Server. Click here for help."); + $("#ip_logging_enable").attr("disabled", true); + $("#ip_logging_enable").attr("checked", false); + } else { + $("#debugLogCheck").html("You must first define your Plex Server Logs folder path under the Plex Media Server tab."); + $("#ip_logging_enable").attr("disabled", true); + $("#ip_logging_enable").attr("checked", false); + } + } + } + $.ajax({ url: 'get_server_pref', data: { pref: 'PublishServerOnPlexOnlineKey' }, From 0e2504fc789caab07cf6ce58db2005eeccef58d8 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 1 Mar 2016 20:31:21 -0800 Subject: [PATCH 17/48] Document remaining time format options --- data/interfaces/default/css/plexpy.css | 9 + data/interfaces/default/settings.html | 241 ++++++++++++++++++++----- plexpy/notification_handler.py | 10 +- 3 files changed, 208 insertions(+), 52 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 9cb75630..6fb199ab 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -2656,6 +2656,7 @@ table[id^='media_info_child'] table[id^='media_info_child'] thead th { margin: 5px 0 0 0.5em; } .notification-params { + width: 100%; margin-top: 10px; background-color: #282828; } @@ -2670,6 +2671,14 @@ table[id^='media_info_child'] table[id^='media_info_child'] thead th { padding-left: 10px; width: 200px; } +.notification-params.time-options td:first-child { + padding-left: 10px; + width: 125px; +} +.notification-params.time-options td:nth-child(2) { + padding-left: 10px; + width: 275px; +} .notification-params td:not(:first-child) { padding-right: 10px; } diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index b6f39a54..75bbe41e 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -1031,24 +1031,45 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
    State
    +
    + + + + + - - - - + - + - + +
    + Year +
    Year
    YYYYNumeric, 4 digitsNumeric, four digits Eg., 1999, 2003
    YYNumeric, 2 digitsNumeric, two digits Eg., 99, 03
    + + - + + + + + + + + + + + + + @@ -1060,23 +1081,41 @@ available_notification_agents = sorted(notifiers.available_notification_agents() + +
    Month
    + Month +
    MMMMTextual, fullJanuary-December
    MMMTextual, three lettersJan-Dec
    MMNumeric, without leading zeros 1-12
    + + - - - + + + + + + + + - - - + + + - + +
    MMMMTextual fullJanuary-December + Day of the Year +
    DDDDNumeric, with leading zeros001-365
    MMMTextual three lettersJan-DecDDDNumeric, without leading zeros1-365
    + + - + + + - - + + @@ -1086,57 +1125,165 @@ available_notification_agents = sorted(notifiers.available_notification_agents() - - + + - + +
    Day
    + Day of the Month +
    DDNumeric, with leading zerosDDNumeric, with leading zeros 01-31
    DoThe English suffix for the day of the monthst, nd or th in the 1st, 2nd or 15th.Numeric, with suffixEg., 1st, 2nd ... 31st.
    + + - + + + + + + + + - - - + + + - - - + + + + +
    Time
    + Day of the Week +
    ddddTextual, fullSunday-Saturday
    aam/pm Lowercaseam, pmdddTextual, three lettersSun-Sat
    AAM/PM UppercaseAM, PMdNumeric0-6
    + + - - - - - - - - - - - - - + + + - + + + + + + + + + + + + + + + + + +
    hHour, 12-hour, without leading zeros1-12
    hhHour, 12-hour, with leading zeros01-12
    HHour, 24-hour, without leading zeros0-23 + Hour +
    HHHour, 24-hour, with leading zeros24-hour, with leading zeros 00-23
    H24-hour, without leading zeros0-23
    hh12-hour, with leading zeros01-12
    h12-hour, without leading zeros1-12
    + + + + + + + - + + + + + + + +
    + Minute +
    mmMinutes, with leading zerosNumeric, with leading zeros 00-59
    mNumeric, without leading zeros0-59
    + + + + + + + - + - - - + + + + + +
    + Second +
    ssSeconds, with leading zerosNumeric, with leading zeros 00-59
    zzTimezone abbreviationEg., EST, MDT ...sNumeric, without leading zeros0-59
    + + + + + + + + + + + + + + + + + + +
    + AM / PM +
    AAM/PM uppercaseAM, PM
    aam/pm lowercaseam, pm
    + + + + + + + + + + + + + + + + + + +
    + Timezone +
    ZZUTC offsetEg., +0100, -0700
    ZUTC offsetEg., +01:00, -07:00
    + + + + + + + + + + +
    + Timestamp +
    XUnix timestampEg., 1456887825
    diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 0ceb4692..43cc71ea 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -441,9 +441,9 @@ def set_notify_state(session, notify_action, agent_info, notify_strings, metadat def build_notify_text(session=None, timeline=None, notify_action=None, agent_id=None): # Get time formats - date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','') - time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','') - duration_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','').replace('a','').replace('A','') + date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','') + time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','') + duration_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('a','').replace('A','') # Get the server name server_name = plexpy.CONFIG.PMS_NAME @@ -900,8 +900,8 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id= def build_server_notify_text(notify_action=None, agent_id=None): # Get time formats - date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','') - time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','') + date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','') + time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','') # Get the server name server_name = plexpy.CONFIG.PMS_NAME From 673fa2b556ef0d56ce72faf9a617690f967a384b Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 1 Mar 2016 20:31:45 -0800 Subject: [PATCH 18/48] Fix auto-refresh of log tabs --- data/interfaces/default/logs.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/data/interfaces/default/logs.html b/data/interfaces/default/logs.html index 56d18729..571257ec 100644 --- a/data/interfaces/default/logs.html +++ b/data/interfaces/default/logs.html @@ -200,8 +200,12 @@ from plexpy import helpers timer = setInterval(function() { if ($("#tabs-1").hasClass("active")) { log_table.ajax.reload(); - } else { + } else if ($("#tabs-2").hasClass("active")) { plex_log_table.ajax.reload(); + } else if ($("#tabs-3").hasClass("active")) { + plex_scanner_log_table.ajax.reload(); + } else if ($("#tabs-4").hasClass("active")) { + notification_log_table.ajax.reload(); } }, 1000*refreshrate.value); } From 795d7d0a93e95faf93fa0d98cea3c4b8f19477e9 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 1 Mar 2016 21:04:57 -0800 Subject: [PATCH 19/48] Add ability to get notified of PMS updates --- .../default/notification_triggers_modal.html | 7 ++ data/interfaces/default/settings.html | 73 ++++++++++-- plexpy/__init__.py | 7 ++ plexpy/activity_pinger.py | 29 ++++- plexpy/common.py | 1 + plexpy/config.py | 23 +++- plexpy/notification_handler.py | 111 ++++++++++++++---- plexpy/notifiers.py | 101 ++++++++++------ plexpy/pmsconnect.py | 71 ++++++++++- plexpy/webserve.py | 13 +- 10 files changed, 360 insertions(+), 76 deletions(-) diff --git a/data/interfaces/default/notification_triggers_modal.html b/data/interfaces/default/notification_triggers_modal.html index 4ae0582a..2ca564fc 100644 --- a/data/interfaces/default/notification_triggers_modal.html +++ b/data/interfaces/default/notification_triggers_modal.html @@ -92,6 +92,13 @@ from plexpy import helpers

    Trigger notification when the Plex Media Server can be reached externally after being down.

    +
    + +

    Trigger notification when an update for the Plex Media Server is available.

    +
    diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index 75bbe41e..e883e17f 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -43,7 +43,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
  • Plex Media Server
  • Plex.tv Account
  • Extra Settings
  • -
  • Monitoring
  • +
  • Activity Monitoring
  • Notifications
  • Notification Agents
  • @@ -437,9 +437,24 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
    -

    Plex Media Server

    +

    Plex Media Server Version unknown

    If you're using websocket monitoring, any server changes require a restart of PlexPy.

    +
    + +

    Enable to have PlexPy check if updates are available for the Plex Media Server.
    + Note: The Plex updater is broken on certain Plex Pass version of Plex Media Server. PlexPy will automatically disable checking for Plex updates if one of these versions is found.

    +
    +
    + + +

    Enable to have PlexPy check if remote access to the Plex Media Server goes down.

    +
    +
    @@ -613,13 +628,6 @@ available_notification_agents = sorted(notifiers.available_notification_agents()

    Instead of polling the server at regular intervals let the server tell PlexPy when something happens.

    -
    - - -

    Enable to have PlexPy check if remote access to the Plex Media Server goes down.

    -

    History Logging

    @@ -971,6 +979,23 @@ available_notification_agents = sorted(notifiers.available_notification_agents() +
  • + + +
    • @@ -1368,6 +1393,10 @@ available_notification_agents = sorted(notifiers.available_notification_agents() {server_uptime} The uptime (in days, hours, mins, secs) of your Plex Server. + + {server_version} + The current version of your Plex Server. + {action} The action that triggered the notification. @@ -1699,6 +1728,29 @@ available_notification_agents = sorted(notifiers.available_notification_agents() + + + + + + + + + + + + + + + + + + + + +
      + Plex Update Available +
      {update_version}The available update version for your Plex Server.
      {update_url}The available update download URL.
      {update_changelog}The changelog for the available update.
    @@ -2061,6 +2113,7 @@ $(document).ready(function() { url: 'get_server_identity', async: true, success: function(data) { + if (data.version){ $("#pms_version").text(data.version); } var version = (data.version ? data.version.split('.') : null); if (version && parseInt(version[0]) >= 0 && parseInt(version[1]) >= 9 && parseInt(version[2]) >= 14) { $("#debugLogCheck").html("IP address is automatically logged for PMS version 0.9.14 and above."); @@ -2091,7 +2144,7 @@ $(document).ready(function() { function checkLogsPath() { if ($("#pms_logs_folder").val().startsWith("%") || $("#pms_logs_folder").val().startsWith("~")) { $("#pms-logs-shortcut").css("color", "#eb8600"); - pms_logs = false + pms_logs = false; } else { $("#pms-logs-shortcut").css("color", "#737373"); pms_logs = ($("#pms_logs_folder").val() == '' ? false : true); diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 9e92bc62..2ea4c4db 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -306,6 +306,13 @@ def initialize_scheduler(): schedule_job(activity_pinger.check_recently_added, 'Check for recently added items', hours=0, minutes=0, seconds=0) + if CONFIG.MONITOR_PMS_UPDATES: + schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates', + hours=0, minutes=0, seconds=10) + else: + schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates', + hours=0, minutes=0, seconds=0) + if CONFIG.MONITOR_REMOTE_ACCESS: schedule_job(activity_pinger.check_server_response, 'Check for Plex remote access', hours=0, minutes=0, seconds=seconds) diff --git a/plexpy/activity_pinger.py b/plexpy/activity_pinger.py index 84474577..a8791032 100644 --- a/plexpy/activity_pinger.py +++ b/plexpy/activity_pinger.py @@ -283,4 +283,31 @@ def check_server_response(): if ext_ping_count == 3: # Fire off notifications threading.Thread(target=notification_handler.notify_timeline, - kwargs=dict(notify_action='extdown')).start() \ No newline at end of file + kwargs=dict(notify_action='extdown')).start() + + +def check_server_updates(): + + with monitor_lock: + logger.info(u"PlexPy Monitor :: Checking for PMS updates...") + + pms_connect = pmsconnect.PmsConnect() + + server_identity = pms_connect.get_server_identity() + update_status = pms_connect.get_update_staus() + + if server_identity and update_status: + version = server_identity['version'] + logger.info(u"PlexPy Monitor :: Current PMS version: %s", version) + + if update_status['state'] == 'available': + update_version = update_status['version'] + logger.info(u"PlexPy Monitor :: PMS update available version: %s", update_version) + + # Check if any notification agents have notifications enabled + if any(d['on_pmsupdate'] for d in notifiers.available_notification_agents()): + # Fire off notifications + threading.Thread(target=notification_handler.notify_timeline, + kwargs=dict(notify_action='pmsupdate')).start() + else: + logger.info(u"PlexPy Monitor :: No PMS update available.") \ No newline at end of file diff --git a/plexpy/common.py b/plexpy/common.py index 22716140..9dce4283 100644 --- a/plexpy/common.py +++ b/plexpy/common.py @@ -62,6 +62,7 @@ MEDIA_FLAGS_VIDEO = {'avc1': 'h264', SCHEDULER_LIST = ['Check GitHub for updates', 'Check for active sessions', 'Check for recently added items', + 'Check for Plex updates', 'Check for Plex remote access', 'Refresh users list', 'Refresh libraries list', diff --git a/plexpy/config.py b/plexpy/config.py index d914ce6b..ccb05c6d 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -48,6 +48,7 @@ _CONFIG_DEFINITIONS = { 'BOXCAR_ON_INTDOWN': (int, 'Boxcar', 0), 'BOXCAR_ON_EXTUP': (int, 'Boxcar', 0), 'BOXCAR_ON_INTUP': (int, 'Boxcar', 0), + 'BOXCAR_ON_PMSUPDATE': (int, 'Boxcar', 0), 'BUFFER_THRESHOLD': (int, 'Monitoring', 3), 'BUFFER_WAIT': (int, 'Monitoring', 900), 'BACKUP_DIR': (str, 'General', ''), @@ -81,6 +82,7 @@ _CONFIG_DEFINITIONS = { 'EMAIL_ON_INTDOWN': (int, 'Email', 0), 'EMAIL_ON_EXTUP': (int, 'Email', 0), 'EMAIL_ON_INTUP': (int, 'Email', 0), + 'EMAIL_ON_PMSUPDATE': (int, 'Email', 0), 'ENABLE_HTTPS': (int, 'General', 0), 'FACEBOOK_ENABLED': (int, 'Facebook', 0), 'FACEBOOK_REDIRECT_URI': (str, 'Facebook', ''), @@ -102,6 +104,7 @@ _CONFIG_DEFINITIONS = { 'FACEBOOK_ON_INTDOWN': (int, 'Facebook', 0), 'FACEBOOK_ON_EXTUP': (int, 'Facebook', 0), 'FACEBOOK_ON_INTUP': (int, 'Facebook', 0), + 'FACEBOOK_ON_PMSUPDATE': (int, 'Facebook', 0), 'FIRST_RUN_COMPLETE': (int, 'General', 0), 'FREEZE_DB': (int, 'General', 0), 'GET_FILE_SIZES': (int, 'General', 0), @@ -127,6 +130,7 @@ _CONFIG_DEFINITIONS = { 'GROWL_ON_INTDOWN': (int, 'Growl', 0), 'GROWL_ON_EXTUP': (int, 'Growl', 0), 'GROWL_ON_INTUP': (int, 'Growl', 0), + 'GROWL_ON_PMSUPDATE': (int, 'Growl', 0), 'HOME_LIBRARY_CARDS': (list, 'General', ['first_run']), 'HOME_STATS_LENGTH': (int, 'General', 30), 'HOME_STATS_TYPE': (int, 'General', 0), @@ -160,6 +164,7 @@ _CONFIG_DEFINITIONS = { 'IFTTT_ON_INTDOWN': (int, 'IFTTT', 0), 'IFTTT_ON_EXTUP': (int, 'IFTTT', 0), 'IFTTT_ON_INTUP': (int, 'IFTTT', 0), + 'IFTTT_ON_PMSUPDATE': (int, 'IFTTT', 0), 'JOURNAL_MODE': (str, 'Advanced', 'wal'), 'LAUNCH_BROWSER': (int, 'General', 1), 'LOG_DIR': (str, 'General', ''), @@ -174,6 +179,7 @@ _CONFIG_DEFINITIONS = { 'MUSIC_NOTIFY_ON_START': (int, 'Monitoring', 1), 'MUSIC_NOTIFY_ON_STOP': (int, 'Monitoring', 0), 'MUSIC_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0), + 'MONITOR_PMS_UPDATES': (int, 'Monitoring', 0), 'MONITOR_REMOTE_ACCESS': (int, 'Monitoring', 0), 'MONITORING_INTERVAL': (int, 'Monitoring', 60), 'MONITORING_USE_WEBSOCKET': (int, 'Monitoring', 0), @@ -191,6 +197,7 @@ _CONFIG_DEFINITIONS = { 'NMA_ON_INTDOWN': (int, 'NMA', 0), 'NMA_ON_EXTUP': (int, 'NMA', 0), 'NMA_ON_INTUP': (int, 'NMA', 0), + 'NMA_ON_PMSUPDATE': (int, 'NMA', 0), 'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1), 'NOTIFY_UPLOAD_POSTERS': (int, 'Monitoring', 0), 'NOTIFY_RECENTLY_ADDED': (int, 'Monitoring', 0), @@ -219,6 +226,8 @@ _CONFIG_DEFINITIONS = { 'NOTIFY_ON_EXTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server remote access is back up.'), 'NOTIFY_ON_INTUP_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'), 'NOTIFY_ON_INTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server is back up.'), + 'NOTIFY_ON_PMSUPDATE_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'), + 'NOTIFY_ON_PMSUPDATE_BODY_TEXT': (unicode, 'Monitoring', 'An update is available for the Plex Media Server (version {update_version}).'), 'NOTIFY_SCRIPTS_ARGS_TEXT': (unicode, 'Monitoring', ''), 'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/PlexPy'), 'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0), @@ -233,6 +242,7 @@ _CONFIG_DEFINITIONS = { 'OSX_NOTIFY_ON_INTDOWN': (int, 'OSX_Notify', 0), 'OSX_NOTIFY_ON_EXTUP': (int, 'OSX_Notify', 0), 'OSX_NOTIFY_ON_INTUP': (int, 'OSX_Notify', 0), + 'OSX_NOTIFY_ON_PMSUPDATE': (int, 'OSX_Notify', 0), 'PLEX_CLIENT_HOST': (str, 'Plex', ''), 'PLEX_ENABLED': (int, 'Plex', 0), 'PLEX_PASSWORD': (str, 'Plex', ''), @@ -248,6 +258,7 @@ _CONFIG_DEFINITIONS = { 'PLEX_ON_INTDOWN': (int, 'Plex', 0), 'PLEX_ON_EXTUP': (int, 'Plex', 0), 'PLEX_ON_INTUP': (int, 'Plex', 0), + 'PLEX_ON_PMSUPDATE': (int, 'Plex', 0), 'PROWL_ENABLED': (int, 'Prowl', 0), 'PROWL_KEYS': (str, 'Prowl', ''), 'PROWL_PRIORITY': (int, 'Prowl', 0), @@ -262,6 +273,7 @@ _CONFIG_DEFINITIONS = { 'PROWL_ON_INTDOWN': (int, 'Prowl', 0), 'PROWL_ON_EXTUP': (int, 'Prowl', 0), 'PROWL_ON_INTUP': (int, 'Prowl', 0), + 'PROWL_ON_PMSUPDATE': (int, 'Prowl', 0), 'PUSHALOT_APIKEY': (str, 'Pushalot', ''), 'PUSHALOT_ENABLED': (int, 'Pushalot', 0), 'PUSHALOT_ON_PLAY': (int, 'Pushalot', 0), @@ -275,6 +287,7 @@ _CONFIG_DEFINITIONS = { 'PUSHALOT_ON_INTDOWN': (int, 'Pushalot', 0), 'PUSHALOT_ON_EXTUP': (int, 'Pushalot', 0), 'PUSHALOT_ON_INTUP': (int, 'Pushalot', 0), + 'PUSHALOT_ON_PMSUPDATE': (int, 'Pushalot', 0), 'PUSHBULLET_APIKEY': (str, 'PushBullet', ''), 'PUSHBULLET_DEVICEID': (str, 'PushBullet', ''), 'PUSHBULLET_CHANNEL_TAG': (str, 'PushBullet', ''), @@ -290,6 +303,7 @@ _CONFIG_DEFINITIONS = { 'PUSHBULLET_ON_INTDOWN': (int, 'PushBullet', 0), 'PUSHBULLET_ON_EXTUP': (int, 'PushBullet', 0), 'PUSHBULLET_ON_INTUP': (int, 'PushBullet', 0), + 'PUSHBULLET_ON_PMSUPDATE': (int, 'PushBullet', 0), 'PUSHOVER_APITOKEN': (str, 'Pushover', ''), 'PUSHOVER_ENABLED': (int, 'Pushover', 0), 'PUSHOVER_HTML_SUPPORT': (int, 'Pushover', 1), @@ -307,6 +321,7 @@ _CONFIG_DEFINITIONS = { 'PUSHOVER_ON_INTDOWN': (int, 'Pushover', 0), 'PUSHOVER_ON_EXTUP': (int, 'Pushover', 0), 'PUSHOVER_ON_INTUP': (int, 'Pushover', 0), + 'PUSHOVER_ON_PMSUPDATE': (int, 'Pushover', 0), 'REFRESH_LIBRARIES_INTERVAL': (int, 'Monitoring', 12), 'REFRESH_LIBRARIES_ON_STARTUP': (int, 'Monitoring', 1), 'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12), @@ -328,6 +343,7 @@ _CONFIG_DEFINITIONS = { 'SLACK_ON_INTDOWN': (int, 'Slack', 0), 'SLACK_ON_EXTUP': (int, 'Slack', 0), 'SLACK_ON_INTUP': (int, 'Slack', 0), + 'SLACK_ON_PMSUPDATE': (int, 'Slack', 0), 'SCRIPTS_ENABLED': (int, 'Scripts', 0), 'SCRIPTS_FOLDER': (unicode, 'Scripts', ''), 'SCRIPTS_ON_PLAY': (int, 'Scripts', 0), @@ -341,6 +357,7 @@ _CONFIG_DEFINITIONS = { 'SCRIPTS_ON_EXTUP': (int, 'Scripts', 0), 'SCRIPTS_ON_INTDOWN': (int, 'Scripts', 0), 'SCRIPTS_ON_INTUP': (int, 'Scripts', 0), + 'SCRIPTS_ON_PMSUPDATE': (int, 'Scripts', 0), 'SCRIPTS_ON_PLAY_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_STOP_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_PAUSE_SCRIPT': (unicode, 'Scripts', ''), @@ -352,6 +369,7 @@ _CONFIG_DEFINITIONS = { 'SCRIPTS_ON_EXTUP_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_INTDOWN_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_INTUP_SCRIPT': (unicode, 'Scripts', ''), + 'SCRIPTS_ON_PMSUPDATE_SCRIPT': (unicode, 'Scripts', ''), 'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''), 'TELEGRAM_ENABLED': (int, 'Telegram', 0), 'TELEGRAM_CHAT_ID': (str, 'Telegram', ''), @@ -367,6 +385,7 @@ _CONFIG_DEFINITIONS = { 'TELEGRAM_ON_INTDOWN': (int, 'Telegram', 0), 'TELEGRAM_ON_EXTUP': (int, 'Telegram', 0), 'TELEGRAM_ON_INTUP': (int, 'Telegram', 0), + 'TELEGRAM_ON_PMSUPDATE': (int, 'Telegram', 0), 'TV_LOGGING_ENABLE': (int, 'Monitoring', 1), 'TV_NOTIFY_ENABLE': (int, 'Monitoring', 0), 'TV_NOTIFY_ON_START': (int, 'Monitoring', 1), @@ -389,6 +408,7 @@ _CONFIG_DEFINITIONS = { 'TWITTER_ON_INTDOWN': (int, 'Twitter', 0), 'TWITTER_ON_EXTUP': (int, 'Twitter', 0), 'TWITTER_ON_INTUP': (int, 'Twitter', 0), + 'TWITTER_ON_PMSUPDATE': (int, 'Twitter', 0), 'UPDATE_DB_INTERVAL': (int, 'General', 24), 'UPDATE_SECTION_IDS': (int, 'General', 1), 'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1), @@ -407,7 +427,8 @@ _CONFIG_DEFINITIONS = { 'XBMC_ON_EXTDOWN': (int, 'XBMC', 0), 'XBMC_ON_INTDOWN': (int, 'XBMC', 0), 'XBMC_ON_EXTUP': (int, 'XBMC', 0), - 'XBMC_ON_INTUP': (int, 'XBMC', 0) + 'XBMC_ON_INTUP': (int, 'XBMC', 0), + 'XBMC_ON_PMSUPDATE': (int, 'XBMC', 0) } diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py index 43cc71ea..08520cdd 100644 --- a/plexpy/notification_handler.py +++ b/plexpy/notification_handler.py @@ -385,6 +385,24 @@ def notify_timeline(timeline_data=None, notify_action=None): notify_strings=notify_strings, metadata={}) + if agent['on_pmsupdate'] and notify_action == 'pmsupdate': + # Build and send notification + notify_strings = build_server_notify_text(notify_action=notify_action, + agent_id=agent['id']) + + notifiers.send_notification(agent_id=agent['id'], + subject=notify_strings[0], + body=notify_strings[1], + script_args=notify_strings[2], + notify_action=notify_action) + + # Set the notification state in the db + set_notify_state(session={}, + notify_action=notify_action, + agent_info=agent, + notify_strings=notify_strings, + metadata={}) + else: logger.debug(u"PlexPy NotificationHandler :: Notify timeline called but incomplete data received.") @@ -452,6 +470,10 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id= plex_tv = plextv.PlexTV() server_times = plex_tv.get_server_times() + # Get the server version + pms_connect = pmsconnect.PmsConnect() + server_identity = pms_connect.get_server_identity() + if server_times: updated_at = server_times[0]['updated_at'] server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at))) @@ -510,21 +532,21 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id= on_created_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT), agent_id) script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT), agent_id) else: - on_start_subject = plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT - on_start_body = plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT - on_stop_subject = plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT - on_stop_body = plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT - on_pause_subject = plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT - on_pause_body = plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT - on_resume_subject = plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT - on_resume_body = plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT - on_buffer_subject = plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT - on_buffer_body = plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT - on_watched_subject = plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT - on_watched_body = plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT - on_created_subject = plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT - on_created_body = plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT - script_args_text = plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT + on_start_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT, agent_id) + on_start_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT, agent_id) + on_stop_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT, agent_id) + on_stop_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT, agent_id) + on_pause_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT, agent_id) + on_pause_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT, agent_id) + on_resume_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT, agent_id) + on_resume_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT, agent_id) + on_buffer_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT, agent_id) + on_buffer_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT, agent_id) + on_watched_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT, agent_id) + on_watched_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT, agent_id) + on_created_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT, agent_id) + on_created_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT, agent_id) + script_args_text = strip_tag(plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT, agent_id) # Create a title if metadata['media_type'] == 'episode' or metadata['media_type'] == 'track': @@ -635,6 +657,7 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id= available_params = {# Global paramaters 'server_name': server_name, 'server_uptime': server_uptime, + 'server_version': server_identity.get('version',''), 'action': notify_action.title(), 'datestamp': arrow.now().format(date_format), 'timestamp': arrow.now().format(time_format), @@ -910,6 +933,14 @@ def build_server_notify_text(notify_action=None, agent_id=None): plex_tv = plextv.PlexTV() server_times = plex_tv.get_server_times() + # Get the server version + pms_connect = pmsconnect.PmsConnect() + server_identity = pms_connect.get_server_identity() + + update_status = {} + if notify_action == 'pmsupdate': + update_status = pms_connect.get_update_staus() + if server_times: updated_at = server_times[0]['updated_at'] server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at))) @@ -919,22 +950,29 @@ def build_server_notify_text(notify_action=None, agent_id=None): pattern = re.compile('\n*[^>]+.\n*|\n*[^>]+.\n*|\n*?[^>]+.\n*', re.IGNORECASE | re.DOTALL) - on_extdown_subject = plexpy.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT - on_extdown_body = plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT - on_intdown_subject = plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT - on_intdown_body = plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT - on_extup_subject = plexpy.CONFIG.NOTIFY_ON_EXTUP_SUBJECT_TEXT - on_extup_body = plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT - on_intup_subject = plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT - on_intup_body = plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT - script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT)) + on_extdown_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT, agent_id) + on_extdown_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT, agent_id) + on_intdown_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT, agent_id) + on_intdown_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT, agent_id) + on_extup_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTUP_SUBJECT_TEXT, agent_id) + on_extup_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT, agent_id) + on_intup_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT, agent_id) + on_intup_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT, agent_id) + on_pmsupdate_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_SUBJECT_TEXT, agent_id) + on_pmsupdate_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_BODY_TEXT, agent_id) + script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT), agent_id) available_params = {# Global paramaters 'server_name': server_name, 'server_uptime': server_uptime, + 'server_version': server_identity.get('version',''), 'action': notify_action.title(), 'datestamp': arrow.now().format(date_format), - 'timestamp': arrow.now().format(time_format)} + 'timestamp': arrow.now().format(time_format), + # Update parameters + 'update_version': update_status.get('version',''), + 'update_url': update_status.get('download_url',''), + 'update_changelog': update_status.get('changelog','')} # Default text subject_text = 'PlexPy (%s)' % server_name @@ -1040,6 +1078,29 @@ def build_server_notify_text(notify_action=None, agent_id=None): else: return [subject_text, body_text, script_args] + elif notify_action == 'pmsupdate': + # Default body text + body_text = 'An update is available for the Plex Media Server (version {update_version}).' + + if on_pmsupdate_subject and on_pmsupdate_body: + try: + subject_text = unicode(on_pmsupdate_subject).format(**available_params) + except LookupError, e: + logger.error(u"PlexPy NotificationHandler :: Unable to parse field %s in notification subject. Using fallback." % e) + except: + logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification subject. Using fallback.") + + try: + body_text = unicode(on_pmsupdate_body).format(**available_params) + except LookupError, e: + logger.error(u"PlexPy NotificationHandler :: Unable to parse field %s in notification body. Using fallback." % e) + except: + logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification body. Using fallback.") + + return [subject_text, body_text, script_args] + else: + return [subject_text, body_text, script_args] + else: return None diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index c8bdc78d..17f67142 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -75,7 +75,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.GROWL_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.GROWL_ON_INTDOWN, 'on_extup': plexpy.CONFIG.GROWL_ON_EXTUP, - 'on_intup': plexpy.CONFIG.GROWL_ON_INTUP + 'on_intup': plexpy.CONFIG.GROWL_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.GROWL_ON_PMSUPDATE }, {'name': 'Prowl', 'id': AGENT_IDS['Prowl'], @@ -92,7 +93,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.PROWL_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.PROWL_ON_INTDOWN, 'on_extup': plexpy.CONFIG.PROWL_ON_EXTUP, - 'on_intup': plexpy.CONFIG.PROWL_ON_INTUP + 'on_intup': plexpy.CONFIG.PROWL_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.PROWL_ON_PMSUPDATE }, {'name': 'XBMC', 'id': AGENT_IDS['XBMC'], @@ -109,7 +111,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.XBMC_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.XBMC_ON_INTDOWN, 'on_extup': plexpy.CONFIG.XBMC_ON_EXTUP, - 'on_intup': plexpy.CONFIG.XBMC_ON_INTUP + 'on_intup': plexpy.CONFIG.XBMC_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.XBMC_ON_PMSUPDATE }, {'name': 'Plex', 'id': AGENT_IDS['Plex'], @@ -126,7 +129,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.PLEX_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN, 'on_extup': plexpy.CONFIG.PLEX_ON_EXTUP, - 'on_intup': plexpy.CONFIG.PLEX_ON_INTUP + 'on_intup': plexpy.CONFIG.PLEX_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.PLEX_ON_PMSUPDATE }, {'name': 'NotifyMyAndroid', 'id': AGENT_IDS['NMA'], @@ -143,7 +147,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.NMA_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.NMA_ON_INTDOWN, 'on_extup': plexpy.CONFIG.NMA_ON_EXTUP, - 'on_intup': plexpy.CONFIG.NMA_ON_INTUP + 'on_intup': plexpy.CONFIG.NMA_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.NMA_ON_PMSUPDATE }, {'name': 'Pushalot', 'id': AGENT_IDS['Pushalot'], @@ -160,7 +165,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.PUSHALOT_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.PUSHALOT_ON_INTDOWN, 'on_extup': plexpy.CONFIG.PUSHALOT_ON_EXTUP, - 'on_intup': plexpy.CONFIG.PUSHALOT_ON_INTUP + 'on_intup': plexpy.CONFIG.PUSHALOT_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.PUSHALOT_ON_PMSUPDATE }, {'name': 'Pushbullet', 'id': AGENT_IDS['Pushbullet'], @@ -177,7 +183,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.PUSHBULLET_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.PUSHBULLET_ON_INTDOWN, 'on_extup': plexpy.CONFIG.PUSHBULLET_ON_EXTUP, - 'on_intup': plexpy.CONFIG.PUSHBULLET_ON_INTUP + 'on_intup': plexpy.CONFIG.PUSHBULLET_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.PUSHBULLET_ON_PMSUPDATE }, {'name': 'Pushover', 'id': AGENT_IDS['Pushover'], @@ -194,7 +201,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.PUSHOVER_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.PUSHOVER_ON_INTDOWN, 'on_extup': plexpy.CONFIG.PUSHOVER_ON_EXTUP, - 'on_intup': plexpy.CONFIG.PUSHOVER_ON_INTUP + 'on_intup': plexpy.CONFIG.PUSHOVER_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.PUSHOVER_ON_PMSUPDATE }, {'name': 'Boxcar2', 'id': AGENT_IDS['Boxcar2'], @@ -211,7 +219,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.BOXCAR_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.BOXCAR_ON_INTDOWN, 'on_extup': plexpy.CONFIG.BOXCAR_ON_EXTUP, - 'on_intup': plexpy.CONFIG.BOXCAR_ON_INTUP + 'on_intup': plexpy.CONFIG.BOXCAR_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.BOXCAR_ON_PMSUPDATE }, {'name': 'E-mail', 'id': AGENT_IDS['Email'], @@ -228,7 +237,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.EMAIL_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.EMAIL_ON_INTDOWN, 'on_extup': plexpy.CONFIG.EMAIL_ON_EXTUP, - 'on_intup': plexpy.CONFIG.EMAIL_ON_INTUP + 'on_intup': plexpy.CONFIG.EMAIL_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.EMAIL_ON_PMSUPDATE }, {'name': 'Twitter', 'id': AGENT_IDS['Twitter'], @@ -245,7 +255,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.TWITTER_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.TWITTER_ON_INTDOWN, 'on_extup': plexpy.CONFIG.TWITTER_ON_EXTUP, - 'on_intup': plexpy.CONFIG.TWITTER_ON_INTUP + 'on_intup': plexpy.CONFIG.TWITTER_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.TWITTER_ON_PMSUPDATE }, {'name': 'IFTTT', 'id': AGENT_IDS['IFTTT'], @@ -262,7 +273,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.IFTTT_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.IFTTT_ON_INTDOWN, 'on_extup': plexpy.CONFIG.IFTTT_ON_EXTUP, - 'on_intup': plexpy.CONFIG.IFTTT_ON_INTUP + 'on_intup': plexpy.CONFIG.IFTTT_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.IFTTT_ON_PMSUPDATE }, {'name': 'Telegram', 'id': AGENT_IDS['Telegram'], @@ -279,7 +291,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.TELEGRAM_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.TELEGRAM_ON_INTDOWN, 'on_extup': plexpy.CONFIG.TELEGRAM_ON_EXTUP, - 'on_intup': plexpy.CONFIG.TELEGRAM_ON_INTUP + 'on_intup': plexpy.CONFIG.TELEGRAM_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.TELEGRAM_ON_PMSUPDATE }, {'name': 'Slack', 'id': AGENT_IDS['Slack'], @@ -296,7 +309,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.SLACK_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.SLACK_ON_INTDOWN, 'on_extup': plexpy.CONFIG.SLACK_ON_EXTUP, - 'on_intup': plexpy.CONFIG.SLACK_ON_INTUP + 'on_intup': plexpy.CONFIG.SLACK_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.SLACK_ON_PMSUPDATE }, {'name': 'Scripts', 'id': AGENT_IDS['Scripts'], @@ -313,7 +327,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.SCRIPTS_ON_EXTDOWN, 'on_extup': plexpy.CONFIG.SCRIPTS_ON_EXTUP, 'on_intdown': plexpy.CONFIG.SCRIPTS_ON_INTDOWN, - 'on_intup': plexpy.CONFIG.SCRIPTS_ON_INTUP + 'on_intup': plexpy.CONFIG.SCRIPTS_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE }, {'name': 'Facebook', 'id': AGENT_IDS['Facebook'], @@ -330,7 +345,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.FACEBOOK_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.FACEBOOK_ON_INTDOWN, 'on_extup': plexpy.CONFIG.FACEBOOK_ON_EXTUP, - 'on_intup': plexpy.CONFIG.FACEBOOK_ON_INTUP + 'on_intup': plexpy.CONFIG.FACEBOOK_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.FACEBOOK_ON_PMSUPDATE } ] @@ -352,7 +368,8 @@ def available_notification_agents(): 'on_extdown': plexpy.CONFIG.OSX_NOTIFY_ON_EXTDOWN, 'on_intdown': plexpy.CONFIG.OSX_NOTIFY_ON_INTDOWN, 'on_extup': plexpy.CONFIG.OSX_NOTIFY_ON_EXTUP, - 'on_intup': plexpy.CONFIG.OSX_NOTIFY_ON_INTUP + 'on_intup': plexpy.CONFIG.OSX_NOTIFY_ON_INTUP, + 'on_pmsupdate': plexpy.CONFIG.OSX_NOTIFY_ON_PMSUPDATE }) return agents @@ -1885,14 +1902,14 @@ class Scripts(object): elif notify_action == 'resume': script = plexpy.CONFIG.SCRIPTS_ON_RESUME_SCRIPT + elif notify_action == 'watched': + script = plexpy.CONFIG.SCRIPTS_ON_WATCHED_SCRIPT + elif notify_action == 'buffer': script = plexpy.CONFIG.SCRIPTS_ON_BUFFER_SCRIPT - elif notify_action == 'extdown': - script = plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT - - elif notify_action == 'extup': - script = plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT + elif notify_action == 'created': + script = plexpy.CONFIG.SCRIPTS_ON_CREATED_SCRIPT elif notify_action == 'intdown': script = plexpy.CONFIG.SCRIPTS_ON_INTDOWN_SCRIPT @@ -1900,11 +1917,14 @@ class Scripts(object): elif notify_action == 'intup': script = plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT - elif notify_action == 'created': - script = plexpy.CONFIG.SCRIPTS_ON_CREATED_SCRIPT + elif notify_action == 'extdown': + script = plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT - elif notify_action == 'watched': - script = plexpy.CONFIG.SCRIPTS_ON_WATCHED_SCRIPT + elif notify_action == 'extup': + script = plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT + + elif notify_action == 'pmsupdate': + script = plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE_SCRIPT else: # For manual scripts @@ -2046,13 +2066,6 @@ class Scripts(object): 'input_type': 'select', 'select_options': self.list_scripts() }, - {'label': 'Plex Remote Access Down', - 'value': plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT, - 'name': 'scripts_on_extdown_script', - 'description': 'Choose the script for Plex remote access down.', - 'input_type': 'select', - 'select_options': self.list_scripts() - }, {'label': 'Plex Server Down', 'value': plexpy.CONFIG.SCRIPTS_ON_INTDOWN_SCRIPT, 'name': 'scripts_on_intdown_script', @@ -2060,6 +2073,20 @@ class Scripts(object): 'input_type': 'select', 'select_options': self.list_scripts() }, + {'label': 'Plex Server Back Up', + 'value': plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT, + 'name': 'scripts_on_intup_script', + 'description': 'Choose the script for Plex server back up.', + 'input_type': 'select', + 'select_options': self.list_scripts() + }, + {'label': 'Plex Remote Access Down', + 'value': plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT, + 'name': 'scripts_on_extdown_script', + 'description': 'Choose the script for Plex remote access down.', + 'input_type': 'select', + 'select_options': self.list_scripts() + }, {'label': 'Plex Remote Access Back Up', 'value': plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT, 'name': 'scripts_on_extup_script', @@ -2067,10 +2094,10 @@ class Scripts(object): 'input_type': 'select', 'select_options': self.list_scripts() }, - {'label': 'Plex Server Back Up', - 'value': plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT, - 'name': 'scripts_on_intup_script', - 'description': 'Choose the script for Plex server back up.', + {'label': 'Plex Update Available', + 'value': plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE_SCRIPT, + 'name': 'scripts_on_pmsupdate_script', + 'description': 'Choose the script for Plex update available.', 'input_type': 'select', 'select_options': self.list_scripts() } diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 05916f88..fc9c70b1 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -389,6 +389,38 @@ class PmsConnect(object): return request + def put_updater(self, output_format=''): + """ + Refresh updater status. + + Optional parameters: output_format { dict, json } + + Output: array + """ + uri = '/updater/check?download=0' + request = self.request_handler.make_request(uri=uri, + proto=self.protocol, + request_type='PUT', + output_format=output_format) + + return request + + def get_updater(self, output_format=''): + """ + Return updater status. + + Optional parameters: output_format { dict, json } + + Output: array + """ + uri = '/updater/status' + request = self.request_handler.make_request(uri=uri, + proto=self.protocol, + request_type='GET', + output_format=output_format) + + return request + def get_recently_added_details(self, section_id='', count='0'): """ Return processed and validated list of recently added items. @@ -1479,7 +1511,7 @@ class PmsConnect(object): xml_head = identity.getElementsByTagName('MediaContainer') except Exception as e: logger.warn(u"PlexPy Pmsconnect :: Unable to parse XML for get_local_server_identity: %s." % e) - return [] + return {} server_identity = {} for a in xml_head: @@ -1995,3 +2027,40 @@ class PmsConnect(object): } return server_response + + def get_update_staus(self): + # Refresh the Plex updater status first + self.put_updater() + updater_status = self.get_updater(output_format='xml') + + try: + xml_head = updater_status.getElementsByTagName('MediaContainer') + except Exception as e: + logger.warn(u"PlexPy Pmsconnect :: Unable to parse XML for get_update_staus: %s." % e) + + # Catch the malformed XML on certain PMX version. + # XML parser helper returns empty list if there is an error parsing XML + if updater_status == []: + logger.warn(u"Plex API updater XML is broken on the current PMS version. Please update your PMS manually.") + logger.info(u"PlexPy is unable to check for Plex updates. Disabling check for Plex updates.") + + # Disable check for Plex updates + plexpy.CONFIG.MONITOR_PMS_UPDATES = 0 + plexpy.initialize_scheduler() + plexpy.CONFIG.write() + + return {} + + updater_info = {} + for a in xml_head: + if a.getElementsByTagName('Release'): + release = a.getElementsByTagName('Release') + for item in release: + updater_info = {'can_install': helpers.get_xml_attr(a, 'canInstall'), + 'download_url': helpers.get_xml_attr(a, 'downloadURL'), + 'version': helpers.get_xml_attr(item, 'version'), + 'state': helpers.get_xml_attr(item, 'state'), + 'changelog': helpers.get_xml_attr(item, 'fixed') + } + + return updater_info \ No newline at end of file diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 2e120524..4cf0ca23 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -1179,6 +1179,7 @@ class WebInterface(object): "tv_notify_on_pause": checked(plexpy.CONFIG.TV_NOTIFY_ON_PAUSE), "movie_notify_on_pause": checked(plexpy.CONFIG.MOVIE_NOTIFY_ON_PAUSE), "music_notify_on_pause": checked(plexpy.CONFIG.MUSIC_NOTIFY_ON_PAUSE), + "monitor_pms_updates": checked(plexpy.CONFIG.MONITOR_PMS_UPDATES), "monitor_remote_access": checked(plexpy.CONFIG.MONITOR_REMOTE_ACCESS), "monitoring_interval": plexpy.CONFIG.MONITORING_INTERVAL, "monitoring_use_websocket": checked(plexpy.CONFIG.MONITORING_USE_WEBSOCKET), @@ -1220,6 +1221,8 @@ class WebInterface(object): "notify_on_extup_body_text": plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT, "notify_on_intup_subject_text": plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT, "notify_on_intup_body_text": plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT, + "notify_on_pmsupdate_subject_text": plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_SUBJECT_TEXT, + "notify_on_pmsupdate_body_text": plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_BODY_TEXT, "notify_scripts_args_text": plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT, "home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH, "home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE), @@ -1247,7 +1250,8 @@ class WebInterface(object): "refresh_libraries_on_startup", "refresh_users_on_startup", "ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable", "pms_is_remote", "home_stats_type", "group_history_tables", "notify_consecutive", "notify_upload_posters", - "notify_recently_added", "notify_recently_added_grandparent", "monitor_remote_access", "get_file_sizes" + "notify_recently_added", "notify_recently_added_grandparent", + "monitor_pms_updates", "monitor_remote_access", "get_file_sizes" ] for checked_config in checked_configs: if checked_config not in kwargs: @@ -1276,6 +1280,7 @@ class WebInterface(object): kwargs.get('refresh_libraries_interval') != str(plexpy.CONFIG.REFRESH_LIBRARIES_INTERVAL) or \ kwargs.get('refresh_users_interval') != str(plexpy.CONFIG.REFRESH_USERS_INTERVAL) or \ kwargs.get('notify_recently_added') != str(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED) or \ + kwargs.get('monitor_pms_updates') != str(plexpy.CONFIG.MONITOR_PMS_UPDATES) or \ kwargs.get('monitor_remote_access') != str(plexpy.CONFIG.MONITOR_REMOTE_ACCESS): reschedule = True @@ -2185,3 +2190,9 @@ class WebInterface(object): a = Api() a.checkParams(*args, **kwargs) return a.fetchData() + + @cherrypy.expose + def check_pms_updater(self): + pms_connect = pmsconnect.PmsConnect() + result = pms_connect.get_update_staus() + return json.dumps(result) \ No newline at end of file From 14f6824931c9b6bdd7cdad8ebcf0deb51bf4765c Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 1 Mar 2016 21:49:12 -0800 Subject: [PATCH 20/48] Use Parsley to verify pms logs folder is not a shortcut --- data/interfaces/default/settings.html | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/data/interfaces/default/settings.html b/data/interfaces/default/settings.html index e883e17f..0d887b4a 100644 --- a/data/interfaces/default/settings.html +++ b/data/interfaces/default/settings.html @@ -500,10 +500,11 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
    - +
    +
    -

    Set the complete folder path where your Plex Server logs are, shortcuts are not recognized.
    +

    Set the complete folder path where your Plex Server logs are, shortcuts are not recognized.
    Click here for help. This is required if you enable IP logging (for PMS 0.9.12 and below).

    @@ -2142,14 +2143,7 @@ $(document).ready(function() { }); function checkLogsPath() { - if ($("#pms_logs_folder").val().startsWith("%") || $("#pms_logs_folder").val().startsWith("~")) { - $("#pms-logs-shortcut").css("color", "#eb8600"); - pms_logs = false; - } else { - $("#pms-logs-shortcut").css("color", "#737373"); - pms_logs = ($("#pms_logs_folder").val() == '' ? false : true); - } - + pms_logs = ($("#pms_logs_folder").val() == '' ? false : true); // Toggle IP logging checkbox depending on debug logs, and logs path if (!(pms_version)) { if (pms_logs_debug && pms_logs) { From 98b4000bc0565995c51ea13e00f0673fc23cff73 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Tue, 1 Mar 2016 23:29:49 -0800 Subject: [PATCH 21/48] Add ability to reset Imgur posters from info page --- data/interfaces/default/css/plexpy.css | 3 ++ data/interfaces/default/info.html | 45 ++++++++++++++++++++++++-- plexpy/datafactory.py | 26 +++++++++++++-- plexpy/webserve.py | 21 ++++++++++++ 4 files changed, 90 insertions(+), 5 deletions(-) diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css index 6fb199ab..0be38e59 100644 --- a/data/interfaces/default/css/plexpy.css +++ b/data/interfaces/default/css/plexpy.css @@ -2408,6 +2408,9 @@ a .home-platforms-instance-list-oval:hover, padding: 0; border: 0; } +.history-thumbnail-popover.popover.left { + margin-left: -15px; +} .history-thumbnail-popover.popover.right { margin-left: 15px; } diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index 2ddd7420..d2d03c00 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -344,6 +344,22 @@ DOCUMENTATION :: END + % if source == 'history': + + Fix Metadata + + % endif + % if data.get('poster_url'): + % if data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track': + + % else: + + % endif + + + % endif
    @@ -519,10 +535,33 @@ DOCUMENTATION :: END $("#runtime").html(millisecondsToMinutes($("#runtime").text(), true)); $('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 }); -% if source == 'history': +% if data.get('poster_url'): % endif % endif diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 1dc5a1a4..7e54a59c 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -860,16 +860,28 @@ class DataFactory(object): return ip_address - def get_poster_url(self, rating_key=''): + def get_poster_url(self, rating_key='', metadata=None): monitor_db = database.MonitorDatabase() poster_url = '' + poster_key = '' if rating_key: + poster_key = rating_key + elif metadata: + if metadata['media_type'] == 'movie' or metadata['media_type'] == 'show' or \ + metadata['media_type'] == 'artist' or metadata['media_type'] == 'album': + poster_key = metadata['rating_key'] + elif metadata['media_type'] == 'episode': + poster_key = metadata['grandparent_rating_key'] + elif metadata['media_type'] == 'season' or metadata['media_type'] == 'track': + poster_key = metadata['parent_rating_key'] + + if poster_key: try: query = 'SELECT id, poster_url FROM notify_log ' \ 'WHERE rating_key = %d OR parent_rating_key = %d OR grandparent_rating_key = %d ' \ - 'ORDER BY id DESC LIMIT 1' % (int(rating_key), int(rating_key), int(rating_key)) + 'ORDER BY id DESC LIMIT 1' % (int(poster_key), int(poster_key), int(poster_key)) result = monitor_db.select(query) except Exception as e: logger.warn(u"PlexPy DataFactory :: Unable to execute database query for get_poster_url: %s." % e) @@ -882,6 +894,16 @@ class DataFactory(object): return poster_url + def delete_poster_url(self, poster_url=''): + monitor_db = database.MonitorDatabase() + + if poster_url: + logger.info(u"PlexPy DataFactory :: Deleting poster_url %s from the notify log database." % poster_url) + monitor_db.upsert('notify_log', {'poster_url': None}, {'poster_url': poster_url}) + return 'Deleted poster_url %s.' % poster_url + else: + return 'Unable to delete poster_url.' + def get_search_query(self, rating_key=''): monitor_db = database.MonitorDatabase() diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 4cf0ca23..8c8a4c65 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -1637,11 +1637,16 @@ class WebInterface(object): if source == 'history': data_factory = datafactory.DataFactory() metadata = data_factory.get_metadata_details(rating_key=rating_key) + poster_url = data_factory.get_poster_url(metadata=metadata) + metadata['poster_url'] = poster_url else: pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_metadata_details(rating_key=rating_key, get_media_info=True) if result: metadata = result['metadata'] + data_factory = datafactory.DataFactory() + poster_url = data_factory.get_poster_url(metadata=metadata) + metadata['poster_url'] = poster_url if metadata: return serve_template(templatename="info.html", data=metadata, title="Info", config=config, source=source) @@ -1688,6 +1693,22 @@ class WebInterface(object): return None + @cherrypy.expose + def delete_poster_url(self, poster_url=''): + + if poster_url: + data_factory = datafactory.DataFactory() + result = data_factory.delete_poster_url(poster_url=poster_url) + else: + result = None + + if result: + cherrypy.response.headers['Content-type'] = 'application/json' + return json.dumps({'message': result}) + else: + cherrypy.response.headers['Content-type'] = 'application/json' + return json.dumps({'message': 'no data received'}) + ##### Search ##### From 1ba3bdfbdaab231e8d4924984795eb84a27d8d59 Mon Sep 17 00:00:00 2001 From: drzoidberg33 Date: Wed, 2 Mar 2016 14:00:30 +0200 Subject: [PATCH 22/48] Don't check for PMS updates every 10 seconds. --- plexpy/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 2ea4c4db..3443fdbe 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -308,7 +308,7 @@ def initialize_scheduler(): if CONFIG.MONITOR_PMS_UPDATES: schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates', - hours=0, minutes=0, seconds=10) + hours=12, minutes=0, seconds=0) else: schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates', hours=0, minutes=0, seconds=0) From bea82c66403a2e931793be6fafdd55dbb3597fab Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Thu, 3 Mar 2016 14:26:39 -0800 Subject: [PATCH 23/48] Fix IPv6 address --- plexpy/pmsconnect.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index fc9c70b1..0aa76bbc 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -1040,7 +1040,7 @@ class PmsConnect(object): 'user_id': user_details['user_id'], 'friendly_name': user_details['friendly_name'], 'user_thumb': user_details['user_thumb'], - 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1], + 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1], 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'machine_id': machine_id, @@ -1162,7 +1162,7 @@ class PmsConnect(object): 'user_id': user_details['user_id'], 'friendly_name': user_details['friendly_name'], 'user_thumb': user_details['user_thumb'], - 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1], + 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1], 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'machine_id': machine_id, @@ -1220,7 +1220,7 @@ class PmsConnect(object): 'user_id': user_details['user_id'], 'friendly_name': user_details['friendly_name'], 'user_thumb': user_details['user_thumb'], - 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1], + 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1], 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'machine_id': machine_id, @@ -1278,7 +1278,7 @@ class PmsConnect(object): 'user_id': user_details['user_id'], 'friendly_name': user_details['friendly_name'], 'user_thumb': user_details['user_thumb'], - 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1], + 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1], 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'machine_id': machine_id, @@ -1369,7 +1369,7 @@ class PmsConnect(object): 'user_id': user_details['user_id'], 'friendly_name': user_details['friendly_name'], 'user_thumb': user_details['user_thumb'], - 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1], + 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1], 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), 'machine_id': machine_id, @@ -2063,4 +2063,4 @@ class PmsConnect(object): 'changelog': helpers.get_xml_attr(item, 'fixed') } - return updater_info \ No newline at end of file + return updater_info From a4d6c6c0d84c58494eca3fc95fa9196330c2556f Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Fri, 4 Mar 2016 22:18:34 -0800 Subject: [PATCH 24/48] Fix datatables modal popups from 464d2a5 --- data/interfaces/default/history.html | 2 +- data/interfaces/default/history_table_modal.html | 6 +++--- data/interfaces/default/info.html | 2 +- data/interfaces/default/js/tables/history_table.js | 10 +++++----- .../default/js/tables/history_table_modal.js | 2 +- data/interfaces/default/js/tables/user_ips.js | 8 ++++---- data/interfaces/default/library.html | 2 +- data/interfaces/default/user.html | 4 ++-- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/data/interfaces/default/history.html b/data/interfaces/default/history.html index 992f2df6..08b7c92a 100644 --- a/data/interfaces/default/history.html +++ b/data/interfaces/default/history.html @@ -22,7 +22,7 @@
    - +
    diff --git a/data/interfaces/default/history_table_modal.html b/data/interfaces/default/history_table_modal.html index a6d6804d..d1a45968 100644 --- a/data/interfaces/default/history_table_modal.html +++ b/data/interfaces/default/history_table_modal.html @@ -10,7 +10,7 @@
    Delete
    +
    @@ -45,9 +45,9 @@ } } - history_table = $('#history_table').DataTable(history_table_modal_options); + history_table = $('#history_table_modal').DataTable(history_table_modal_options); - clearSearchButton('history_table', history_table); + clearSearchButton('history_table_modal', history_table); // Move #info-modal to parent container if (!($('#history-modal').next().is('#info-modal'))) { diff --git a/data/interfaces/default/info.html b/data/interfaces/default/info.html index d2d03c00..3186595d 100644 --- a/data/interfaces/default/info.html +++ b/data/interfaces/default/info.html @@ -364,7 +364,7 @@ DOCUMENTATION :: END
    -
    Started
    +
    diff --git a/data/interfaces/default/js/tables/history_table.js b/data/interfaces/default/js/tables/history_table.js index 2f337189..1b3cc10d 100644 --- a/data/interfaces/default/js/tables/history_table.js +++ b/data/interfaces/default/js/tables/history_table.js @@ -107,7 +107,7 @@ history_table_options = { } }, "width": "10%", - "className": "no-wrap hidden-md hidden-sm hidden-xs modal-control" + "className": "no-wrap hidden-md hidden-sm hidden-xs" }, { "targets": [5], @@ -307,7 +307,7 @@ history_table_options = { } // Parent table platform modal -$('#history_table').on('click', '> tbody > tr > td.modal-control', function () { +$('.history_table').on('click', '> tbody > tr > td.modal-control', function () { var tr = $(this).closest('tr'); var row = history_table.row( tr ); var rowData = row.data(); @@ -327,7 +327,7 @@ $('#history_table').on('click', '> tbody > tr > td.modal-control', function () { }); // Parent table ip address modal -$('#history_table').on('click', '> tbody > tr > td.modal-control-ip', function () { +$('.history_table').on('click', '> tbody > tr > td.modal-control-ip', function () { var tr = $(this).closest('tr'); var row = history_table.row( tr ); var rowData = row.data(); @@ -350,7 +350,7 @@ $('#history_table').on('click', '> tbody > tr > td.modal-control-ip', function ( }); // Parent table delete mode -$('#history_table').on('click', '> tbody > tr > td.delete-control > button', function () { +$('.history_table').on('click', '> tbody > tr > td.delete-control > button', function () { var tr = $(this).closest('tr'); var row = history_table.row( tr ); var rowData = row.data(); @@ -399,7 +399,7 @@ $('#history_table').on('click', '> tbody > tr > td.delete-control > button', fun }); // Parent table expand detailed history -$('#history_table').on('click', '> tbody > tr > td.expand-history a', function () { +$('.history_table').on('click', '> tbody > tr > td.expand-history a', function () { var tr = $(this).closest('tr'); var row = history_table.row(tr); var rowData = row.data(); diff --git a/data/interfaces/default/js/tables/history_table_modal.js b/data/interfaces/default/js/tables/history_table_modal.js index e9cbee8d..df733cbf 100644 --- a/data/interfaces/default/js/tables/history_table_modal.js +++ b/data/interfaces/default/js/tables/history_table_modal.js @@ -148,7 +148,7 @@ history_table_modal_options = { } } -$('#history_table').on('click', 'td.modal-control', function () { +$('.history_table').on('click', 'td.modal-control', function () { var tr = $(this).parents('tr'); var row = history_table.row(tr); var rowData = row.data(); diff --git a/data/interfaces/default/js/tables/user_ips.js b/data/interfaces/default/js/tables/user_ips.js index d025d7d6..fbcaff74 100644 --- a/data/interfaces/default/js/tables/user_ips.js +++ b/data/interfaces/default/js/tables/user_ips.js @@ -56,7 +56,7 @@ user_ip_table_options = { } }, "width": "15%", - "className": "no-wrap hidden-md hidden-sm hidden-xs modal-control" + "className": "no-wrap hidden-md hidden-sm hidden-xs" }, { "targets": [3], @@ -146,11 +146,11 @@ user_ip_table_options = { } } -$('#user_ip_table').on('mouseenter', 'td.modal-control span', function () { +$('.user_ip_table').on('mouseenter', 'td.modal-control span', function () { $(this).tooltip(); }); -$('#user_ip_table').on('click', 'td.modal-control', function () { +$('.user_ip_table').on('click', 'td.modal-control', function () { var tr = $(this).parents('tr'); var row = user_ip_table.row(tr); var rowData = row.data(); @@ -169,7 +169,7 @@ $('#user_ip_table').on('click', 'td.modal-control', function () { showStreamDetails(); }); -$('#user_ip_table').on('click', 'td.modal-control-ip', function () { +$('.user_ip_table').on('click', 'td.modal-control-ip', function () { var tr = $(this).parents('tr'); var row = user_ip_table.row( tr ); var rowData = row.data(); diff --git a/data/interfaces/default/library.html b/data/interfaces/default/library.html index 08bb0a18..735ca61a 100644 --- a/data/interfaces/default/library.html +++ b/data/interfaces/default/library.html @@ -186,7 +186,7 @@ DOCUMENTATION :: END
    -
    Delete
    +
    diff --git a/data/interfaces/default/user.html b/data/interfaces/default/user.html index ad0c8711..7b26f313 100644 --- a/data/interfaces/default/user.html +++ b/data/interfaces/default/user.html @@ -148,7 +148,7 @@ from plexpy import helpers
    -
    Delete
    +
    @@ -186,7 +186,7 @@ from plexpy import helpers
    -
    Last Seen
    +
    From 59628a72fb214f9e830608fb73f15fc1b158cf62 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Fri, 4 Mar 2016 22:19:16 -0800 Subject: [PATCH 25/48] Fix typo in PlexWatch importer --- plexpy/plexwatch_import.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plexpy/plexwatch_import.py b/plexpy/plexwatch_import.py index 533b5bdc..e9d6b13e 100644 --- a/plexpy/plexwatch_import.py +++ b/plexpy/plexwatch_import.py @@ -251,7 +251,7 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval hours=0, minutes=0, seconds=0) plexpy.schedule_job(activity_pinger.check_recently_added, 'Check for recently added items', hours=0, minutes=0, seconds=0) - plexpy.schedule_job(activity_pinger.check_server_response, 'Check for server response', + plexpy.schedule_job(activity_pinger.check_server_response, 'Check for Plex remote access', hours=0, minutes=0, seconds=0) ap = activity_processor.ActivityProcessor() @@ -302,7 +302,7 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval # Skip line if we don't have a ratingKey to work with if not row['rating_key']: - logger.error(u"PlexPy Importer :: Skipping record due to null ratingRey.") + logger.error(u"PlexPy Importer :: Skipping record due to null ratingKey.") continue # If the user_id no longer exists in the friends list, pull it from the xml. From e8a65df7f0ebb5274e8849f45ccb0578777ac6f0 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Fri, 4 Mar 2016 22:20:15 -0800 Subject: [PATCH 26/48] Add transcode_decision to media_info table --- .../default/js/tables/history_table.js | 6 +- .../default/js/tables/history_table_modal.js | 6 +- data/interfaces/default/js/tables/user_ips.js | 6 +- data/interfaces/default/js/tables/users.js | 6 +- plexpy/__init__.py | 19 ++- plexpy/activity_processor.py | 16 ++- plexpy/datafactory.py | 16 +-- plexpy/graphs.py | 111 ++++++++---------- plexpy/libraries.py | 1 - plexpy/users.py | 12 +- 10 files changed, 101 insertions(+), 98 deletions(-) diff --git a/data/interfaces/default/js/tables/history_table.js b/data/interfaces/default/js/tables/history_table.js index 1b3cc10d..3b176385 100644 --- a/data/interfaces/default/js/tables/history_table.js +++ b/data/interfaces/default/js/tables/history_table.js @@ -115,11 +115,11 @@ history_table_options = { "createdCell": function (td, cellData, rowData, row, col) { if (cellData !== '') { var transcode_dec = ''; - if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') { + if (rowData['transcode_decision'] === 'transcode') { transcode_dec = ''; - } else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') { + } else if (rowData['transcode_decision'] === 'copy') { transcode_dec = ''; - } else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') { + } else if (rowData['transcode_decision'] === 'direct play') { transcode_dec = ''; } $(td).html(''); diff --git a/data/interfaces/default/js/tables/history_table_modal.js b/data/interfaces/default/js/tables/history_table_modal.js index df733cbf..a58ca2f7 100644 --- a/data/interfaces/default/js/tables/history_table_modal.js +++ b/data/interfaces/default/js/tables/history_table_modal.js @@ -79,11 +79,11 @@ history_table_modal_options = { "createdCell": function (td, cellData, rowData, row, col) { if (cellData !== '') { var transcode_dec = ''; - if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') { + if (rowData['transcode_decision'] === 'transcode') { transcode_dec = ''; - } else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') { + } else if (rowData['transcode_decision'] === 'copy') { transcode_dec = ''; - } else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') { + } else if (rowData['transcode_decision'] === 'direct play') { transcode_dec = ''; } $(td).html(''); diff --git a/data/interfaces/default/js/tables/user_ips.js b/data/interfaces/default/js/tables/user_ips.js index fbcaff74..b97a4407 100644 --- a/data/interfaces/default/js/tables/user_ips.js +++ b/data/interfaces/default/js/tables/user_ips.js @@ -64,11 +64,11 @@ user_ip_table_options = { "createdCell": function (td, cellData, rowData, row, col) { if (cellData) { var transcode_dec = ''; - if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') { + if (rowData['transcode_decision'] === 'transcode') { transcode_dec = ''; - } else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') { + } else if (rowData['transcode_decision'] === 'copy') { transcode_dec = ''; - } else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') { + } else if (rowData['transcode_decision'] === 'direct play') { transcode_dec = ''; } $(td).html(''); diff --git a/data/interfaces/default/js/tables/users.js b/data/interfaces/default/js/tables/users.js index b667e803..cc6ea22c 100644 --- a/data/interfaces/default/js/tables/users.js +++ b/data/interfaces/default/js/tables/users.js @@ -120,11 +120,11 @@ users_list_table_options = { "createdCell": function (td, cellData, rowData, row, col) { if (cellData !== null && cellData !== '') { var transcode_dec = ''; - if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') { + if (rowData['transcode_decision'] === 'transcode') { transcode_dec = ''; - } else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') { + } else if (rowData['transcode_decision'] === 'copy') { transcode_dec = ''; - } else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') { + } else if (rowData['transcode_decision'] === 'direct play') { transcode_dec = ''; } $(td).html(''); diff --git a/plexpy/__init__.py b/plexpy/__init__.py index 3443fdbe..2f544920 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -420,8 +420,8 @@ def dbcheck(): # session_history_media_info table :: This is a table which logs each session's media info c_db.execute( - 'CREATE TABLE IF NOT EXISTS session_history_media_info (id INTEGER PRIMARY KEY, ' - 'rating_key INTEGER, video_decision TEXT, audio_decision TEXT, duration INTEGER DEFAULT 0, width INTEGER, ' + 'CREATE TABLE IF NOT EXISTS session_history_media_info (id INTEGER PRIMARY KEY, rating_key INTEGER, ' + 'video_decision TEXT, audio_decision TEXT, transcode_decision TEXT, duration INTEGER DEFAULT 0, width INTEGER, ' 'height INTEGER, container TEXT, video_codec TEXT, audio_codec TEXT, bitrate INTEGER, video_resolution TEXT, ' 'video_framerate TEXT, aspect_ratio TEXT, audio_channels INTEGER, transcode_protocol TEXT, ' 'transcode_container TEXT, transcode_video_codec TEXT, transcode_audio_codec TEXT, ' @@ -671,6 +671,21 @@ def dbcheck(): 'ALTER TABLE session_history_metadata ADD COLUMN section_id INTEGER' ) + # Upgrade session_history_media_info table from earlier versions + try: + c_db.execute('SELECT transcode_decision FROM session_history_media_info') + except sqlite3.OperationalError: + logger.debug(u"Altering database. Updating database table session_history_media_info.") + c_db.execute( + 'ALTER TABLE session_history_media_info ADD COLUMN transcode_decision TEXT' + ) + c_db.execute( + 'UPDATE session_history_media_info SET transcode_decision = (CASE ' + 'WHEN video_decision = "transcode" OR audio_decision = "transcode" THEN "transcode" ' + 'WHEN video_decision = "copy" OR audio_decision = "copy" THEN "copy" ' + 'WHEN video_decision = "direct play" OR audio_decision = "direct play" THEN "direct play" END)' + ) + # Upgrade users table from earlier versions try: c_db.execute('SELECT do_notify FROM users') diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py index 0ea6e532..f14cd432 100644 --- a/plexpy/activity_processor.py +++ b/plexpy/activity_processor.py @@ -218,13 +218,22 @@ class ActivityProcessor(object): # % last_id) # Write the session_history_media_info table + + # Generate a combined transcode decision value + if session['video_decision'] == 'transcode' or session['audio_decision'] == 'transcode': + transcode_decision = 'transcode' + elif session['video_decision'] == 'copy' or session['audio_decision'] == 'copy': + transcode_decision = 'copy' + else: + transcode_decision = 'direct play' + # logger.debug(u"PlexPy ActivityProcessor :: Attempting to write to session_history_media_info table...") query = 'INSERT INTO session_history_media_info (id, rating_key, video_decision, audio_decision, ' \ 'duration, width, height, container, video_codec, audio_codec, bitrate, video_resolution, ' \ 'video_framerate, aspect_ratio, audio_channels, transcode_protocol, transcode_container, ' \ 'transcode_video_codec, transcode_audio_codec, transcode_audio_channels, transcode_width, ' \ - 'transcode_height) VALUES ' \ - '(last_insert_rowid(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' + 'transcode_height, transcode_decision) VALUES ' \ + '(last_insert_rowid(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' args = [session['rating_key'], session['video_decision'], session['audio_decision'], session['duration'], session['width'], session['height'], session['container'], @@ -232,7 +241,8 @@ class ActivityProcessor(object): session['video_resolution'], session['video_framerate'], session['aspect_ratio'], session['audio_channels'], session['transcode_protocol'], session['transcode_container'], session['transcode_video_codec'], session['transcode_audio_codec'], - session['transcode_audio_channels'], session['transcode_width'], session['transcode_height']] + session['transcode_audio_channels'], session['transcode_width'], session['transcode_height'], + transcode_decision] # logger.debug(u"PlexPy ActivityProcessor :: Writing session_history_media_info transaction...") self.db.action(query=query, args=args) diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 7e54a59c..8b6d151d 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -61,8 +61,7 @@ class DataFactory(object): 'MAX((CASE WHEN (view_offset IS NULL OR view_offset = "") THEN 0.1 ELSE view_offset * 1.0 END) / \ (CASE WHEN (session_history_metadata.duration IS NULL OR session_history_metadata.duration = "") \ THEN 1.0 ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete', - 'session_history_media_info.video_decision', - 'session_history_media_info.audio_decision', + 'session_history_media_info.transcode_decision', 'COUNT(*) AS group_count', 'GROUP_CONCAT(session_history.id) AS group_ids' ] @@ -138,8 +137,7 @@ class DataFactory(object): 'media_index': item['media_index'], 'parent_media_index': item['parent_media_index'], 'thumb': thumb, - 'video_decision': item['video_decision'], - 'audio_decision': item['audio_decision'], + 'transcode_decision': item['transcode_decision'], 'percent_complete': int(round(item['percent_complete'])), 'watched_status': watched_status, 'group_count': item['group_count'], @@ -626,24 +624,21 @@ class DataFactory(object): title = 'Concurrent Transcodes' query = base_query \ - + 'AND (session_history_media_info.video_decision = "transcode" ' \ - 'OR session_history_media_info.audio_decision = "transcode") ' + + 'AND session_history_media_info.transcode_decision = "transcode" ' result = monitor_db.select(query) if result: most_concurrent.append(calc_most_concurrent(title, result)) title = 'Concurrent Direct Streams' query = base_query \ - + 'AND (session_history_media_info.video_decision != "transcode" ' \ - 'AND session_history_media_info.audio_decision = "copy") ' + + 'AND session_history_media_info.transcode_decision = "copy" ' result = monitor_db.select(query) if result: most_concurrent.append(calc_most_concurrent(title, result)) title = 'Concurrent Direct Plays' query = base_query \ - + 'AND (session_history_media_info.video_decision = "direct play" ' \ - 'OR session_history_media_info.audio_decision = "direct play") ' + + 'AND session_history_media_info.transcode_decision = "direct play" ' result = monitor_db.select(query) if result: most_concurrent.append(calc_most_concurrent(title, result)) @@ -828,6 +823,7 @@ class DataFactory(object): 'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS total_duration ' \ 'FROM session_history ' \ 'JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \ + 'JOIN session_history_media_info ON session_history_media_info.id = session_history.id ' \ '%s ' % where result = monitor_db.select(query) except Exception as e: diff --git a/plexpy/graphs.py b/plexpy/graphs.py index e5d93720..d9f9cd55 100644 --- a/plexpy/graphs.py +++ b/plexpy/graphs.py @@ -490,40 +490,39 @@ class Graphs(object): try: if y_axis == 'plays': query = 'SELECT date(session_history.started, "unixepoch", "localtime") AS date_played, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ - 'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ - 'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ - 'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \ + 'THEN 1 ELSE 0 END) AS dp_count, ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \ + 'THEN 1 ELSE 0 END) AS ds_count, ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \ + 'THEN 1 ELSE 0 END) AS tc_count ' \ 'FROM session_history ' \ 'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \ 'WHERE (datetime(session_history.stopped, "unixepoch", "localtime") >= ' \ 'datetime("now", "-%s days", "localtime")) AND ' \ - '(session_history.media_type = "episode" OR session_history.media_type = "movie" OR session_history.media_type = "track") ' \ + '(session_history.media_type = "episode" OR session_history.media_type = "movie" OR ' \ + 'session_history.media_type = "track") ' \ 'GROUP BY date_played ' \ 'ORDER BY started ASC' % time_range result = monitor_db.select(query) else: query = 'SELECT date(session_history.started, "unixepoch", "localtime") AS date_played, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ - 'OR session_history_media_info.audio_decision = "direct play") ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ - 'AND session_history_media_info.audio_decision = "copy") ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ - 'OR session_history_media_info.audio_decision = "transcode") ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count ' \ 'FROM session_history ' \ 'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \ 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \ 'datetime("now", "-%s days", "localtime") AND ' \ - '(session_history.media_type = "episode" OR session_history.media_type = "movie" OR session_history.media_type = "track") ' \ + '(session_history.media_type = "episode" OR session_history.media_type = "movie" OR ' \ + 'session_history.media_type = "track") ' \ 'GROUP BY date_played ' \ 'ORDER BY started ASC' % time_range @@ -583,12 +582,12 @@ class Graphs(object): try: if y_axis == 'plays': query = 'SELECT session_history_media_info.video_resolution AS resolution, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ - 'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ - 'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ - 'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \ + 'THEN 1 ELSE 0 END) AS dp_count, ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \ + 'THEN 1 ELSE 0 END) AS ds_count, ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \ + 'THEN 1 ELSE 0 END) AS tc_count, ' \ 'COUNT(session_history.id) AS total_count ' \ 'FROM session_history ' \ 'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \ @@ -602,16 +601,13 @@ class Graphs(object): result = monitor_db.select(query) else: query = 'SELECT session_history_media_info.video_resolution AS resolution,' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ - 'OR session_history_media_info.audio_decision = "direct play") ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ - 'AND session_history_media_info.audio_decision = "copy") ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ - 'OR session_history_media_info.audio_decision = "transcode") ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \ 'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ' \ @@ -671,12 +667,12 @@ class Graphs(object): 'WHEN session_history_media_info.transcode_height <= 1440 THEN "QHD" ' \ 'WHEN session_history_media_info.transcode_height <= 2160 THEN "4K" ' \ 'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) AS resolution, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ - 'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ - 'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" '\ - 'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \ + 'THEN 1 ELSE 0 END) AS dp_count, ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \ + 'THEN 1 ELSE 0 END) AS ds_count, ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" '\ + 'THEN 1 ELSE 0 END) AS tc_count, ' \ 'COUNT(session_history.id) AS total_count ' \ 'FROM session_history ' \ 'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \ @@ -700,16 +696,13 @@ class Graphs(object): 'WHEN session_history_media_info.transcode_height <= 1440 THEN "QHD" ' \ 'WHEN session_history_media_info.transcode_height <= 2160 THEN "4K" ' \ 'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) AS resolution, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ - 'OR session_history_media_info.audio_decision = "direct play") ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ - 'AND session_history_media_info.audio_decision = "copy") ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ - 'OR session_history_media_info.audio_decision = "transcode") ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \ 'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ' \ @@ -759,12 +752,12 @@ class Graphs(object): try: if y_axis == 'plays': query = 'SELECT session_history.platform AS platform, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ - 'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ - 'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ - 'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \ + 'THEN 1 ELSE 0 END) AS dp_count, ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \ + 'THEN 1 ELSE 0 END) AS ds_count, ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \ + 'THEN 1 ELSE 0 END) AS tc_count, ' \ 'COUNT(session_history.id) AS total_count ' \ 'FROM session_history ' \ 'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \ @@ -777,16 +770,13 @@ class Graphs(object): result = monitor_db.select(query) else: query = 'SELECT session_history.platform AS platform, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ - 'OR session_history_media_info.audio_decision = "direct play") ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ - 'AND session_history_media_info.audio_decision = "copy") ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ - 'AND session_history_media_info.audio_decision = "transcode") ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \ 'SUM(CASE WHEN session_history.stopped > 0 ' \ @@ -838,12 +828,12 @@ class Graphs(object): if y_axis == 'plays': query = 'SELECT ' \ '(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) AS username, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ - 'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ - 'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ - 'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \ + 'THEN 1 ELSE 0 END) AS dp_count, ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \ + 'THEN 1 ELSE 0 END) AS ds_count, ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \ + 'THEN 1 ELSE 0 END) AS tc_count, ' \ 'COUNT(session_history.id) AS total_count ' \ 'FROM session_history ' \ 'JOIN users ON session_history.user_id = users.user_id ' \ @@ -858,16 +848,13 @@ class Graphs(object): else: query = 'SELECT ' \ '(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) AS username, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \ - 'OR session_history_media_info.audio_decision = "direct play") ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \ - 'AND session_history_media_info.audio_decision = "copy") ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \ - 'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \ - 'AND session_history_media_info.audio_decision = "transcode") ' \ + 'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \ 'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \ ' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \ 'SUM(CASE WHEN session_history.stopped > 0 ' \ diff --git a/plexpy/libraries.py b/plexpy/libraries.py index 4b6f47f5..3f01d614 100644 --- a/plexpy/libraries.py +++ b/plexpy/libraries.py @@ -148,7 +148,6 @@ class Libraries(object): 'session_history_metadata.year', 'session_history_metadata.media_index', 'session_history_metadata.parent_media_index', - 'session_history_media_info.video_decision', 'library_sections.do_notify', 'library_sections.do_notify_created', 'library_sections.keep_history' diff --git a/plexpy/users.py b/plexpy/users.py index e4e7ee3c..44b68090 100644 --- a/plexpy/users.py +++ b/plexpy/users.py @@ -50,8 +50,7 @@ class Users(object): 'session_history_metadata.year', 'session_history_metadata.media_index', 'session_history_metadata.parent_media_index', - 'session_history_media_info.video_decision', - 'session_history_media_info.audio_decision', + 'session_history_media_info.transcode_decision', 'users.do_notify as do_notify', 'users.keep_history as keep_history' ] @@ -117,8 +116,7 @@ class Users(object): 'year': item['year'], 'media_index': item['media_index'], 'parent_media_index': item['parent_media_index'], - 'video_decision': item['video_decision'], - 'audio_decision': item['audio_decision'], + 'transcode_decision': item['transcode_decision'], 'do_notify': helpers.checked(item['do_notify']), 'keep_history': helpers.checked(item['keep_history']) } @@ -154,8 +152,7 @@ class Users(object): 'session_history_metadata.year', 'session_history_metadata.media_index', 'session_history_metadata.parent_media_index', - 'session_history_media_info.video_decision', - 'session_history_media_info.audio_decision', + 'session_history_media_info.transcode_decision', 'session_history.user', 'session_history.user_id as custom_user_id', '(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE \ @@ -213,8 +210,7 @@ class Users(object): 'year': item['year'], 'media_index': item['media_index'], 'parent_media_index': item['parent_media_index'], - 'video_decision': item['video_decision'], - 'audio_decision': item['audio_decision'], + 'transcode_decision': item['transcode_decision'], 'friendly_name': item['friendly_name'] } From efdc050a28fe9e2f0bd7e8a9c12b3af507cb6308 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Fri, 4 Mar 2016 22:21:34 -0800 Subject: [PATCH 27/48] Filter history modal on graphs based on clicked series --- data/interfaces/default/graphs.html | 23 +++++++++++++++---- .../default/history_table_modal.html | 10 ++++---- .../default/js/graphs/plays_by_day.js | 7 +++--- .../default/js/graphs/plays_by_stream_type.js | 5 ++-- plexpy/webserve.py | 8 +++++-- 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/data/interfaces/default/graphs.html b/data/interfaces/default/graphs.html index 9143bb5a..8f2d1221 100644 --- a/data/interfaces/default/graphs.html +++ b/data/interfaces/default/graphs.html @@ -249,7 +249,7 @@ diff --git a/data/interfaces/default/history_table_modal.html b/data/interfaces/default/history_table_modal.html index d1a45968..5b732d0c 100644 --- a/data/interfaces/default/history_table_modal.html +++ b/data/interfaces/default/history_table_modal.html @@ -5,7 +5,7 @@ @@ -32,7 +32,7 @@
    Delete