Beispiel #1
0
    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
Beispiel #2
0
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>"
Beispiel #3
0
    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
Beispiel #4
0
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)
Beispiel #5
0
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>'
Beispiel #6
0
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
Beispiel #7
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.')
Beispiel #8
0
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)
Beispiel #9
0
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'
Beispiel #10
0
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
Beispiel #11
0
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"
Beispiel #12
0
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
Beispiel #13
0
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.')
Beispiel #14
0
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)
Beispiel #15
0
    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))
Beispiel #16
0
    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
Beispiel #17
0
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()
Beispiel #18
0
    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
Beispiel #19
0
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.")
Beispiel #20
0
    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
Beispiel #21
0
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'
Beispiel #22
0
    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))
Beispiel #23
0
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)
Beispiel #24
0
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'
Beispiel #25
0
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)
Beispiel #26
0
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()
Beispiel #27
0
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
Beispiel #28
0
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()
Beispiel #29
0
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
Beispiel #30
0
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
Beispiel #31
0
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
Beispiel #32
0
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
Beispiel #33
0
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
Beispiel #34
0
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