Files
JellyPy/plexpy/monitor.py
Tim c4416572cf Remove unnecessary transparency on update notification and column selection.
Keep the update message closed for 1 hour if close button is clicked. Will reset if manual update check is done.
Don't ignore user logging when doing import (we still need an option in the import to override this).
2015-08-14 20:15:30 +02:00

357 lines
21 KiB
Python

# This file is part of PlexPy.
#
# PlexPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PlexPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, pmsconnect, notification_handler, log_reader, common, database, helpers
import threading
import plexpy
import re
import time
monitor_lock = threading.Lock()
def check_active_sessions():
with monitor_lock:
pms_connect = pmsconnect.PmsConnect()
session_list = pms_connect.get_current_activity()
monitor_db = database.MonitorDatabase()
monitor_process = MonitorProcessing()
# logger.debug(u"PlexPy Monitor :: Checking for active streams.")
if session_list:
media_container = session_list['sessions']
# Check our temp table for what we must do with the new streams
db_streams = monitor_db.select('SELECT started, session_key, rating_key, media_type, title, parent_title, '
'grandparent_title, user_id, user, friendly_name, ip_address, player, '
'platform, machine_id, parent_rating_key, grandparent_rating_key, state, '
'view_offset, duration, video_decision, audio_decision, 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, paused_counter '
'FROM sessions')
for stream in db_streams:
if any(d['session_key'] == str(stream['session_key']) and d['rating_key'] == str(stream['rating_key'])
for d in media_container):
# The user's session is still active
for session in media_container:
if session['session_key'] == str(stream['session_key']) and \
session['rating_key'] == str(stream['rating_key']):
# The user is still playing the same media item
# Here we can check the play states
if session['state'] != stream['state']:
if session['state'] == 'paused':
# Push any notifications -
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='pause')).start()
if stream['state'] == 'paused':
# The stream is still paused so we need to increment the paused_counter
# Using the set config parameter as the interval, probably not the most accurate but
# it will have to do for now.
paused_counter = int(stream['paused_counter']) + plexpy.CONFIG.MONITORING_INTERVAL
monitor_db.action('UPDATE sessions SET paused_counter = ? '
'WHERE session_key = ? AND rating_key = ?',
[paused_counter, stream['session_key'], stream['rating_key']])
# Check if the user has reached the offset in the media we defined as the "watched" percent
# Don't trigger if state is buffer as some clients push the progress to the end when
# buffering on start.
if session['progress'] and session['duration'] and session['state'] != 'buffering':
if helpers.get_percent(session['progress'],
session['duration']) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT:
# Push any notifications -
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='watched')).start()
else:
# The user has stopped playing a stream
logger.debug(u"PlexPy Monitor :: Removing sessionKey %s ratingKey %s from session queue"
% (stream['session_key'], stream['rating_key']))
monitor_db.action('DELETE FROM sessions WHERE session_key = ? AND rating_key = ?',
[stream['session_key'], stream['rating_key']])
# Check if the user has reached the offset in the media we defined as the "watched" percent
if stream['view_offset'] and stream['duration']:
if helpers.get_percent(stream['view_offset'],
stream['duration']) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT:
# Push any notifications -
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='watched')).start()
# Push any notifications - Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='stop')).start()
# Write the item history on playback stop
monitor_process.write_session_history(session=stream)
# Process the newly received session data
for session in media_container:
monitor_process.write_session(session)
else:
logger.debug(u"PlexPy Monitor :: Unable to read session list.")
class MonitorProcessing(object):
def __init__(self):
self.db = database.MonitorDatabase()
def write_session(self, session=None):
values = {'session_key': session['session_key'],
'rating_key': session['rating_key'],
'media_type': session['type'],
'state': session['state'],
'user_id': session['user_id'],
'user': session['user'],
'machine_id': session['machine_id'],
'title': session['title'],
'parent_title': session['parent_title'],
'grandparent_title': session['grandparent_title'],
'friendly_name': session['friendly_name'],
'player': session['player'],
'platform': session['platform'],
'parent_rating_key': session['parent_rating_key'],
'grandparent_rating_key': session['grandparent_rating_key'],
'view_offset': session['progress'],
'duration': session['duration'],
'video_decision': session['video_decision'],
'audio_decision': session['audio_decision'],
'width': session['width'],
'height': session['height'],
'container': session['container'],
'video_codec': session['video_codec'],
'audio_codec': session['audio_codec'],
'bitrate': session['bitrate'],
'video_resolution': session['video_resolution'],
'video_framerate': session['video_framerate'],
'aspect_ratio': session['aspect_ratio'],
'audio_channels': session['audio_channels'],
'transcode_protocol': session['transcode_protocol'],
'transcode_container': session['transcode_container'],
'transcode_video_codec': session['transcode_video_codec'],
'transcode_audio_codec': session['transcode_audio_codec'],
'transcode_audio_channels': session['transcode_audio_channels'],
'transcode_width': session['transcode_width'],
'transcode_height': session['transcode_height']
}
keys = {'session_key': session['session_key'],
'rating_key': session['rating_key']}
result = self.db.upsert('sessions', values, keys)
if result == 'insert':
# Push any notifications - Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=values,notify_action='play')).start()
started = int(time.time())
# Try and grab IP address from logs
if plexpy.CONFIG.IP_LOGGING_ENABLE and plexpy.CONFIG.PMS_LOGS_FOLDER:
ip_address = self.find_session_ip(rating_key=session['rating_key'],
machine_id=session['machine_id'])
else:
ip_address = None
timestamp = {'started': started,
'ip_address': ip_address}
# If it's our first write then time stamp it.
self.db.upsert('sessions', timestamp, keys)
def write_session_history(self, session=None, import_metadata=None, is_import=False, import_ignore_interval=0):
from plexpy import datafactory
data_factory = datafactory.DataFactory()
user_details = data_factory.get_user_friendly_name(user=session['user'])
if session:
logging_enabled = False
if is_import:
if str(session['stopped']).isdigit():
stopped = session['stopped']
else:
stopped = int(time.time())
else:
stopped = int(time.time())
if plexpy.CONFIG.VIDEO_LOGGING_ENABLE and \
(session['media_type'] == 'movie' or session['media_type'] == 'episode'):
logging_enabled = True
elif plexpy.CONFIG.MUSIC_LOGGING_ENABLE and \
session['media_type'] == 'track':
logging_enabled = True
else:
logger.debug(u"PlexPy Monitor :: ratingKey %s not logged. Does not meet logging criteria. "
u"Media type is '%s'" % (session['rating_key'], session['media_type']))
if plexpy.CONFIG.LOGGING_IGNORE_INTERVAL and not is_import:
if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \
(int(stopped) - session['started'] < int(plexpy.CONFIG.LOGGING_IGNORE_INTERVAL)):
logging_enabled = False
logger.debug(u"PlexPy Monitor :: Play duration for ratingKey %s is %s secs which is less than %s "
u"seconds, so we're not logging it." %
(session['rating_key'], str(int(stopped) - session['started']),
plexpy.CONFIG.LOGGING_IGNORE_INTERVAL))
elif is_import and import_ignore_interval:
if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \
(int(stopped) - session['started'] < int(import_ignore_interval)):
logging_enabled = False
logger.debug(u"PlexPy Monitor :: Play duration for ratingKey %s is %s secs which is less than %s "
u"seconds, so we're not logging it." %
(session['rating_key'], str(int(stopped) - session['started']),
import_ignore_interval))
if not user_details['keep_history'] and not is_import:
logging_enabled = False
logger.debug(u"PlexPy Monitor :: History logging for user '%s' is disabled." % session['user'])
if logging_enabled:
# logger.debug(u"PlexPy Monitor :: Attempting to write to session_history table...")
query = 'INSERT INTO session_history (started, stopped, rating_key, parent_rating_key, ' \
'grandparent_rating_key, media_type, user_id, user, ip_address, paused_counter, player, ' \
'platform, machine_id, view_offset) VALUES ' \
'(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
args = [session['started'], stopped, session['rating_key'], session['parent_rating_key'],
session['grandparent_rating_key'], session['media_type'], session['user_id'], session['user'],
session['ip_address'], session['paused_counter'], session['player'], session['platform'],
session['machine_id'], session['view_offset']]
# logger.debug(u"PlexPy Monitor :: Writing session_history transaction...")
self.db.action(query=query, args=args)
# logger.debug(u"PlexPy Monitor :: Successfully written history item, last id for session_history is %s"
# % last_id)
# Write the session_history_media_info table
# logger.debug(u"PlexPy Monitor :: 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(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
args = [session['rating_key'], session['video_decision'], session['audio_decision'],
session['duration'], session['width'], session['height'], session['container'],
session['video_codec'], session['audio_codec'], session['bitrate'],
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']]
# logger.debug(u"PlexPy Monitor :: Writing session_history_media_info transaction...")
self.db.action(query=query, args=args)
if not is_import:
logger.debug(u"PlexPy Monitor :: Fetching metadata for item ratingKey %s" % session['rating_key'])
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key=str(session['rating_key']))
metadata = result['metadata']
else:
metadata = import_metadata
# Write the session_history_metadata table
directors = ";".join(metadata['directors'])
writers = ";".join(metadata['writers'])
actors = ";".join(metadata['actors'])
genres = ";".join(metadata['genres'])
# Build media item title
if session['media_type'] == 'episode' or session['media_type'] == 'track':
full_title = '%s - %s' % (metadata['grandparent_title'], metadata['title'])
elif session['media_type'] == 'movie':
full_title = metadata['title']
else:
full_title = metadata['title']
# logger.debug(u"PlexPy Monitor :: Attempting to write to session_history_metadata table...")
query = 'INSERT INTO session_history_metadata (id, rating_key, parent_rating_key, ' \
'grandparent_rating_key, title, parent_title, grandparent_title, full_title, media_index, ' \
'parent_media_index, thumb, parent_thumb, grandparent_thumb, art, media_type, year, ' \
'originally_available_at, added_at, updated_at, last_viewed_at, content_rating, summary, ' \
'rating, duration, guid, directors, writers, actors, genres, studio) VALUES ' \
'(last_insert_rowid(), ' \
'?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
args = [session['rating_key'], session['parent_rating_key'], session['grandparent_rating_key'],
session['title'], session['parent_title'], session['grandparent_title'], full_title,
metadata['index'], metadata['parent_index'], metadata['thumb'], metadata['parent_thumb'],
metadata['grandparent_thumb'], metadata['art'], session['media_type'], metadata['year'],
metadata['originally_available_at'], metadata['added_at'], metadata['updated_at'],
metadata['last_viewed_at'], metadata['content_rating'], metadata['summary'], metadata['rating'],
metadata['duration'], metadata['guid'], directors, writers, actors, genres, metadata['studio']]
# logger.debug(u"PlexPy Monitor :: Writing session_history_metadata transaction...")
self.db.action(query=query, args=args)
def find_session_ip(self, rating_key=None, machine_id=None):
logger.debug(u"PlexPy Monitor :: Requesting log lines...")
log_lines = log_reader.get_log_tail(window=5000, parsed=False)
rating_key_line = 'ratingKey=' + rating_key
rating_key_line_2 = '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 or rating_key_line_2 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"PlexPy Monitor :: Matched IP address (%s) for stream ratingKey %s "
u"and machineIdentifier %s."
% (ipv4[0], rating_key, machine_id))
return ipv4[0]
logger.debug(u"PlexPy Monitor :: Unable to find IP address on first pass. "
u"Attempting fallback check in 5 seconds...")
# Wait for the log to catch up and read in new lines
time.sleep(5)
logger.debug(u"PlexPy Monitor :: 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 or rating_key_line_2 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"PlexPy Monitor :: Matched IP address (%s) for stream ratingKey %s." %
(ipv4[0], rating_key))
return ipv4[0]
logger.debug(u"PlexPy Monitor :: Unable to find IP address on fallback search. Not logging IP address.")
return None