Esempio n. 1
0
    def __init__(self):
        # Check for updates first (required for use in header)
        self.update_checker = UpdateChecker()

        Header.show(self)

        if 'nowPlaying' in Dict and type(Dict['nowPlaying']) is dict:
            self.cleanup()
            Dict.Save()
        else:
            Dict['nowPlaying'] = dict()

        Main.update_config()

        self.session_manager = SessionManager()

        PlexActivity.on_update_collection.subscribe(self.update_collection)
Esempio n. 2
0
    def __init__(self):
        self.update_checker = UpdateChecker()
        self.session_manager = SessionManager()

        Header.show(self)

        if 'nowPlaying' in Dict and type(Dict['nowPlaying']) is dict:
            self.cleanup()
            Dict.Save()
        else:
            Dict['nowPlaying'] = dict()

        Main.update_config()

        # Initialize modules
        for module in self.modules:
            if hasattr(module, 'initialize'):
                log.debug("Initializing module %s", module)
                module.initialize()
Esempio n. 3
0
class Main(object):
    modules = [
        # core
        UpdateChecker()
    ]

    def __init__(self):
        Header.show(self)

        # Initial configuration update
        self.on_configuration_changed()

        # Initialize clients
        self.init_trakt()
        self.init_plex()
        self.init()

        # Initialize modules
        ModuleManager.initialize()

    def init(self):
        names = []

        # Initialize modules
        for module in self.modules:
            names.append(get_class_name(module))

            if hasattr(module, 'initialize'):
                module.initialize()

        log.info('Initialized %s modules: %s', len(names), ', '.join(names))

    @staticmethod
    def init_plex():
        # Ensure client identifier has been generated
        if not Dict['plex.client.identifier']:
            # Generate identifier
            Dict['plex.client.identifier'] = uuid.uuid4()

        # Retrieve current client identifier
        client_id = Dict['plex.client.identifier']

        if isinstance(client_id, uuid.UUID):
            client_id = str(client_id)

        # plex.py
        Plex.configuration.defaults.authentication(os.environ.get('PLEXTOKEN'))

        Plex.configuration.defaults.client(identifier=client_id,
                                           product='trakt (for Plex)',
                                           version=PLUGIN_VERSION)

        # plex.activity.py
        path = os.path.join(LOG_HANDLER.baseFilename, '..', '..',
                            'Plex Media Server.log')
        path = os.path.abspath(path)

        Activity['logging'].add_hint(path)

        # plex.metadata.py
        Metadata.configure(client=Plex.client)

    @classmethod
    def init_trakt(cls):
        config = Configuration.advanced['trakt']

        # Build timeout value
        timeout = (config.get_float('connect_timeout', 6.05),
                   config.get_float('read_timeout', 24))

        # Client
        Trakt.configuration.defaults.client(
            id=
            'c9ccd3684988a7862a8542ae0000535e0fbd2d1c0ca35583af7ea4e784650a61',
            secret=
            'bf00575b1ad252b514f14b2c6171fe650d474091daad5eb6fa890ef24d581f65')

        # Application
        Trakt.configuration.defaults.app(name='trakt (for Plex)',
                                         version=PLUGIN_VERSION)

        # Http
        Trakt.base_url = (config.get('protocol', 'https') + '://' +
                          config.get('hostname', 'api.trakt.tv'))

        Trakt.configuration.defaults.http(timeout=timeout)

        # Configure keep-alive
        Trakt.http.keep_alive = config.get_boolean('keep_alive', True)

        # Configure requests adapter
        Trakt.http.adapter_kwargs = {
            'pool_connections':
            config.get_int('pool_connections', 10),
            'pool_maxsize':
            config.get_int('pool_size', 10),
            'max_retries':
            Retry(total=config.get_int('connect_retries', 3), read=0)
        }

        Trakt.http.rebuild()

        # Bind to events
        Trakt.on('oauth.refresh', cls.on_trakt_refresh)
        Trakt.on('oauth.refresh.rejected', cls.on_trakt_refresh_rejected)

        log.info(
            'Configured trakt.py (timeout=%r, base_url=%r, keep_alive=%r, adapter_kwargs=%r)',
            timeout,
            Trakt.base_url,
            Trakt.http.keep_alive,
            Trakt.http.adapter_kwargs,
        )

    @classmethod
    def on_trakt_refresh(cls, username, authorization):
        log.debug('[Trakt.tv] Token has been refreshed for %r', username)

        # Retrieve trakt account matching this `authorization`
        with Trakt.configuration.http(retry=True).oauth(
                token=authorization.get('access_token')):
            settings = Trakt['users/settings'].get(validate_token=False)

        if not settings:
            log.warn('[Trakt.tv] Unable to retrieve account details for token')
            return False

        # Retrieve trakt account username from `settings`
        s_username = settings.get('user', {}).get('username')

        if not s_username:
            log.warn('[Trakt.tv] Unable to retrieve username for token')
            return False

        if s_username != username:
            log.warn('[Trakt.tv] Token mismatch (%r != %r)', s_username,
                     username)
            return False

        # Find matching trakt account
        trakt_account = (TraktAccount.select().where(
            TraktAccount.username == username)).first()

        if not trakt_account:
            log.warn('[Trakt.tv] Unable to find account with the username: %r',
                     username)
            return False

        # Update OAuth credential
        TraktAccountManager.update.from_dict(
            trakt_account, {'authorization': {
                'oauth': authorization
            }},
            settings=settings)

        log.info('[Trakt.tv] Token updated for %r', trakt_account)
        return True

    @classmethod
    def on_trakt_refresh_rejected(cls, username):
        log.debug('[Trakt.tv] Token refresh for %r has been rejected',
                  username)

        # Find matching trakt account
        account = (TraktAccount.select().where(
            TraktAccount.username == username)).first()

        if not account:
            log.warn('[Trakt.tv] Unable to find account with the username: %r',
                     username)
            return False

        # Delete OAuth credential
        TraktOAuthCredentialManager.delete(account=account.id)

        log.info('[Trakt.tv] Token cleared for %r', account)
        return True

    def start(self):
        # Construct main thread
        spawn(self.run, daemon=True, thread_name='main')

    def run(self):
        # Check for authentication token
        log.info('X-Plex-Token: %s',
                 'available' if os.environ.get('PLEXTOKEN') else 'unavailable')

        # Process server startup state
        self.process_server_state()

        # Start new-style modules
        module_start()

        # Start modules
        names = []

        for module in self.modules:
            if not hasattr(module, 'start'):
                continue

            names.append(get_class_name(module))

            module.start()

        log.info('Started %s modules: %s', len(names), ', '.join(names))

        ModuleManager.start()

        # Start plex.activity.py
        Activity.start(ACTIVITY_MODE.get(Preferences.get('activity.mode')))

    @classmethod
    def process_server_state(cls):
        # Check startup state
        server = Plex.detail()

        if server is None:
            log.info('Unable to check startup state, detail request failed')
            return

        # Check server startup state
        if server.start_state is None:
            return

        if server.start_state == 'startingPlugins':
            return cls.on_starting_plugins()

        log.error('Unhandled server start state %r', server.start_state)

    @staticmethod
    def on_starting_plugins():
        log.debug('on_starting_plugins')

        SessionPrefix.increment()

    @classmethod
    def on_configuration_changed(cls):
        # Update proxies (for requests)
        cls.update_proxies()

        # Refresh loggers
        LoggerManager.refresh()

    @staticmethod
    def update_proxies():
        # Retrieve proxy host
        host = Prefs['proxy_host']

        if not host:
            if not Trakt.http.proxies and not os.environ.get(
                    'HTTP_PROXY') and not os.environ.get('HTTPS_PROXY'):
                return

            # Update trakt client
            Trakt.http.proxies = {}

            # Update environment variables
            if 'HTTP_PROXY' in os.environ:
                del os.environ['HTTP_PROXY']

            if 'HTTPS_PROXY' in os.environ:
                del os.environ['HTTPS_PROXY']

            log.info('HTTP Proxy has been disabled')
            return

        # Parse URL
        host_parsed = urlsplit(host)

        # Expand components
        scheme, netloc, path, query, fragment = host_parsed

        if not scheme:
            scheme = 'http'

        # Retrieve proxy credentials
        username = Prefs['proxy_username']
        password = Prefs['proxy_password']

        # Build URL
        if username and password and '@' not in netloc:
            netloc = '%s:%s@%s' % (quote_plus(username), quote_plus(password),
                                   netloc)

        url = urlunsplit((scheme, netloc, path, query, fragment))

        # Update trakt client
        Trakt.http.proxies = {'http': url, 'https': url}

        # Update environment variables
        os.environ.update({'HTTP_PROXY': url, 'HTTPS_PROXY': url})

        # Display message in log file
        if not host_parsed.username and not host_parsed.password:
            log.info('HTTP Proxy has been enabled (host: %r)', host)
        else:
            log.info('HTTP Proxy has been enabled (host: <sensitive>)')
Esempio n. 4
0
class Main(object):
    modules = [
        # pts
        Activity,
        Scrobbler,

        # sync
        SyncManager,

        # plex
        PlexMetadata
    ]

    loggers_allowed = [
        'requests',
        'trakt'
    ]

    def __init__(self):
        self.update_checker = UpdateChecker()
        self.session_manager = SessionManager()

        Header.show(self)

        if 'nowPlaying' in Dict and type(Dict['nowPlaying']) is dict:
            self.cleanup()
            Dict.Save()
        else:
            Dict['nowPlaying'] = dict()

        Main.update_config()

        self.init_logging()
        self.init_trakt()

        # Initialize modules
        for module in self.modules:
            if hasattr(module, 'initialize'):
                log.debug("Initializing module %s", module)
                module.initialize()

    @classmethod
    def init_logging(cls):
        logging.basicConfig(level=logging.DEBUG)

        for name in cls.loggers_allowed:
            logger = logging.getLogger(name)

            logger.setLevel(logging.DEBUG)
            logger.handlers = [PlexHandler()]

    @staticmethod
    def init_trakt():
        def get_credentials():
            password_hash = hashlib.sha1(Prefs['password'])

            return (
                Prefs['username'],
                password_hash.hexdigest()
            )

        Trakt.configure(
            # Application
            api_key='ba5aa61249c02dc5406232da20f6e768f3c82b28',

            # Version
            plugin_version=PLUGIN_VERSION,
            media_center_version=PlexMediaServer.get_version(),

            # Account
            credentials=get_credentials
        )

    @classmethod
    def update_config(cls, valid=None):
        preferences = Dict['preferences'] or {}

        # If no validation provided, use last stored result or assume true
        if valid is None:
            valid = preferences.get('valid', True)

        preferences['valid'] = valid

        Configuration.process(preferences)

        # Ensure preferences dictionary is stored
        Dict['preferences'] = preferences
        Dict.Save()

        log.info('Preferences updated %s', preferences)
        EventManager.fire('preferences.updated', preferences)

    @classmethod
    def validate_auth(cls, retry_interval=30):
        if not Prefs['username'] or not Prefs['password']:
            log.warn('Authentication failed, username or password field empty')

            cls.update_config(False)
            return False

        success = Trakt['account'].test()

        if not success:
            # status - False = invalid credentials, None = request failed
            if success is False:
                log.warn('Authentication failed, username or password is incorrect')
            else:
                # Increase retry interval each time to a maximum of 30 minutes
                if retry_interval < 60 * 30:
                    retry_interval = int(retry_interval * 1.3)

                # Ensure we never go over 30 minutes
                if retry_interval > 60 * 30:
                    retry_interval = 60 * 30

                log.warn('Unable to verify account details, will try again in %s seconds', retry_interval)
                schedule(cls.validate_auth, retry_interval, retry_interval)

            Main.update_config(False)
            return False

        log.info('Authentication successful')

        Main.update_config(True)
        return True

    def start(self):
        # Validate username/password
        spawn(self.validate_auth)

        # Check for updates
        self.update_checker.run_once(async=True)

        self.session_manager.start()

        # Start modules
        for module in self.modules:
            if hasattr(module, 'start'):
                log.debug("Starting module %s", module)
                module.start()

    @staticmethod
    def cleanup():
        Log.Debug('Cleaning up stale or invalid sessions')

        for key, session in Dict['nowPlaying'].items():
            delete = False

            # Destroy invalid sessions
            if type(session) is not dict:
                delete = True
            elif 'update_required' not in session:
                delete = True
            elif 'last_updated' not in session:
                delete = True
            elif type(session['last_updated']) is not datetime:
                delete = True
            elif total_seconds(datetime.now() - session['last_updated']) / 60 / 60 > 24:
                # Destroy sessions last updated over 24 hours ago
                Log.Debug('Session %s was last updated over 24 hours ago, queued for deletion', key)
                delete = True

            # Delete session or flag for update
            if delete:
                Log.Info('Session %s looks stale or invalid, deleting it now', key)
                del Dict['nowPlaying'][key]
            elif not session['update_required']:
                Log.Info('Queueing session %s for update', key)
                session['update_required'] = True

                # Update session in storage
                Dict['nowPlaying'][key] = session

        Log.Debug('Finished cleaning up')
Esempio n. 5
0
class Main:
    def __init__(self):
        # Check for updates first (required for use in header)
        self.update_checker = UpdateChecker()

        Header.show(self)

        if 'nowPlaying' in Dict and type(Dict['nowPlaying']) is dict:
            self.cleanup()
            Dict.Save()
        else:
            Dict['nowPlaying'] = dict()

        Main.update_config()

        self.session_manager = SessionManager()

        PlexActivity.on_update_collection.subscribe(self.update_collection)

    @staticmethod
    def update_config():
        if Prefs['start_scrobble'] and Prefs['username'] is not None:
            Log('Autostart scrobbling')
            Dict["scrobble"] = True
        else:
            Dict["scrobble"] = False

        if Prefs['new_sync_collection'] and Prefs['username'] is not None:
            Log('Automatically sync new Items to Collection')
            Dict["new_sync_collection"] = True
        else:
            Dict["new_sync_collection"] = False

    def start(self):
        # Start syncing
        if Prefs['sync_startup'] and Prefs['username'] is not None:
            Log('Will autosync in 1 minute')
            Thread.CreateTimer(60, SyncTrakt)

        # Get current server version and save it to dict.
        server_version = PMS.get_server_version()
        if server_version:
            Log('Server Version is %s' % server_version)
            Dict['server_version'] = server_version

        # Start the plex activity monitor
        Thread.Create(PlexActivity.run)

        self.update_checker.run_once(async=True)

        self.session_manager.start()

    @staticmethod
    def update_collection(item_id, action):
        # delay sync to wait for metadata
        Thread.CreateTimer(120, CollectionSync, True, item_id, 'add')

    @staticmethod
    def cleanup():
        Log.Debug('Cleaning up stale or invalid sessions')

        for key, session in Dict['nowPlaying'].items():
            delete = False

            # Destroy invalid sessions
            if type(session) is not dict:
                delete = True
            elif 'update_required' not in session:
                delete = True
            elif 'last_updated' not in session:
                delete = True
            elif type(session['last_updated']) is not datetime:
                delete = True
            elif total_seconds(datetime.now() - session['last_updated']) / 60 / 60 > 24:
                # Destroy sessions last updated over 24 hours ago
                Log.Debug('Session %s was last updated over 24 hours ago, queued for deletion', key)
                delete = True

            # Delete session or flag for update
            if delete:
                Log.Info('Session %s looks stale or invalid, deleting it now', key)
                del Dict['nowPlaying'][key]
            elif not session['update_required']:
                Log.Info('Queueing session %s for update', key)
                session['update_required'] = True

                # Update session in storage
                Dict['nowPlaying'][key] = session

        Log.Debug('Finished cleaning up')
Esempio n. 6
0
class Main(object):
    modules = [
        # pts
        Activity,
        Scrobbler,

        # sync
        SyncManager,

        # plex
        PlexMetadata
    ]

    def __init__(self):
        self.update_checker = UpdateChecker()
        self.session_manager = SessionManager()

        Header.show(self)

        if 'nowPlaying' in Dict and type(Dict['nowPlaying']) is dict:
            self.cleanup()
            Dict.Save()
        else:
            Dict['nowPlaying'] = dict()

        Main.update_config()

        # Initialize modules
        for module in self.modules:
            if hasattr(module, 'initialize'):
                log.debug("Initializing module %s", module)
                module.initialize()

    @classmethod
    def update_config(cls, valid=None):
        preferences = Dict['preferences'] or {}

        # If no validation provided, use last stored result or assume true
        if valid is None:
            valid = preferences.get('valid', True)

        preferences['valid'] = valid

        Configuration.process(preferences)

        # Ensure preferences dictionary is stored
        Dict['preferences'] = preferences
        Dict.Save()

        log.info('Preferences updated %s', preferences)
        EventManager.fire('preferences.updated', preferences)

    @classmethod
    def validate_auth(cls, retry_interval=30):
        if not Prefs['username'] or not Prefs['password']:
            log.warn('Authentication failed, username or password field empty')

            cls.update_config(False)
            return False

        status = Trakt.Account.test()

        if not status['success']:
            if status.get('status') == 'failure':
                log.warn('Authentication failed, username or password is incorrect (trakt returned: %s)', status['message'])
            else:
                # Increase retry interval each time to a maximum of 30 minutes
                if retry_interval < 60 * 30:
                    retry_interval = int(retry_interval * 1.3)

                # Ensure we never go over 30 minutes
                if retry_interval > 60 * 30:
                    retry_interval = 60 * 30

                log.warn('Unable to verify account details, will try again in %s seconds', retry_interval)
                schedule(cls.validate_auth, retry_interval, retry_interval)

            Main.update_config(False)
            return False

        log.info('Authentication successful')

        Main.update_config(True)
        return True

    def start(self):
        # Get current server version and save it to dict.
        server_version = PlexMediaServer.get_version()
        if server_version:
            Log('Server Version is %s' % server_version)
            Dict['server_version'] = server_version

        # Validate username/password
        spawn(self.validate_auth)

        # Check for updates
        self.update_checker.run_once(async=True)

        self.session_manager.start()

        # Start modules
        for module in self.modules:
            if hasattr(module, 'start'):
                log.debug("Starting module %s", module)
                module.start()

    @staticmethod
    def cleanup():
        Log.Debug('Cleaning up stale or invalid sessions')

        for key, session in Dict['nowPlaying'].items():
            delete = False

            # Destroy invalid sessions
            if type(session) is not dict:
                delete = True
            elif 'update_required' not in session:
                delete = True
            elif 'last_updated' not in session:
                delete = True
            elif type(session['last_updated']) is not datetime:
                delete = True
            elif total_seconds(datetime.now() - session['last_updated']) / 60 / 60 > 24:
                # Destroy sessions last updated over 24 hours ago
                Log.Debug('Session %s was last updated over 24 hours ago, queued for deletion', key)
                delete = True

            # Delete session or flag for update
            if delete:
                Log.Info('Session %s looks stale or invalid, deleting it now', key)
                del Dict['nowPlaying'][key]
            elif not session['update_required']:
                Log.Info('Queueing session %s for update', key)
                session['update_required'] = True

                # Update session in storage
                Dict['nowPlaying'][key] = session

        Log.Debug('Finished cleaning up')
Esempio n. 7
0
class Main(object):
    modules = [
        # core
        UpdateChecker()
    ]

    def __init__(self):
        Header.show(self)

        LoggerManager.refresh()

        self.init_trakt()
        self.init_plex()
        self.init()

        ModuleManager.initialize()

    def init(self):
        names = []

        # Initialize modules
        for module in self.modules:
            names.append(get_class_name(module))

            if hasattr(module, 'initialize'):
                module.initialize()

        log.info('Initialized %s modules: %s', len(names), ', '.join(names))

    @staticmethod
    def init_plex():
        # Ensure client identifier has been generated
        if not Dict['plex.client.identifier']:
            # Generate identifier
            Dict['plex.client.identifier'] = uuid.uuid4()

        # plex.py
        Plex.configuration.defaults.authentication(
            os.environ.get('PLEXTOKEN')
        )

        Plex.configuration.defaults.client(
            identifier=Dict['plex.client.identifier'],

            product='trakt (for Plex)',
            version=PLUGIN_VERSION
        )

        # plex.activity.py
        path = os.path.join(LOG_HANDLER.baseFilename, '..', '..', 'Plex Media Server.log')
        path = os.path.abspath(path)

        Activity['logging'].add_hint(path)

        # plex.metadata.py
        Metadata.configure(
            cache=CacheManager.get(
                'plex.metadata',
                serializer='pickle:///?protocol=2'
            ),
            client=Plex.client
        )

    @classmethod
    def init_trakt(cls):
        # Client
        Trakt.configuration.defaults.client(
            id='c9ccd3684988a7862a8542ae0000535e0fbd2d1c0ca35583af7ea4e784650a61',
            secret='bf00575b1ad252b514f14b2c6171fe650d474091daad5eb6fa890ef24d581f65'
        )

        # Application
        Trakt.configuration.defaults.app(
            name='trakt (for Plex)',
            version=PLUGIN_VERSION
        )

        # Setup request retrying
        Trakt.http.adapter_kwargs = {'max_retries': Retry(total=3, read=0)}
        Trakt.http.rebuild()

        Trakt.on('oauth.token_refreshed', cls.on_token_refreshed)

    @classmethod
    def on_token_refreshed(cls, authorization):
        log.debug('Authentication - PIN authorization refreshed')

        # Retrieve trakt account matching this `authorization`
        with Trakt.configuration.http(retry=True).oauth(token=authorization.get('access_token')):
            settings = Trakt['users/settings'].get()

        if not settings:
            log.warn('Authentication - Unable to retrieve account details for authorization')
            return

        # Retrieve trakt account username from `settings`
        username = settings.get('user', {}).get('username')

        if not username:
            log.warn('Authentication - Unable to retrieve username for authorization')
            return None

        # Find matching trakt account
        trakt_account = (TraktAccount
            .select()
            .where(
                TraktAccount.username == username
            )
        ).first()

        if not trakt_account:
            log.warn('Authentication - Unable to find TraktAccount with the username %r', username)

        # Update oauth credential
        TraktAccountManager.update.from_dict(trakt_account, {
            'authorization': {
                'oauth': authorization
            }
        })

        log.info('Authentication - Updated OAuth credential for %r', trakt_account)

    def start(self):
        # Construct main thread
        spawn(self.run, thread_name='main')

    def run(self):
        # Check for authentication token
        log.info('X-Plex-Token: %s', 'available' if os.environ.get('PLEXTOKEN') else 'unavailable')

        # Process server startup state
        self.process_server_state()

        # Start new-style modules
        module_start()

        # Start modules
        names = []

        for module in self.modules:
            if not hasattr(module, 'start'):
                continue

            names.append(get_class_name(module))

            module.start()

        log.info('Started %s modules: %s', len(names), ', '.join(names))

        ModuleManager.start()

        # Start plex.activity.py
        Activity.start(ACTIVITY_MODE.get(Preferences.get('activity.mode')))

    @classmethod
    def process_server_state(cls):
        # Check startup state
        server = Plex.detail()

        if server is None:
            log.info('Unable to check startup state, detail request failed')
            return

        # Check server startup state
        if server.start_state is None:
            return

        if server.start_state == 'startingPlugins':
            return cls.on_starting_plugins()

        log.error('Unhandled server start state %r', server.start_state)

    @staticmethod
    def on_starting_plugins():
        log.debug('on_starting_plugins')

        SessionPrefix.increment()

    @staticmethod
    def on_configuration_changed():
        LoggerManager.refresh()