From 7a5cad1a31cb0e7a98af45b449b6219527a9cd72 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 6 Jul 2015 00:45:42 +0200 Subject: [PATCH] Add very early attempt at retrieving IP address per stream. --- data/interfaces/default/current_activity.html | 5 +- plexpy/log_reader.py | 37 ++++++----- plexpy/monitor.py | 63 ++++++++++++++++++- plexpy/pmsconnect.py | 52 ++++++++++----- plexpy/webserve.py | 2 +- 5 files changed, 125 insertions(+), 34 deletions(-) diff --git a/data/interfaces/default/current_activity.html b/data/interfaces/default/current_activity.html index 73fd99d8..1b09eb4b 100644 --- a/data/interfaces/default/current_activity.html +++ b/data/interfaces/default/current_activity.html @@ -22,10 +22,13 @@ thumb Returns the location of the item's thumbnail. Use with p art Returns the location of the item's artwork progress_percent Returns the current progress of the item. 0 to 100. user Returns the name of the user owning the session. +user_id Returns the Plex user id if available. +machine_id Returns the machine id of the players being used. friendly_name Returns the friendlly name of the user owning the session. state Returns the state of the current session. Either 'playing', 'paused' or 'buffering'. title Returns the name of the episode, movie or music track. player Returns the name of the platform used to play the stream. +platform Returns the type of platform used to play the stream. audio_decision Returns the audio transcode decision. Either 'transcode', 'copy' or 'direct play'. audio_codec Returns the name of the audio codec. audio_channels Returns the number of audio channels. @@ -134,7 +137,7 @@ DOCUMENTATION :: END % endfor diff --git a/plexpy/log_reader.py b/plexpy/log_reader.py index cd386ee8..a9f2ee33 100644 --- a/plexpy/log_reader.py +++ b/plexpy/log_reader.py @@ -18,7 +18,7 @@ import re import os import plexpy -def get_log_tail(window=20): +def get_log_tail(window=20, parsed=True): if plexpy.CONFIG.PMS_LOGS_FOLDER: log_file = os.path.join(plexpy.CONFIG.PMS_LOGS_FOLDER, 'Plex Media Server.log') @@ -33,24 +33,27 @@ def get_log_tail(window=20): log_lines = tail(logfile, window) - line_error = False - clean_lines = [] - for i in log_lines: - try: - log_time = i.split(' [')[0] - log_level = i.split('] ', 1)[1].split(' - ',1)[0] - log_msg = i.split('] ', 1)[1].split(' - ',1)[1] - full_line = [log_time, log_level, log_msg] - clean_lines.append(full_line) - except: - line_error = True - full_line = ['', '', 'Unable to parse log line.'] - clean_lines.append(full_line) + if parsed: + line_error = False + clean_lines = [] + for i in log_lines: + try: + log_time = i.split(' [')[0] + log_level = i.split('] ', 1)[1].split(' - ',1)[0] + log_msg = i.split('] ', 1)[1].split(' - ',1)[1] + full_line = [log_time, log_level, log_msg] + clean_lines.append(full_line) + except: + line_error = True + full_line = ['', '', 'Unable to parse log line.'] + clean_lines.append(full_line) - if line_error: - logger.error('PlexPy was unable to parse some lines of the Plex Media Server log.') + if line_error: + logger.error('PlexPy was unable to parse some lines of the Plex Media Server log.') - return clean_lines + return clean_lines + + return log_lines # http://stackoverflow.com/a/13790289/2405162 def tail(f, lines=1, _buffer=4098): diff --git a/plexpy/monitor.py b/plexpy/monitor.py index 2d402702..d5f7aac4 100644 --- a/plexpy/monitor.py +++ b/plexpy/monitor.py @@ -13,7 +13,7 @@ # You should have received a copy of the GNU General Public License # along with PlexPy. If not, see . -from plexpy import logger, helpers, plexwatch, pmsconnect, notification_handler, config +from plexpy import logger, helpers, plexwatch, pmsconnect, notification_handler, config, log_reader from xml.dom import minidom from httplib import HTTPSConnection @@ -23,6 +23,8 @@ import os import sqlite3 import threading import plexpy +import re +import time monitor_lock = threading.Lock() @@ -32,6 +34,7 @@ def check_active_sessions(): pms_connect = pmsconnect.PmsConnect() session_list = pms_connect.get_current_activity() monitor_db = MonitorDatabase() + # logger.debug(u"Checking for active streams.") if session_list['stream_count'] != '0': media_container = session_list['sessions'] @@ -46,6 +49,7 @@ def check_active_sessions(): title = session['title'] parent_title = session['parent_title'] grandparent_title = session['grandparent_title'] + machine_id = session['machine_id'] write_session = monitor_db.write_session_key(session_key, rating_key, media_type) if write_session == 'insert': @@ -60,6 +64,12 @@ def check_active_sessions(): pushmessage = '%s (%s) starting playing %s' % (friendly_name, platform, item_title) notification_handler.push_nofitications(pushmessage, 'PlexPy Playback started', 'Playback Started') + # Try and grab IP address from logs + if plexpy.CONFIG.PMS_LOGS_FOLDER: + monitor_processing = MonitorProcessing() + ip_address = monitor_processing.find_session_ip(rating_key=rating_key, + machine_id=machine_id) + keys = {'session_key': session_key, 'rating_key': rating_key} active_streams.append(keys) @@ -186,3 +196,54 @@ class MonitorDatabase(object): # We want to know if it was an update or insert return trans_type + + +class MonitorProcessing(object): + + def __init__(self): + pass + + def find_session_ip(self, rating_key=None, machine_id=None): + + logger.debug(u"Requesting log lines...") + log_lines = log_reader.get_log_tail(window=5000, parsed=False) + + rating_key_line = 'metadata%2F' + rating_key + machine_id_line = 'session=' + machine_id + + for line in reversed(log_lines): + # We're good if we find a line with both machine id and rating key + # This is usually when there is a transcode session + if machine_id_line in line and rating_key_line in line: + # Currently only checking for ipv4 addresses + ipv4 = re.findall(r'[0-9]+(?:\.[0-9]+){3}', line) + if ipv4: + # The logged IP will always be the first match and we don't want localhost entries + if ipv4[0] != '127.0.0.1': + logger.debug(u"Matched IP address (%s) for stream ratingKey %s and machineIdentifier %s." + % (ipv4[0], rating_key, machine_id)) + return ipv4[0] + + logger.debug(u"Unable to find IP address on first pass. Attempting fallback check in 5 seconds...") + + # Wait for the log to catch up and read in new lines + time.sleep(5) + + logger.debug(u"Requesting log lines...") + log_lines = log_reader.get_log_tail(window=5000, parsed=False) + + for line in reversed(log_lines): + if 'GET /:/timeline' in line and rating_key_line in line: + # Currently only checking for ipv4 addresses + # This method can return the wrong IP address if more than one user + # starts watching the same media item around the same time. + ipv4 = re.findall(r'[0-9]+(?:\.[0-9]+){3}', line) + if ipv4: + # The logged IP will always be the first match and we don't want localhost entries + if ipv4[0] != '127.0.0.1': + logger.debug(u"Matched IP address (%s) for stream ratingKey %s." % (ipv4[0], rating_key)) + return ipv4[0] + + logger.debug(u"Unable to find IP address on fallback search. Not logging IP address.") + + return None diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 91ae25a6..c62fb558 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -24,7 +24,7 @@ class PmsConnect(object): """ def __init__(self): - self.protocol = 'HTTPS' + self.protocol = 'HTTP' self.request_handler = http_handler.HTTPHandler(host=plexpy.CONFIG.PMS_IP, port=plexpy.CONFIG.PMS_PORT, token=plexpy.CONFIG.PMS_TOKEN) @@ -196,7 +196,7 @@ class PmsConnect(object): for a in xml_head: if a.getAttribute('size'): if a.getAttribute('size') == '0': - output = {'recently_added': None} + output = {'recently_added': []} return output if a.getElementsByTagName('Directory'): @@ -459,14 +459,24 @@ class PmsConnect(object): duration = helpers.get_xml_attr(media_info, 'duration') progress = helpers.get_xml_attr(session, 'viewOffset') + user_details = plex_watch.get_user_details( + user=helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title')) + + if helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'machineIdentifier').endswith('_Track'): + machine_id = helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'machineIdentifier')[:-6] + else: + machine_id = helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'machineIdentifier') + session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), 'art': helpers.get_xml_attr(session, 'art'), 'parent_thumb': helpers.get_xml_attr(session, 'parentThumb'), 'thumb': helpers.get_xml_attr(session, 'thumb'), 'user': helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), - 'friendly_name': plex_watch.get_user_friendly_name( - helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title')), - 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), + 'user_id': user_details['user_id'], + 'friendly_name': user_details['friendly_name'], + 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), + 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), + 'machine_id': machine_id, 'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'), @@ -528,15 +538,25 @@ class PmsConnect(object): thumb = helpers.get_xml_attr(session, 'thumb') use_indexes = 0 + user_details = plex_watch.get_user_details( + user=helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title')) + + if helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'machineIdentifier').endswith('_Video'): + machine_id = helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'machineIdentifier')[:-6] + else: + machine_id = helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'machineIdentifier') + if helpers.get_xml_attr(session, 'type') == 'episode': session_output = {'session_key': helpers.get_xml_attr(session, 'sessionKey'), 'art': helpers.get_xml_attr(session, 'art'), 'parent_thumb': helpers.get_xml_attr(session, 'parentThumb'), 'thumb': thumb, 'user': helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), - 'friendly_name': plex_watch.get_user_friendly_name( - helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title')), - 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), + 'user_id': user_details['user_id'], + 'friendly_name': user_details['friendly_name'], + 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), + 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), + 'machine_id': machine_id, 'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'), @@ -561,9 +581,11 @@ class PmsConnect(object): 'thumb': thumb, 'parent_thumb': helpers.get_xml_attr(session, 'parentThumb'), 'user': helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), - 'friendly_name': plex_watch.get_user_friendly_name( - helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title')), - 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), + 'user_id': user_details['user_id'], + 'friendly_name': user_details['friendly_name'], + 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), + 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), + 'machine_id': machine_id, 'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'), @@ -588,9 +610,11 @@ class PmsConnect(object): 'thumb': thumb, 'parent_thumb': helpers.get_xml_attr(session, 'parentThumb'), 'user': helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title'), - 'friendly_name': plex_watch.get_user_friendly_name( - helpers.get_xml_attr(session.getElementsByTagName('User')[0], 'title')), - 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), + 'user_id': user_details['user_id'], + 'friendly_name': user_details['friendly_name'], + 'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'), + 'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'), + 'machine_id': machine_id, 'state': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'state'), 'grandparent_title': helpers.get_xml_attr(session, 'grandparentTitle'), 'parent_title': helpers.get_xml_attr(session, 'parentTitle'), diff --git a/plexpy/webserve.py b/plexpy/webserve.py index 1c9288fb..6e095ba3 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -952,4 +952,4 @@ class WebInterface(object): cherrypy.response.headers['Content-type'] = 'application/json' return result else: - logger.warn('Unable to retrieve data.') \ No newline at end of file + logger.warn('Unable to retrieve data.')