Ejemplo n.º 1
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.')
Ejemplo n.º 2
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()
Ejemplo n.º 3
0
def main():
    """
    Plex:CS application entry point. Parses arguments, setups encoding and
    initializes the application.
    """

    # Fixed paths to Plex:CS
    if hasattr(sys, 'frozen'):
        plexcs.FULL_PATH = os.path.abspath(sys.executable)
    else:
        plexcs.FULL_PATH = os.path.abspath(__file__)

    plexcs.PROG_DIR = os.path.dirname(plexcs.FULL_PATH)
    plexcs.ARGS = sys.argv[1:]

    # From sickbeard
    plexcs.SYS_PLATFORM = sys.platform
    plexcs.SYS_ENCODING = None

    try:
        locale.setlocale(locale.LC_ALL, "")
        plexcs.SYS_ENCODING = locale.getpreferredencoding()
    except (locale.Error, IOError):
        pass

    # for OSes that are poorly configured I'll just force UTF-8
    if not plexcs.SYS_ENCODING or plexcs.SYS_ENCODING in ('ANSI_X3.4-1968',
                                                          'US-ASCII', 'ASCII'):
        plexcs.SYS_ENCODING = 'UTF-8'

    # Set up and gather command line arguments
    parser = argparse.ArgumentParser(
        description=
        'A Python based monitoring and tracking tool for Plex Media Server.')

    parser.add_argument('-v',
                        '--verbose',
                        action='store_true',
                        help='Increase console logging verbosity')
    parser.add_argument('-q',
                        '--quiet',
                        action='store_true',
                        help='Turn off console logging')
    parser.add_argument('-d',
                        '--daemon',
                        action='store_true',
                        help='Run as a daemon')
    parser.add_argument('-p',
                        '--port',
                        type=int,
                        help='Force Plex:CS to run on a specified port')
    parser.add_argument(
        '--datadir', help='Specify a directory where to store your data files')
    parser.add_argument('--config', help='Specify a config file to use')
    parser.add_argument('--nolaunch',
                        action='store_true',
                        help='Prevent browser from launching on startup')
    parser.add_argument(
        '--pidfile',
        help='Create a pid file (only relevant when running as a daemon)')

    args = parser.parse_args()

    if args.verbose:
        plexcs.VERBOSE = True
    if args.quiet:
        plexcs.QUIET = True

    # Do an intial setup of the logger.
    logger.initLogger(console=not plexcs.QUIET,
                      log_dir=False,
                      verbose=plexcs.VERBOSE)

    if args.daemon:
        if sys.platform == 'win32':
            sys.stderr.write(
                "Daemonizing not supported under Windows, starting normally\n")
        else:
            plexcs.DAEMON = True
            plexcs.QUIET = True

    if args.pidfile:
        plexcs.PIDFILE = str(args.pidfile)

        # If the pidfile already exists, plexcs may still be running, so
        # exit
        if os.path.exists(plexcs.PIDFILE):
            raise SystemExit("PID file '%s' already exists. Exiting." %
                             plexcs.PIDFILE)

        # The pidfile is only useful in daemon mode, make sure we can write the
        # file properly
        if plexcs.DAEMON:
            plexcs.CREATEPID = True

            try:
                with open(plexcs.PIDFILE, 'w') as fp:
                    fp.write("pid\n")
            except IOError as e:
                raise SystemExit("Unable to write PID file: %s", e)
        else:
            logger.warn("Not running in daemon mode. PID file creation " \
                        "disabled.")

    # Determine which data directory and config file to use
    if args.datadir:
        plexcs.DATA_DIR = args.datadir
    else:
        plexcs.DATA_DIR = plexcs.PROG_DIR

    if args.config:
        config_file = args.config
    else:
        config_file = os.path.join(plexcs.DATA_DIR, 'config.ini')

    # Try to create the DATA_DIR if it doesn't exist
    if not os.path.exists(plexcs.DATA_DIR):
        try:
            os.makedirs(plexcs.DATA_DIR)
        except OSError:
            raise SystemExit('Could not create data directory: ' +
                             plexcs.DATA_DIR + '. Exiting....')

    # Make sure the DATA_DIR is writeable
    if not os.access(plexcs.DATA_DIR, os.W_OK):
        raise SystemExit('Cannot write to the data directory: ' +
                         plexcs.DATA_DIR + '. Exiting...')

    # Put the database in the DATA_DIR
    plexcs.DB_FILE = os.path.join(plexcs.DATA_DIR, 'plexcs.db')

    # Read config and start logging
    plexcs.initialize(config_file)

    if plexcs.DAEMON:
        plexcs.daemonize()

    # Force the http port if neccessary
    if args.port:
        http_port = args.port
        logger.info('Using forced web server port: %i', http_port)
    else:
        http_port = int(plexcs.CONFIG.HTTP_PORT)

    # Check if pyOpenSSL is installed. It is required for certificate generation
    # and for CherryPy.
    if plexcs.CONFIG.ENABLE_HTTPS:
        try:
            import OpenSSL
        except ImportError:
            logger.warn("The pyOpenSSL module is missing. Install this " \
                        "module to enable HTTPS. HTTPS will be disabled.")
            plexcs.CONFIG.ENABLE_HTTPS = False

    # Try to start the server. Will exit here is address is already in use.
    web_config = {
        'http_port': http_port,
        'http_host': plexcs.CONFIG.HTTP_HOST,
        'http_root': plexcs.CONFIG.HTTP_ROOT,
        'http_proxy': plexcs.CONFIG.HTTP_PROXY,
        'enable_https': plexcs.CONFIG.ENABLE_HTTPS,
        'https_cert': plexcs.CONFIG.HTTPS_CERT,
        'https_key': plexcs.CONFIG.HTTPS_KEY,
        'http_username': plexcs.CONFIG.HTTP_USERNAME,
        'http_password': plexcs.CONFIG.HTTP_PASSWORD,
    }
    webstart.initialize(web_config)

    # Start the background threads
    plexcs.start()

    # Open connection for websocket
    if plexcs.CONFIG.MONITORING_USE_WEBSOCKET:
        try:
            web_socket.start_thread()
        except:
            logger.warn(u"Websocket :: Unable to open connection.")
            # Fallback to polling
            plexcs.POLLING_FAILOVER = True
            plexcs.initialize_scheduler()

    # Open webbrowser
    if plexcs.CONFIG.LAUNCH_BROWSER and not args.nolaunch:
        plexcs.launch_browser(plexcs.CONFIG.HTTP_HOST, http_port,
                              plexcs.CONFIG.HTTP_ROOT)

    # Wait endlessy for a signal to happen
    while True:
        if not plexcs.SIGNAL:
            try:
                time.sleep(1)
            except KeyboardInterrupt:
                plexcs.SIGNAL = 'shutdown'
        else:
            logger.info('Received signal: %s', plexcs.SIGNAL)

            if plexcs.SIGNAL == 'shutdown':
                plexcs.shutdown()
            elif plexcs.SIGNAL == 'restart':
                plexcs.shutdown(restart=True)
            else:
                plexcs.shutdown(restart=True, update=True)

            plexcs.SIGNAL = None
Ejemplo n.º 4
0
def main():
    """
    Plex:CS application entry point. Parses arguments, setups encoding and
    initializes the application.
    """

    # Fixed paths to Plex:CS
    if hasattr(sys, 'frozen'):
        plexcs.FULL_PATH = os.path.abspath(sys.executable)
    else:
        plexcs.FULL_PATH = os.path.abspath(__file__)

    plexcs.PROG_DIR = os.path.dirname(plexcs.FULL_PATH)
    plexcs.ARGS = sys.argv[1:]

    # From sickbeard
    plexcs.SYS_PLATFORM = sys.platform
    plexcs.SYS_ENCODING = None

    try:
        locale.setlocale(locale.LC_ALL, "")
        plexcs.SYS_ENCODING = locale.getpreferredencoding()
    except (locale.Error, IOError):
        pass

    # for OSes that are poorly configured I'll just force UTF-8
    if not plexcs.SYS_ENCODING or plexcs.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
        plexcs.SYS_ENCODING = 'UTF-8'

    # Set up and gather command line arguments
    parser = argparse.ArgumentParser(
        description='A Python based monitoring and tracking tool for Plex Media Server.')

    parser.add_argument(
        '-v', '--verbose', action='store_true', help='Increase console logging verbosity')
    parser.add_argument(
        '-q', '--quiet', action='store_true', help='Turn off console logging')
    parser.add_argument(
        '-d', '--daemon', action='store_true', help='Run as a daemon')
    parser.add_argument(
        '-p', '--port', type=int, help='Force Plex:CS to run on a specified port')
    parser.add_argument(
        '--datadir', help='Specify a directory where to store your data files')
    parser.add_argument('--config', help='Specify a config file to use')
    parser.add_argument('--nolaunch', action='store_true',
                        help='Prevent browser from launching on startup')
    parser.add_argument(
        '--pidfile', help='Create a pid file (only relevant when running as a daemon)')

    args = parser.parse_args()

    if args.verbose:
        plexcs.VERBOSE = True
    if args.quiet:
        plexcs.QUIET = True

    # Do an intial setup of the logger.
    logger.initLogger(console=not plexcs.QUIET, log_dir=False,
                      verbose=plexcs.VERBOSE)

    if args.daemon:
        if sys.platform == 'win32':
            sys.stderr.write(
                "Daemonizing not supported under Windows, starting normally\n")
        else:
            plexcs.DAEMON = True
            plexcs.QUIET = True

    if args.pidfile:
        plexcs.PIDFILE = str(args.pidfile)

        # If the pidfile already exists, plexcs may still be running, so
        # exit
        if os.path.exists(plexcs.PIDFILE):
            raise SystemExit("PID file '%s' already exists. Exiting." %
                             plexcs.PIDFILE)

        # The pidfile is only useful in daemon mode, make sure we can write the
        # file properly
        if plexcs.DAEMON:
            plexcs.CREATEPID = True

            try:
                with open(plexcs.PIDFILE, 'w') as fp:
                    fp.write("pid\n")
            except IOError as e:
                raise SystemExit("Unable to write PID file: %s", e)
        else:
            logger.warn("Not running in daemon mode. PID file creation " \
                        "disabled.")

    # Determine which data directory and config file to use
    if args.datadir:
        plexcs.DATA_DIR = args.datadir
    else:
        plexcs.DATA_DIR = plexcs.PROG_DIR

    if args.config:
        config_file = args.config
    else:
        config_file = os.path.join(plexcs.DATA_DIR, 'config.ini')

    # Try to create the DATA_DIR if it doesn't exist
    if not os.path.exists(plexcs.DATA_DIR):
        try:
            os.makedirs(plexcs.DATA_DIR)
        except OSError:
            raise SystemExit(
                'Could not create data directory: ' + plexcs.DATA_DIR + '. Exiting....')

    # Make sure the DATA_DIR is writeable
    if not os.access(plexcs.DATA_DIR, os.W_OK):
        raise SystemExit(
            'Cannot write to the data directory: ' + plexcs.DATA_DIR + '. Exiting...')

    # Put the database in the DATA_DIR
    plexcs.DB_FILE = os.path.join(plexcs.DATA_DIR, 'plexcs.db')

    # Read config and start logging
    plexcs.initialize(config_file)

    if plexcs.DAEMON:
        plexcs.daemonize()

    # Force the http port if neccessary
    if args.port:
        http_port = args.port
        logger.info('Using forced web server port: %i', http_port)
    else:
        http_port = int(plexcs.CONFIG.HTTP_PORT)

    # Check if pyOpenSSL is installed. It is required for certificate generation
    # and for CherryPy.
    if plexcs.CONFIG.ENABLE_HTTPS:
        try:
            import OpenSSL
        except ImportError:
            logger.warn("The pyOpenSSL module is missing. Install this " \
                        "module to enable HTTPS. HTTPS will be disabled.")
            plexcs.CONFIG.ENABLE_HTTPS = False

    # Try to start the server. Will exit here is address is already in use.
    web_config = {
        'http_port': http_port,
        'http_host': plexcs.CONFIG.HTTP_HOST,
        'http_root': plexcs.CONFIG.HTTP_ROOT,
        'http_proxy': plexcs.CONFIG.HTTP_PROXY,
        'enable_https': plexcs.CONFIG.ENABLE_HTTPS,
        'https_cert': plexcs.CONFIG.HTTPS_CERT,
        'https_key': plexcs.CONFIG.HTTPS_KEY,
        'http_username': plexcs.CONFIG.HTTP_USERNAME,
        'http_password': plexcs.CONFIG.HTTP_PASSWORD,
    }
    webstart.initialize(web_config)

    # Start the background threads
    plexcs.start()

    # Open connection for websocket
    if plexcs.CONFIG.MONITORING_USE_WEBSOCKET:
        try:
            web_socket.start_thread()
        except:
            logger.warn(u"Websocket :: Unable to open connection.")
            # Fallback to polling
            plexcs.POLLING_FAILOVER = True
            plexcs.initialize_scheduler()

    # Open webbrowser
    if plexcs.CONFIG.LAUNCH_BROWSER and not args.nolaunch:
        plexcs.launch_browser(plexcs.CONFIG.HTTP_HOST, http_port,
                              plexcs.CONFIG.HTTP_ROOT)

    # Wait endlessy for a signal to happen
    while True:
        if not plexcs.SIGNAL:
            try:
                time.sleep(1)
            except KeyboardInterrupt:
                plexcs.SIGNAL = 'shutdown'
        else:
            logger.info('Received signal: %s', plexcs.SIGNAL)

            if plexcs.SIGNAL == 'shutdown':
                plexcs.shutdown()
            elif plexcs.SIGNAL == 'restart':
                plexcs.shutdown(restart=True)
            else:
                plexcs.shutdown(restart=True, update=True)

            plexcs.SIGNAL = None
Ejemplo n.º 5
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()