mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-07 21:51:14 -07:00
371 lines
No EOL
17 KiB
Python
371 lines
No EOL
17 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/>.
|
|
|
|
import threading
|
|
import time
|
|
|
|
import plexpy
|
|
import activity_processor
|
|
import datafactory
|
|
import helpers
|
|
import logger
|
|
import notification_handler
|
|
import notifiers
|
|
import pmsconnect
|
|
|
|
|
|
RECENTLY_ADDED_QUEUE = {}
|
|
|
|
class ActivityHandler(object):
|
|
|
|
def __init__(self, timeline):
|
|
self.timeline = timeline
|
|
# print timeline
|
|
|
|
def is_valid_session(self):
|
|
if 'sessionKey' in self.timeline:
|
|
if str(self.timeline['sessionKey']).isdigit():
|
|
return True
|
|
|
|
return False
|
|
|
|
def get_session_key(self):
|
|
if self.is_valid_session():
|
|
return int(self.timeline['sessionKey'])
|
|
|
|
return None
|
|
|
|
def get_rating_key(self):
|
|
if self.is_valid_session():
|
|
return int(self.timeline['ratingKey'])
|
|
|
|
return None
|
|
|
|
def get_live_session(self):
|
|
pms_connect = pmsconnect.PmsConnect()
|
|
session_list = pms_connect.get_current_activity()
|
|
|
|
if session_list:
|
|
for session in session_list['sessions']:
|
|
if int(session['session_key']) == self.get_session_key():
|
|
return session
|
|
|
|
return None
|
|
|
|
def update_db_session(self, session=None):
|
|
# Update our session temp table values
|
|
monitor_proc = activity_processor.ActivityProcessor()
|
|
monitor_proc.write_session(session=session, notify=False)
|
|
|
|
def on_start(self):
|
|
if self.is_valid_session() and self.get_live_session():
|
|
session = self.get_live_session()
|
|
|
|
logger.debug(u"PlexPy ActivityHandler :: Session %s started by user %s with ratingKey %s."
|
|
% (str(session['session_key']), str(session['user_id']), str(session['rating_key'])))
|
|
|
|
plexpy.NOTIFY_QUEUE.put({'stream_data': session, 'notify_action': 'on_play'})
|
|
|
|
# Write the new session to our temp session table
|
|
self.update_db_session(session=session)
|
|
|
|
plexpy.NOTIFY_QUEUE.put({'stream_data': session, 'notify_action': 'on_concurrent'})
|
|
plexpy.NOTIFY_QUEUE.put({'stream_data': session, 'notify_action': 'on_newdevice'})
|
|
|
|
def on_stop(self, force_stop=False):
|
|
if self.is_valid_session():
|
|
logger.debug(u"PlexPy ActivityHandler :: Session %s stopped." % str(self.get_session_key()))
|
|
|
|
# Set the session last_paused timestamp
|
|
ap = activity_processor.ActivityProcessor()
|
|
ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=None)
|
|
|
|
# Update the session state and viewOffset
|
|
# Set force_stop to true to disable the state set
|
|
if not force_stop:
|
|
ap.set_session_state(session_key=self.get_session_key(),
|
|
state=self.timeline['state'],
|
|
view_offset=self.timeline['viewOffset'],
|
|
stopped=int(time.time()))
|
|
|
|
# Retrieve the session data from our temp table
|
|
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
|
|
|
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session, 'notify_action': 'on_stop'})
|
|
|
|
# Write it to the history table
|
|
monitor_proc = activity_processor.ActivityProcessor()
|
|
monitor_proc.write_session_history(session=db_session)
|
|
|
|
# Remove the session from our temp session table
|
|
logger.debug(u"PlexPy ActivityHandler :: Removing sessionKey %s ratingKey %s from session queue"
|
|
% (str(self.get_session_key()), str(self.get_rating_key())))
|
|
ap.delete_session(session_key=self.get_session_key())
|
|
|
|
def on_pause(self):
|
|
if self.is_valid_session():
|
|
logger.debug(u"PlexPy ActivityHandler :: Session %s paused." % str(self.get_session_key()))
|
|
|
|
# Set the session last_paused timestamp
|
|
ap = activity_processor.ActivityProcessor()
|
|
ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=int(time.time()))
|
|
|
|
# Update the session state and viewOffset
|
|
ap.set_session_state(session_key=self.get_session_key(),
|
|
state=self.timeline['state'],
|
|
view_offset=self.timeline['viewOffset'])
|
|
|
|
# Retrieve the session data from our temp table
|
|
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
|
|
|
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session, 'notify_action': 'on_pause'})
|
|
|
|
def on_resume(self):
|
|
if self.is_valid_session():
|
|
logger.debug(u"PlexPy ActivityHandler :: Session %s resumed." % str(self.get_session_key()))
|
|
|
|
# Set the session last_paused timestamp
|
|
ap = activity_processor.ActivityProcessor()
|
|
ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=None)
|
|
|
|
# Update the session state and viewOffset
|
|
ap.set_session_state(session_key=self.get_session_key(),
|
|
state=self.timeline['state'],
|
|
view_offset=self.timeline['viewOffset'])
|
|
|
|
# Retrieve the session data from our temp table
|
|
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
|
|
|
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session, 'notify_action': 'on_resume'})
|
|
|
|
def on_buffer(self):
|
|
if self.is_valid_session():
|
|
logger.debug(u"PlexPy ActivityHandler :: Session %s is buffering." % self.get_session_key())
|
|
ap = activity_processor.ActivityProcessor()
|
|
db_stream = ap.get_session_by_key(session_key=self.get_session_key())
|
|
|
|
# Increment our buffer count
|
|
ap.increment_session_buffer_count(session_key=self.get_session_key())
|
|
|
|
# Get our current buffer count
|
|
current_buffer_count = ap.get_session_buffer_count(self.get_session_key())
|
|
logger.debug(u"PlexPy ActivityHandler :: Session %s buffer count is %s." %
|
|
(self.get_session_key(), current_buffer_count))
|
|
|
|
# Get our last triggered time
|
|
buffer_last_triggered = ap.get_session_buffer_trigger_time(self.get_session_key())
|
|
|
|
time_since_last_trigger = 0
|
|
if buffer_last_triggered:
|
|
logger.debug(u"PlexPy ActivityHandler :: Session %s buffer last triggered at %s." %
|
|
(self.get_session_key(), buffer_last_triggered))
|
|
time_since_last_trigger = int(time.time()) - int(buffer_last_triggered)
|
|
|
|
if plexpy.CONFIG.BUFFER_THRESHOLD > 0 and (current_buffer_count >= plexpy.CONFIG.BUFFER_THRESHOLD and \
|
|
time_since_last_trigger == 0 or time_since_last_trigger >= plexpy.CONFIG.BUFFER_WAIT):
|
|
ap.set_session_buffer_trigger_time(session_key=self.get_session_key())
|
|
|
|
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session, 'notify_action': 'on_buffer'})
|
|
|
|
# This function receives events from our websocket connection
|
|
def process(self):
|
|
if self.is_valid_session():
|
|
ap = activity_processor.ActivityProcessor()
|
|
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
|
|
|
this_state = self.timeline['state']
|
|
this_key = str(self.timeline['ratingKey'])
|
|
|
|
# If we already have this session in the temp table, check for state changes
|
|
if db_session:
|
|
last_state = db_session['state']
|
|
last_key = str(db_session['rating_key'])
|
|
|
|
# Make sure the same item is being played
|
|
if this_key == last_key:
|
|
# Update the session state and viewOffset
|
|
if this_state == 'playing':
|
|
ap.set_session_state(session_key=self.get_session_key(),
|
|
state=this_state,
|
|
view_offset=self.timeline['viewOffset'])
|
|
# Start our state checks
|
|
if this_state != last_state:
|
|
if this_state == 'paused':
|
|
self.on_pause()
|
|
elif last_state == 'paused' and this_state == 'playing':
|
|
self.on_resume()
|
|
elif this_state == 'stopped':
|
|
self.on_stop()
|
|
elif this_state == 'buffering':
|
|
self.on_buffer()
|
|
# If a client doesn't register stop events (I'm looking at you PHT!) check if the ratingKey has changed
|
|
else:
|
|
# Manually stop and start
|
|
# Set force_stop so that we don't overwrite our last viewOffset
|
|
self.on_stop(force_stop=True)
|
|
self.on_start()
|
|
|
|
# Monitor if the stream has reached the watch percentage for notifications
|
|
# The only purpose of this is for notifications
|
|
if this_state != 'buffering':
|
|
progress_percent = helpers.get_percent(db_session['view_offset'], db_session['duration'])
|
|
notify_states = notification_handler.get_notify_state(session=db_session)
|
|
if progress_percent >= plexpy.CONFIG.NOTIFY_WATCHED_PERCENT \
|
|
and not any(d['notify_action'] == 'on_watched' for d in notify_states):
|
|
plexpy.NOTIFY_QUEUE.put({'stream_data': db_session, 'notify_action': 'on_watched'})
|
|
|
|
else:
|
|
# We don't have this session in our table yet, start a new one.
|
|
if this_state != 'buffering':
|
|
self.on_start()
|
|
|
|
class TimelineHandler(object):
|
|
|
|
def __init__(self, timeline):
|
|
self.timeline = timeline
|
|
#logger.debug(timeline)
|
|
|
|
def is_item(self):
|
|
if 'itemID' in self.timeline:
|
|
return True
|
|
|
|
return False
|
|
|
|
def get_rating_key(self):
|
|
if self.is_item():
|
|
return int(self.timeline['itemID'])
|
|
|
|
return None
|
|
|
|
def get_metadata(self):
|
|
pms_connect = pmsconnect.PmsConnect()
|
|
metadata_list = pms_connect.get_metadata_details(self.get_rating_key())
|
|
|
|
if metadata_list:
|
|
return metadata_list['metadata']
|
|
|
|
return None
|
|
|
|
def on_created(self, rating_key, **kwargs):
|
|
if self.is_item():
|
|
logger.debug(u"PlexPy TimelineHandler :: Library item %s added to Plex." % str(rating_key))
|
|
pms_connect = pmsconnect.PmsConnect()
|
|
metadata_list = pms_connect.get_metadata_details(rating_key)
|
|
|
|
if metadata_list:
|
|
metadata = metadata_list['metadata']
|
|
data = {'timeline_data': metadata, 'notify_action': 'on_created'}
|
|
data.update(kwargs)
|
|
plexpy.NOTIFY_QUEUE.put(data)
|
|
else:
|
|
logger.error(u"PlexPy TimelineHandler :: Unable to retrieve metadata for rating_key %s" \
|
|
% str(rating_key))
|
|
|
|
# This function receives events from our websocket connection
|
|
def process(self):
|
|
if self.is_item():
|
|
global RECENTLY_ADDED_QUEUE
|
|
|
|
rating_key = self.get_rating_key()
|
|
|
|
state_types = {0: 'created',
|
|
5: 'processed'}
|
|
media_types = {1: 'movie',
|
|
2: 'show',
|
|
3: 'season',
|
|
4: 'episode',
|
|
8: 'artist',
|
|
9: 'album',
|
|
10: 'track'}
|
|
|
|
state_type = state_types.get(self.timeline['state'])
|
|
media_type = media_types.get(self.timeline['type'])
|
|
section_id = self.timeline['sectionID']
|
|
metadata_state = self.timeline.get('metadataState')
|
|
|
|
|
|
if state_type == 'created' and media_type and section_id > 0 and metadata_state == 'created':
|
|
if media_type == 'episode' or media_type == 'track':
|
|
metadata = self.get_metadata()
|
|
if metadata:
|
|
grandparent_rating_key = int(metadata['grandparent_rating_key'])
|
|
|
|
logger.debug(u"PlexPy TimelineHandler :: Library item %s (grandparent %s) added to recently added queue."
|
|
% (str(rating_key), str(grandparent_rating_key)))
|
|
RECENTLY_ADDED_QUEUE[grandparent_rating_key] = RECENTLY_ADDED_QUEUE.get(grandparent_rating_key, []) + [(media_type, rating_key)]
|
|
|
|
elif media_type == 'season' or media_type == 'album':
|
|
metadata = self.get_metadata()
|
|
if metadata:
|
|
parent_rating_key = int(metadata['parent_rating_key'])
|
|
|
|
logger.debug(u"PlexPy TimelineHandler :: Library item %s (parent %s) added to recently added queue."
|
|
% (str(rating_key), str(parent_rating_key)))
|
|
RECENTLY_ADDED_QUEUE[parent_rating_key] = RECENTLY_ADDED_QUEUE.get(parent_rating_key, []) + [(media_type, rating_key)]
|
|
|
|
else:
|
|
logger.debug(u"PlexPy TimelineHandler :: Library item %s added to recently added queue." % str(rating_key))
|
|
RECENTLY_ADDED_QUEUE[rating_key] = RECENTLY_ADDED_QUEUE.get(rating_key, []) + [(media_type, rating_key)]
|
|
|
|
if state_type == 'processed' and media_type and section_id > 0 and metadata_state is None and rating_key in RECENTLY_ADDED_QUEUE:
|
|
logger.debug(u"PlexPy TimelineHandler :: Library item %s done processing metadata." % str(rating_key))
|
|
child_keys = RECENTLY_ADDED_QUEUE.pop(rating_key)
|
|
|
|
def notify_keys(keys, **kwargs):
|
|
for key in keys: self.on_created(key, **kwargs)
|
|
|
|
if plexpy.CONFIG.NOTIFY_GROUP_RECENTLY_ADDED:
|
|
media_type_dict = {}
|
|
for type, key in child_keys:
|
|
media_type_dict[type] = media_type_dict.get(type, []) + [key]
|
|
|
|
if len(media_type_dict.get('episode', [])) > 1:
|
|
if len(media_type_dict.get('season', [])) > 1:
|
|
if media_type_dict.get('show', []):
|
|
notify_keys(media_type_dict['show'],
|
|
child_keys=media_type_dict.get('season', []),
|
|
grandchild_keys=media_type_dict.get('episode', []))
|
|
else:
|
|
notify_keys(media_type_dict['season'],
|
|
child_keys=media_type_dict.get('episode', []))
|
|
elif media_type_dict.get('season', []):
|
|
notify_keys(media_type_dict['season'],
|
|
child_keys=media_type_dict.get('episode', []))
|
|
else:
|
|
notify_keys(media_type_dict['episode'])
|
|
else:
|
|
notify_keys(media_type_dict.get('episode', []))
|
|
|
|
if len(media_type_dict.get('track', [])) > 1:
|
|
if len(media_type_dict.get('album', [])) > 1:
|
|
if media_type_dict.get('artist', []):
|
|
notify_keys(media_type_dict['artist'],
|
|
child_keys=media_type_dict.get('album', []),
|
|
grandchild_keys=media_type_dict.get('track', []))
|
|
else:
|
|
notify_keys(media_type_dict['album'],
|
|
child_keys=media_type_dict.get('track', []))
|
|
elif media_type_dict.get('album', []):
|
|
notify_keys(media_type_dict['album'],
|
|
child_keys=media_type_dict.get('track', []))
|
|
else:
|
|
notify_keys(media_type_dict['track'])
|
|
else:
|
|
notify_keys(media_type_dict.get('track', []))
|
|
|
|
notify_keys(media_type_dict.get('movie', []))
|
|
|
|
else:
|
|
notify_keys([key for type, key in child_keys if type in ('movie', 'episode', 'track')]) |