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 read_changelog(): changelog_file = os.path.join(plexcs.PROG_DIR, "CHANGELOG.md") try: logfile = open(changelog_file, "r") except IOError as e: logger.error("Plex:CS Version Checker :: Unable to open changelog file. %s" % e) return None if logfile: output = "" lines = logfile.readlines() previous_line = "" for line in lines: if line[:2] == "# ": output += "<h3>" + line[2:] + "</h3>" elif line[:3] == "## ": output += "<h4>" + line[3:] + "</h4>" elif line[:2] == "* " and previous_line.strip() == "": output += "<ul><li>" + line[2:] + "</li>" elif line[:2] == "* ": output += "<li>" + line[2:] + "</li>" elif line.strip() == "" and previous_line[:2] == "* ": output += "</ul></br>" else: output += line + "</br>" previous_line = line return output else: return "<h4>No changelog data</h4>"
def request_json(url, **kwargs): """ Wrapper for `request_response', which will decode the response as JSON object and return the result, if no exceptions are raised. As an option, a validator callback can be given, which should return True if the result is valid. """ validator = kwargs.pop("validator", None) response = request_response(url, **kwargs) if response is not None: try: result = response.json() if validator and not validator(result): logger.error("JSON validation result failed") else: return result except ValueError: logger.error("Response returned invalid JSON data") # Debug response if plexcs.VERBOSE: server_message(response)
def read_changelog(): changelog_file = os.path.join(plexcs.PROG_DIR, 'CHANGELOG.md') try: logfile = open(changelog_file, "r") except IOError as e: logger.error( 'Plex:CS Version Checker :: Unable to open changelog file. %s' % e) return None if logfile: output = '' lines = logfile.readlines() previous_line = '' for line in lines: if line[:2] == '# ': output += '<h3>' + line[2:] + '</h3>' elif line[:3] == '## ': output += '<h4>' + line[3:] + '</h4>' elif line[:2] == '* ' and previous_line.strip() == '': output += '<ul><li>' + line[2:] + '</li>' elif line[:2] == '* ': output += '<li>' + line[2:] + '</li>' elif line.strip() == '' and previous_line[:2] == '* ': output += '</ul></br>' else: output += line + '</br>' previous_line = line return output else: return '<h4>No changelog data</h4>'
def create_https_certificates(ssl_cert, ssl_key): """ Create a pair of self-signed HTTPS certificares and store in them in 'ssl_cert' and 'ssl_key'. Method assumes pyOpenSSL is installed. This code is stolen from SickBeard (http://github.com/midgetspy/Sick-Beard). """ from plexcs import logger from OpenSSL import crypto from certgen import createKeyPair, createCertRequest, createCertificate, \ TYPE_RSA, serial # Create the CA Certificate cakey = createKeyPair(TYPE_RSA, 2048) careq = createCertRequest(cakey, CN="Certificate Authority") cacert = createCertificate(careq, (careq, cakey), serial, (0, 60 * 60 * 24 * 365 * 10)) # ten years pkey = createKeyPair(TYPE_RSA, 2048) req = createCertRequest(pkey, CN="Plex:CS") cert = createCertificate(req, (cacert, cakey), serial, (0, 60 * 60 * 24 * 365 * 10)) # ten years # Save the key and certificate to disk try: with open(ssl_key, "w") as fp: fp.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)) with open(ssl_cert, "w") as fp: fp.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) except IOError as e: logger.error("Error creating SSL key and certificate: %s", e) return False return True
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 launch_browser(host, port, root): if not no_browser: if host == '0.0.0.0': host = 'localhost' if CONFIG.ENABLE_HTTPS: protocol = 'https' else: protocol = 'http' try: webbrowser.open('%s://%s:%i%s' % (protocol, host, port, root)) except Exception as e: logger.error('Could not launch browser: %s', e)
def getVersion(): if version.PLEXCS_VERSION.startswith('win32build'): plexcs.INSTALL_TYPE = 'win' # Don't have a way to update exe yet, but don't want to set VERSION to None return 'Windows Install', 'master' elif os.path.isdir(os.path.join(plexcs.PROG_DIR, '.git')): plexcs.INSTALL_TYPE = 'git' output, err = runGit('rev-parse HEAD') if not output: logger.error('Couldn\'t find latest installed version.') cur_commit_hash = None cur_commit_hash = str(output) if not re.match('^[a-z0-9]+$', cur_commit_hash): logger.error('Output doesn\'t look like a hash, not using it') cur_commit_hash = None if plexcs.CONFIG.DO_NOT_OVERRIDE_GIT_BRANCH and plexcs.CONFIG.GIT_BRANCH: branch_name = plexcs.CONFIG.GIT_BRANCH else: branch_name, err = runGit('rev-parse --abbrev-ref HEAD') branch_name = branch_name if not branch_name and plexcs.CONFIG.GIT_BRANCH: logger.error( 'Could not retrieve branch name from git. Falling back to %s' % plexcs.CONFIG.GIT_BRANCH) branch_name = plexcs.CONFIG.GIT_BRANCH if not branch_name: logger.error( 'Could not retrieve branch name from git. Defaulting to master' ) branch_name = 'master' return cur_commit_hash, branch_name else: plexcs.INSTALL_TYPE = 'source' version_file = os.path.join(plexcs.PROG_DIR, 'version.txt') if not os.path.isfile(version_file): return None, 'master' with open(version_file, 'r') as f: current_version = f.read().strip(' \n\r') if current_version: return current_version, plexcs.CONFIG.GIT_BRANCH else: return None, 'master'
def get_log_tail(window=20, parsed=True): if plexcs.CONFIG.PMS_LOGS_FOLDER: log_file = os.path.join(plexcs.CONFIG.PMS_LOGS_FOLDER, 'Plex Media Server.log') else: return [] try: logfile = open(log_file, "r") except IOError as e: logger.error('Unable to open Plex Log file. %s' % e) return [] log_lines = tail(logfile, window) if parsed: line_error = False clean_lines = [] for i in log_lines: try: i = helpers.latinToAscii(i) log_time = i.split(' [')[0] log_level = i.split('] ', 1)[1].split(' - ', 1)[0] log_msg = i.split('] ', 1)[1].split(' - ', 1)[1] full_line = [log_time, log_level, log_msg] clean_lines.append(full_line) except: line_error = True full_line = ['', '', 'Unable to parse log line.'] clean_lines.append(full_line) if line_error: logger.error( 'Plex:CS was unable to parse some lines of the Plex Media Server log.' ) return clean_lines else: raw_lines = [] for i in log_lines: raw_lines.append(helpers.latinToAscii(i)) return raw_lines return log_lines
def getVersion(): if version.PLEXCS_VERSION.startswith("win32build"): plexcs.INSTALL_TYPE = "win" # Don't have a way to update exe yet, but don't want to set VERSION to None return "Windows Install", "master" elif os.path.isdir(os.path.join(plexcs.PROG_DIR, ".git")): plexcs.INSTALL_TYPE = "git" output, err = runGit("rev-parse HEAD") if not output: logger.error("Couldn't find latest installed version.") cur_commit_hash = None cur_commit_hash = str(output) if not re.match("^[a-z0-9]+$", cur_commit_hash): logger.error("Output doesn't look like a hash, not using it") cur_commit_hash = None if plexcs.CONFIG.DO_NOT_OVERRIDE_GIT_BRANCH and plexcs.CONFIG.GIT_BRANCH: branch_name = plexcs.CONFIG.GIT_BRANCH else: branch_name, err = runGit("rev-parse --abbrev-ref HEAD") branch_name = branch_name if not branch_name and plexcs.CONFIG.GIT_BRANCH: logger.error("Could not retrieve branch name from git. Falling back to %s" % plexcs.CONFIG.GIT_BRANCH) branch_name = plexcs.CONFIG.GIT_BRANCH if not branch_name: logger.error("Could not retrieve branch name from git. Defaulting to master") branch_name = "master" return cur_commit_hash, branch_name else: plexcs.INSTALL_TYPE = "source" version_file = os.path.join(plexcs.PROG_DIR, "version.txt") if not os.path.isfile(version_file): return None, "master" with open(version_file, "r") as f: current_version = f.read().strip(" \n\r") if current_version: return current_version, plexcs.CONFIG.GIT_BRANCH else: return None, "master"
def get_log_tail(window=20, parsed=True): if plexcs.CONFIG.PMS_LOGS_FOLDER: log_file = os.path.join(plexcs.CONFIG.PMS_LOGS_FOLDER, 'Plex Media Server.log') else: return [] try: logfile = open(log_file, "r") except IOError as e: logger.error('Unable to open Plex Log file. %s' % e) return [] log_lines = tail(logfile, window) if parsed: line_error = False clean_lines = [] for i in log_lines: try: i = helpers.latinToAscii(i) log_time = i.split(' [')[0] log_level = i.split('] ', 1)[1].split(' - ',1)[0] log_msg = i.split('] ', 1)[1].split(' - ',1)[1] full_line = [log_time, log_level, log_msg] clean_lines.append(full_line) except: line_error = True full_line = ['', '', 'Unable to parse log line.'] clean_lines.append(full_line) if line_error: logger.error('Plex:CS was unable to parse some lines of the Plex Media Server log.') return clean_lines else: raw_lines = [] for i in log_lines: raw_lines.append(helpers.latinToAscii(i)) return raw_lines return log_lines
def set_notify_state(session, state, agent_info): if session and state and agent_info: monitor_db = database.MonitorDatabase() if state == 'play': values = {'on_play': int(time.time())} elif state == 'stop': values = {'on_stop': int(time.time())} elif state == 'pause': values = {'on_pause': int(time.time())} elif state == 'resume': values = {'on_resume': int(time.time())} elif state == 'buffer': values = {'on_buffer': int(time.time())} elif state == 'watched': values = {'on_watched': int(time.time())} elif state == 'created': values = {'on_created': int(time.time())} else: return if state == 'created': keys = { 'rating_key': session['rating_key'], 'agent_id': agent_info['id'], 'agent_name': agent_info['name'] } else: keys = { 'session_key': session['session_key'], 'rating_key': session['rating_key'], 'user_id': session['user_id'], 'user': session['user'], 'agent_id': agent_info['id'], 'agent_name': agent_info['name'] } monitor_db.upsert(table_name='notify_log', key_dict=keys, value_dict=values) else: logger.error('Plex:CS Notifier :: Unable to set notify state.')
def runGit(args): if plexcs.CONFIG.GIT_PATH: git_locations = ['"' + plexcs.CONFIG.GIT_PATH + '"'] else: git_locations = ['git'] if platform.system().lower() == 'darwin': git_locations.append('/usr/local/git/bin/git') output = err = None for cur_git in git_locations: cmd = cur_git + ' ' + args try: logger.debug('Trying to execute: "' + cmd + '" with shell in ' + plexcs.PROG_DIR) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, cwd=plexcs.PROG_DIR) output, err = p.communicate() output = output.strip() logger.debug('Git output: ' + output) except OSError: logger.debug('Command failed: %s', cmd) continue if 'not found' in output or "not recognized as an internal or external command" in output: logger.debug('Unable to find git with command ' + cmd) output = None elif 'fatal:' in output or err: logger.error( 'Git returned bad info. Are you sure this is a git installation?' ) output = None elif output: break return (output, err)
def fetchData(self): logger.info('Recieved API command: %s' % self.cmd) if self.cmd and self.authenticated: methodtocall = getattr(self, "_" + self.cmd) # Let the traceback hit cherrypy so we can # see the traceback there if self.debug: methodtocall(**self.kwargs) else: try: methodtocall(**self.kwargs) except Exception as e: logger.error(traceback.format_exc()) # Im just lazy, fix me plx if self.data or isinstance(self.data, (dict, list)): if len(self.data): self.result_type = 'success' return self._out_as(self._responds(result_type=self.result_type, msg=self.msg, data=self.data))
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 check_recently_added(): with monitor_lock: # add delay to allow for metadata processing delay = plexcs.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY time_threshold = int(time.time()) - delay time_interval = plexcs.CONFIG.MONITORING_INTERVAL pms_connect = pmsconnect.PmsConnect() recently_added_list = pms_connect.get_recently_added_details(count='10') if recently_added_list: recently_added = recently_added_list['recently_added'] for item in recently_added: metadata = [] if 0 < time_threshold - int(item['added_at']) <= time_interval: if item['media_type'] == 'movie': metadata_list = pms_connect.get_metadata_details(item['rating_key']) if metadata_list: metadata = [metadata_list['metadata']] else: logger.error(u"Plex:CS Monitor :: Unable to retrieve metadata for rating_key %s" \ % str(item['rating_key'])) else: metadata_list = pms_connect.get_metadata_children_details(item['rating_key']) if metadata_list: metadata = metadata_list['metadata'] else: logger.error(u"Plex:CS Monitor :: Unable to retrieve children metadata for rating_key %s" \ % str(item['rating_key'])) if metadata: if not plexcs.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT: for item in metadata: if 0 < time_threshold - int(item['added_at']) <= time_interval: logger.debug(u"Plex:CS Monitor :: Library item %s has been added to Plex." % str(item['rating_key'])) # Fire off notifications threading.Thread(target=notification_handler.notify_timeline, kwargs=dict(timeline_data=item, notify_action='created')).start() else: item = max(metadata, key=lambda x:x['added_at']) if 0 < time_threshold - int(item['added_at']) <= time_interval: if item['media_type'] == 'episode' or item['media_type'] == 'track': metadata_list = pms_connect.get_metadata_details(item['grandparent_rating_key']) if metadata_list: item = metadata_list['metadata'] else: logger.error(u"Plex:CS Monitor :: Unable to retrieve grandparent metadata for grandparent_rating_key %s" \ % str(item['rating_key'])) logger.debug(u"Plex:CS Monitor :: Library item %s has been added to Plex." % str(item['rating_key'])) # Fire off notifications threading.Thread(target=notification_handler.notify_timeline, kwargs=dict(timeline_data=item, notify_action='created')).start()
def _out_as(self, out): if self.out_type == 'json': cherrypy.response.headers[ 'Content-Type'] = 'application/json;charset=UTF-8' try: out = json.dumps(out, indent=4, sort_keys=True) if self.callback is not None: cherrypy.response.headers[ 'Content-Type'] = 'application/javascript' # wrap with JSONP call if requested out = self.callback + '(' + out + ');' # if we fail to generate the output fake an error except Exception as e: logger.info(u"API :: " + traceback.format_exc()) out['message'] = traceback.format_exc() out['result'] = 'error' if self.out_type == 'xml': cherrypy.response.headers['Content-Type'] = 'application/xml' try: out = xmltodict.unparse(out, pretty=True) except ValueError as e: logger.error('Failed to parse xml result') try: out['message'] = e out['result'] = 'error' out = xmltodict.unparse(out, pretty=True) except Exception as e: logger.error('Failed to parse xml result error message') out = '''<?xml version="1.0" encoding="utf-8"?> <response> <message>%s</message> <data></data> <result>error</result> </response> ''' % e return out
def set_notify_state(session, state, agent_info): if session and state and agent_info: monitor_db = database.MonitorDatabase() if state == "play": values = {"on_play": int(time.time())} elif state == "stop": values = {"on_stop": int(time.time())} elif state == "pause": values = {"on_pause": int(time.time())} elif state == "resume": values = {"on_resume": int(time.time())} elif state == "buffer": values = {"on_buffer": int(time.time())} elif state == "watched": values = {"on_watched": int(time.time())} elif state == "created": values = {"on_created": int(time.time())} else: return if state == "created": keys = {"rating_key": session["rating_key"], "agent_id": agent_info["id"], "agent_name": agent_info["name"]} else: keys = { "session_key": session["session_key"], "rating_key": session["rating_key"], "user_id": session["user_id"], "user": session["user"], "agent_id": agent_info["id"], "agent_name": agent_info["name"], } monitor_db.upsert(table_name="notify_log", key_dict=keys, value_dict=values) else: logger.error("Plex:CS Notifier :: Unable to set notify state.")
def _out_as(self, out): if self.out_type == 'json': cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8' try: out = json.dumps(out, indent=4, sort_keys=True) if self.callback is not None: cherrypy.response.headers['Content-Type'] = 'application/javascript' # wrap with JSONP call if requested out = self.callback + '(' + out + ');' # if we fail to generate the output fake an error except Exception as e: logger.info(u"API :: " + traceback.format_exc()) out['message'] = traceback.format_exc() out['result'] = 'error' if self.out_type == 'xml': cherrypy.response.headers['Content-Type'] = 'application/xml' try: out = xmltodict.unparse(out, pretty=True) except ValueError as e: logger.error('Failed to parse xml result') try: out['message'] = e out['result'] = 'error' out = xmltodict.unparse(out, pretty=True) except Exception as e: logger.error('Failed to parse xml result error message') out = '''<?xml version="1.0" encoding="utf-8"?> <response> <message>%s</message> <data></data> <result>error</result> </response> ''' % e return out
def validate_database(database=None, table_name=None): try: connection = sqlite3.connect(database, timeout=20) except sqlite3.OperationalError: logger.error('Plex:CS Importer :: Invalid database specified.') return 'Invalid database specified.' except ValueError: logger.error('Plex:CS Importer :: Invalid database specified.') return 'Invalid database specified.' except: logger.error('Plex:CS Importer :: Uncaught exception.') return 'Uncaught exception.' try: connection.execute('SELECT ratingKey from %s' % table_name) connection.close() except sqlite3.OperationalError: logger.error('Plex:CS Importer :: Invalid database specified.') return 'Invalid database specified.' except: logger.error('Plex:CS Importer :: Uncaught exception.') return 'Uncaught exception.' return 'success'
def fetchData(self): logger.info('Recieved API command: %s' % self.cmd) if self.cmd and self.authenticated: methodtocall = getattr(self, "_" + self.cmd) # Let the traceback hit cherrypy so we can # see the traceback there if self.debug: methodtocall(**self.kwargs) else: try: methodtocall(**self.kwargs) except Exception as e: logger.error(traceback.format_exc()) # Im just lazy, fix me plx if self.data or isinstance(self.data, (dict, list)): if len(self.data): self.result_type = 'success' return self._out_as( self._responds(result_type=self.result_type, msg=self.msg, data=self.data))
def runGit(args): if plexcs.CONFIG.GIT_PATH: git_locations = ['"' + plexcs.CONFIG.GIT_PATH + '"'] else: git_locations = ["git"] if platform.system().lower() == "darwin": git_locations.append("/usr/local/git/bin/git") output = err = None for cur_git in git_locations: cmd = cur_git + " " + args try: logger.debug('Trying to execute: "' + cmd + '" with shell in ' + plexcs.PROG_DIR) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, cwd=plexcs.PROG_DIR) output, err = p.communicate() output = output.strip() logger.debug("Git output: " + output) except OSError: logger.debug("Command failed: %s", cmd) continue if "not found" in output or "not recognized as an internal or external command" in output: logger.debug("Unable to find git with command " + cmd) output = None elif "fatal:" in output or err: logger.error("Git returned bad info. Are you sure this is a git installation?") output = None elif output: break return (output, err)
def request_response(url, method="get", auto_raise=True, whitelist_status_code=None, lock=fake_lock, **kwargs): """ Convenient wrapper for `requests.get', which will capture the exceptions and log them. On success, the Response object is returned. In case of a exception, None is returned. Additionally, there is support for rate limiting. To use this feature, supply a tuple of (lock, request_limit). The lock is used to make sure no other request with the same lock is executed. The request limit is the minimal time between two requests (and so 1/request_limit is the number of requests per seconds). """ # Convert whitelist_status_code to a list if needed if whitelist_status_code and type(whitelist_status_code) != list: whitelist_status_code = [whitelist_status_code] # Disable verification of SSL certificates if requested. Note: this could # pose a security issue! kwargs["verify"] = bool(plexcs.CONFIG.VERIFY_SSL_CERT) # Map method to the request.XXX method. This is a simple hack, but it # allows requests to apply more magic per method. See lib/requests/api.py. request_method = getattr(requests, method.lower()) try: # Request URL and wait for response with lock: logger.debug( "Requesting URL via %s method: %s", method.upper(), url) response = request_method(url, **kwargs) # If status code != OK, then raise exception, except if the status code # is white listed. if whitelist_status_code and auto_raise: if response.status_code not in whitelist_status_code: try: response.raise_for_status() except: logger.debug( "Response status code %d is not white " "listed, raised exception", response.status_code) raise elif auto_raise: response.raise_for_status() return response except requests.exceptions.SSLError as e: if kwargs["verify"]: logger.error( "Unable to connect to remote host because of a SSL error. " "It is likely that your system cannot verify the validity" "of the certificate. The remote certificate is either " "self-signed, or the remote server uses SNI. See the wiki for " "more information on this topic.") else: logger.error( "SSL error raised during connection, with certificate " "verification turned off: %s", e) except requests.ConnectionError: logger.error( "Unable to connect to remote host. Check if the remote " "host is up and running.") except requests.Timeout: logger.error( "Request timed out. The remote host did not respond timely.") except requests.HTTPError as e: if e.response is not None: if e.response.status_code >= 500: cause = "remote server error" elif e.response.status_code >= 400: cause = "local client error" else: # I don't think we will end up here, but for completeness cause = "unknown" logger.error( "Request raise HTTP error with status code %d (%s).", e.response.status_code, cause) # Debug response if plexcs.VERBOSE: server_message(e.response) else: logger.error("Request raised HTTP error.") except requests.RequestException as e: logger.error("Request raised exception: %s", e)
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('Plex:CS Importer :: Invalid filename.') return None except ValueError: logger.error('Plex:CS Importer :: Invalid filename.') return None try: connection.execute('SELECT ratingKey from %s' % table_name) except sqlite3.OperationalError: logger.error( 'Plex:CS Importer :: Database specified does not contain the required fields.' ) return None logger.debug(u"Plex:CS Importer :: PlexWatch data import in progress...") logger.debug( u"Plex:CS Importer :: Disabling monitoring while import in progress.") plexcs.schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions', hours=0, minutes=0, seconds=0) plexcs.schedule_job(activity_pinger.check_recently_added, 'Check for recently added items', 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"Plex:CS 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"Plex:CS 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"Plex:CS 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'], '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"Plex:CS Importer :: Item has bad rating_key: %s" % session_history_metadata['rating_key']) logger.debug(u"Plex:CS Importer :: PlexWatch data import complete.") import_users() logger.debug(u"Plex:CS Importer :: Re-enabling monitoring.") plexcs.initialize_scheduler()
def initialize(config_file): with INIT_LOCK: global CONFIG global _INITIALIZED global CURRENT_VERSION global LATEST_VERSION global UMASK global POLLING_FAILOVER CONFIG = plexcs.config.Config(config_file) assert CONFIG is not None if _INITIALIZED: return False if CONFIG.HTTP_PORT < 21 or CONFIG.HTTP_PORT > 65535: plexcs.logger.warn( 'HTTP_PORT out of bounds: 21 < %s < 65535', CONFIG.HTTP_PORT) CONFIG.HTTP_PORT = 8182 if CONFIG.HTTPS_CERT == '': CONFIG.HTTPS_CERT = os.path.join(DATA_DIR, 'server.crt') if CONFIG.HTTPS_KEY == '': CONFIG.HTTPS_KEY = os.path.join(DATA_DIR, 'server.key') if not CONFIG.LOG_DIR: CONFIG.LOG_DIR = os.path.join(DATA_DIR, 'logs') if not os.path.exists(CONFIG.LOG_DIR): try: os.makedirs(CONFIG.LOG_DIR) except OSError: CONFIG.LOG_DIR = None if not QUIET: sys.stderr.write("Unable to create the log directory. " \ "Logging to screen only.\n") # Start the logger, disable console if needed logger.initLogger(console=not QUIET, log_dir=CONFIG.LOG_DIR, verbose=VERBOSE) if not CONFIG.CACHE_DIR: # Put the cache dir in the data dir for now CONFIG.CACHE_DIR = os.path.join(DATA_DIR, 'cache') if not os.path.exists(CONFIG.CACHE_DIR): try: os.makedirs(CONFIG.CACHE_DIR) except OSError as e: logger.error("Could not create cache dir '%s': %s", DATA_DIR, e) # Initialize the database logger.info('Checking to see if the database has all tables....') try: dbcheck() except Exception as e: logger.error("Can't connect to the database: %s", e) # Check if Plex:CS has a uuid if CONFIG.PMS_UUID == '' or not CONFIG.PMS_UUID: my_uuid = generate_uuid() CONFIG.__setattr__('PMS_UUID', my_uuid) CONFIG.write() # Get the currently installed version. Returns None, 'win32' or the git # hash. CURRENT_VERSION, CONFIG.GIT_BRANCH = versioncheck.getVersion() # Write current version to a file, so we know which version did work. # This allowes one to restore to that version. The idea is that if we # arrive here, most parts of Plex:CS seem to work. if CURRENT_VERSION: version_lock_file = os.path.join(DATA_DIR, "version.lock") try: with open(version_lock_file, "w") as fp: fp.write(CURRENT_VERSION) except IOError as e: logger.error("Unable to write current version to file '%s': %s", version_lock_file, e) # Check for new versions if CONFIG.CHECK_GITHUB_ON_STARTUP and CONFIG.CHECK_GITHUB: try: LATEST_VERSION = versioncheck.checkGithub() except: logger.exception("Unhandled exception") LATEST_VERSION = CURRENT_VERSION else: LATEST_VERSION = CURRENT_VERSION # Get the real PMS urls for SSL and remote access if CONFIG.PMS_TOKEN and CONFIG.PMS_IP and CONFIG.PMS_PORT: plextv.get_real_pms_url() pmsconnect.get_server_friendly_name() # Refresh the users list on startup if CONFIG.PMS_TOKEN and CONFIG.REFRESH_USERS_ON_STARTUP: plextv.refresh_users() # Store the original umask UMASK = os.umask(0) os.umask(UMASK) _INITIALIZED = True return True
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('Plex:CS Importer :: Invalid filename.') return None except ValueError: logger.error('Plex:CS Importer :: Invalid filename.') return None try: connection.execute('SELECT ratingKey from %s' % table_name) except sqlite3.OperationalError: logger.error('Plex:CS Importer :: Database specified does not contain the required fields.') return None logger.debug(u"Plex:CS Importer :: PlexWatch data import in progress...") logger.debug(u"Plex:CS Importer :: Disabling monitoring while import in progress.") plexcs.schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions', hours=0, minutes=0, seconds=0) plexcs.schedule_job(activity_pinger.check_recently_added, 'Check for recently added items', 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"Plex:CS 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"Plex:CS 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"Plex:CS 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'], '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"Plex:CS Importer :: Item has bad rating_key: %s" % session_history_metadata['rating_key']) logger.debug(u"Plex:CS Importer :: PlexWatch data import complete.") import_users() logger.debug(u"Plex:CS Importer :: Re-enabling monitoring.") plexcs.initialize_scheduler()
def build_notify_text(session=None, timeline=None, state=None): import re # Get the server name server_name = plexcs.CONFIG.PMS_NAME # Get the server uptime plex_tv = plextv.PlexTV() server_times = plex_tv.get_server_times() if server_times: updated_at = server_times[0]["updated_at"] server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_float(updated_at))) else: logger.error(u"Plex:CS Notifier :: Unable to retrieve server uptime.") server_uptime = "N/A" # Get metadata feed for item if session: rating_key = session["rating_key"] elif timeline: rating_key = timeline["rating_key"] pms_connect = pmsconnect.PmsConnect() metadata_list = pms_connect.get_metadata_details(rating_key=rating_key) if metadata_list: metadata = metadata_list["metadata"] else: logger.error(u"Plex:CS Notifier :: Unable to retrieve metadata for rating_key %s" % str(rating_key)) return [] # Check for exclusion tags if metadata["media_type"] == "movie": # Regex pattern to remove the text in the tags we don't want pattern = re.compile("\n*<tv>[^>]+.</tv>\n*|\n*<music>[^>]+.</music>\n*", re.IGNORECASE | re.DOTALL) elif metadata["media_type"] == "show" or metadata["media_type"] == "episode": # Regex pattern to remove the text in the tags we don't want pattern = re.compile("\n*<movie>[^>]+.</movie>\n*|\n*?<music>[^>]+.</music>\n*", re.IGNORECASE | re.DOTALL) elif metadata["media_type"] == "artist" or metadata["media_type"] == "track": # Regex pattern to remove the text in the tags we don't want pattern = re.compile("\n*<tv>[^>]+.</tv>\n*|\n*<movie>[^>]+.</movie>\n*", re.IGNORECASE | re.DOTALL) else: pattern = None if ( metadata["media_type"] == "movie" or metadata["media_type"] == "show" or metadata["media_type"] == "episode" or metadata["media_type"] == "artist" or metadata["media_type"] == "track" and pattern ): # Remove the unwanted tags and strip any unmatch tags too. on_start_subject = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT)) on_start_body = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_START_BODY_TEXT)) on_stop_subject = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT)) on_stop_body = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_STOP_BODY_TEXT)) on_pause_subject = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT)) on_pause_body = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT)) on_resume_subject = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT)) on_resume_body = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT)) on_buffer_subject = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT)) on_buffer_body = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT)) on_watched_subject = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT)) on_watched_body = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT)) on_created_subject = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT)) on_created_body = strip_tag(re.sub(pattern, "", plexcs.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT)) else: on_start_subject = plexcs.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT on_start_body = plexcs.CONFIG.NOTIFY_ON_START_BODY_TEXT on_stop_subject = plexcs.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT on_stop_body = plexcs.CONFIG.NOTIFY_ON_STOP_BODY_TEXT on_pause_subject = plexcs.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT on_pause_body = plexcs.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT on_resume_subject = plexcs.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT on_resume_body = plexcs.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT on_buffer_subject = plexcs.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT on_buffer_body = plexcs.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT on_watched_subject = plexcs.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT on_watched_body = plexcs.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT on_created_subject = plexcs.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT on_created_body = plexcs.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT # Create a title if metadata["media_type"] == "episode" or metadata["media_type"] == "track": full_title = "%s - %s" % (metadata["grandparent_title"], metadata["title"]) else: full_title = metadata["title"] duration = helpers.convert_milliseconds_to_minutes(metadata["duration"]) # Default values video_decision = "" audio_decision = "" transcode_decision = "" stream_duration = 0 view_offset = 0 user = "" platform = "" player = "" ip_address = "N/A" # Session values if session: # Generate a combined transcode decision value video_decision = session["video_decision"].title() audio_decision = session["audio_decision"].title() if session["video_decision"] == "transcode" or session["audio_decision"] == "transcode": transcode_decision = "Transcode" elif session["video_decision"] == "copy" or session["audio_decision"] == "copy": transcode_decision = "Direct Stream" else: transcode_decision = "Direct Play" if state != "play": if session["paused_counter"]: stream_duration = int( ( time.time() - helpers.cast_to_float(session["started"]) - helpers.cast_to_float(session["paused_counter"]) ) / 60 ) else: stream_duration = int((time.time() - helpers.cast_to_float(session["started"])) / 60) view_offset = helpers.convert_milliseconds_to_minutes(session["view_offset"]) user = session["friendly_name"] platform = session["platform"] player = session["player"] ip_address = session["ip_address"] if session["ip_address"] else "N/A" progress_percent = helpers.get_percent(view_offset, duration) available_params = { "server_name": server_name, "server_uptime": server_uptime, "user": user, "platform": platform, "player": player, "ip_address": ip_address, "media_type": metadata["media_type"], "title": full_title, "show_name": metadata["grandparent_title"], "episode_name": metadata["title"], "artist_name": metadata["grandparent_title"], "album_name": metadata["parent_title"], "track_name": metadata["title"], "season_num": metadata["parent_index"].zfill(1), "season_num00": metadata["parent_index"].zfill(2), "episode_num": metadata["index"].zfill(1), "episode_num00": metadata["index"].zfill(2), "video_decision": video_decision, "audio_decision": audio_decision, "transcode_decision": transcode_decision, "year": metadata["year"], "studio": metadata["studio"], "content_rating": metadata["content_rating"], "directors": ", ".join(metadata["directors"]), "writers": ", ".join(metadata["writers"]), "actors": ", ".join(metadata["actors"]), "genres": ", ".join(metadata["genres"]), "summary": metadata["summary"], "tagline": metadata["tagline"], "rating": metadata["rating"], "duration": duration, "stream_duration": stream_duration, "remaining_duration": duration - view_offset, "progress": view_offset, "progress_percent": progress_percent, } # Default subject text subject_text = "Plex:CS (%s)" % server_name if state == "play": # Default body text body_text = "%s (%s) is watching %s" % (session["friendly_name"], session["player"], full_title) if on_start_subject and on_start_body: try: subject_text = unicode(on_start_subject).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_start_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] elif state == "stop": # Default body text body_text = "%s (%s) has stopped %s" % (session["friendly_name"], session["player"], full_title) if on_stop_subject and on_stop_body: try: subject_text = unicode(on_stop_subject).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_stop_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] elif state == "pause": # Default body text body_text = "%s (%s) has paused %s" % (session["friendly_name"], session["player"], full_title) if on_pause_subject and on_pause_body: try: subject_text = unicode(on_pause_subject).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_pause_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] elif state == "resume": # Default body text body_text = "%s (%s) has resumed %s" % (session["friendly_name"], session["player"], full_title) if on_resume_subject and on_resume_body: try: subject_text = unicode(on_resume_subject).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_resume_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] elif state == "buffer": # Default body text body_text = "%s (%s) is buffering %s" % (session["friendly_name"], session["player"], full_title) if on_buffer_subject and on_buffer_body: try: subject_text = unicode(on_buffer_subject).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_buffer_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] elif state == "watched": # Default body text body_text = "%s (%s) has watched %s" % (session["friendly_name"], session["player"], full_title) if on_watched_subject and on_watched_body: try: subject_text = unicode(on_watched_subject).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_watched_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] elif state == "created": # Default body text body_text = "%s was recently added to Plex." % full_title if on_created_subject and on_created_body: try: subject_text = unicode(on_created_subject).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_created_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] else: return None
def update(): if plexcs.INSTALL_TYPE == 'win': logger.info('Windows .exe updating not supported yet.') elif plexcs.INSTALL_TYPE == 'git': output, err = runGit('pull origin ' + plexcs.CONFIG.GIT_BRANCH) if not output: logger.error('Couldn\'t download latest version') for line in output.split('\n'): if 'Already up-to-date.' in line: logger.info('No update available, not updating') logger.info('Output: ' + str(output)) elif line.endswith('Aborting.'): logger.error('Unable to update from git: ' + line) logger.info('Output: ' + str(output)) else: tar_download_url = 'https://github.com/%s/plex-cs/tarball/%s' % ( plexcs.CONFIG.GIT_USER, plexcs.CONFIG.GIT_BRANCH) update_dir = os.path.join(plexcs.PROG_DIR, 'update') version_path = os.path.join(plexcs.PROG_DIR, 'version.txt') logger.info('Downloading update from: ' + tar_download_url) data = request.request_content(tar_download_url) if not data: logger.error( "Unable to retrieve new version from '%s', can't update", tar_download_url) return download_name = plexcs.CONFIG.GIT_BRANCH + '-github' tar_download_path = os.path.join(plexcs.PROG_DIR, download_name) # Save tar to disk with open(tar_download_path, 'wb') as f: f.write(data) # Extract the tar to update folder logger.info('Extracting file: ' + tar_download_path) tar = tarfile.open(tar_download_path) tar.extractall(update_dir) tar.close() # Delete the tar.gz logger.info('Deleting file: ' + tar_download_path) os.remove(tar_download_path) # Find update dir name update_dir_contents = [ x for x in os.listdir(update_dir) if os.path.isdir(os.path.join(update_dir, x)) ] if len(update_dir_contents) != 1: logger.error("Invalid update data, update failed: " + str(update_dir_contents)) return content_dir = os.path.join(update_dir, update_dir_contents[0]) # walk temp folder and move files to main folder for dirname, dirnames, filenames in os.walk(content_dir): dirname = dirname[len(content_dir) + 1:] for curfile in filenames: old_path = os.path.join(content_dir, dirname, curfile) new_path = os.path.join(plexcs.PROG_DIR, dirname, curfile) if os.path.isfile(new_path): os.remove(new_path) os.renames(old_path, new_path) # Update version.txt try: with open(version_path, 'w') as f: f.write(str(plexcs.LATEST_VERSION)) except IOError as e: logger.error( "Unable to write current version to version.txt, update not complete: %s", e) return
def build_server_notify_text(state=None): # Get the server name server_name = plexcs.CONFIG.PMS_NAME # Get the server uptime plex_tv = plextv.PlexTV() server_times = plex_tv.get_server_times() if server_times: updated_at = server_times[0]['updated_at'] server_uptime = helpers.human_duration( int(time.time() - helpers.cast_to_float(updated_at))) else: logger.error(u"Plex:CS Notifier :: Unable to retrieve server uptime.") server_uptime = 'N/A' on_extdown_subject = plexcs.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT on_extdown_body = plexcs.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT on_intdown_subject = plexcs.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT on_intdown_body = plexcs.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT available_params = { 'server_name': server_name, 'server_uptime': server_uptime } # Default text subject_text = 'Plex:CS (%s)' % server_name if state == 'extdown': # Default body text body_text = 'The Plex Media Server remote access is down.' if on_extdown_subject and on_extdown_body: try: subject_text = unicode(on_extdown_subject).format( **available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_extdown_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] elif state == 'intdown': # Default body text body_text = 'The Plex Media Server is down.' if on_intdown_subject and on_intdown_body: try: subject_text = unicode(on_intdown_subject).format( **available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_intdown_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] else: return None
def build_notify_text(session=None, timeline=None, state=None): import re # Get the server name server_name = plexcs.CONFIG.PMS_NAME # Get the server uptime plex_tv = plextv.PlexTV() server_times = plex_tv.get_server_times() if server_times: updated_at = server_times[0]['updated_at'] server_uptime = helpers.human_duration( int(time.time() - helpers.cast_to_float(updated_at))) else: logger.error(u"Plex:CS Notifier :: Unable to retrieve server uptime.") server_uptime = 'N/A' # Get metadata feed for item if session: rating_key = session['rating_key'] elif timeline: rating_key = timeline['rating_key'] pms_connect = pmsconnect.PmsConnect() metadata_list = pms_connect.get_metadata_details(rating_key=rating_key) if metadata_list: metadata = metadata_list['metadata'] else: logger.error( u"Plex:CS Notifier :: Unable to retrieve metadata for rating_key %s" % str(rating_key)) return [] # Check for exclusion tags if metadata['media_type'] == 'movie': # Regex pattern to remove the text in the tags we don't want pattern = re.compile( '\n*<tv>[^>]+.</tv>\n*|\n*<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL) elif metadata['media_type'] == 'show' or metadata[ 'media_type'] == 'episode': # Regex pattern to remove the text in the tags we don't want pattern = re.compile( '\n*<movie>[^>]+.</movie>\n*|\n*?<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL) elif metadata['media_type'] == 'artist' or metadata[ 'media_type'] == 'track': # Regex pattern to remove the text in the tags we don't want pattern = re.compile( '\n*<tv>[^>]+.</tv>\n*|\n*<movie>[^>]+.</movie>\n*', re.IGNORECASE | re.DOTALL) else: pattern = None if metadata['media_type'] == 'movie' \ or metadata['media_type'] == 'show' or metadata['media_type'] == 'episode' \ or metadata['media_type'] == 'artist' or metadata['media_type'] == 'track' \ and pattern: # Remove the unwanted tags and strip any unmatch tags too. on_start_subject = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT)) on_start_body = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_START_BODY_TEXT)) on_stop_subject = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT)) on_stop_body = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_STOP_BODY_TEXT)) on_pause_subject = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT)) on_pause_body = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT)) on_resume_subject = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT)) on_resume_body = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT)) on_buffer_subject = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT)) on_buffer_body = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT)) on_watched_subject = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT)) on_watched_body = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT)) on_created_subject = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT)) on_created_body = strip_tag( re.sub(pattern, '', plexcs.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT)) else: on_start_subject = plexcs.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT on_start_body = plexcs.CONFIG.NOTIFY_ON_START_BODY_TEXT on_stop_subject = plexcs.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT on_stop_body = plexcs.CONFIG.NOTIFY_ON_STOP_BODY_TEXT on_pause_subject = plexcs.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT on_pause_body = plexcs.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT on_resume_subject = plexcs.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT on_resume_body = plexcs.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT on_buffer_subject = plexcs.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT on_buffer_body = plexcs.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT on_watched_subject = plexcs.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT on_watched_body = plexcs.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT on_created_subject = plexcs.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT on_created_body = plexcs.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT # Create a title if metadata['media_type'] == 'episode' or metadata['media_type'] == 'track': full_title = '%s - %s' % (metadata['grandparent_title'], metadata['title']) else: full_title = metadata['title'] duration = helpers.convert_milliseconds_to_minutes(metadata['duration']) # Default values video_decision = '' audio_decision = '' transcode_decision = '' stream_duration = 0 view_offset = 0 user = '' platform = '' player = '' ip_address = 'N/A' # Session values if session: # Generate a combined transcode decision value video_decision = session['video_decision'].title() audio_decision = session['audio_decision'].title() if session['video_decision'] == 'transcode' or session[ 'audio_decision'] == 'transcode': transcode_decision = 'Transcode' elif session['video_decision'] == 'copy' or session[ 'audio_decision'] == 'copy': transcode_decision = 'Direct Stream' else: transcode_decision = 'Direct Play' if state != 'play': if session['paused_counter']: stream_duration = int( (time.time() - helpers.cast_to_float(session['started']) - helpers.cast_to_float(session['paused_counter'])) / 60) else: stream_duration = int( (time.time() - helpers.cast_to_float(session['started'])) / 60) view_offset = helpers.convert_milliseconds_to_minutes( session['view_offset']) user = session['friendly_name'] platform = session['platform'] player = session['player'] ip_address = session['ip_address'] if session['ip_address'] else 'N/A' progress_percent = helpers.get_percent(view_offset, duration) available_params = { 'server_name': server_name, 'server_uptime': server_uptime, 'user': user, 'platform': platform, 'player': player, 'ip_address': ip_address, 'media_type': metadata['media_type'], 'title': full_title, 'show_name': metadata['grandparent_title'], 'episode_name': metadata['title'], 'artist_name': metadata['grandparent_title'], 'album_name': metadata['parent_title'], 'track_name': metadata['title'], 'season_num': metadata['parent_index'].zfill(1), 'season_num00': metadata['parent_index'].zfill(2), 'episode_num': metadata['index'].zfill(1), 'episode_num00': metadata['index'].zfill(2), 'video_decision': video_decision, 'audio_decision': audio_decision, 'transcode_decision': transcode_decision, 'year': metadata['year'], 'studio': metadata['studio'], 'content_rating': metadata['content_rating'], 'directors': ', '.join(metadata['directors']), 'writers': ', '.join(metadata['writers']), 'actors': ', '.join(metadata['actors']), 'genres': ', '.join(metadata['genres']), 'summary': metadata['summary'], 'tagline': metadata['tagline'], 'rating': metadata['rating'], 'duration': duration, 'stream_duration': stream_duration, 'remaining_duration': duration - view_offset, 'progress': view_offset, 'progress_percent': progress_percent } # Default subject text subject_text = 'Plex:CS (%s)' % server_name if state == 'play': # Default body text body_text = '%s (%s) is watching %s' % (session['friendly_name'], session['player'], full_title) if on_start_subject and on_start_body: try: subject_text = unicode(on_start_subject).format( **available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_start_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] elif state == 'stop': # Default body text body_text = '%s (%s) has stopped %s' % (session['friendly_name'], session['player'], full_title) if on_stop_subject and on_stop_body: try: subject_text = unicode(on_stop_subject).format( **available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_stop_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] elif state == 'pause': # Default body text body_text = '%s (%s) has paused %s' % (session['friendly_name'], session['player'], full_title) if on_pause_subject and on_pause_body: try: subject_text = unicode(on_pause_subject).format( **available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_pause_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] elif state == 'resume': # Default body text body_text = '%s (%s) has resumed %s' % (session['friendly_name'], session['player'], full_title) if on_resume_subject and on_resume_body: try: subject_text = unicode(on_resume_subject).format( **available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_resume_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] elif state == 'buffer': # Default body text body_text = '%s (%s) is buffering %s' % (session['friendly_name'], session['player'], full_title) if on_buffer_subject and on_buffer_body: try: subject_text = unicode(on_buffer_subject).format( **available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_buffer_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] elif state == 'watched': # Default body text body_text = '%s (%s) has watched %s' % (session['friendly_name'], session['player'], full_title) if on_watched_subject and on_watched_body: try: subject_text = unicode(on_watched_subject).format( **available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_watched_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] elif state == 'created': # Default body text body_text = '%s was recently added to Plex.' % full_title if on_created_subject and on_created_body: try: subject_text = unicode(on_created_subject).format( **available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback." ) try: body_text = unicode(on_created_body).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error( u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback." ) return [subject_text, body_text] else: return [subject_text, body_text] else: return None
def update(): if plexcs.INSTALL_TYPE == "win": logger.info("Windows .exe updating not supported yet.") elif plexcs.INSTALL_TYPE == "git": output, err = runGit("pull origin " + plexcs.CONFIG.GIT_BRANCH) if not output: logger.error("Couldn't download latest version") for line in output.split("\n"): if "Already up-to-date." in line: logger.info("No update available, not updating") logger.info("Output: " + str(output)) elif line.endswith("Aborting."): logger.error("Unable to update from git: " + line) logger.info("Output: " + str(output)) else: tar_download_url = "https://github.com/%s/plex-cs/tarball/%s" % ( plexcs.CONFIG.GIT_USER, plexcs.CONFIG.GIT_BRANCH, ) update_dir = os.path.join(plexcs.PROG_DIR, "update") version_path = os.path.join(plexcs.PROG_DIR, "version.txt") logger.info("Downloading update from: " + tar_download_url) data = request.request_content(tar_download_url) if not data: logger.error("Unable to retrieve new version from '%s', can't update", tar_download_url) return download_name = plexcs.CONFIG.GIT_BRANCH + "-github" tar_download_path = os.path.join(plexcs.PROG_DIR, download_name) # Save tar to disk with open(tar_download_path, "wb") as f: f.write(data) # Extract the tar to update folder logger.info("Extracting file: " + tar_download_path) tar = tarfile.open(tar_download_path) tar.extractall(update_dir) tar.close() # Delete the tar.gz logger.info("Deleting file: " + tar_download_path) os.remove(tar_download_path) # Find update dir name update_dir_contents = [x for x in os.listdir(update_dir) if os.path.isdir(os.path.join(update_dir, x))] if len(update_dir_contents) != 1: logger.error("Invalid update data, update failed: " + str(update_dir_contents)) return content_dir = os.path.join(update_dir, update_dir_contents[0]) # walk temp folder and move files to main folder for dirname, dirnames, filenames in os.walk(content_dir): dirname = dirname[len(content_dir) + 1 :] for curfile in filenames: old_path = os.path.join(content_dir, dirname, curfile) new_path = os.path.join(plexcs.PROG_DIR, dirname, curfile) if os.path.isfile(new_path): os.remove(new_path) os.renames(old_path, new_path) # Update version.txt try: with open(version_path, "w") as f: f.write(str(plexcs.LATEST_VERSION)) except IOError as e: logger.error("Unable to write current version to version.txt, update not complete: %s", e) return
def build_server_notify_text(state=None): # Get the server name server_name = plexcs.CONFIG.PMS_NAME # Get the server uptime plex_tv = plextv.PlexTV() server_times = plex_tv.get_server_times() if server_times: updated_at = server_times[0]["updated_at"] server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_float(updated_at))) else: logger.error(u"Plex:CS Notifier :: Unable to retrieve server uptime.") server_uptime = "N/A" on_extdown_subject = plexcs.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT on_extdown_body = plexcs.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT on_intdown_subject = plexcs.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT on_intdown_body = plexcs.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT available_params = {"server_name": server_name, "server_uptime": server_uptime} # Default text subject_text = "Plex:CS (%s)" % server_name if state == "extdown": # Default body text body_text = "The Plex Media Server remote access is down." if on_extdown_subject and on_extdown_body: try: subject_text = unicode(on_extdown_subject).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_extdown_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] elif state == "intdown": # Default body text body_text = "The Plex Media Server is down." if on_intdown_subject and on_intdown_body: try: subject_text = unicode(on_intdown_subject).format(**available_params) except LookupError as e: logger.error( u"Plex:CS Notifier :: Unable to parse field %s in notification subject. Using fallback." % e ) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification subject. Using fallback.") try: body_text = unicode(on_intdown_body).format(**available_params) except LookupError as e: logger.error(u"Plex:CS Notifier :: Unable to parse field %s in notification body. Using fallback." % e) except: logger.error(u"Plex:CS Notifier :: Unable to parse custom notification body. Using fallback.") return [subject_text, body_text] else: return [subject_text, body_text] else: return None