def checkGithub(): plexcs.COMMITS_BEHIND = 0 # Get the latest version available from github logger.info('Retrieving latest version information from GitHub') url = 'https://api.github.com/repos/%s/plex-cs/commits/%s' % ( plexcs.CONFIG.GIT_USER, plexcs.CONFIG.GIT_BRANCH) version = request.request_json(url, timeout=20, validator=lambda x: type(x) == dict) if version is None: logger.warn( 'Could not get the latest version from GitHub. Are you running a local development version?' ) return plexcs.CURRENT_VERSION plexcs.LATEST_VERSION = version['sha'] logger.debug("Latest version is %s", plexcs.LATEST_VERSION) # See how many commits behind we are if not plexcs.CURRENT_VERSION: logger.info( 'You are running an unknown version of Plex:CS. Run the updater to identify your version' ) return plexcs.LATEST_VERSION if plexcs.LATEST_VERSION == plexcs.CURRENT_VERSION: logger.info('Plex:CS is up to date') return plexcs.LATEST_VERSION logger.info( 'Comparing currently installed version with latest GitHub version') url = 'https://api.github.com/repos/%s/plex-cs/compare/%s...%s' % ( plexcs.CONFIG.GIT_USER, plexcs.LATEST_VERSION, plexcs.CURRENT_VERSION) commits = request.request_json(url, timeout=20, whitelist_status_code=404, validator=lambda x: type(x) == dict) if commits is None: logger.warn('Could not get commits behind from GitHub.') return plexcs.LATEST_VERSION try: plexcs.COMMITS_BEHIND = int(commits['behind_by']) logger.debug("In total, %d commits behind", plexcs.COMMITS_BEHIND) except KeyError: logger.info( 'Cannot compare versions. Are you running a local development version?' ) plexcs.COMMITS_BEHIND = 0 if plexcs.COMMITS_BEHIND > 0: logger.info('New version is available. You are %s commits behind' % plexcs.COMMITS_BEHIND) elif plexcs.COMMITS_BEHIND == 0: logger.info('Plex:CS is up to date') return plexcs.LATEST_VERSION
def action(self, query, args=None, return_last_id=False): if query is None: return with db_lock: sql_result = None attempts = 0 while attempts < 5: try: with self.connection as c: if args is None: sql_result = c.execute(query) else: sql_result = c.execute(query, args) # Our transaction was successful, leave the loop break except sqlite3.OperationalError as e: if "unable to open database file" in e.message or "database is locked" in e.message: logger.warn('Database Error: %s', e) attempts += 1 time.sleep(1) else: logger.error('Database error: %s', e) raise except sqlite3.DatabaseError as e: logger.error('Fatal Error executing %s :: %s', query, e) raise return sql_result
def refresh_users(): logger.info("Requesting users list refresh...") result = PlexTV().get_full_users_list() monitor_db = database.MonitorDatabase() if len(result) > 0: for item in result: control_value_dict = {"user_id": item['user_id']} new_value_dict = {"username": item['username'], "thumb": item['thumb'], "email": item['email'], "is_home_user": item['is_home_user'], "is_allow_sync": item['is_allow_sync'], "is_restricted": item['is_restricted'] } # Check if we've set a custom avatar if so don't overwrite it. if item['user_id']: avatar_urls = monitor_db.select('SELECT thumb, custom_avatar_url ' 'FROM users WHERE user_id = ?', [item['user_id']]) if avatar_urls: if not avatar_urls[0]['custom_avatar_url'] or \ avatar_urls[0]['custom_avatar_url'] == avatar_urls[0]['thumb']: new_value_dict['custom_avatar_url'] = item['thumb'] else: new_value_dict['custom_avatar_url'] = item['thumb'] monitor_db.upsert('users', new_value_dict, control_value_dict) logger.info("Users list refreshed.") else: logger.warn("Unable to refresh users list.")
def check_server_response(): with monitor_lock: pms_connect = pmsconnect.PmsConnect() server_response = pms_connect.get_server_response() global ext_ping_count # Check for remote access if server_response: mapping_state = server_response['mapping_state'] mapping_error = server_response['mapping_error'] # Check if the port is mapped if not mapping_state == 'mapped': ext_ping_count += 1 logger.warn(u"Plex:CS Monitor :: Plex remote access port not mapped, ping attempt %s." \ % str(ext_ping_count)) # Check if the port is open elif mapping_error == 'unreachable': ext_ping_count += 1 logger.warn(u"Plex:CS Monitor :: Plex remote access port mapped, but mapping failed, ping attempt %s." \ % str(ext_ping_count)) # Reset external ping counter else: ext_ping_count = 0 if ext_ping_count == 3: # Fire off notifications threading.Thread(target=notification_handler.notify_timeline, kwargs=dict(notify_action='extdown')).start()
def discover(self): """ Query plex for all servers online. Returns the ones you own in a selectize format """ result = self.get_plextv_resources(include_https=True, output_format='raw') servers = xmltodict.parse(result, process_namespaces=True, attr_prefix='') clean_servers = [] try: if servers: # Fix if its only one "device" if int(servers['MediaContainer']['size']) == 1: servers['MediaContainer']['Device'] = [servers['MediaContainer']['Device']] for server in servers['MediaContainer']['Device']: # Only grab servers online and own if server.get('presence', None) == '1' and server.get('owned', None) == '1' and server.get('provides', None) == 'server': # If someone only has one connection.. if isinstance(server['Connection'], dict): server['Connection'] = [server['Connection']] for s in server['Connection']: # to avoid circular ref d = {} d.update(s) d.update(server) d['label'] = d['name'] d['value'] = d['address'] del d['Connection'] clean_servers.append(d) except Exception as e: logger.warn('Failed to get servers from plex %s' % e) return clean_servers return json.dumps(clean_servers, indent=4)
def shutdown(restart=False, update=False): cherrypy.engine.exit() SCHED.shutdown(wait=False) CONFIG.write() if not restart and not update: logger.info('Plex:CS is shutting down...') if update: logger.info('Plex:CS is updating...') try: versioncheck.update() except Exception as e: logger.warn('Plex:CS failed to update: %s. Restarting.', e) if CREATEPID: logger.info('Removing pidfile %s', PIDFILE) os.remove(PIDFILE) if restart: logger.info('Plex:CS is restarting...') popen_list = [sys.executable, FULL_PATH] popen_list += ARGS if '--nolaunch' not in popen_list: popen_list += ['--nolaunch'] logger.info('Restarting Plex:CS with %s', popen_list) subprocess.Popen(popen_list, cwd=os.getcwd()) os._exit(0)
def run(): from websocket import create_connection uri = 'ws://%s:%s/:/websockets/notifications' % ( plexcs.CONFIG.PMS_IP, plexcs.CONFIG.PMS_PORT ) # Set authentication token (if one is available) if plexcs.CONFIG.PMS_TOKEN: uri += '?X-Plex-Token=' + plexcs.CONFIG.PMS_TOKEN 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'Plex:CS WebSocket :: Opening websocket, connection attempt %s.' % str(reconnects + 1)) ws = create_connection(uri) reconnects = 0 ws_connected = True logger.info(u'Plex:CS WebSocket :: Ready') except IOError as e: logger.error(u'Plex:CS 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: if reconnects <= 15: reconnects += 1 # Sleep 5 between connection attempts if reconnects > 1: time.sleep(5) logger.warn(u'Plex:CS WebSocket :: Connection has closed, reconnecting...') try: ws = create_connection(uri) except IOError as e: logger.info(u'Plex:CS WebSocket :: %s.' % e) else: ws_connected = False break if not ws_connected: logger.error(u'Plex:CS WebSocket :: Connection unavailable, falling back to polling.') plexcs.POLLING_FAILOVER = True plexcs.initialize_scheduler() logger.debug(u'Plex:CS WebSocket :: Leaving thread.')
def _getPlayby(self, time_range='30', y_axis='plays', playtype='total_plays_per_month', **kwargs): graph = graphs.Graphs() if playtype == 'total_plays_per_month': result = graph.get_total_plays_per_month(y_axis=y_axis) elif playtype == 'total_plays_per_day': result = graph.get_total_plays_per_day(time_range=time_range, y_axis=y_axis) elif playtype == 'total_plays_per_hourofday': result = graph.get_total_plays_per_hourofday(time_range=time_range, y_axis=y_axis) elif playtype == 'total_plays_per_dayofweek': result = graph.get_total_plays_per_dayofweek(time_range=time_range, y_axis=y_axis) elif playtype == 'stream_type_by_top_10_users': result = graph.get_stream_type_by_top_10_users( time_range=time_range, y_axis=y_axis) elif playtype == 'stream_type_by_top_10_platforms': result = graph.get_stream_type_by_top_10_platforms( time_range=time_range, y_axis=y_axis) elif playtype == 'total_plays_by_stream_resolution': result = graph.get_total_plays_by_stream_resolution( time_range=time_range, y_axis=y_axis) elif playtype == 'total_plays_by_source_resolution': result = graph.get_total_plays_by_source_resolution( time_range=time_range, y_axis=y_axis) elif playtype == 'total_plays_per_stream_type': result = graph.get_total_plays_per_stream_type( time_range=time_range, y_axis=y_axis) elif playtype == 'total_plays_by_top_10_users': result = graph.get_total_plays_by_top_10_users( time_range=time_range, y_axis=y_axis) elif playtype == 'total_plays_by_top_10_platforms': result = graph.get_total_plays_by_top_10_platforms( time_range=time_range, y_axis=y_axis) if result: self.data = result return result else: logger.warn('Unable to retrieve %s from db' % playtype)
def _getMetadata(self, rating_key='', **kwargs): pms_connect = pmsconnect.PmsConnect() result = pms_connect.get_metadata(rating_key, 'dict') if result: self.data = result return result else: self.msg = 'Unable to retrive metadata %s' % rating_key logger.warn('Unable to retrieve data.')
def get_token(self): plextv_response = self.get_plex_auth(output_format='xml') if plextv_response: xml_head = plextv_response.getElementsByTagName('user') if not xml_head: logger.warn("Error parsing XML for Plex.tv token") return [] auth_token = xml_head[0].getAttribute('authenticationToken') return auth_token else: return []
def daemonize(): if threading.activeCount() != 1: logger.warn( 'There are %r active threads. Daemonizing may cause' ' strange behavior.', threading.enumerate()) sys.stdout.flush() sys.stderr.flush() # Do first fork try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: sys.exit(0) except OSError as e: raise RuntimeError("1st fork failed: %s [%d]", e.strerror, e.errno) os.setsid() # Make sure I can read my own files and shut out others prev = os.umask(0) # @UndefinedVariable - only available in UNIX os.umask(prev and int('077', 8)) # Make the child a session-leader by detaching from the terminal try: pid = os.fork() # @UndefinedVariable - only available in UNIX if pid != 0: sys.exit(0) except OSError as e: raise RuntimeError("2nd fork failed: %s [%d]", e.strerror, e.errno) dev_null = file('/dev/null', 'r') os.dup2(dev_null.fileno(), sys.stdin.fileno()) si = open('/dev/null', "r") so = open('/dev/null', "a+") se = open('/dev/null', "a+") os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) pid = os.getpid() logger.info('Daemonized to PID: %d', pid) if CREATEPID: logger.info("Writing PID %d to %s", pid, PIDFILE) with file(PIDFILE, 'w') as fp: fp.write("%s\n" % pid)
def checkGithub(): plexcs.COMMITS_BEHIND = 0 # Get the latest version available from github logger.info("Retrieving latest version information from GitHub") url = "https://api.github.com/repos/%s/plex-cs/commits/%s" % (plexcs.CONFIG.GIT_USER, plexcs.CONFIG.GIT_BRANCH) version = request.request_json(url, timeout=20, validator=lambda x: type(x) == dict) if version is None: logger.warn("Could not get the latest version from GitHub. Are you running a local development version?") return plexcs.CURRENT_VERSION plexcs.LATEST_VERSION = version["sha"] logger.debug("Latest version is %s", plexcs.LATEST_VERSION) # See how many commits behind we are if not plexcs.CURRENT_VERSION: logger.info("You are running an unknown version of Plex:CS. Run the updater to identify your version") return plexcs.LATEST_VERSION if plexcs.LATEST_VERSION == plexcs.CURRENT_VERSION: logger.info("Plex:CS is up to date") return plexcs.LATEST_VERSION logger.info("Comparing currently installed version with latest GitHub version") url = "https://api.github.com/repos/%s/plex-cs/compare/%s...%s" % ( plexcs.CONFIG.GIT_USER, plexcs.LATEST_VERSION, plexcs.CURRENT_VERSION, ) commits = request.request_json(url, timeout=20, whitelist_status_code=404, validator=lambda x: type(x) == dict) if commits is None: logger.warn("Could not get commits behind from GitHub.") return plexcs.LATEST_VERSION try: plexcs.COMMITS_BEHIND = int(commits["behind_by"]) logger.debug("In total, %d commits behind", plexcs.COMMITS_BEHIND) except KeyError: logger.info("Cannot compare versions. Are you running a local development version?") plexcs.COMMITS_BEHIND = 0 if plexcs.COMMITS_BEHIND > 0: logger.info("New version is available. You are %s commits behind" % plexcs.COMMITS_BEHIND) elif plexcs.COMMITS_BEHIND == 0: logger.info("Plex:CS is up to date") return plexcs.LATEST_VERSION
def _getSync(self, machine_id=None, user_id=None, **kwargs): pms_connect = pmsconnect.PmsConnect() server_id = pms_connect.get_server_identity() plex_tv = plextv.PlexTV() if not machine_id: result = plex_tv.get_synced_items(machine_id=server_id['machine_identifier'], user_id=user_id) else: result = plex_tv.get_synced_items(machine_id=machine_id, user_id=user_id) if result: self.data = result return result else: self.msg = 'Unable to retrieve sync data for user' logger.warn('Unable to retrieve sync data for user.')
def get_server_times(self): servers = self.get_plextv_server_list(output_format='xml') server_times = [] try: xml_head = servers.getElementsByTagName('Server') except: logger.warn("Error parsing XML for Plex servers.") return [] for a in xml_head: if helpers.get_xml_attr(a, 'machineIdentifier') == plexcs.CONFIG.PMS_IDENTIFIER: server_times.append({"created_at": helpers.get_xml_attr(a, 'createdAt'), "updated_at": helpers.get_xml_attr(a, 'updatedAt') }) break return server_times
def _getSync(self, machine_id=None, user_id=None, **kwargs): pms_connect = pmsconnect.PmsConnect() server_id = pms_connect.get_server_identity() plex_tv = plextv.PlexTV() if not machine_id: result = plex_tv.get_synced_items( machine_id=server_id['machine_identifier'], user_id=user_id) else: result = plex_tv.get_synced_items(machine_id=machine_id, user_id=user_id) if result: self.data = result return result else: self.msg = 'Unable to retrieve sync data for user' logger.warn('Unable to retrieve sync data for user.')
def _getPlayby(self, time_range='30', y_axis='plays', playtype='total_plays_per_month', **kwargs): graph = graphs.Graphs() if playtype == 'total_plays_per_month': result = graph.get_total_plays_per_month(y_axis=y_axis) elif playtype == 'total_plays_per_day': result = graph.get_total_plays_per_day(time_range=time_range, y_axis=y_axis) elif playtype == 'total_plays_per_hourofday': result = graph.get_total_plays_per_hourofday(time_range=time_range, y_axis=y_axis) elif playtype == 'total_plays_per_dayofweek': result = graph.get_total_plays_per_dayofweek(time_range=time_range, y_axis=y_axis) elif playtype == 'stream_type_by_top_10_users': result = graph.get_stream_type_by_top_10_users(time_range=time_range, y_axis=y_axis) elif playtype == 'stream_type_by_top_10_platforms': result = graph.get_stream_type_by_top_10_platforms(time_range=time_range, y_axis=y_axis) elif playtype == 'total_plays_by_stream_resolution': result = graph.get_total_plays_by_stream_resolution(time_range=time_range, y_axis=y_axis) elif playtype == 'total_plays_by_source_resolution': result = graph.get_total_plays_by_source_resolution(time_range=time_range, y_axis=y_axis) elif playtype == 'total_plays_per_stream_type': result = graph.get_total_plays_per_stream_type(time_range=time_range, y_axis=y_axis) elif playtype == 'total_plays_by_top_10_users': result = graph.get_total_plays_by_top_10_users(time_range=time_range, y_axis=y_axis) elif playtype == 'total_plays_by_top_10_platforms': result = graph.get_total_plays_by_top_10_platforms(time_range=time_range, y_axis=y_axis) if result: self.data = result return result else: logger.warn('Unable to retrieve %s from db' % playtype)
def get_user_player_stats(self, user=None, user_id=None): monitor_db = database.MonitorDatabase() player_stats = [] result_id = 0 try: if user_id: query = 'SELECT player, COUNT(player) as player_count, platform ' \ 'FROM session_history ' \ 'WHERE user_id = ? ' \ 'GROUP BY player ' \ 'ORDER BY player_count DESC' result = monitor_db.select(query, args=[user_id]) else: query = 'SELECT player, COUNT(player) as player_count, platform ' \ 'FROM session_history ' \ 'WHERE user = ? ' \ 'GROUP BY player ' \ 'ORDER BY player_count DESC' result = monitor_db.select(query, args=[user]) except: logger.warn("Unable to execute database query.") return None for item in result: # Rename Mystery platform names platform_type = common.PLATFORM_NAME_OVERRIDES.get( item['platform'], item['platform']) row = { 'player_name': item['player'], 'platform_type': platform_type, 'total_plays': item['player_count'], 'result_id': result_id } player_stats.append(row) result_id += 1 return player_stats
def process(opcode, data): from plexcs import activity_handler if opcode not in opcode_data: return False try: info = json.loads(data) except Exception as ex: logger.warn(u'Plex:CS WebSocket :: Error decoding message from websocket: %s' % ex) logger.debug(data) return False type = info.get('type') if not type: return False if type == 'playing': # logger.debug('%s.playing %s' % (name, info)) try: time_line = info.get('_children') except: logger.debug(u"Plex:CS WebSocket :: Session found but unable to get timeline data.") return False activity = activity_handler.ActivityHandler(timeline=time_line[0]) activity.process() #if type == 'timeline': # try: # time_line = info.get('_children') # except: # logger.debug(u"Plex:CS WebSocket :: Timeline event found but unable to get timeline data.") # return False # activity = activity_handler.TimelineHandler(timeline=time_line[0]) # activity.process() return True
def get_user_player_stats(self, user=None, user_id=None): monitor_db = database.MonitorDatabase() player_stats = [] result_id = 0 try: if user_id: query = 'SELECT player, COUNT(player) as player_count, platform ' \ 'FROM session_history ' \ 'WHERE user_id = ? ' \ 'GROUP BY player ' \ 'ORDER BY player_count DESC' result = monitor_db.select(query, args=[user_id]) else: query = 'SELECT player, COUNT(player) as player_count, platform ' \ 'FROM session_history ' \ 'WHERE user = ? ' \ 'GROUP BY player ' \ 'ORDER BY player_count DESC' result = monitor_db.select(query, args=[user]) except: logger.warn("Unable to execute database query.") return None for item in result: # Rename Mystery platform names platform_type = common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform']) row = {'player_name': item['player'], 'platform_type': platform_type, 'total_plays': item['player_count'], 'result_id': result_id } player_stats.append(row) result_id += 1 return player_stats
def get_real_pms_url(): logger.info("Requesting URLs for server...") # Reset any current PMS_URL value plexcs.CONFIG.__setattr__('PMS_URL', '') plexcs.CONFIG.write() fallback_url = 'http://' + plexcs.CONFIG.PMS_IP + ':' + str(plexcs.CONFIG.PMS_PORT) if plexcs.CONFIG.PMS_SSL: result = PlexTV().get_server_urls(include_https=True) process_urls = True elif plexcs.CONFIG.PMS_IS_REMOTE: result = PlexTV().get_server_urls(include_https=False) process_urls = True else: process_urls = False if process_urls: if len(result) > 0: for item in result: if plexcs.CONFIG.PMS_IS_REMOTE and item['local'] == '0': plexcs.CONFIG.__setattr__('PMS_URL', item['uri']) plexcs.CONFIG.write() logger.info("Server URL retrieved.") if not plexcs.CONFIG.PMS_IS_REMOTE and item['local'] == '1': plexcs.CONFIG.__setattr__('PMS_URL', item['uri']) plexcs.CONFIG.write() logger.info("Server URL retrieved.") else: plexcs.CONFIG.__setattr__('PMS_URL', fallback_url) plexcs.CONFIG.write() logger.warn("Unable to retrieve server URLs. Using user-defined value.") else: plexcs.CONFIG.__setattr__('PMS_URL', fallback_url) plexcs.CONFIG.write()
def parse_xml(unparsed=None): from plexcs import logger if unparsed: try: xml_parse = minidom.parseString(unparsed) return xml_parse except Exception as e: logger.warn("Error parsing XML. %s" % e) return [] except: logger.warn("Error parsing XML.") return [] else: logger.warn("XML parse request made but no data received.") return []
def get_server_urls(self, include_https=True): if plexcs.CONFIG.PMS_IDENTIFIER: server_id = plexcs.CONFIG.PMS_IDENTIFIER else: logger.error('Plex:CS PlexTV connector :: Unable to retrieve server identity.') return [] plextv_resources = self.get_plextv_resources(include_https=include_https) server_urls = [] try: xml_parse = minidom.parseString(plextv_resources) except Exception as e: logger.warn("Error parsing XML for Plex resources: %s" % e) return [] except: logger.warn("Error parsing XML for Plex resources.") return [] try: xml_head = xml_parse.getElementsByTagName('Device') except: logger.warn("Error parsing XML for Plex resources.") return [] for a in xml_head: if helpers.get_xml_attr(a, 'clientIdentifier') == server_id: connections = a.getElementsByTagName('Connection') for connection in connections: server_details = {"protocol": helpers.get_xml_attr(connection, 'protocol'), "address": helpers.get_xml_attr(connection, 'address'), "port": helpers.get_xml_attr(connection, 'port'), "uri": helpers.get_xml_attr(connection, 'uri'), "local": helpers.get_xml_attr(connection, 'local') } server_urls.append(server_details) return server_urls
def get_user_list(self, kwargs=None): data_tables = datatables.DataTables() custom_where = ['users.deleted_user', 0] columns = ['session_history.id', 'users.user_id as user_id', 'users.custom_avatar_url as user_thumb', '(case when users.friendly_name is null then users.username else \ users.friendly_name end) as friendly_name', 'MAX(session_history.started) as last_seen', 'session_history.ip_address as ip_address', 'COUNT(session_history.id) as plays', 'session_history.platform as platform', 'session_history.player as player', 'session_history_metadata.full_title as last_watched', 'session_history_metadata.thumb', 'session_history_metadata.parent_thumb', 'session_history_metadata.grandparent_thumb', 'session_history_metadata.media_type', 'session_history.rating_key as rating_key', 'session_history_media_info.video_decision', 'users.username as user', 'users.do_notify as do_notify', 'users.keep_history as keep_history' ] try: query = data_tables.ssp_query(table_name='users', columns=columns, custom_where=[custom_where], group_by=['users.user_id'], join_types=['LEFT OUTER JOIN', 'LEFT OUTER JOIN', 'LEFT OUTER JOIN'], join_tables=['session_history', 'session_history_metadata', 'session_history_media_info'], join_evals=[['session_history.user_id', 'users.user_id'], ['session_history.id', 'session_history_metadata.id'], ['session_history.id', 'session_history_media_info.id']], kwargs=kwargs) except: logger.warn("Unable to execute database query.") return {'recordsFiltered': 0, 'recordsTotal': 0, 'draw': 0, 'data': 'null', 'error': 'Unable to execute database query.'} users = query['result'] rows = [] for item in users: if item["media_type"] == 'episode' and item["parent_thumb"]: thumb = item["parent_thumb"] elif item["media_type"] == 'episode': thumb = item["grandparent_thumb"] else: thumb = item["thumb"] if not item['user_thumb'] or item['user_thumb'] == '': user_thumb = common.DEFAULT_USER_THUMB else: user_thumb = item['user_thumb'] # Rename Mystery platform names platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"]) row = {"id": item['id'], "plays": item['plays'], "last_seen": item['last_seen'], "friendly_name": item['friendly_name'], "ip_address": item['ip_address'], "platform": platform, "player": item["player"], "last_watched": item['last_watched'], "thumb": thumb, "media_type": item['media_type'], "rating_key": item['rating_key'], "video_decision": item['video_decision'], "user_thumb": user_thumb, "user": item["user"], "user_id": item['user_id'], "do_notify": helpers.checked(item['do_notify']), "keep_history": helpers.checked(item['keep_history']) } rows.append(row) dict = {'recordsFiltered': query['filteredCount'], 'recordsTotal': query['totalCount'], 'data': rows, 'draw': query['draw'] } return dict
def get_user_details(self, user=None, user_id=None): from plexcs import plextv monitor_db = database.MonitorDatabase() if user: query = 'SELECT user_id, username, friendly_name, email, ' \ 'custom_avatar_url as thumb, is_home_user, is_allow_sync, is_restricted, do_notify ' \ 'FROM users ' \ 'WHERE username = ? ' \ 'UNION ALL ' \ 'SELECT null, user, null, null, null, null, null, null, null ' \ 'FROM session_history ' \ 'WHERE user = ? ' \ 'GROUP BY user ' \ 'LIMIT 1' result = monitor_db.select(query, args=[user, user]) elif user_id: query = 'SELECT user_id, username, friendly_name, email, ' \ 'custom_avatar_url as thumb, is_home_user, is_allow_sync, is_restricted, do_notify ' \ 'FROM users ' \ 'WHERE user_id = ? ' \ 'UNION ALL ' \ 'SELECT user_id, user, null, null, null, null, null, null, null ' \ 'FROM session_history ' \ 'WHERE user_id = ? ' \ 'GROUP BY user ' \ 'LIMIT 1' result = monitor_db.select(query, args=[user_id, user_id]) else: result = None if result: user_details = {} for item in result: if not item['friendly_name']: friendly_name = item['username'] else: friendly_name = item['friendly_name'] if not item['thumb'] or item['thumb'] == '': user_thumb = common.DEFAULT_USER_THUMB else: user_thumb = item['thumb'] user_details = {"user_id": item['user_id'], "username": item['username'], "friendly_name": friendly_name, "email": item['email'], "thumb": user_thumb, "is_home_user": item['is_home_user'], "is_allow_sync": item['is_allow_sync'], "is_restricted": item['is_restricted'], "do_notify": item['do_notify'] } return user_details else: logger.warn(u"Plex:CS :: Unable to retrieve user from local database. Requesting user list refresh.") # Let's first refresh the user list to make sure the user isn't newly added and not in the db yet if user: # Refresh users plextv.refresh_users() query = 'SELECT user_id, username, friendly_name, email, ' \ 'custom_avatar_url as thumb, is_home_user, is_allow_sync, is_restricted, do_notify ' \ 'FROM users ' \ 'WHERE username = ? ' \ 'UNION ALL ' \ 'SELECT null, user, null, null, null, null, null, null, null ' \ 'FROM session_history ' \ 'WHERE user = ? ' \ 'GROUP BY user ' \ 'LIMIT 1' result = monitor_db.select(query, args=[user, user]) elif user_id: # Refresh users plextv.refresh_users() query = 'SELECT user_id, username, friendly_name, email, ' \ 'custom_avatar_url as thumb, is_home_user, is_allow_sync, is_restricted, do_notify ' \ 'FROM users ' \ 'WHERE user_id = ? ' \ 'UNION ALL ' \ 'SELECT user_id, user, null, null, null, null, null, null, null ' \ 'FROM session_history ' \ 'WHERE user_id = ? ' \ 'GROUP BY user ' \ 'LIMIT 1' result = monitor_db.select(query, args=[user_id, user_id]) else: result = None if result: user_details = {} for item in result: if not item['friendly_name']: friendly_name = item['username'] else: friendly_name = item['friendly_name'] if not item['thumb'] or item['thumb'] == '': user_thumb = common.DEFAULT_USER_THUMB else: user_thumb = item['thumb'] user_details = {"user_id": item['user_id'], "username": item['username'], "friendly_name": friendly_name, "email": item['email'], "thumb": user_thumb, "is_home_user": item['is_home_user'], "is_allow_sync": item['is_allow_sync'], "is_restricted": item['is_restricted'], "do_notify": item['do_notify'] } return user_details else: # If there is no user data we must return something # Use "Local" user to retain compatibility with PlexWatch database value return {"user_id": None, "username": '******', "friendly_name": 'Local', "email": '', "thumb": '', "is_home_user": 0, "is_allow_sync": 0, "is_restricted": 0, "do_notify": 0 }
def extract_plexwatch_xml(xml=None): output = {} clean_xml = helpers.latinToAscii(xml) try: xml_parse = minidom.parseString(clean_xml) except: logger.warn("Error parsing XML for Plexwatch database.") return None xml_head = xml_parse.getElementsByTagName('opt') if not xml_head: logger.warn("Error parsing XML for Plexwatch database.") return None for a in xml_head: added_at = helpers.get_xml_attr(a, 'addedAt') art = helpers.get_xml_attr(a, 'art') duration = helpers.get_xml_attr(a, 'duration') grandparent_thumb = helpers.get_xml_attr(a, 'grandparentThumb') grandparent_title = helpers.get_xml_attr(a, 'grandparentTitle') guid = helpers.get_xml_attr(a, 'guid') media_index = helpers.get_xml_attr(a, 'index') originally_available_at = helpers.get_xml_attr(a, 'originallyAvailableAt') last_viewed_at = helpers.get_xml_attr(a, 'lastViewedAt') parent_media_index = helpers.get_xml_attr(a, 'parentIndex') parent_thumb = helpers.get_xml_attr(a, 'parentThumb') rating = helpers.get_xml_attr(a, 'rating') thumb = helpers.get_xml_attr(a, 'thumb') media_type = helpers.get_xml_attr(a, 'type') updated_at = helpers.get_xml_attr(a, 'updatedAt') view_offset = helpers.get_xml_attr(a, 'viewOffset') year = helpers.get_xml_attr(a, 'year') parent_title = helpers.get_xml_attr(a, 'parentTitle') studio = helpers.get_xml_attr(a, 'studio') title = helpers.get_xml_attr(a, 'title') tagline = helpers.get_xml_attr(a, 'tagline') directors = [] if a.getElementsByTagName('Director'): director_elem = a.getElementsByTagName('Director') for b in director_elem: directors.append(helpers.get_xml_attr(b, 'tag')) aspect_ratio = '' audio_channels = None audio_codec = '' bitrate = None container = '' height = None video_codec = '' video_framerate = '' video_resolution = '' width = None if a.getElementsByTagName('Media'): media_elem = a.getElementsByTagName('Media') for c in media_elem: aspect_ratio = helpers.get_xml_attr(c, 'aspectRatio') audio_channels = helpers.get_xml_attr(c, 'audioChannels') audio_codec = helpers.get_xml_attr(c, 'audioCodec') bitrate = helpers.get_xml_attr(c, 'bitrate') container = helpers.get_xml_attr(c, 'container') height = helpers.get_xml_attr(c, 'height') video_codec = helpers.get_xml_attr(c, 'videoCodec') video_framerate = helpers.get_xml_attr(c, 'videoFrameRate') video_resolution = helpers.get_xml_attr(c, 'videoResolution') width = helpers.get_xml_attr(c, 'width') machine_id = '' platform = '' player = '' if a.getElementsByTagName('Player'): player_elem = a.getElementsByTagName('Player') for d in player_elem: machine_id = helpers.get_xml_attr(d, 'machineIdentifier') platform = helpers.get_xml_attr(d, 'platform') player = helpers.get_xml_attr(d, 'title') transcode_audio_channels = None transcode_audio_codec = '' audio_decision = 'direct play' transcode_container = '' transcode_height = None transcode_protocol = '' transcode_video_codec = '' video_decision = 'direct play' transcode_width = None if a.getElementsByTagName('TranscodeSession'): transcode_elem = a.getElementsByTagName('TranscodeSession') for e in transcode_elem: transcode_audio_channels = helpers.get_xml_attr(e, 'audioChannels') transcode_audio_codec = helpers.get_xml_attr(e, 'audioCodec') audio_decision = helpers.get_xml_attr(e, 'audioDecision') transcode_container = helpers.get_xml_attr(e, 'container') transcode_height = helpers.get_xml_attr(e, 'height') transcode_protocol = helpers.get_xml_attr(e, 'protocol') transcode_video_codec = helpers.get_xml_attr(e, 'videoCodec') video_decision = helpers.get_xml_attr(e, 'videoDecision') transcode_width = helpers.get_xml_attr(e, 'width') user_id = None if a.getElementsByTagName('User'): user_elem = a.getElementsByTagName('User') for f in user_elem: user_id = helpers.get_xml_attr(f, 'id') writers = [] if a.getElementsByTagName('Writer'): writer_elem = a.getElementsByTagName('Writer') for g in writer_elem: writers.append(helpers.get_xml_attr(g, 'tag')) actors = [] if a.getElementsByTagName('Role'): actor_elem = a.getElementsByTagName('Role') for h in actor_elem: actors.append(helpers.get_xml_attr(h, 'tag')) genres = [] if a.getElementsByTagName('Genre'): genre_elem = a.getElementsByTagName('Genre') for i in genre_elem: genres.append(helpers.get_xml_attr(i, 'tag')) output = {'added_at': added_at, 'art': art, 'duration': duration, 'grandparent_thumb': grandparent_thumb, 'grandparent_title': grandparent_title, 'parent_title': parent_title, 'title': title, 'tagline': tagline, 'guid': guid, 'media_index': media_index, 'originally_available_at': originally_available_at, 'last_viewed_at': last_viewed_at, 'parent_media_index': parent_media_index, 'parent_thumb': parent_thumb, 'rating': rating, 'thumb': thumb, 'media_type': media_type, 'updated_at': updated_at, 'view_offset': view_offset, 'year': year, 'directors': directors, 'aspect_ratio': aspect_ratio, 'audio_channels': audio_channels, 'audio_codec': audio_codec, 'bitrate': bitrate, 'container': container, 'height': height, 'video_codec': video_codec, 'video_framerate': video_framerate, 'video_resolution': video_resolution, 'width': width, 'machine_id': machine_id, 'platform': platform, 'player': player, 'transcode_audio_channels': transcode_audio_channels, 'transcode_audio_codec': transcode_audio_codec, 'audio_decision': audio_decision, 'transcode_container': transcode_container, 'transcode_height': transcode_height, 'transcode_protocol': transcode_protocol, 'transcode_video_codec': transcode_video_codec, 'video_decision': video_decision, 'transcode_width': transcode_width, 'user_id': user_id, 'writers': writers, 'actors': actors, 'genres': genres, 'studio': studio } return output
def get_user_unique_ips(self, kwargs=None, custom_where=None): data_tables = datatables.DataTables() # Change custom_where column name due to ambiguous column name after JOIN custom_where[0][0] = 'custom_user_id' if custom_where[0][ 0] == 'user_id' else custom_where[0][0] columns = [ 'session_history.id', 'session_history.started as last_seen', 'session_history.ip_address as ip_address', 'COUNT(session_history.id) as play_count', 'session_history.platform as platform', 'session_history.player as player', 'session_history_metadata.full_title as last_watched', 'session_history_metadata.thumb', 'session_history_metadata.parent_thumb', 'session_history_metadata.grandparent_thumb', 'session_history_metadata.media_type', 'session_history.rating_key as rating_key', 'session_history_media_info.video_decision', 'session_history.user as user', 'session_history.user_id as custom_user_id', '(case when users.friendly_name is null then users.username else \ users.friendly_name end) as friendly_name' ] try: query = data_tables.ssp_query( table_name='session_history', columns=columns, custom_where=custom_where, group_by=['ip_address'], join_types=['JOIN', 'JOIN', 'JOIN'], join_tables=[ 'users', 'session_history_metadata', 'session_history_media_info' ], join_evals=[ ['session_history.user_id', 'users.user_id'], ['session_history.id', 'session_history_metadata.id'], ['session_history.id', 'session_history_media_info.id'] ], kwargs=kwargs) except: logger.warn("Unable to execute database query.") return { 'recordsFiltered': 0, 'recordsTotal': 0, 'draw': 0, 'data': 'null', 'error': 'Unable to execute database query.' } results = query['result'] rows = [] for item in results: if item["media_type"] == 'episode' and item["parent_thumb"]: thumb = item["parent_thumb"] elif item["media_type"] == 'episode': thumb = item["grandparent_thumb"] else: thumb = item["thumb"] # Rename Mystery platform names platform = common.PLATFORM_NAME_OVERRIDES.get( item["platform"], item["platform"]) row = { "id": item['id'], "last_seen": item['last_seen'], "ip_address": item['ip_address'], "play_count": item['play_count'], "platform": platform, "player": item['player'], "last_watched": item['last_watched'], "thumb": thumb, "media_type": item['media_type'], "rating_key": item['rating_key'], "video_decision": item['video_decision'], "friendly_name": item['friendly_name'] } rows.append(row) dict = { 'recordsFiltered': query['filteredCount'], 'recordsTotal': query['totalCount'], 'data': rows, 'draw': query['draw'] } return dict
def get_user_unique_ips(self, kwargs=None, custom_where=None): data_tables = datatables.DataTables() # Change custom_where column name due to ambiguous column name after JOIN custom_where[0][0] = 'custom_user_id' if custom_where[0][0] == 'user_id' else custom_where[0][0] columns = ['session_history.id', 'session_history.started as last_seen', 'session_history.ip_address as ip_address', 'COUNT(session_history.id) as play_count', 'session_history.platform as platform', 'session_history.player as player', 'session_history_metadata.full_title as last_watched', 'session_history_metadata.thumb', 'session_history_metadata.parent_thumb', 'session_history_metadata.grandparent_thumb', 'session_history_metadata.media_type', 'session_history.rating_key as rating_key', 'session_history_media_info.video_decision', 'session_history.user as user', 'session_history.user_id as custom_user_id', '(case when users.friendly_name is null then users.username else \ users.friendly_name end) as friendly_name' ] try: query = data_tables.ssp_query(table_name='session_history', columns=columns, custom_where=custom_where, group_by=['ip_address'], join_types=['JOIN', 'JOIN', 'JOIN'], join_tables=['users', 'session_history_metadata', 'session_history_media_info'], join_evals=[['session_history.user_id', 'users.user_id'], ['session_history.id', 'session_history_metadata.id'], ['session_history.id', 'session_history_media_info.id']], kwargs=kwargs) except: logger.warn("Unable to execute database query.") return {'recordsFiltered': 0, 'recordsTotal': 0, 'draw': 0, 'data': 'null', 'error': 'Unable to execute database query.'} results = query['result'] rows = [] for item in results: if item["media_type"] == 'episode' and item["parent_thumb"]: thumb = item["parent_thumb"] elif item["media_type"] == 'episode': thumb = item["grandparent_thumb"] else: thumb = item["thumb"] # Rename Mystery platform names platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"]) row = {"id": item['id'], "last_seen": item['last_seen'], "ip_address": item['ip_address'], "play_count": item['play_count'], "platform": platform, "player": item['player'], "last_watched": item['last_watched'], "thumb": thumb, "media_type": item['media_type'], "rating_key": item['rating_key'], "video_decision": item['video_decision'], "friendly_name": item['friendly_name'] } rows.append(row) dict = {'recordsFiltered': query['filteredCount'], 'recordsTotal': query['totalCount'], 'data': rows, 'draw': query['draw'] } return dict
def get_user_details(self, user=None, user_id=None): from plexcs import plextv monitor_db = database.MonitorDatabase() if user: query = 'SELECT user_id, username, friendly_name, email, ' \ 'custom_avatar_url as thumb, is_home_user, is_allow_sync, is_restricted, do_notify ' \ 'FROM users ' \ 'WHERE username = ? ' \ 'UNION ALL ' \ 'SELECT null, user, null, null, null, null, null, null, null ' \ 'FROM session_history ' \ 'WHERE user = ? ' \ 'GROUP BY user ' \ 'LIMIT 1' result = monitor_db.select(query, args=[user, user]) elif user_id: query = 'SELECT user_id, username, friendly_name, email, ' \ 'custom_avatar_url as thumb, is_home_user, is_allow_sync, is_restricted, do_notify ' \ 'FROM users ' \ 'WHERE user_id = ? ' \ 'UNION ALL ' \ 'SELECT user_id, user, null, null, null, null, null, null, null ' \ 'FROM session_history ' \ 'WHERE user_id = ? ' \ 'GROUP BY user ' \ 'LIMIT 1' result = monitor_db.select(query, args=[user_id, user_id]) else: result = None if result: user_details = {} for item in result: if not item['friendly_name']: friendly_name = item['username'] else: friendly_name = item['friendly_name'] if not item['thumb'] or item['thumb'] == '': user_thumb = common.DEFAULT_USER_THUMB else: user_thumb = item['thumb'] user_details = { "user_id": item['user_id'], "username": item['username'], "friendly_name": friendly_name, "email": item['email'], "thumb": user_thumb, "is_home_user": item['is_home_user'], "is_allow_sync": item['is_allow_sync'], "is_restricted": item['is_restricted'], "do_notify": item['do_notify'] } return user_details else: logger.warn( u"Plex:CS :: Unable to retrieve user from local database. Requesting user list refresh." ) # Let's first refresh the user list to make sure the user isn't newly added and not in the db yet if user: # Refresh users plextv.refresh_users() query = 'SELECT user_id, username, friendly_name, email, ' \ 'custom_avatar_url as thumb, is_home_user, is_allow_sync, is_restricted, do_notify ' \ 'FROM users ' \ 'WHERE username = ? ' \ 'UNION ALL ' \ 'SELECT null, user, null, null, null, null, null, null, null ' \ 'FROM session_history ' \ 'WHERE user = ? ' \ 'GROUP BY user ' \ 'LIMIT 1' result = monitor_db.select(query, args=[user, user]) elif user_id: # Refresh users plextv.refresh_users() query = 'SELECT user_id, username, friendly_name, email, ' \ 'custom_avatar_url as thumb, is_home_user, is_allow_sync, is_restricted, do_notify ' \ 'FROM users ' \ 'WHERE user_id = ? ' \ 'UNION ALL ' \ 'SELECT user_id, user, null, null, null, null, null, null, null ' \ 'FROM session_history ' \ 'WHERE user_id = ? ' \ 'GROUP BY user ' \ 'LIMIT 1' result = monitor_db.select(query, args=[user_id, user_id]) else: result = None if result: user_details = {} for item in result: if not item['friendly_name']: friendly_name = item['username'] else: friendly_name = item['friendly_name'] if not item['thumb'] or item['thumb'] == '': user_thumb = common.DEFAULT_USER_THUMB else: user_thumb = item['thumb'] user_details = { "user_id": item['user_id'], "username": item['username'], "friendly_name": friendly_name, "email": item['email'], "thumb": user_thumb, "is_home_user": item['is_home_user'], "is_allow_sync": item['is_allow_sync'], "is_restricted": item['is_restricted'], "do_notify": item['do_notify'] } return user_details else: # If there is no user data we must return something # Use "Local" user to retain compatibility with PlexWatch database value return { "user_id": None, "username": '******', "friendly_name": 'Local', "email": '', "thumb": '', "is_home_user": 0, "is_allow_sync": 0, "is_restricted": 0, "do_notify": 0 }
def get_total_plays_per_stream_type(self, time_range='30', y_axis='plays'): monitor_db = database.MonitorDatabase() if not time_range.isdigit(): time_range = '30' try: if y_axis == 'plays': query = 'SELECT date(session_history.started, "unixepoch", "localtime") as date_played, ' \ 'SUM(case when session_history_media_info.video_decision = "direct play" ' \ 'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "direct play") ' \ 'then 1 else 0 end) as dp_count, ' \ 'SUM(case when session_history_media_info.video_decision = "copy" ' \ 'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "copy") ' \ 'then 1 else 0 end) as ds_count, ' \ 'SUM(case when session_history_media_info.video_decision = "transcode" ' \ 'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "transcode") ' \ 'then 1 else 0 end) as tc_count ' \ 'FROM session_history ' \ 'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \ 'WHERE (datetime(session_history.stopped, "unixepoch", "localtime") >= ' \ 'datetime("now", "-%s days", "localtime")) AND ' \ '(session_history.media_type = "episode" OR session_history.media_type = "movie" OR session_history.media_type = "track") ' \ 'GROUP BY date_played ' \ 'ORDER BY started ASC' % time_range result = monitor_db.select(query) else: query = 'SELECT date(session_history.started, "unixepoch", "localtime") as date_played, ' \ 'SUM(case when (session_history_media_info.video_decision = "direct play" ' \ 'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "direct play")) ' \ 'and session_history.stopped > 0 then (session_history.stopped - session_history.started) ' \ ' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as dp_count, ' \ 'SUM(case when (session_history_media_info.video_decision = "copy" ' \ 'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "copy")) ' \ 'and session_history.stopped > 0 then (session_history.stopped - session_history.started) ' \ ' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as ds_count, ' \ 'SUM(case when (session_history_media_info.video_decision = "transcode" ' \ 'or (session_history_media_info.video_decision = "" and session_history_media_info.audio_decision = "transcode")) ' \ 'and session_history.stopped > 0 then (session_history.stopped - session_history.started) ' \ ' - (case when paused_counter is NULL then 0 else paused_counter end) else 0 end) as tc_count ' \ 'FROM session_history ' \ 'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \ 'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \ 'datetime("now", "-%s days", "localtime") AND ' \ '(session_history.media_type = "episode" OR session_history.media_type = "movie" OR session_history.media_type = "track") ' \ 'GROUP BY date_played ' \ 'ORDER BY started ASC' % time_range result = monitor_db.select(query) except: logger.warn("Unable to execute database query.") return None # create our date range as some days may not have any data # but we still want to display them base = datetime.date.today() date_list = [base - datetime.timedelta(days=x) for x in range(0, int(time_range))] categories = [] series_1 = [] series_2 = [] series_3 = [] for date_item in sorted(date_list): date_string = date_item.strftime('%Y-%m-%d') categories.append(date_string) series_1_value = 0 series_2_value = 0 series_3_value = 0 for item in result: if date_string == item['date_played']: series_1_value = item['dp_count'] series_2_value = item['ds_count'] series_3_value = item['tc_count'] break else: series_1_value = 0 series_2_value = 0 series_3_value = 0 series_1.append(series_1_value) series_2.append(series_2_value) series_3.append(series_3_value) series_1_output = {'name': 'Direct Play', 'data': series_1} series_2_output = {'name': 'Direct Stream', 'data': series_2} series_3_output = {'name': 'Transcode', 'data': series_3} output = {'categories': categories, 'series': [series_1_output, series_2_output, series_3_output]} return output
def get_synced_items(self, machine_id=None, user_id=None): sync_list = self.get_plextv_sync_lists(machine_id) user_data = users.Users() synced_items = [] try: xml_parse = minidom.parseString(sync_list) except Exception as e: logger.warn("Error parsing XML for Plex sync lists: %s" % e) return [] except: logger.warn("Error parsing XML for Plex sync lists.") return [] xml_head = xml_parse.getElementsByTagName('SyncList') if not xml_head: logger.warn("Error parsing XML for Plex sync lists.") else: for a in xml_head: client_id = helpers.get_xml_attr(a, 'id') sync_device = a.getElementsByTagName('Device') for device in sync_device: device_user_id = helpers.get_xml_attr(device, 'userID') try: device_username = user_data.get_user_details(user_id=device_user_id)['username'] device_friendly_name = user_data.get_user_details(user_id=device_user_id)['friendly_name'] except: device_username = '' device_friendly_name = '' device_name = helpers.get_xml_attr(device, 'name') device_product = helpers.get_xml_attr(device, 'product') device_product_version = helpers.get_xml_attr(device, 'productVersion') device_platform = helpers.get_xml_attr(device, 'platform') device_platform_version = helpers.get_xml_attr(device, 'platformVersion') device_type = helpers.get_xml_attr(device, 'device') device_model = helpers.get_xml_attr(device, 'model') device_last_seen = helpers.get_xml_attr(device, 'lastSeenAt') # Filter by user_id if user_id and user_id != device_user_id: continue for synced in a.getElementsByTagName('SyncItems'): sync_item = synced.getElementsByTagName('SyncItem') for item in sync_item: sync_id = helpers.get_xml_attr(item, 'id') sync_version = helpers.get_xml_attr(item, 'version') sync_root_title = helpers.get_xml_attr(item, 'rootTitle') sync_title = helpers.get_xml_attr(item, 'title') sync_metadata_type = helpers.get_xml_attr(item, 'metadataType') sync_content_type = helpers.get_xml_attr(item, 'contentType') for status in item.getElementsByTagName('Status'): status_failure_code = helpers.get_xml_attr(status, 'failureCode') status_failure = helpers.get_xml_attr(status, 'failure') status_state = helpers.get_xml_attr(status, 'state') status_item_count = helpers.get_xml_attr(status, 'itemsCount') status_item_complete_count = helpers.get_xml_attr(status, 'itemsCompleteCount') status_item_downloaded_count = helpers.get_xml_attr(status, 'itemsDownloadedCount') status_item_ready_count = helpers.get_xml_attr(status, 'itemsReadyCount') status_item_successful_count = helpers.get_xml_attr(status, 'itemsSuccessfulCount') status_total_size = helpers.get_xml_attr(status, 'totalSize') status_item_download_percent_complete = helpers.get_percent( status_item_downloaded_count, status_item_count) for settings in item.getElementsByTagName('MediaSettings'): settings_audio_boost = helpers.get_xml_attr(settings, 'audioBoost') settings_music_bitrate = helpers.get_xml_attr(settings, 'musicBitrate') settings_photo_quality = helpers.get_xml_attr(settings, 'photoQuality') settings_photo_resolution = helpers.get_xml_attr(settings, 'photoResolution') settings_video_quality = helpers.get_xml_attr(settings, 'videoQuality') settings_video_resolution = helpers.get_xml_attr(settings, 'videoResolution') if helpers.get_xml_attr(item.getElementsByTagName('Location')[0], 'uri').endswith('%2Fchildren'): clean_uri = helpers.get_xml_attr(item.getElementsByTagName('Location')[0], 'uri')[:-11] else: clean_uri = helpers.get_xml_attr(item.getElementsByTagName('Location')[0], 'uri') rating_key = clean_uri.rpartition('%2F')[-1] sync_details = {"device_name": helpers.sanitize(device_name), "platform": helpers.sanitize(device_platform), "username": helpers.sanitize(device_username), "friendly_name": helpers.sanitize(device_friendly_name), "user_id": device_user_id, "root_title": helpers.sanitize(sync_root_title), "title": helpers.sanitize(sync_title), "metadata_type": sync_metadata_type, "content_type": sync_content_type, "rating_key": rating_key, "state": status_state, "item_count": status_item_count, "item_complete_count": status_item_complete_count, "item_downloaded_count": status_item_downloaded_count, "item_downloaded_percent_complete": status_item_download_percent_complete, "music_bitrate": settings_music_bitrate, "photo_quality": settings_photo_quality, "video_quality": settings_video_quality, "total_size": status_total_size, "failure": status_failure, "sync_id": sync_id } synced_items.append(sync_details) return synced_items
def get_full_users_list(self): friends_list = self.get_plextv_friends() own_account = self.get_plextv_user_details() users_list = [] try: xml_parse = minidom.parseString(own_account) except Exception as e: logger.warn("Error parsing XML for Plex account details: %s" % e) return [] except: logger.warn("Error parsing XML for Plex account details.") return [] xml_head = xml_parse.getElementsByTagName('user') if not xml_head: logger.warn("Error parsing XML for Plex account details.") else: for a in xml_head: own_details = {"user_id": helpers.get_xml_attr(a, 'id'), "username": helpers.get_xml_attr(a, 'username'), "thumb": helpers.get_xml_attr(a, 'thumb'), "email": helpers.get_xml_attr(a, 'email'), "is_home_user": helpers.get_xml_attr(a, 'home'), "is_allow_sync": None, "is_restricted": helpers.get_xml_attr(a, 'restricted') } users_list.append(own_details) try: xml_parse = minidom.parseString(friends_list) except Exception as e: logger.warn("Error parsing XML for Plex friends list: %s" % e) except: logger.warn("Error parsing XML for Plex friends list.") xml_head = xml_parse.getElementsByTagName('User') if not xml_head: logger.warn("Error parsing XML for Plex friends list.") else: for a in xml_head: friend = {"user_id": helpers.get_xml_attr(a, 'id'), "username": helpers.get_xml_attr(a, 'title'), "thumb": helpers.get_xml_attr(a, 'thumb'), "email": helpers.get_xml_attr(a, 'email'), "is_home_user": helpers.get_xml_attr(a, 'home'), "is_allow_sync": helpers.get_xml_attr(a, 'allowSync'), "is_restricted": helpers.get_xml_attr(a, 'restricted') } users_list.append(friend) return users_list
def check_active_sessions(ws_request=False): with monitor_lock: pms_connect = pmsconnect.PmsConnect() session_list = pms_connect.get_current_activity() monitor_db = database.MonitorDatabase() monitor_process = activity_processor.ActivityProcessor() # logger.debug(u"Plex:CS Monitor :: Checking for active streams.") global int_ping_count if session_list: int_ping_count = 0 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, last_paused ' '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 session['state'] == 'playing' and stream['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='resume')).start() if stream['state'] == 'paused' and not ws_request: # 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. If it's a websocket request don't use this method. paused_counter = int(stream['paused_counter']) + plexcs.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']]) if session['state'] == 'buffering' and plexcs.CONFIG.BUFFER_THRESHOLD > 0: # The stream is buffering so we need to increment the buffer_count # We're going just increment on every monitor ping, # would be difficult to keep track otherwise monitor_db.action('UPDATE sessions SET buffer_count = buffer_count + 1 ' 'WHERE session_key = ? AND rating_key = ?', [stream['session_key'], stream['rating_key']]) # Check the current buffer count and last buffer to determine if we should notify buffer_values = monitor_db.select('SELECT buffer_count, buffer_last_triggered ' 'FROM sessions ' 'WHERE session_key = ? AND rating_key = ?', [stream['session_key'], stream['rating_key']]) if buffer_values[0]['buffer_count'] >= plexcs.CONFIG.BUFFER_THRESHOLD: # Push any notifications - # Push it on it's own thread so we don't hold up our db actions # Our first buffer notification if buffer_values[0]['buffer_count'] == plexcs.CONFIG.BUFFER_THRESHOLD: logger.info(u"Plex:CS Monitor :: User '%s' has triggered a buffer warning." % stream['user']) # Set the buffer trigger time monitor_db.action('UPDATE sessions ' 'SET buffer_last_triggered = strftime("%s","now") ' 'WHERE session_key = ? AND rating_key = ?', [stream['session_key'], stream['rating_key']]) threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='buffer')).start() else: # Subsequent buffer notifications after wait time if int(time.time()) > buffer_values[0]['buffer_last_triggered'] + \ plexcs.CONFIG.BUFFER_WAIT: logger.info(u"Plex:CS Monitor :: User '%s' has triggered multiple buffer warnings." % stream['user']) # Set the buffer trigger time monitor_db.action('UPDATE sessions ' 'SET buffer_last_triggered = strftime("%s","now") ' 'WHERE session_key = ? AND rating_key = ?', [stream['session_key'], stream['rating_key']]) threading.Thread(target=notification_handler.notify, kwargs=dict(stream_data=stream, notify_action='buffer')).start() logger.debug(u"Plex:CS Monitor :: Stream buffering. Count is now %s. Last triggered %s." % (buffer_values[0]['buffer_count'], buffer_values[0]['buffer_last_triggered'])) # 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['view_offset'] and session['duration'] and session['state'] != 'buffering': if helpers.get_percent(session['view_offset'], session['duration']) > plexcs.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"Plex:CS 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']) > plexcs.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"Plex:CS Monitor :: Unable to read session list.") int_ping_count += 1 logger.warn(u"Plex:CS Monitor :: Unable to get an internal response from the server, ping attempt %s." \ % str(int_ping_count)) if int_ping_count == 3: # Fire off notifications threading.Thread(target=notification_handler.notify_timeline, kwargs=dict(notify_action='intdown')).start()
def extract_plexwatch_xml(xml=None): output = {} clean_xml = helpers.latinToAscii(xml) try: xml_parse = minidom.parseString(clean_xml) except: logger.warn("Error parsing XML for Plexwatch database.") return None xml_head = xml_parse.getElementsByTagName('opt') if not xml_head: logger.warn("Error parsing XML for Plexwatch database.") return None for a in xml_head: added_at = helpers.get_xml_attr(a, 'addedAt') art = helpers.get_xml_attr(a, 'art') duration = helpers.get_xml_attr(a, 'duration') grandparent_thumb = helpers.get_xml_attr(a, 'grandparentThumb') grandparent_title = helpers.get_xml_attr(a, 'grandparentTitle') guid = helpers.get_xml_attr(a, 'guid') media_index = helpers.get_xml_attr(a, 'index') originally_available_at = helpers.get_xml_attr( a, 'originallyAvailableAt') last_viewed_at = helpers.get_xml_attr(a, 'lastViewedAt') parent_media_index = helpers.get_xml_attr(a, 'parentIndex') parent_thumb = helpers.get_xml_attr(a, 'parentThumb') rating = helpers.get_xml_attr(a, 'rating') thumb = helpers.get_xml_attr(a, 'thumb') media_type = helpers.get_xml_attr(a, 'type') updated_at = helpers.get_xml_attr(a, 'updatedAt') view_offset = helpers.get_xml_attr(a, 'viewOffset') year = helpers.get_xml_attr(a, 'year') parent_title = helpers.get_xml_attr(a, 'parentTitle') studio = helpers.get_xml_attr(a, 'studio') title = helpers.get_xml_attr(a, 'title') tagline = helpers.get_xml_attr(a, 'tagline') directors = [] if a.getElementsByTagName('Director'): director_elem = a.getElementsByTagName('Director') for b in director_elem: directors.append(helpers.get_xml_attr(b, 'tag')) aspect_ratio = '' audio_channels = None audio_codec = '' bitrate = None container = '' height = None video_codec = '' video_framerate = '' video_resolution = '' width = None if a.getElementsByTagName('Media'): media_elem = a.getElementsByTagName('Media') for c in media_elem: aspect_ratio = helpers.get_xml_attr(c, 'aspectRatio') audio_channels = helpers.get_xml_attr(c, 'audioChannels') audio_codec = helpers.get_xml_attr(c, 'audioCodec') bitrate = helpers.get_xml_attr(c, 'bitrate') container = helpers.get_xml_attr(c, 'container') height = helpers.get_xml_attr(c, 'height') video_codec = helpers.get_xml_attr(c, 'videoCodec') video_framerate = helpers.get_xml_attr(c, 'videoFrameRate') video_resolution = helpers.get_xml_attr(c, 'videoResolution') width = helpers.get_xml_attr(c, 'width') machine_id = '' platform = '' player = '' if a.getElementsByTagName('Player'): player_elem = a.getElementsByTagName('Player') for d in player_elem: machine_id = helpers.get_xml_attr(d, 'machineIdentifier') platform = helpers.get_xml_attr(d, 'platform') player = helpers.get_xml_attr(d, 'title') transcode_audio_channels = None transcode_audio_codec = '' audio_decision = 'direct play' transcode_container = '' transcode_height = None transcode_protocol = '' transcode_video_codec = '' video_decision = 'direct play' transcode_width = None if a.getElementsByTagName('TranscodeSession'): transcode_elem = a.getElementsByTagName('TranscodeSession') for e in transcode_elem: transcode_audio_channels = helpers.get_xml_attr( e, 'audioChannels') transcode_audio_codec = helpers.get_xml_attr(e, 'audioCodec') audio_decision = helpers.get_xml_attr(e, 'audioDecision') transcode_container = helpers.get_xml_attr(e, 'container') transcode_height = helpers.get_xml_attr(e, 'height') transcode_protocol = helpers.get_xml_attr(e, 'protocol') transcode_video_codec = helpers.get_xml_attr(e, 'videoCodec') video_decision = helpers.get_xml_attr(e, 'videoDecision') transcode_width = helpers.get_xml_attr(e, 'width') user_id = None if a.getElementsByTagName('User'): user_elem = a.getElementsByTagName('User') for f in user_elem: user_id = helpers.get_xml_attr(f, 'id') writers = [] if a.getElementsByTagName('Writer'): writer_elem = a.getElementsByTagName('Writer') for g in writer_elem: writers.append(helpers.get_xml_attr(g, 'tag')) actors = [] if a.getElementsByTagName('Role'): actor_elem = a.getElementsByTagName('Role') for h in actor_elem: actors.append(helpers.get_xml_attr(h, 'tag')) genres = [] if a.getElementsByTagName('Genre'): genre_elem = a.getElementsByTagName('Genre') for i in genre_elem: genres.append(helpers.get_xml_attr(i, 'tag')) output = { 'added_at': added_at, 'art': art, 'duration': duration, 'grandparent_thumb': grandparent_thumb, 'grandparent_title': grandparent_title, 'parent_title': parent_title, 'title': title, 'tagline': tagline, 'guid': guid, 'media_index': media_index, 'originally_available_at': originally_available_at, 'last_viewed_at': last_viewed_at, 'parent_media_index': parent_media_index, 'parent_thumb': parent_thumb, 'rating': rating, 'thumb': thumb, 'media_type': media_type, 'updated_at': updated_at, 'view_offset': view_offset, 'year': year, 'directors': directors, 'aspect_ratio': aspect_ratio, 'audio_channels': audio_channels, 'audio_codec': audio_codec, 'bitrate': bitrate, 'container': container, 'height': height, 'video_codec': video_codec, 'video_framerate': video_framerate, 'video_resolution': video_resolution, 'width': width, 'machine_id': machine_id, 'platform': platform, 'player': player, 'transcode_audio_channels': transcode_audio_channels, 'transcode_audio_codec': transcode_audio_codec, 'audio_decision': audio_decision, 'transcode_container': transcode_container, 'transcode_height': transcode_height, 'transcode_protocol': transcode_protocol, 'transcode_video_codec': transcode_video_codec, 'video_decision': video_decision, 'transcode_width': transcode_width, 'user_id': user_id, 'writers': writers, 'actors': actors, 'genres': genres, 'studio': studio } return output
def get_user_list(self, kwargs=None): data_tables = datatables.DataTables() custom_where = ['users.deleted_user', 0] columns = [ 'session_history.id', 'users.user_id as user_id', 'users.custom_avatar_url as user_thumb', '(case when users.friendly_name is null then users.username else \ users.friendly_name end) as friendly_name', 'MAX(session_history.started) as last_seen', 'session_history.ip_address as ip_address', 'COUNT(session_history.id) as plays', 'session_history.platform as platform', 'session_history.player as player', 'session_history_metadata.full_title as last_watched', 'session_history_metadata.thumb', 'session_history_metadata.parent_thumb', 'session_history_metadata.grandparent_thumb', 'session_history_metadata.media_type', 'session_history.rating_key as rating_key', 'session_history_media_info.video_decision', 'users.username as user', 'users.do_notify as do_notify', 'users.keep_history as keep_history' ] try: query = data_tables.ssp_query( table_name='users', columns=columns, custom_where=[custom_where], group_by=['users.user_id'], join_types=[ 'LEFT OUTER JOIN', 'LEFT OUTER JOIN', 'LEFT OUTER JOIN' ], join_tables=[ 'session_history', 'session_history_metadata', 'session_history_media_info' ], join_evals=[ ['session_history.user_id', 'users.user_id'], ['session_history.id', 'session_history_metadata.id'], ['session_history.id', 'session_history_media_info.id'] ], kwargs=kwargs) except: logger.warn("Unable to execute database query.") return { 'recordsFiltered': 0, 'recordsTotal': 0, 'draw': 0, 'data': 'null', 'error': 'Unable to execute database query.' } users = query['result'] rows = [] for item in users: if item["media_type"] == 'episode' and item["parent_thumb"]: thumb = item["parent_thumb"] elif item["media_type"] == 'episode': thumb = item["grandparent_thumb"] else: thumb = item["thumb"] if not item['user_thumb'] or item['user_thumb'] == '': user_thumb = common.DEFAULT_USER_THUMB else: user_thumb = item['user_thumb'] # Rename Mystery platform names platform = common.PLATFORM_NAME_OVERRIDES.get( item["platform"], item["platform"]) row = { "id": item['id'], "plays": item['plays'], "last_seen": item['last_seen'], "friendly_name": item['friendly_name'], "ip_address": item['ip_address'], "platform": platform, "player": item["player"], "last_watched": item['last_watched'], "thumb": thumb, "media_type": item['media_type'], "rating_key": item['rating_key'], "video_decision": item['video_decision'], "user_thumb": user_thumb, "user": item["user"], "user_id": item['user_id'], "do_notify": helpers.checked(item['do_notify']), "keep_history": helpers.checked(item['keep_history']) } rows.append(row) dict = { 'recordsFiltered': query['filteredCount'], 'recordsTotal': query['totalCount'], 'data': rows, 'draw': query['draw'] } return dict
def main(): """ Plex:CS application entry point. Parses arguments, setups encoding and initializes the application. """ # Fixed paths to Plex:CS if hasattr(sys, 'frozen'): plexcs.FULL_PATH = os.path.abspath(sys.executable) else: plexcs.FULL_PATH = os.path.abspath(__file__) plexcs.PROG_DIR = os.path.dirname(plexcs.FULL_PATH) plexcs.ARGS = sys.argv[1:] # From sickbeard plexcs.SYS_PLATFORM = sys.platform plexcs.SYS_ENCODING = None try: locale.setlocale(locale.LC_ALL, "") plexcs.SYS_ENCODING = locale.getpreferredencoding() except (locale.Error, IOError): pass # for OSes that are poorly configured I'll just force UTF-8 if not plexcs.SYS_ENCODING or plexcs.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'): plexcs.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 Plex:CS to run on a specified port') 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: plexcs.VERBOSE = True if args.quiet: plexcs.QUIET = True # Do an intial setup of the logger. logger.initLogger(console=not plexcs.QUIET, log_dir=False, verbose=plexcs.VERBOSE) if args.daemon: if sys.platform == 'win32': sys.stderr.write( "Daemonizing not supported under Windows, starting normally\n") else: plexcs.DAEMON = True plexcs.QUIET = True if args.pidfile: plexcs.PIDFILE = str(args.pidfile) # If the pidfile already exists, plexcs may still be running, so # exit if os.path.exists(plexcs.PIDFILE): raise SystemExit("PID file '%s' already exists. Exiting." % plexcs.PIDFILE) # The pidfile is only useful in daemon mode, make sure we can write the # file properly if plexcs.DAEMON: plexcs.CREATEPID = True try: with open(plexcs.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: plexcs.DATA_DIR = args.datadir else: plexcs.DATA_DIR = plexcs.PROG_DIR if args.config: config_file = args.config else: config_file = os.path.join(plexcs.DATA_DIR, 'config.ini') # Try to create the DATA_DIR if it doesn't exist if not os.path.exists(plexcs.DATA_DIR): try: os.makedirs(plexcs.DATA_DIR) except OSError: raise SystemExit('Could not create data directory: ' + plexcs.DATA_DIR + '. Exiting....') # Make sure the DATA_DIR is writeable if not os.access(plexcs.DATA_DIR, os.W_OK): raise SystemExit('Cannot write to the data directory: ' + plexcs.DATA_DIR + '. Exiting...') # Put the database in the DATA_DIR plexcs.DB_FILE = os.path.join(plexcs.DATA_DIR, 'plexcs.db') # Read config and start logging plexcs.initialize(config_file) if plexcs.DAEMON: plexcs.daemonize() # 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(plexcs.CONFIG.HTTP_PORT) # Check if pyOpenSSL is installed. It is required for certificate generation # and for CherryPy. if plexcs.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.") plexcs.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': plexcs.CONFIG.HTTP_HOST, 'http_root': plexcs.CONFIG.HTTP_ROOT, 'http_proxy': plexcs.CONFIG.HTTP_PROXY, 'enable_https': plexcs.CONFIG.ENABLE_HTTPS, 'https_cert': plexcs.CONFIG.HTTPS_CERT, 'https_key': plexcs.CONFIG.HTTPS_KEY, 'http_username': plexcs.CONFIG.HTTP_USERNAME, 'http_password': plexcs.CONFIG.HTTP_PASSWORD, } webstart.initialize(web_config) # Start the background threads plexcs.start() # Open connection for websocket if plexcs.CONFIG.MONITORING_USE_WEBSOCKET: try: web_socket.start_thread() except: logger.warn(u"Websocket :: Unable to open connection.") # Fallback to polling plexcs.POLLING_FAILOVER = True plexcs.initialize_scheduler() # Open webbrowser if plexcs.CONFIG.LAUNCH_BROWSER and not args.nolaunch: plexcs.launch_browser(plexcs.CONFIG.HTTP_HOST, http_port, plexcs.CONFIG.HTTP_ROOT) # Wait endlessy for a signal to happen while True: if not plexcs.SIGNAL: try: time.sleep(1) except KeyboardInterrupt: plexcs.SIGNAL = 'shutdown' else: logger.info('Received signal: %s', plexcs.SIGNAL) if plexcs.SIGNAL == 'shutdown': plexcs.shutdown() elif plexcs.SIGNAL == 'restart': plexcs.shutdown(restart=True) else: plexcs.shutdown(restart=True, update=True) plexcs.SIGNAL = None
def make_request(self, uri=None, proto='HTTP', request_type='GET', headers=None, output_format='raw', return_type=False, no_token=False): valid_request_types = ['GET', 'POST', 'PUT', 'DELETE'] if request_type.upper() not in valid_request_types: logger.debug(u"HTTP request made but unsupported request type given.") return None if uri: if proto.upper() == 'HTTPS': if not self.ssl_verify and hasattr(ssl, '_create_unverified_context'): context = ssl._create_unverified_context() handler = HTTPSConnection(host=self.host, port=self.port, timeout=10, context=context) logger.warn(u"Plex:CS HTTP Handler :: Unverified HTTPS request made. This connection is not secure.") else: handler = HTTPSConnection(host=self.host, port=self.port, timeout=10) else: handler = HTTPConnection(host=self.host, port=self.port, timeout=10) token_string = '' if not no_token: if uri.find('?') > 0: token_string = '&X-Plex-Token=' + self.token else: token_string = '?X-Plex-Token=' + self.token try: if headers: handler.request(request_type, uri + token_string, headers=headers) else: handler.request(request_type, uri + token_string) response = handler.getresponse() request_status = response.status request_content = response.read() content_type = response.getheader('content-type') except IOError as e: logger.warn(u"Failed to access uri endpoint %s with error %s" % (uri, e)) return None except Exception as e: logger.warn(u"Failed to access uri endpoint %s. Is your server maybe accepting SSL connections only? %s" % (uri, e)) return None except: logger.warn(u"Failed to access uri endpoint %s with Uncaught exception." % uri) return None if request_status == 200: try: if output_format == 'dict': output = helpers.convert_xml_to_dict(request_content) elif output_format == 'json': output = helpers.convert_xml_to_json(request_content) elif output_format == 'xml': output = helpers.parse_xml(request_content) else: output = request_content if return_type: return output, content_type return output except Exception as e: logger.warn(u"Failed format response from uri %s to %s error %s" % (uri, output_format, e)) return None else: logger.warn(u"Failed to access uri endpoint %s. Status code %r" % (uri, request_status)) return None else: logger.debug(u"HTTP request made but no enpoint given.") return None
def main(): """ Plex:CS application entry point. Parses arguments, setups encoding and initializes the application. """ # Fixed paths to Plex:CS if hasattr(sys, 'frozen'): plexcs.FULL_PATH = os.path.abspath(sys.executable) else: plexcs.FULL_PATH = os.path.abspath(__file__) plexcs.PROG_DIR = os.path.dirname(plexcs.FULL_PATH) plexcs.ARGS = sys.argv[1:] # From sickbeard plexcs.SYS_PLATFORM = sys.platform plexcs.SYS_ENCODING = None try: locale.setlocale(locale.LC_ALL, "") plexcs.SYS_ENCODING = locale.getpreferredencoding() except (locale.Error, IOError): pass # for OSes that are poorly configured I'll just force UTF-8 if not plexcs.SYS_ENCODING or plexcs.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'): plexcs.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 Plex:CS to run on a specified port') 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: plexcs.VERBOSE = True if args.quiet: plexcs.QUIET = True # Do an intial setup of the logger. logger.initLogger(console=not plexcs.QUIET, log_dir=False, verbose=plexcs.VERBOSE) if args.daemon: if sys.platform == 'win32': sys.stderr.write( "Daemonizing not supported under Windows, starting normally\n") else: plexcs.DAEMON = True plexcs.QUIET = True if args.pidfile: plexcs.PIDFILE = str(args.pidfile) # If the pidfile already exists, plexcs may still be running, so # exit if os.path.exists(plexcs.PIDFILE): raise SystemExit("PID file '%s' already exists. Exiting." % plexcs.PIDFILE) # The pidfile is only useful in daemon mode, make sure we can write the # file properly if plexcs.DAEMON: plexcs.CREATEPID = True try: with open(plexcs.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: plexcs.DATA_DIR = args.datadir else: plexcs.DATA_DIR = plexcs.PROG_DIR if args.config: config_file = args.config else: config_file = os.path.join(plexcs.DATA_DIR, 'config.ini') # Try to create the DATA_DIR if it doesn't exist if not os.path.exists(plexcs.DATA_DIR): try: os.makedirs(plexcs.DATA_DIR) except OSError: raise SystemExit( 'Could not create data directory: ' + plexcs.DATA_DIR + '. Exiting....') # Make sure the DATA_DIR is writeable if not os.access(plexcs.DATA_DIR, os.W_OK): raise SystemExit( 'Cannot write to the data directory: ' + plexcs.DATA_DIR + '. Exiting...') # Put the database in the DATA_DIR plexcs.DB_FILE = os.path.join(plexcs.DATA_DIR, 'plexcs.db') # Read config and start logging plexcs.initialize(config_file) if plexcs.DAEMON: plexcs.daemonize() # 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(plexcs.CONFIG.HTTP_PORT) # Check if pyOpenSSL is installed. It is required for certificate generation # and for CherryPy. if plexcs.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.") plexcs.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': plexcs.CONFIG.HTTP_HOST, 'http_root': plexcs.CONFIG.HTTP_ROOT, 'http_proxy': plexcs.CONFIG.HTTP_PROXY, 'enable_https': plexcs.CONFIG.ENABLE_HTTPS, 'https_cert': plexcs.CONFIG.HTTPS_CERT, 'https_key': plexcs.CONFIG.HTTPS_KEY, 'http_username': plexcs.CONFIG.HTTP_USERNAME, 'http_password': plexcs.CONFIG.HTTP_PASSWORD, } webstart.initialize(web_config) # Start the background threads plexcs.start() # Open connection for websocket if plexcs.CONFIG.MONITORING_USE_WEBSOCKET: try: web_socket.start_thread() except: logger.warn(u"Websocket :: Unable to open connection.") # Fallback to polling plexcs.POLLING_FAILOVER = True plexcs.initialize_scheduler() # Open webbrowser if plexcs.CONFIG.LAUNCH_BROWSER and not args.nolaunch: plexcs.launch_browser(plexcs.CONFIG.HTTP_HOST, http_port, plexcs.CONFIG.HTTP_ROOT) # Wait endlessy for a signal to happen while True: if not plexcs.SIGNAL: try: time.sleep(1) except KeyboardInterrupt: plexcs.SIGNAL = 'shutdown' else: logger.info('Received signal: %s', plexcs.SIGNAL) if plexcs.SIGNAL == 'shutdown': plexcs.shutdown() elif plexcs.SIGNAL == 'restart': plexcs.shutdown(restart=True) else: plexcs.shutdown(restart=True, update=True) plexcs.SIGNAL = None
def initialize(options): # HTTPS stuff stolen from sickbeard enable_https = options['enable_https'] https_cert = options['https_cert'] https_key = options['https_key'] if enable_https: # If either the HTTPS certificate or key do not exist, try to make # self-signed ones. if not (https_cert and os.path.exists(https_cert)) or not (https_key and os.path.exists(https_key)): if not create_https_certificates(https_cert, https_key): logger.warn("Unable to create certificate and key. Disabling " \ "HTTPS") enable_https = False if not (os.path.exists(https_cert) and os.path.exists(https_key)): logger.warn("Disabled HTTPS because of missing certificate and " \ "key.") enable_https = False options_dict = { 'server.socket_port': options['http_port'], 'server.socket_host': options['http_host'], 'server.thread_pool': 10, 'tools.encode.on': True, 'tools.encode.encoding': 'utf-8', 'tools.decode.on': True, 'log.screen': False, 'engine.autoreload.on': False, } if enable_https: options_dict['server.ssl_certificate'] = https_cert options_dict['server.ssl_private_key'] = https_key protocol = "https" else: protocol = "http" logger.info("Starting Plex:CS web server on %s://%s:%d/", protocol, options['http_host'], options['http_port']) cherrypy.config.update(options_dict) conf = { '/': { 'tools.staticdir.root': os.path.join(plexcs.PROG_DIR, 'data'), 'tools.proxy.on': options['http_proxy'] # pay attention to X-Forwarded-Proto header }, '/interfaces': { 'tools.staticdir.on': True, 'tools.staticdir.dir': "interfaces" }, '/images': { 'tools.staticdir.on': True, 'tools.staticdir.dir': "images" }, '/css': { 'tools.staticdir.on': True, 'tools.staticdir.dir': "css" }, '/js': { 'tools.staticdir.on': True, 'tools.staticdir.dir': "js" }, '/favicon.ico': { 'tools.staticfile.on': True, 'tools.staticfile.filename': os.path.join(os.path.abspath( os.curdir), "images" + os.sep + "favicon.ico") }, '/cache': { 'tools.staticdir.on': True, 'tools.staticdir.dir': plexcs.CONFIG.CACHE_DIR } } if options['http_password']: logger.info("Web server authentication is enabled, username is '%s'", options['http_username']) conf['/'].update({ 'tools.auth_basic.on': True, 'tools.auth_basic.realm': 'Plex:CS web server', 'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic.checkpassword_dict({ options['http_username']: options['http_password'] }) }) conf['/api'] = {'tools.auth_basic.on': False} # Prevent time-outs cherrypy.engine.timeout_monitor.unsubscribe() cherrypy.tree.mount(WebInterface(), str(options['http_root']), config=conf) try: cherrypy.process.servers.check_port(str(options['http_host']), options['http_port']) cherrypy.server.start() except IOError: sys.stderr.write('Failed to start on port: %i. Is something else running?\n' % (options['http_port'])) sys.exit(1) cherrypy.server.wait()