def on_connect(): if plexpy.PLEX_SERVER_UP is None: plexpy.PLEX_SERVER_UP = True if not plexpy.PLEX_SERVER_UP: logger.info(u"Tautulli WebSocket :: The Plex Media Server is back up.") plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_intup'}) plexpy.PLEX_SERVER_UP = True plexpy.initialize_scheduler()
def configUpdate(self, **kwargs): # Handle the variable config options. Note - keys with False values aren't getting passed checked_configs = [ "launch_browser", "enable_https", "api_enabled", "freeze_db", "growl_enabled", "prowl_enabled", "xbmc_enabled", "lms_enabled", "plex_enabled", "nma_enabled", "pushalot_enabled", "synoindex_enabled", "pushover_enabled", "pushbullet_enabled", "subsonic_enabled", "twitter_enabled", "osx_notify_enabled", "boxcar_enabled", "mpc_enabled", "email_enabled", "email_tls", "grouping_global_history", "grouping_user_history", "grouping_charts" ] for checked_config in checked_configs: if checked_config not in kwargs: # checked items should be zero or one. if they were not sent then the item was not checked kwargs[checked_config] = 0 # Write Plex token to the config if (not plexpy.CONFIG.PMS_TOKEN or plexpy.CONFIG.PMS_TOKEN == '' \ or kwargs['pms_username'] != plexpy.CONFIG.PMS_USERNAME) \ and (kwargs['pms_username'] != '' or kwargs['pms_password'] != ''): plex_tv = plextv.PlexTV(kwargs['pms_username'], kwargs['pms_password']) token = plex_tv.get_token() if token: kwargs['pms_token'] = token logger.info('Plex.tv token sucessfully written to config.') else: logger.warn('Unable to write Plex.tv token to config.') # Clear Plex token if username or password set to blank if kwargs['pms_username'] == '' or kwargs['pms_password'] == '': kwargs['pms_token'] = '' # If passwords exists in config, do not overwrite when blank value received if kwargs['http_password'] == ' ' and plexpy.CONFIG.HTTP_PASSWORD != '': kwargs['http_password'] = plexpy.CONFIG.HTTP_PASSWORD if kwargs['pms_password'] == ' ' and plexpy.CONFIG.PMS_PASSWORD != '': kwargs['pms_password'] = plexpy.CONFIG.PMS_PASSWORD for plain_config, use_config in [(x[4:], x) for x in kwargs if x.startswith('use_')]: # the use prefix is fairly nice in the html, but does not match the actual config kwargs[plain_config] = kwargs[use_config] del kwargs[use_config] plexpy.CONFIG.process_kwargs(kwargs) # Write the config plexpy.CONFIG.write() # Reconfigure scheduler plexpy.initialize_scheduler() raise cherrypy.HTTPRedirect("config")
def on_disconnect(): if plexpy.PLEX_SERVER_UP is None: plexpy.PLEX_SERVER_UP = False if plexpy.PLEX_SERVER_UP: logger.info("Tautulli WebSocket :: Unable to get a response from the server, Plex server is down.") plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_intdown'}) plexpy.PLEX_SERVER_UP = False activity_processor.ActivityProcessor().set_temp_stopped() plexpy.initialize_scheduler()
def on_connect(): if plexpy.PLEX_SERVER_UP is None: plexpy.PLEX_SERVER_UP = True if not plexpy.PLEX_SERVER_UP: logger.info("Tautulli WebSocket :: The Plex Media Server is back up.") plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_intup'}) plexpy.PLEX_SERVER_UP = True plexpy.initialize_scheduler() if plexpy.CONFIG.WEBSOCKET_MONITOR_PING_PONG: send_ping()
def configUpdate(self, **kwargs): # Handle the variable config options. Note - keys with False values aren't getting passed checked_configs = [ "launch_browser", "enable_https", "api_enabled", "freeze_db", "growl_enabled", "prowl_enabled", "xbmc_enabled", "check_github", "plex_enabled", "nma_enabled", "pushalot_enabled", "pushover_enabled", "pushbullet_enabled", "twitter_enabled", "osx_notify_enabled", "boxcar_enabled", "email_enabled", "email_tls", "grouping_global_history", "grouping_user_history", "grouping_charts", "pms_use_bif", "pms_ssl", "tv_notify_enable", "movie_notify_enable", "music_notify_enable", "tv_notify_on_start", "movie_notify_on_start", "music_notify_on_start", "tv_notify_on_stop", "movie_notify_on_stop", "music_notify_on_stop", "tv_notify_on_pause", "movie_notify_on_pause", "music_notify_on_pause", "refresh_users_on_startup", "ip_logging_enable", "video_logging_enable", "music_logging_enable", "pms_is_remote" ] for checked_config in checked_configs: if checked_config not in kwargs: # checked items should be zero or one. if they were not sent then the item was not checked kwargs[checked_config] = 0 # If http password exists in config, do not overwrite when blank value received if 'http_password' in kwargs: if kwargs['http_password'] == ' ' and plexpy.CONFIG.HTTP_PASSWORD != '': kwargs['http_password'] = plexpy.CONFIG.HTTP_PASSWORD for plain_config, use_config in [(x[4:], x) for x in kwargs if x.startswith('use_')]: # the use prefix is fairly nice in the html, but does not match the actual config kwargs[plain_config] = kwargs[use_config] del kwargs[use_config] plexpy.CONFIG.process_kwargs(kwargs) # Write the config plexpy.CONFIG.write() # Reconfigure scheduler plexpy.initialize_scheduler() plextv.get_real_pms_url() # Refresh users table. Probably shouldn't do this on every config save, will improve this later. threading.Thread(target=plextv.refresh_users).start() raise cherrypy.HTTPRedirect("settings")
def on_connect(): if plexpy.PLEX_SERVER_UP is None: plexpy.PLEX_SERVER_UP = True if not plexpy.PLEX_SERVER_UP: logger.info("Tautulli WebSocket :: The Plex Media Server is back up.") plexpy.PLEX_SERVER_UP = True if activity_handler.ACTIVITY_SCHED.get_job('on_intdown'): logger.debug( "Tautulli WebSocket :: Cancelling scheduled Plex server down callback." ) activity_handler.schedule_callback('on_intdown', remove_job=True) else: on_intup() plexpy.initialize_scheduler() if plexpy.CONFIG.WEBSOCKET_MONITOR_PING_PONG: send_ping()
def on_disconnect(): if plexpy.PLEX_SERVER_UP is None: plexpy.PLEX_SERVER_UP = False if plexpy.PLEX_SERVER_UP: logger.info( "Tautulli WebSocket :: Unable to get a response from the server, Plex server is down." ) plexpy.PLEX_SERVER_UP = False logger.debug( "Tautulli WebSocket :: Scheduling Plex server down callback in %d seconds.", plexpy.CONFIG.NOTIFY_SERVER_CONNECTION_THRESHOLD) activity_handler.schedule_callback( 'on_intdown', func=on_intdown, seconds=plexpy.CONFIG.NOTIFY_SERVER_CONNECTION_THRESHOLD) activity_processor.ActivityProcessor().set_temp_stopped() plexpy.initialize_scheduler()
def configUpdate(self, **kwargs): # Handle the variable config options. Note - keys with False values aren't getting passed checked_configs = [ "launch_browser", "enable_https", "api_enabled", "freeze_db", "growl_enabled", "prowl_enabled", "xbmc_enabled", "plex_enabled", "nma_enabled", "pushalot_enabled", "pushover_enabled", "pushbullet_enabled", "twitter_enabled", "osx_notify_enabled", "boxcar_enabled", "email_enabled", "email_tls", "grouping_global_history", "grouping_user_history", "grouping_charts", "pms_use_bif" ] for checked_config in checked_configs: if checked_config not in kwargs: # checked items should be zero or one. if they were not sent then the item was not checked kwargs[checked_config] = 0 # If http password exists in config, do not overwrite when blank value received if kwargs[ 'http_password'] == ' ' and plexpy.CONFIG.HTTP_PASSWORD != '': kwargs['http_password'] = plexpy.CONFIG.HTTP_PASSWORD for plain_config, use_config in [(x[4:], x) for x in kwargs if x.startswith('use_')]: # the use prefix is fairly nice in the html, but does not match the actual config kwargs[plain_config] = kwargs[use_config] del kwargs[use_config] plexpy.CONFIG.process_kwargs(kwargs) # Write the config plexpy.CONFIG.write() # Check if we have our users table plexwatch.check_db_tables() # Reconfigure scheduler plexpy.initialize_scheduler() raise cherrypy.HTTPRedirect("config")
def configUpdate(self, **kwargs): # Handle the variable config options. Note - keys with False values aren't getting passed checked_configs = [ "launch_browser", "enable_https", "api_enabled", "freeze_db", "growl_enabled", "prowl_enabled", "xbmc_enabled", "plex_enabled", "nma_enabled", "pushalot_enabled", "pushover_enabled", "pushbullet_enabled", "twitter_enabled", "osx_notify_enabled", "boxcar_enabled", "email_enabled", "email_tls", "grouping_global_history", "grouping_user_history", "grouping_charts", "pms_use_bif" ] for checked_config in checked_configs: if checked_config not in kwargs: # checked items should be zero or one. if they were not sent then the item was not checked kwargs[checked_config] = 0 # If http password exists in config, do not overwrite when blank value received if kwargs['http_password'] == ' ' and plexpy.CONFIG.HTTP_PASSWORD != '': kwargs['http_password'] = plexpy.CONFIG.HTTP_PASSWORD for plain_config, use_config in [(x[4:], x) for x in kwargs if x.startswith('use_')]: # the use prefix is fairly nice in the html, but does not match the actual config kwargs[plain_config] = kwargs[use_config] del kwargs[use_config] plexpy.CONFIG.process_kwargs(kwargs) # Write the config plexpy.CONFIG.write() # Check if we have our users table plexwatch.check_db_tables() # Reconfigure scheduler plexpy.initialize_scheduler() raise cherrypy.HTTPRedirect("config")
def import_from_plexwatch(database=None, table_name=None, import_ignore_interval=0): try: connection = sqlite3.connect(database, timeout=20) connection.row_factory = sqlite3.Row except sqlite3.OperationalError: logger.error(u"PlexPy Importer :: Invalid filename.") return None except ValueError: logger.error(u"PlexPy Importer :: Invalid filename.") return None try: connection.execute('SELECT ratingKey from %s' % table_name) except sqlite3.OperationalError: logger.error(u"PlexPy Importer :: Database specified does not contain the required fields.") return None logger.debug(u"PlexPy Importer :: PlexWatch data import in progress...") logger.debug(u"PlexPy Importer :: Disabling monitoring while import in progress.") plexpy.schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions', 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 Plex remote access', hours=0, minutes=0, seconds=0) ap = activity_processor.ActivityProcessor() user_data = users.Users() # Get the latest friends list so we can pull user id's try: plextv.refresh_users() except: logger.debug(u"PlexPy Importer :: Unable to refresh the users list. Aborting import.") return None query = 'SELECT time AS started, ' \ 'stopped, ' \ 'cast(ratingKey as text) AS rating_key, ' \ 'null AS user_id, ' \ 'user, ' \ 'ip_address, ' \ 'paused_counter, ' \ 'platform AS player, ' \ 'null AS platform, ' \ 'null as machine_id, ' \ 'parentRatingKey as parent_rating_key, ' \ 'grandparentRatingKey as grandparent_rating_key, ' \ 'null AS media_type, ' \ 'null AS view_offset, ' \ 'xml, ' \ 'rating as content_rating,' \ 'summary,' \ 'title AS full_title,' \ '(case when orig_title_ep = "" then orig_title else ' \ 'orig_title_ep end) as title,' \ '(case when orig_title_ep != "" then orig_title else ' \ 'null end) as grandparent_title ' \ 'FROM ' + table_name + ' ORDER BY id' result = connection.execute(query) for row in result: # Extract the xml from the Plexwatch db xml field. extracted_xml = extract_plexwatch_xml(row['xml']) # If we get back None from our xml extractor skip over the record and log error. if not extracted_xml: logger.error(u"PlexPy Importer :: Skipping record with ratingKey %s due to malformed xml." % str(row['rating_key'])) continue # 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 ratingKey.") continue # If the user_id no longer exists in the friends list, pull it from the xml. if user_data.get_user_id(user=row['user']): user_id = user_data.get_user_id(user=row['user']) else: user_id = extracted_xml['user_id'] session_history = {'started': row['started'], 'stopped': row['stopped'], 'rating_key': row['rating_key'], 'title': row['title'], 'parent_title': extracted_xml['parent_title'], 'grandparent_title': row['grandparent_title'], 'user_id': user_id, 'user': row['user'], 'ip_address': row['ip_address'], 'paused_counter': row['paused_counter'], 'player': row['player'], 'platform': extracted_xml['platform'], 'machine_id': extracted_xml['machine_id'], 'parent_rating_key': row['parent_rating_key'], 'grandparent_rating_key': row['grandparent_rating_key'], 'media_type': extracted_xml['media_type'], 'view_offset': extracted_xml['view_offset'], 'video_decision': extracted_xml['video_decision'], 'audio_decision': extracted_xml['audio_decision'], 'duration': extracted_xml['duration'], 'width': extracted_xml['width'], 'height': extracted_xml['height'], 'container': extracted_xml['container'], 'video_codec': extracted_xml['video_codec'], 'audio_codec': extracted_xml['audio_codec'], 'bitrate': extracted_xml['bitrate'], 'video_resolution': extracted_xml['video_resolution'], 'video_framerate': extracted_xml['video_framerate'], 'aspect_ratio': extracted_xml['aspect_ratio'], 'audio_channels': extracted_xml['audio_channels'], 'transcode_protocol': extracted_xml['transcode_protocol'], 'transcode_container': extracted_xml['transcode_container'], 'transcode_video_codec': extracted_xml['transcode_video_codec'], 'transcode_audio_codec': extracted_xml['transcode_audio_codec'], 'transcode_audio_channels': extracted_xml['transcode_audio_channels'], 'transcode_width': extracted_xml['transcode_width'], 'transcode_height': extracted_xml['transcode_height'] } session_history_metadata = {'rating_key': helpers.latinToAscii(row['rating_key']), 'parent_rating_key': row['parent_rating_key'], 'grandparent_rating_key': row['grandparent_rating_key'], 'title': row['title'], 'parent_title': extracted_xml['parent_title'], 'grandparent_title': row['grandparent_title'], 'media_index': extracted_xml['media_index'], 'parent_media_index': extracted_xml['parent_media_index'], 'thumb': extracted_xml['thumb'], 'parent_thumb': extracted_xml['parent_thumb'], 'grandparent_thumb': extracted_xml['grandparent_thumb'], 'art': extracted_xml['art'], 'media_type': extracted_xml['media_type'], 'year': extracted_xml['year'], 'originally_available_at': extracted_xml['originally_available_at'], 'added_at': extracted_xml['added_at'], 'updated_at': extracted_xml['updated_at'], 'last_viewed_at': extracted_xml['last_viewed_at'], 'content_rating': row['content_rating'], 'summary': row['summary'], 'tagline': extracted_xml['tagline'], 'rating': extracted_xml['rating'], 'duration': extracted_xml['duration'], 'guid': extracted_xml['guid'], 'section_id': extracted_xml['section_id'], 'directors': extracted_xml['directors'], 'writers': extracted_xml['writers'], 'actors': extracted_xml['actors'], 'genres': extracted_xml['genres'], 'studio': extracted_xml['studio'], 'full_title': row['full_title'] } # On older versions of PMS, "clip" items were still classified as "movie" and had bad ratingKey values # Just make sure that the ratingKey is indeed an integer if session_history_metadata['rating_key'].isdigit(): ap.write_session_history(session=session_history, import_metadata=session_history_metadata, is_import=True, import_ignore_interval=import_ignore_interval) else: logger.debug(u"PlexPy Importer :: Item has bad rating_key: %s" % session_history_metadata['rating_key']) logger.debug(u"PlexPy Importer :: PlexWatch data import complete.") import_users() logger.debug(u"PlexPy Importer :: Re-enabling monitoring.") plexpy.initialize_scheduler()
def import_from_plexwatch(database=None, table_name=None, import_ignore_interval=0): try: connection = sqlite3.connect(database, timeout=20) connection.row_factory = sqlite3.Row except sqlite3.OperationalError: logger.error("PlexPy Importer :: Invalid filename.") return None except ValueError: logger.error("PlexPy Importer :: Invalid filename.") return None try: connection.execute("SELECT ratingKey from %s" % table_name) except sqlite3.OperationalError: logger.error("PlexPy Importer :: Database specified does not contain the required fields.") return None logger.debug(u"PlexPy Importer :: PlexWatch data import in progress...") logger.debug(u"PlexPy Importer :: Disabling monitoring while import in progress.") plexpy.schedule_job( activity_pinger.check_active_sessions, "Check for active sessions", hours=0, minutes=0, seconds=0 ) ap = activity_processor.ActivityProcessor() user_data = users.Users() # Get the latest friends list so we can pull user id's try: plextv.refresh_users() except: logger.debug(u"PlexPy Importer :: Unable to refresh the users list. Aborting import.") return None query = ( "SELECT time AS started, " "stopped, " "cast(ratingKey as text) AS rating_key, " "null AS user_id, " "user, " "ip_address, " "paused_counter, " "platform AS player, " "null AS platform, " "null as machine_id, " "parentRatingKey as parent_rating_key, " "grandparentRatingKey as grandparent_rating_key, " "null AS media_type, " "null AS view_offset, " "xml, " "rating as content_rating," "summary," "title AS full_title," '(case when orig_title_ep = "" then orig_title else ' "orig_title_ep end) as title," '(case when orig_title_ep != "" then orig_title else ' "null end) as grandparent_title " "FROM " + table_name + " ORDER BY id" ) result = connection.execute(query) for row in result: # Extract the xml from the Plexwatch db xml field. extracted_xml = extract_plexwatch_xml(row["xml"]) # If we get back None from our xml extractor skip over the record and log error. if not extracted_xml: logger.error( u"PlexPy Importer :: Skipping line with ratingKey %s due to malformed xml." % str(row["rating_key"]) ) continue # If the user_id no longer exists in the friends list, pull it from the xml. if user_data.get_user_id(user=row["user"]): user_id = user_data.get_user_id(user=row["user"]) else: user_id = extracted_xml["user_id"] session_history = { "started": row["started"], "stopped": row["stopped"], "rating_key": row["rating_key"], "title": row["title"], "parent_title": extracted_xml["parent_title"], "grandparent_title": row["grandparent_title"], "user_id": user_id, "user": row["user"], "ip_address": row["ip_address"], "paused_counter": row["paused_counter"], "player": row["player"], "platform": extracted_xml["platform"], "machine_id": extracted_xml["machine_id"], "parent_rating_key": row["parent_rating_key"], "grandparent_rating_key": row["grandparent_rating_key"], "media_type": extracted_xml["media_type"], "view_offset": extracted_xml["view_offset"], "video_decision": extracted_xml["video_decision"], "audio_decision": extracted_xml["audio_decision"], "duration": extracted_xml["duration"], "width": extracted_xml["width"], "height": extracted_xml["height"], "container": extracted_xml["container"], "video_codec": extracted_xml["video_codec"], "audio_codec": extracted_xml["audio_codec"], "bitrate": extracted_xml["bitrate"], "video_resolution": extracted_xml["video_resolution"], "video_framerate": extracted_xml["video_framerate"], "aspect_ratio": extracted_xml["aspect_ratio"], "audio_channels": extracted_xml["audio_channels"], "transcode_protocol": extracted_xml["transcode_protocol"], "transcode_container": extracted_xml["transcode_container"], "transcode_video_codec": extracted_xml["transcode_video_codec"], "transcode_audio_codec": extracted_xml["transcode_audio_codec"], "transcode_audio_channels": extracted_xml["transcode_audio_channels"], "transcode_width": extracted_xml["transcode_width"], "transcode_height": extracted_xml["transcode_height"], } session_history_metadata = { "rating_key": helpers.latinToAscii(row["rating_key"]), "parent_rating_key": row["parent_rating_key"], "grandparent_rating_key": row["grandparent_rating_key"], "title": row["title"], "parent_title": extracted_xml["parent_title"], "grandparent_title": row["grandparent_title"], "index": extracted_xml["media_index"], "parent_index": extracted_xml["parent_media_index"], "thumb": extracted_xml["thumb"], "parent_thumb": extracted_xml["parent_thumb"], "grandparent_thumb": extracted_xml["grandparent_thumb"], "art": extracted_xml["art"], "media_type": extracted_xml["media_type"], "year": extracted_xml["year"], "originally_available_at": extracted_xml["originally_available_at"], "added_at": extracted_xml["added_at"], "updated_at": extracted_xml["updated_at"], "last_viewed_at": extracted_xml["last_viewed_at"], "content_rating": row["content_rating"], "summary": row["summary"], "tagline": extracted_xml["tagline"], "rating": extracted_xml["rating"], "duration": extracted_xml["duration"], "guid": extracted_xml["guid"], "directors": extracted_xml["directors"], "writers": extracted_xml["writers"], "actors": extracted_xml["actors"], "genres": extracted_xml["genres"], "studio": extracted_xml["studio"], "full_title": row["full_title"], } # On older versions of PMS, "clip" items were still classified as "movie" and had bad ratingKey values # Just make sure that the ratingKey is indeed an integer if session_history_metadata["rating_key"].isdigit(): ap.write_session_history( session=session_history, import_metadata=session_history_metadata, is_import=True, import_ignore_interval=import_ignore_interval, ) else: logger.debug(u"PlexPy Importer :: Item has bad rating_key: %s" % session_history_metadata["rating_key"]) logger.debug(u"PlexPy Importer :: PlexWatch data import complete.") import_users() logger.debug(u"PlexPy Importer :: Re-enabling monitoring.") plexpy.initialize_scheduler()
def run(): from websocket import create_connection if plexpy.CONFIG.PMS_SSL and plexpy.CONFIG.PMS_URL[:5] == 'https': uri = plexpy.CONFIG.PMS_URL.replace('https://', 'wss://') + '/:/websockets/notifications' secure = ' secure' else: uri = 'ws://%s:%s/:/websockets/notifications' % ( plexpy.CONFIG.PMS_IP, plexpy.CONFIG.PMS_PORT ) secure = '' # Set authentication token (if one is available) if plexpy.CONFIG.PMS_TOKEN: header = ["X-Plex-Token: %s" % plexpy.CONFIG.PMS_TOKEN] else: header = [] 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))) ws = create_connection(uri, header=header) reconnects = 0 ws_connected = True logger.info(u"PlexPy WebSocket :: Ready") except IOError as e: logger.error(u"PlexPy WebSocket :: %s." % e) reconnects += 1 time.sleep(5) while ws_connected: try: process(*receive(ws)) # successfully received data, reset reconnects counter reconnects = 0 except (websocket.WebSocketConnectionClosedException, Exception): if reconnects <= 15: reconnects += 1 # Sleep 5 between connection attempts if reconnects > 1: time.sleep(5) logger.warn(u"PlexPy WebSocket :: Connection has closed, reconnection attempt %s." % reconnects) try: ws = create_connection(uri, header=header) except IOError as e: logger.info(u"PlexPy WebSocket :: %s." % e) else: ws.shutdown() ws_connected = False break # 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.")
def import_from_plexwatch(database=None, table_name=None, import_ignore_interval=0): try: connection = sqlite3.connect(database, timeout=20) except sqlite3.OperationalError: logger.error('PlexPy Importer :: Invalid filename.') return None except ValueError: logger.error('PlexPy Importer :: Invalid filename.') return None try: connection.execute('SELECT ratingKey from %s' % table_name) except sqlite3.OperationalError: logger.error('PlexPy Importer :: Database specified does not contain the required fields.') return None logger.debug(u"PlexPy Importer :: PlexWatch data import in progress...") logger.debug(u"PlexPy Importer :: Disabling monitoring while import in progress.") plexpy.schedule_job(monitor.check_active_sessions, 'Check for active sessions', hours=0, minutes=0, seconds=0) monitor_processing = monitor.MonitorProcessing() data_factory = datafactory.DataFactory() # Get the latest friends list so we can pull user id's try: plextv.refresh_users() except: logger.debug(u"PlexPy Importer :: Unable to refresh the users list. Aborting import.") return None query = 'SELECT time AS started, ' \ 'stopped, ' \ 'ratingKey AS rating_key, ' \ 'null AS user_id, ' \ 'user, ' \ 'ip_address, ' \ 'paused_counter, ' \ 'platform AS player, ' \ 'null AS platform, ' \ 'null as machine_id, ' \ 'parentRatingKey as parent_rating_key, ' \ 'grandparentRatingKey as grandparent_rating_key, ' \ 'null AS media_type, ' \ 'null AS view_offset, ' \ 'xml, ' \ 'rating as content_rating,' \ 'summary,' \ 'title AS full_title,' \ 'orig_title AS title, ' \ 'orig_title_ep AS grandparent_title ' \ 'FROM ' + table_name + ' ORDER BY id' result = connection.execute(query) for row in result: # Extract the xml from the Plexwatch db xml field. extracted_xml = extract_plexwatch_xml(row[14]) # If the user_id no longer exists in the friends list, pull it from the xml. if data_factory.get_user_id(user=row[4]): user_id = data_factory.get_user_id(user=row[4]) else: user_id = extracted_xml['user_id'] session_history = {'started': row[0], 'stopped': row[1], 'rating_key': row[2], 'title': extracted_xml['title'], 'parent_title': extracted_xml['parent_title'], 'grandparent_title': extracted_xml['grandparent_title'], 'user_id': user_id, 'user': row[4], 'ip_address': row[5], 'paused_counter': row[6], 'player': row[7], 'platform': extracted_xml['platform'], 'machine_id': extracted_xml['machine_id'], 'parent_rating_key': row[10], 'grandparent_rating_key': row[11], 'media_type': extracted_xml['media_type'], 'view_offset': extracted_xml['view_offset'], 'video_decision': extracted_xml['video_decision'], 'audio_decision': extracted_xml['audio_decision'], 'duration': extracted_xml['duration'], 'width': extracted_xml['width'], 'height': extracted_xml['height'], 'container': extracted_xml['container'], 'video_codec': extracted_xml['video_codec'], 'audio_codec': extracted_xml['audio_codec'], 'bitrate': extracted_xml['bitrate'], 'video_resolution': extracted_xml['video_resolution'], 'video_framerate': extracted_xml['video_framerate'], 'aspect_ratio': extracted_xml['aspect_ratio'], 'audio_channels': extracted_xml['audio_channels'], 'transcode_protocol': extracted_xml['transcode_protocol'], 'transcode_container': extracted_xml['transcode_container'], 'transcode_video_codec': extracted_xml['transcode_video_codec'], 'transcode_audio_codec': extracted_xml['transcode_audio_codec'], 'transcode_audio_channels': extracted_xml['transcode_audio_channels'], 'transcode_width': extracted_xml['transcode_width'], 'transcode_height': extracted_xml['transcode_height'] } session_history_metadata = {'rating_key': row[2], 'parent_rating_key': row[10], 'grandparent_rating_key': row[11], 'title': extracted_xml['title'], 'parent_title': extracted_xml['parent_title'], 'grandparent_title': extracted_xml['grandparent_title'], 'index': extracted_xml['media_index'], 'parent_index': extracted_xml['parent_media_index'], 'thumb': extracted_xml['thumb'], 'parent_thumb': extracted_xml['parent_thumb'], 'grandparent_thumb': extracted_xml['grandparent_thumb'], 'art': extracted_xml['art'], 'media_type': extracted_xml['media_type'], 'year': extracted_xml['year'], 'originally_available_at': extracted_xml['originally_available_at'], 'added_at': extracted_xml['added_at'], 'updated_at': extracted_xml['updated_at'], 'last_viewed_at': extracted_xml['last_viewed_at'], 'content_rating': row[15], 'summary': row[16], 'rating': extracted_xml['rating'], 'duration': extracted_xml['duration'], 'guid': extracted_xml['guid'], 'directors': extracted_xml['directors'], 'writers': extracted_xml['writers'], 'actors': extracted_xml['actors'], 'genres': extracted_xml['genres'], 'studio': extracted_xml['studio'], 'full_title': row[17] } # On older versions of PMS, "clip" items were still classified as "movie" and had bad ratingKey values # Just make sure that the ratingKey is indeed an integer if str(row[2]).isdigit(): monitor_processing.write_session_history(session=session_history, import_metadata=session_history_metadata, is_import=True, import_ignore_interval=import_ignore_interval) else: logger.debug(u"PlexPy Importer :: Item has bad rating_key: %s" % str(row[2])) logger.debug(u"PlexPy Importer :: PlexWatch data import complete.") logger.debug(u"PlexPy Importer :: Re-enabling monitoring.") plexpy.initialize_scheduler()
def run(): from websocket import create_connection if plexpy.CONFIG.PMS_SSL and plexpy.CONFIG.PMS_URL[:5] == 'https': uri = plexpy.CONFIG.PMS_URL.replace( 'https://', 'wss://') + '/:/websockets/notifications' secure = ' secure' else: uri = 'ws://%s:%s/:/websockets/notifications' % ( plexpy.CONFIG.PMS_IP, plexpy.CONFIG.PMS_PORT) secure = '' # Set authentication token (if one is available) if plexpy.CONFIG.PMS_TOKEN: header = ["X-Plex-Token: %s" % plexpy.CONFIG.PMS_TOKEN] else: header = [] 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))) ws = create_connection(uri, header=header) reconnects = 0 ws_connected = True logger.info(u"PlexPy WebSocket :: Ready") except IOError as e: logger.error(u"PlexPy WebSocket :: %s." % e) reconnects += 1 time.sleep(5) while ws_connected: try: process(*receive(ws)) # successfully received data, reset reconnects counter reconnects = 0 except (websocket.WebSocketConnectionClosedException, Exception): if reconnects <= 15: reconnects += 1 # Sleep 5 between connection attempts if reconnects > 1: time.sleep(5) logger.warn( u"PlexPy WebSocket :: Connection has closed, reconnection attempt %s." % reconnects) try: ws = create_connection(uri, header=header) except IOError as e: logger.info(u"PlexPy WebSocket :: %s." % e) else: ws.shutdown() ws_connected = False break # 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.")
def main(): """ PlexPy application entry point. Parses arguments, setups encoding and initializes the application. """ # Fixed paths to PlexPy if hasattr(sys, 'frozen'): plexpy.FULL_PATH = os.path.abspath(sys.executable) else: plexpy.FULL_PATH = os.path.abspath(__file__) plexpy.PROG_DIR = os.path.dirname(plexpy.FULL_PATH) plexpy.ARGS = sys.argv[1:] # From sickbeard plexpy.SYS_PLATFORM = sys.platform plexpy.SYS_ENCODING = None try: locale.setlocale(locale.LC_ALL, "") plexpy.SYS_ENCODING = locale.getpreferredencoding() except (locale.Error, IOError): pass # for OSes that are poorly configured I'll just force UTF-8 if not plexpy.SYS_ENCODING or plexpy.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'): plexpy.SYS_ENCODING = 'UTF-8' # Set up and gather command line arguments parser = argparse.ArgumentParser( description= 'A Python based monitoring and tracking tool for Plex Media Server.') parser.add_argument('-v', '--verbose', action='store_true', help='Increase console logging verbosity') parser.add_argument('-q', '--quiet', action='store_true', help='Turn off console logging') parser.add_argument('-d', '--daemon', action='store_true', help='Run as a daemon') parser.add_argument('-p', '--port', type=int, help='Force PlexPy to run on a specified port') parser.add_argument('--dev', action='store_true', help='Start PlexPy in the development environment') parser.add_argument( '--datadir', help='Specify a directory where to store your data files') parser.add_argument('--config', help='Specify a config file to use') parser.add_argument('--nolaunch', action='store_true', help='Prevent browser from launching on startup') parser.add_argument( '--pidfile', help='Create a pid file (only relevant when running as a daemon)') parser.add_argument( '--nofork', action='store_true', help='Start PlexPy as a service, do not fork when restarting') args = parser.parse_args() if args.verbose: plexpy.VERBOSE = True if args.quiet: plexpy.QUIET = True # Do an intial setup of the logger. logger.initLogger(console=not plexpy.QUIET, log_dir=False, verbose=plexpy.VERBOSE) if args.dev: plexpy.DEV = True logger.debug(u"PlexPy is running in the dev environment.") if args.daemon: if sys.platform == 'win32': sys.stderr.write( "Daemonizing not supported under Windows, starting normally\n") else: plexpy.DAEMON = True plexpy.QUIET = True if args.nofork: plexpy.NOFORK = True logger.info( "PlexPy is running as a service, it will not fork when restarted.") if args.pidfile: plexpy.PIDFILE = str(args.pidfile) # If the pidfile already exists, plexpy may still be running, so # exit if os.path.exists(plexpy.PIDFILE): try: with open(plexpy.PIDFILE, 'r') as fp: pid = int(fp.read()) os.kill(pid, 0) except IOError as e: raise SystemExit("Unable to read PID file: %s", e) except OSError: logger.warn("PID file '%s' already exists, but PID %d is " \ "not running. Ignoring PID file." % (plexpy.PIDFILE, pid)) else: # The pidfile exists and points to a live PID. plexpy may # still be running, so exit. raise SystemExit("PID file '%s' already exists. Exiting." % plexpy.PIDFILE) # The pidfile is only useful in daemon mode, make sure we can write the # file properly if plexpy.DAEMON: plexpy.CREATEPID = True try: with open(plexpy.PIDFILE, 'w') as fp: fp.write("pid\n") except IOError as e: raise SystemExit("Unable to write PID file: %s", e) else: logger.warn("Not running in daemon mode. PID file creation " \ "disabled.") # Determine which data directory and config file to use if args.datadir: plexpy.DATA_DIR = args.datadir else: plexpy.DATA_DIR = plexpy.PROG_DIR if args.config: config_file = args.config else: config_file = os.path.join(plexpy.DATA_DIR, config.FILENAME) # Try to create the DATA_DIR if it doesn't exist if not os.path.exists(plexpy.DATA_DIR): try: os.makedirs(plexpy.DATA_DIR) except OSError: raise SystemExit('Could not create data directory: ' + plexpy.DATA_DIR + '. Exiting....') # Make sure the DATA_DIR is writeable if not os.access(plexpy.DATA_DIR, os.W_OK): raise SystemExit('Cannot write to the data directory: ' + plexpy.DATA_DIR + '. Exiting...') # Put the database in the DATA_DIR plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, database.FILENAME) if plexpy.DAEMON: plexpy.daemonize() # Read config and start logging plexpy.initialize(config_file) # Start the background threads plexpy.start() # Open connection for websocket if plexpy.CONFIG.MONITORING_USE_WEBSOCKET: try: web_socket.start_thread() except: logger.warn(u"Websocket :: Unable to open connection.") # Fallback to polling plexpy.POLLING_FAILOVER = True plexpy.initialize_scheduler() # Force the http port if neccessary if args.port: http_port = args.port logger.info('Using forced web server port: %i', http_port) else: http_port = int(plexpy.CONFIG.HTTP_PORT) # Check if pyOpenSSL is installed. It is required for certificate generation # and for CherryPy. if plexpy.CONFIG.ENABLE_HTTPS: try: import OpenSSL except ImportError: logger.warn("The pyOpenSSL module is missing. Install this " \ "module to enable HTTPS. HTTPS will be disabled.") plexpy.CONFIG.ENABLE_HTTPS = False # Try to start the server. Will exit here is address is already in use. web_config = { 'http_port': http_port, 'http_host': plexpy.CONFIG.HTTP_HOST, 'http_root': plexpy.CONFIG.HTTP_ROOT, 'http_environment': plexpy.CONFIG.HTTP_ENVIRONMENT, 'http_proxy': plexpy.CONFIG.HTTP_PROXY, 'enable_https': plexpy.CONFIG.ENABLE_HTTPS, 'https_cert': plexpy.CONFIG.HTTPS_CERT, 'https_key': plexpy.CONFIG.HTTPS_KEY, 'http_username': plexpy.CONFIG.HTTP_USERNAME, 'http_password': plexpy.CONFIG.HTTP_PASSWORD, 'http_basic_auth': plexpy.CONFIG.HTTP_BASIC_AUTH } webstart.initialize(web_config) # Open webbrowser if plexpy.CONFIG.LAUNCH_BROWSER and not args.nolaunch and not plexpy.DEV: plexpy.launch_browser(plexpy.CONFIG.HTTP_HOST, http_port, plexpy.CONFIG.HTTP_ROOT) # Wait endlessy for a signal to happen while True: if not plexpy.SIGNAL: try: time.sleep(1) except KeyboardInterrupt: plexpy.SIGNAL = 'shutdown' else: logger.info('Received signal: %s', plexpy.SIGNAL) if plexpy.SIGNAL == 'shutdown': plexpy.shutdown() elif plexpy.SIGNAL == 'restart': plexpy.shutdown(restart=True) else: plexpy.shutdown(restart=True, update=True) plexpy.SIGNAL = None
time.sleep(5) logger.warn(u'PlexPy WebSocket :: Connection has closed, reconnecting...') try: ws = create_connection(uri) except IOError, e: logger.info(u'PlexPy WebSocket :: %s.' % e) else: ws_connected = False break if not ws_connected: 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.') def receive(ws): frame = ws.recv_frame() if not frame: raise websocket.WebSocketException("Not a valid frame %s" % frame) elif frame.opcode in opcode_data: return frame.opcode, frame.data elif frame.opcode == websocket.ABNF.OPCODE_CLOSE: ws.send_close() return frame.opcode, None elif frame.opcode == websocket.ABNF.OPCODE_PING:
def main(): """ PlexPy application entry point. Parses arguments, setups encoding and initializes the application. """ # Fixed paths to PlexPy if hasattr(sys, 'frozen'): plexpy.FULL_PATH = os.path.abspath(sys.executable) else: plexpy.FULL_PATH = os.path.abspath(__file__) plexpy.PROG_DIR = os.path.dirname(plexpy.FULL_PATH) plexpy.ARGS = sys.argv[1:] # From sickbeard plexpy.SYS_PLATFORM = sys.platform plexpy.SYS_ENCODING = None try: locale.setlocale(locale.LC_ALL, "") plexpy.SYS_ENCODING = locale.getpreferredencoding() except (locale.Error, IOError): pass # for OSes that are poorly configured I'll just force UTF-8 if not plexpy.SYS_ENCODING or plexpy.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'): plexpy.SYS_ENCODING = 'UTF-8' # Set up and gather command line arguments parser = argparse.ArgumentParser( description='A Python based monitoring and tracking tool for Plex Media Server.') parser.add_argument( '-v', '--verbose', action='store_true', help='Increase console logging verbosity') parser.add_argument( '-q', '--quiet', action='store_true', help='Turn off console logging') parser.add_argument( '-d', '--daemon', action='store_true', help='Run as a daemon') parser.add_argument( '-p', '--port', type=int, help='Force PlexPy to run on a specified port') parser.add_argument( '--dev', action='store_true', help='Start PlexPy in the development environment') parser.add_argument( '--datadir', help='Specify a directory where to store your data files') parser.add_argument( '--config', help='Specify a config file to use') parser.add_argument( '--nolaunch', action='store_true', help='Prevent browser from launching on startup') parser.add_argument( '--pidfile', help='Create a pid file (only relevant when running as a daemon)') args = parser.parse_args() if args.verbose: plexpy.VERBOSE = True if args.quiet: plexpy.QUIET = True # Do an intial setup of the logger. logger.initLogger(console=not plexpy.QUIET, log_dir=False, verbose=plexpy.VERBOSE) if args.dev: plexpy.DEV = True logger.debug(u"PlexPy is running in the dev environment.") if args.daemon: if sys.platform == 'win32': sys.stderr.write( "Daemonizing not supported under Windows, starting normally\n") else: plexpy.DAEMON = True plexpy.QUIET = True if args.pidfile: plexpy.PIDFILE = str(args.pidfile) # If the pidfile already exists, plexpy may still be running, so # exit if os.path.exists(plexpy.PIDFILE): raise SystemExit("PID file '%s' already exists. Exiting." % plexpy.PIDFILE) # The pidfile is only useful in daemon mode, make sure we can write the # file properly if plexpy.DAEMON: plexpy.CREATEPID = True try: with open(plexpy.PIDFILE, 'w') as fp: fp.write("pid\n") except IOError as e: raise SystemExit("Unable to write PID file: %s", e) else: logger.warn("Not running in daemon mode. PID file creation " \ "disabled.") # Determine which data directory and config file to use if args.datadir: plexpy.DATA_DIR = args.datadir else: plexpy.DATA_DIR = plexpy.PROG_DIR if args.config: config_file = args.config else: config_file = os.path.join(plexpy.DATA_DIR, config.FILENAME) # Try to create the DATA_DIR if it doesn't exist if not os.path.exists(plexpy.DATA_DIR): try: os.makedirs(plexpy.DATA_DIR) except OSError: raise SystemExit( 'Could not create data directory: ' + plexpy.DATA_DIR + '. Exiting....') # Make sure the DATA_DIR is writeable if not os.access(plexpy.DATA_DIR, os.W_OK): raise SystemExit( 'Cannot write to the data directory: ' + plexpy.DATA_DIR + '. Exiting...') # Put the database in the DATA_DIR plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, database.FILENAME) if plexpy.DAEMON: plexpy.daemonize() # Read config and start logging plexpy.initialize(config_file) # Start the background threads plexpy.start() # Open connection for websocket if plexpy.CONFIG.MONITORING_USE_WEBSOCKET: try: web_socket.start_thread() except: logger.warn(u"Websocket :: Unable to open connection.") # Fallback to polling plexpy.POLLING_FAILOVER = True plexpy.initialize_scheduler() # Force the http port if neccessary if args.port: http_port = args.port logger.info('Using forced web server port: %i', http_port) else: http_port = int(plexpy.CONFIG.HTTP_PORT) # Check if pyOpenSSL is installed. It is required for certificate generation # and for CherryPy. if plexpy.CONFIG.ENABLE_HTTPS: try: import OpenSSL except ImportError: logger.warn("The pyOpenSSL module is missing. Install this " \ "module to enable HTTPS. HTTPS will be disabled.") plexpy.CONFIG.ENABLE_HTTPS = False # Try to start the server. Will exit here is address is already in use. web_config = { 'http_port': http_port, 'http_host': plexpy.CONFIG.HTTP_HOST, 'http_root': plexpy.CONFIG.HTTP_ROOT, 'http_environment': plexpy.CONFIG.HTTP_ENVIRONMENT, 'http_proxy': plexpy.CONFIG.HTTP_PROXY, 'enable_https': plexpy.CONFIG.ENABLE_HTTPS, 'https_cert': plexpy.CONFIG.HTTPS_CERT, 'https_key': plexpy.CONFIG.HTTPS_KEY, 'http_username': plexpy.CONFIG.HTTP_USERNAME, 'http_password': plexpy.CONFIG.HTTP_PASSWORD, 'http_basic_auth': plexpy.CONFIG.HTTP_BASIC_AUTH } webstart.initialize(web_config) # Open webbrowser if plexpy.CONFIG.LAUNCH_BROWSER and not args.nolaunch and not plexpy.DEV: plexpy.launch_browser(plexpy.CONFIG.HTTP_HOST, http_port, plexpy.CONFIG.HTTP_ROOT) # Wait endlessy for a signal to happen while True: if not plexpy.SIGNAL: try: time.sleep(1) except KeyboardInterrupt: plexpy.SIGNAL = 'shutdown' else: logger.info('Received signal: %s', plexpy.SIGNAL) if plexpy.SIGNAL == 'shutdown': plexpy.shutdown() elif plexpy.SIGNAL == 'restart': plexpy.shutdown(restart=True) else: plexpy.shutdown(restart=True, update=True) plexpy.SIGNAL = None
def import_from_plexwatch(database=None, table_name=None, import_ignore_interval=0): try: connection = sqlite3.connect(database, timeout=20) connection.row_factory = sqlite3.Row except sqlite3.OperationalError: logger.error(u"PlexPy Importer :: Invalid filename.") return None except ValueError: logger.error(u"PlexPy Importer :: Invalid filename.") return None try: connection.execute('SELECT ratingKey from %s' % table_name) except sqlite3.OperationalError: logger.error( u"PlexPy Importer :: Database specified does not contain the required fields." ) return None logger.debug(u"PlexPy Importer :: PlexWatch data import in progress...") logger.debug( u"PlexPy Importer :: Disabling monitoring while import in progress.") plexpy.schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions', 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', hours=0, minutes=0, seconds=0) ap = activity_processor.ActivityProcessor() user_data = users.Users() # Get the latest friends list so we can pull user id's try: plextv.refresh_users() except: logger.debug( u"PlexPy Importer :: Unable to refresh the users list. Aborting import." ) return None query = 'SELECT time AS started, ' \ 'stopped, ' \ 'cast(ratingKey as text) AS rating_key, ' \ 'null AS user_id, ' \ 'user, ' \ 'ip_address, ' \ 'paused_counter, ' \ 'platform AS player, ' \ 'null AS platform, ' \ 'null as machine_id, ' \ 'parentRatingKey as parent_rating_key, ' \ 'grandparentRatingKey as grandparent_rating_key, ' \ 'null AS media_type, ' \ 'null AS view_offset, ' \ 'xml, ' \ 'rating as content_rating,' \ 'summary,' \ 'title AS full_title,' \ '(case when orig_title_ep = "" then orig_title else ' \ 'orig_title_ep end) as title,' \ '(case when orig_title_ep != "" then orig_title else ' \ 'null end) as grandparent_title ' \ 'FROM ' + table_name + ' ORDER BY id' result = connection.execute(query) for row in result: # Extract the xml from the Plexwatch db xml field. extracted_xml = extract_plexwatch_xml(row['xml']) # If we get back None from our xml extractor skip over the record and log error. if not extracted_xml: logger.error( u"PlexPy Importer :: Skipping record with ratingKey %s due to malformed xml." % str(row['rating_key'])) continue # 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.") continue # If the user_id no longer exists in the friends list, pull it from the xml. if user_data.get_user_id(user=row['user']): user_id = user_data.get_user_id(user=row['user']) else: user_id = extracted_xml['user_id'] session_history = { 'started': row['started'], 'stopped': row['stopped'], 'rating_key': row['rating_key'], 'title': row['title'], 'parent_title': extracted_xml['parent_title'], 'grandparent_title': row['grandparent_title'], 'user_id': user_id, 'user': row['user'], 'ip_address': row['ip_address'], 'paused_counter': row['paused_counter'], 'player': row['player'], 'platform': extracted_xml['platform'], 'machine_id': extracted_xml['machine_id'], 'parent_rating_key': row['parent_rating_key'], 'grandparent_rating_key': row['grandparent_rating_key'], 'media_type': extracted_xml['media_type'], 'view_offset': extracted_xml['view_offset'], 'video_decision': extracted_xml['video_decision'], 'audio_decision': extracted_xml['audio_decision'], 'duration': extracted_xml['duration'], 'width': extracted_xml['width'], 'height': extracted_xml['height'], 'container': extracted_xml['container'], 'video_codec': extracted_xml['video_codec'], 'audio_codec': extracted_xml['audio_codec'], 'bitrate': extracted_xml['bitrate'], 'video_resolution': extracted_xml['video_resolution'], 'video_framerate': extracted_xml['video_framerate'], 'aspect_ratio': extracted_xml['aspect_ratio'], 'audio_channels': extracted_xml['audio_channels'], 'transcode_protocol': extracted_xml['transcode_protocol'], 'transcode_container': extracted_xml['transcode_container'], 'transcode_video_codec': extracted_xml['transcode_video_codec'], 'transcode_audio_codec': extracted_xml['transcode_audio_codec'], 'transcode_audio_channels': extracted_xml['transcode_audio_channels'], 'transcode_width': extracted_xml['transcode_width'], 'transcode_height': extracted_xml['transcode_height'] } session_history_metadata = { 'rating_key': helpers.latinToAscii(row['rating_key']), 'parent_rating_key': row['parent_rating_key'], 'grandparent_rating_key': row['grandparent_rating_key'], 'title': row['title'], 'parent_title': extracted_xml['parent_title'], 'grandparent_title': row['grandparent_title'], 'media_index': extracted_xml['media_index'], 'parent_media_index': extracted_xml['parent_media_index'], 'thumb': extracted_xml['thumb'], 'parent_thumb': extracted_xml['parent_thumb'], 'grandparent_thumb': extracted_xml['grandparent_thumb'], 'art': extracted_xml['art'], 'media_type': extracted_xml['media_type'], 'year': extracted_xml['year'], 'originally_available_at': extracted_xml['originally_available_at'], 'added_at': extracted_xml['added_at'], 'updated_at': extracted_xml['updated_at'], 'last_viewed_at': extracted_xml['last_viewed_at'], 'content_rating': row['content_rating'], 'summary': row['summary'], 'tagline': extracted_xml['tagline'], 'rating': extracted_xml['rating'], 'duration': extracted_xml['duration'], 'guid': extracted_xml['guid'], 'section_id': extracted_xml['section_id'], 'directors': extracted_xml['directors'], 'writers': extracted_xml['writers'], 'actors': extracted_xml['actors'], 'genres': extracted_xml['genres'], 'studio': extracted_xml['studio'], 'full_title': row['full_title'] } # On older versions of PMS, "clip" items were still classified as "movie" and had bad ratingKey values # Just make sure that the ratingKey is indeed an integer if session_history_metadata['rating_key'].isdigit(): ap.write_session_history( session=session_history, import_metadata=session_history_metadata, is_import=True, import_ignore_interval=import_ignore_interval) else: logger.debug(u"PlexPy Importer :: Item has bad rating_key: %s" % session_history_metadata['rating_key']) logger.debug(u"PlexPy Importer :: PlexWatch data import complete.") import_users() logger.debug(u"PlexPy Importer :: Re-enabling monitoring.") plexpy.initialize_scheduler()
def update_section_ids(): from plexpy import pmsconnect, activity_pinger import threading plexpy.CONFIG.UPDATE_SECTION_IDS = -1 logger.info(u"PlexPy Libraries :: Updating section_id's in database.") logger.debug(u"PlexPy Libraries :: Disabling monitoring while update in progress.") plexpy.schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions', 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', hours=0, minutes=0, seconds=0) monitor_db = database.MonitorDatabase() try: query = 'SELECT id, rating_key FROM session_history_metadata WHERE section_id IS NULL' result = monitor_db.select(query=query) except Exception as e: logger.warn(u"PlexPy Libraries :: Unable to execute database query for update_section_ids: %s." % e) logger.warn(u"PlexPy Libraries :: Unable to update section_id's in database.") plexpy.CONFIG.__setattr__('UPDATE_SECTION_IDS', 1) plexpy.CONFIG.write() logger.debug(u"PlexPy Libraries :: Re-enabling monitoring.") plexpy.initialize_scheduler() return None # Add thread filter to the logger logger.debug(u"PlexPy Libraries :: Disabling logging in the current thread while update in progress.") thread_filter = logger.NoThreadFilter(threading.current_thread().name) for handler in logger.logger.handlers: handler.addFilter(thread_filter) pms_connect = pmsconnect.PmsConnect() error_keys = set() for item in result: id = item['id'] rating_key = item['rating_key'] metadata = pms_connect.get_metadata_details(rating_key=rating_key) if metadata: metadata = metadata['metadata'] section_keys = {'id': id} section_values = {'section_id': metadata['section_id']} monitor_db.upsert('session_history_metadata', key_dict=section_keys, value_dict=section_values) else: error_keys.add(rating_key) # Remove thread filter from the logger for handler in logger.logger.handlers: handler.removeFilter(thread_filter) logger.debug(u"PlexPy Libraries :: Re-enabling logging in the current thread.") if error_keys: logger.info(u"PlexPy Libraries :: Updated all section_id's in database except for rating_keys: %s." % ', '.join(str(key) for key in error_keys)) else: logger.info(u"PlexPy Libraries :: Updated all section_id's in database.") plexpy.CONFIG.__setattr__('UPDATE_SECTION_IDS', 0) plexpy.CONFIG.write() logger.debug(u"PlexPy Libraries :: Re-enabling monitoring.") plexpy.initialize_scheduler() return True