Beispiel #1
0
    def run(self, force=False):

        self.amActive = True
        refresh_shows = []  # A list of shows, that need to be refreshed
        season_updates = []  # A list of show seasons that have passed their next_update timestamp
        update_max_weeks = 12

        network_timezones.update_network_dict()

        # Refresh the exceptions_cache from db.
        refresh_exceptions_cache()

        logger.info(u'Started periodic show updates')

        # Cache for the indexers list of updated show
        indexer_updated_shows = {}
        # Cache for the last indexer update timestamp
        last_updates = {}

        # Loop through the list of shows, and per show evaluate if we can use the .get_last_updated_seasons()
        for show in app.showList:
            if show.paused:
                logger.info(u'The show {show} is paused, not updating it.', show=show.name)
                continue

            indexer_api_params = indexerApi(show.indexer).api_params.copy()
            try:
                indexer_api = indexerApi(show.indexer).indexer(**indexer_api_params)
            except IndexerUnavailable:
                logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                               u'connectivity issues. While trying to look for show updates on show: {show}',
                               indexer_name=indexerApi(show.indexer).name, show=show.name)
                continue

            # Get the lastUpdate timestamp for this indexer.
            if indexerApi(show.indexer).name not in last_updates:
                last_updates[indexerApi(show.indexer).name] = \
                    self.update_cache.get_last_indexer_update(indexerApi(show.indexer).name)
            last_update = last_updates[indexerApi(show.indexer).name]

            # Get a list of updated shows from the indexer, since last update.
            # Use the list, to limit the shows for which are requested for the last updated seasons.
            if last_update and last_update > time.time() - (604800 * update_max_weeks):
                if show.indexer not in indexer_updated_shows:
                    try:
                        indexer_updated_shows[show.indexer] = indexer_api.get_last_updated_series(
                            last_update, update_max_weeks
                        )
                    except IndexerUnavailable:
                        logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                                       u'connectivity issues while trying to look for show updates on show: {show}',
                                       indexer_name=indexerApi(show.indexer).name, show=show.name)
                        continue
                    except IndexerException as error:
                        logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                                       u'issues while trying to get updates for show {show}. Cause: {cause}',
                                       indexer_name=indexerApi(show.indexer).name, show=show.name, cause=error.message)
                        continue
                    except RequestException as error:
                        logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                                       u'issues while trying to get updates for show {show}. ',
                                       indexer_name=indexerApi(show.indexer).name, show=show.name)

                        if isinstance(error, HTTPError):
                            if error.response.status_code == 503:
                                logger.warning(u'API Service offline: '
                                               u'This service is temporarily offline, try again later.')
                            elif error.response.status_code == 429:
                                logger.warning(u'Your request count (#) is over the allowed limit of (40).')

                        logger.warning(u'Cause: {cause}.', cause=error)
                        continue
                    except Exception as error:
                        logger.exception(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                                         u'issues while trying to get updates for show {show}. Cause: {cause}.',
                                         indexer_name=indexerApi(show.indexer).name, show=show.name, cause=error)
                        continue

                # If the current show is not in the list, move on to the next.
                # Only do this for shows, if the indexer has had a successful update run within the last 12 weeks.
                if all([isinstance(indexer_updated_shows[show.indexer], list),
                        show.indexerid not in indexer_updated_shows.get(show.indexer)]):
                    logger.debug(u'Skipping show update for {show}. As the show is not '
                                 u'in the indexers {indexer_name} list with updated '
                                 u'shows within the last {weeks} weeks.', show=show.name,
                                 indexer_name=indexerApi(show.indexer).name, weeks=update_max_weeks)
                    continue

            # These are the criteria for performing a full show refresh.
            if any([not hasattr(indexer_api, 'get_last_updated_seasons'),
                    not last_update,
                    last_update < time.time() - 604800 * update_max_weeks]):
                # no entry in lastUpdate, or last update was too long ago,
                # let's refresh the show for this indexer
                logger.debug(u'Trying to update {show}. Your lastUpdate for {indexer_name} is older then {weeks} weeks,'
                             u" or the indexer doesn't support per season updates. Doing a full update.",
                             show=show.name, indexer_name=indexerApi(show.indexer).name,
                             weeks=update_max_weeks)
                refresh_shows.append(show)

            # Else fall back to per season updates.
            elif hasattr(indexer_api, 'get_last_updated_seasons'):
                # Get updated seasons and add them to the season update list.
                try:
                    updated_seasons = indexer_api.get_last_updated_seasons([show.indexerid], last_update,
                                                                           update_max_weeks)
                except IndexerUnavailable:
                    logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                                   u'connectivity issues while trying to look for showupdates on show: {show}',
                                   indexer_name=indexerApi(show.indexer).name, show=show.name)
                    continue
                except IndexerException as e:
                    logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                                   u'issues while trying to get updates for show {show}. Cause: {cause}',
                                   indexer_name=indexerApi(show.indexer).name, show=show.name, cause=e.message)
                    continue
                except Exception as e:
                    logger.exception(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                                     u'issues while trying to get updates for show {show}. Cause: {cause}',
                                     indexer_name=indexerApi(show.indexer).name, show=show.name, cause=e)
                    continue

                if updated_seasons[show.indexerid]:
                    logger.info(u'{show_name}: Adding the following seasons for update to queue: {seasons}',
                                show_name=show.name, seasons=updated_seasons[show.indexerid])
                    for season in updated_seasons[show.indexerid]:
                        season_updates.append((show.indexer, show, season))

        pi_list = []

        # Full refreshes
        for show in refresh_shows:
            # If the cur_show is not 'paused' then add to the show_queue_scheduler
            if not show.paused:
                logger.info(u'Full update on show: {show}', show=show.name)
                try:
                    pi_list.append(app.show_queue_scheduler.action.updateShow(show))
                except (CantUpdateShowException, CantRefreshShowException) as e:
                    logger.warning(u'Automatic update failed. Error: {error}', error=e)
                except Exception as e:
                    logger.error(u'Automatic update failed: Error: {error}', error=e)
            else:
                logger.info(u'Show update skipped, show: {show} is paused.', show=show.name)

        # Only update expired season
        for show in season_updates:
            # If the cur_show is not 'paused' then add to the show_queue_scheduler
            if not show[1].paused:
                logger.info(u'Updating season {season} for show: {show}.', season=show[2], show=show[1].name)
                try:
                    pi_list.append(app.show_queue_scheduler.action.updateShow(show[1], season=show[2]))
                except CantUpdateShowException as e:
                    logger.warning(u'Automatic update failed. Error: {error}', error=e)
                except Exception as e:
                    logger.error(u'Automatic update failed: Error: {error}', error=e)
            else:
                logger.info(u'Show update skipped, show: {show} is paused.', show=show[1].name)

        ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator('Daily Update', pi_list))

        # Only refresh updated shows that have been updated using the season updates.
        # The full refreshed shows, are updated from the queueItem.
        for show in set(show[1] for show in season_updates):
            if not show.paused:
                try:
                    app.show_queue_scheduler.action.refreshShow(show, True)
                except CantRefreshShowException as e:
                    logger.warning(u'Show refresh on show {show_name} failed. Error: {error}',
                                   show_name=show.name, error=e)
                except Exception as e:
                    logger.error(u'Show refresh on show {show_name} failed: Unexpected Error: {error}',
                                 show_name=show.name, error=e)
            else:
                logger.info(u'Show refresh skipped, show: {show_name} is paused.', show_name=show.name)

        if refresh_shows or season_updates:
            for indexer in set([show.indexer for show in refresh_shows] + [s[1].indexer for s in season_updates]):
                indexer_api = indexerApi(indexer)
                self.update_cache.set_last_indexer_update(indexer_api.name)
                logger.info(u'Updated lastUpdate timestamp for {indexer_name}', indexer_name=indexer_api.name)
            logger.info(u'Completed scheduling updates on shows')
        else:
            logger.info(u'Completed but there was nothing to update')

        self.amActive = False
Beispiel #2
0
    def start(self, args):  # pylint: disable=too-many-branches,too-many-statements
        """
        Start Application
        """
        # do some preliminary stuff
        app.MY_FULLNAME = ek(os.path.normpath, ek(os.path.abspath, __file__))
        app.MY_NAME = ek(os.path.basename, app.MY_FULLNAME)
        app.PROG_DIR = ek(os.path.dirname, app.MY_FULLNAME)
        app.DATA_DIR = app.PROG_DIR
        app.MY_ARGS = args

        try:
            locale.setlocale(locale.LC_ALL, '')
            app.SYS_ENCODING = locale.getpreferredencoding()
        except (locale.Error, IOError):
            app.SYS_ENCODING = 'UTF-8'

        # pylint: disable=no-member
        if not app.SYS_ENCODING or app.SYS_ENCODING.lower() in ('ansi_x3.4-1968', 'us-ascii', 'ascii', 'charmap') or \
                (sys.platform.startswith('win') and sys.getwindowsversion()[0] >= 6 and str(getattr(sys.stdout, 'device', sys.stdout).encoding).lower() in ('cp65001', 'charmap')):
            app.SYS_ENCODING = 'UTF-8'

        # TODO: Continue working on making this unnecessary, this hack creates all sorts of hellish problems
        if not hasattr(sys, 'setdefaultencoding'):
            reload(sys)

        try:
            # On non-unicode builds this will raise an AttributeError, if encoding type is not valid it throws a LookupError
            sys.setdefaultencoding(app.SYS_ENCODING)  # pylint: disable=no-member
        except (AttributeError, LookupError):
            sys.exit(
                'Sorry, you MUST add the Medusa folder to the PYTHONPATH environment variable\n'
                'or find another way to force Python to use %s for string encoding.'
                % app.SYS_ENCODING)

        # Need console logging for SickBeard.py and SickBeard-console.exe
        self.console_logging = (not hasattr(
            sys, 'frozen')) or (app.MY_NAME.lower().find('-console') > 0)

        # Rename the main thread
        threading.currentThread().name = 'MAIN'

        try:
            opts, _ = getopt.getopt(args, 'hqdp::', [
                'help', 'quiet', 'nolaunch', 'daemon', 'pidfile=', 'port=',
                'datadir=', 'config=', 'noresize'
            ])
        except getopt.GetoptError:
            sys.exit(self.help_message())

        for option, value in opts:
            # Prints help message
            if option in ('-h', '--help'):
                sys.exit(self.help_message())

            # For now we'll just silence the logging
            if option in ('-q', '--quiet'):
                self.console_logging = False

            # Suppress launching web browser
            # Needed for OSes without default browser assigned
            # Prevent duplicate browser window when restarting in the app
            if option in ('--nolaunch', ):
                self.no_launch = True

            # Override default/configured port
            if option in ('-p', '--port'):
                try:
                    self.forced_port = int(value)
                except ValueError:
                    sys.exit('Port: %s is not a number. Exiting.' % value)

            # Run as a double forked daemon
            if option in ('-d', '--daemon'):
                self.run_as_daemon = True
                # When running as daemon disable console_logging and don't start browser
                self.console_logging = False
                self.no_launch = True

                if sys.platform == 'win32' or sys.platform == 'darwin':
                    self.run_as_daemon = False

            # Write a pid file if requested
            if option in ('--pidfile', ):
                self.create_pid = True
                self.pid_file = str(value)

                # If the pid file already exists, Medusa may still be running, so exit
                if ek(os.path.exists, self.pid_file):
                    sys.exit('PID file: %s already exists. Exiting.' %
                             self.pid_file)

            # Specify folder to load the config file from
            if option in ('--config', ):
                app.CONFIG_FILE = ek(os.path.abspath, value)

            # Specify folder to use as the data directory
            if option in ('--datadir', ):
                app.DATA_DIR = ek(os.path.abspath, value)

            # Prevent resizing of the banner/posters even if PIL is installed
            if option in ('--noresize', ):
                app.NO_RESIZE = True

        # The pid file is only useful in daemon mode, make sure we can write the file properly
        if self.create_pid:
            if self.run_as_daemon:
                pid_dir = ek(os.path.dirname, self.pid_file)
                if not ek(os.access, pid_dir, os.F_OK):
                    sys.exit('PID dir: %s doesn\'t exist. Exiting.' % pid_dir)
                if not ek(os.access, pid_dir, os.W_OK):
                    sys.exit(
                        'PID dir: %s must be writable (write permissions). Exiting.'
                        % pid_dir)

            else:
                if self.console_logging:
                    sys.stdout.write(
                        'Not running in daemon mode. PID file creation disabled.\n'
                    )

                self.create_pid = False

        # If they don't specify a config file then put it in the data dir
        if not app.CONFIG_FILE:
            app.CONFIG_FILE = ek(os.path.join, app.DATA_DIR, 'config.ini')

        # Make sure that we can create the data dir
        if not ek(os.access, app.DATA_DIR, os.F_OK):
            try:
                ek(os.makedirs, app.DATA_DIR, 0o744)
            except os.error:
                raise SystemExit('Unable to create data directory: %s' %
                                 app.DATA_DIR)

        # Make sure we can write to the data dir
        if not ek(os.access, app.DATA_DIR, os.W_OK):
            raise SystemExit('Data directory must be writeable: %s' %
                             app.DATA_DIR)

        # Make sure we can write to the config file
        if not ek(os.access, app.CONFIG_FILE, os.W_OK):
            if ek(os.path.isfile, app.CONFIG_FILE):
                raise SystemExit('Config file must be writeable: %s' %
                                 app.CONFIG_FILE)
            elif not ek(os.access, ek(os.path.dirname, app.CONFIG_FILE),
                        os.W_OK):
                raise SystemExit('Config file root dir must be writeable: %s' %
                                 ek(os.path.dirname, app.CONFIG_FILE))

        ek(os.chdir, app.DATA_DIR)

        # Check if we need to perform a restore first
        restore_dir = ek(os.path.join, app.DATA_DIR, 'restore')
        if ek(os.path.exists, restore_dir):
            success = self.restore_db(restore_dir, app.DATA_DIR)
            if self.console_logging:
                sys.stdout.write('Restore: restoring DB and config.ini %s!\n' %
                                 ('FAILED', 'SUCCESSFUL')[success])

        # Load the config and publish it to the application package
        if self.console_logging and not ek(os.path.isfile, app.CONFIG_FILE):
            sys.stdout.write(
                'Unable to find %s, all settings will be default!\n' %
                app.CONFIG_FILE)

        app.CFG = ConfigObj(app.CONFIG_FILE)

        # Initialize the config and our threads
        app.initialize(consoleLogging=self.console_logging)

        if self.run_as_daemon:
            self.daemonize()

        # Get PID
        app.PID = os.getpid()

        # Build from the DB to start with
        self.load_shows_from_db()

        logger.log('Starting Medusa [{branch}] using \'{config}\''.format(
            branch=app.BRANCH, config=app.CONFIG_FILE))

        self.clear_cache()

        if self.forced_port:
            logger.log('Forcing web server to port {port}'.format(
                port=self.forced_port))
            self.start_port = self.forced_port
        else:
            self.start_port = app.WEB_PORT

        if app.WEB_LOG:
            self.log_dir = app.LOG_DIR
        else:
            self.log_dir = None

        # app.WEB_HOST is available as a configuration value in various
        # places but is not configurable. It is supported here for historic reasons.
        if app.WEB_HOST and app.WEB_HOST != '0.0.0.0':
            self.web_host = app.WEB_HOST
        else:
            self.web_host = '' if app.WEB_IPV6 else '0.0.0.0'

        # web server options
        self.web_options = {
            'port': int(self.start_port),
            'host': self.web_host,
            'data_root': ek(os.path.join, app.PROG_DIR, 'gui', app.GUI_NAME),
            'web_root': app.WEB_ROOT,
            'log_dir': self.log_dir,
            'username': app.WEB_USERNAME,
            'password': app.WEB_PASSWORD,
            'enable_https': app.ENABLE_HTTPS,
            'handle_reverse_proxy': app.HANDLE_REVERSE_PROXY,
            'https_cert': ek(os.path.join, app.PROG_DIR, app.HTTPS_CERT),
            'https_key': ek(os.path.join, app.PROG_DIR, app.HTTPS_KEY),
        }

        # start web server
        self.web_server = SRWebServer(self.web_options)
        self.web_server.start()

        # Fire up all our threads
        app.start()

        # Build internal name cache
        name_cache.buildNameCache()

        # Pre-populate network timezones, it isn't thread safe
        network_timezones.update_network_dict()

        # sure, why not?
        if app.USE_FAILED_DOWNLOADS:
            failed_history.trimHistory()

        # # Check for metadata indexer updates for shows (Disabled until we use api)
        # app.showUpdateScheduler.forceRun()

        # Launch browser
        if app.LAUNCH_BROWSER and not (self.no_launch or self.run_as_daemon):
            app.launchBrowser('https' if app.ENABLE_HTTPS else 'http',
                              self.start_port, app.WEB_ROOT)

        # main loop
        while True:
            time.sleep(1)
Beispiel #3
0
    def run(self, force=False):
        """
        Run the daily searcher, queuing selected episodes for search.

        :param force: Force search
        """
        if self.amActive:
            log.debug('Daily search is still running, not starting it again')
            return
        elif app.forced_search_queue_scheduler.action.is_forced_search_in_progress() and not force:
            log.warning('Manual search is running. Unable to start Daily search')
            return

        self.amActive = True
        # Let's keep track of the exact time the scheduler kicked in,
        # as we need to compare to this time for each provider.
        scheduler_start_time = int(time())

        if not network_dict:
            update_network_dict()

        # The tvshows airdate_offset field is used to configure a search offset for specific shows.
        # This way we can search/accept results early or late, depending on the value.
        main_db_con = DBConnection()
        min_offset_show = main_db_con.select(
            'SELECT COUNT(*) as offsets, MIN(airdate_offset) AS min_offset '
            'FROM tv_shows '
            'WHERE paused = 0 AND airdate_offset < 0'
        )
        additional_search_offset = 0
        if min_offset_show and min_offset_show[0]['offsets'] > 0:
            additional_search_offset = int(ceil(abs(min_offset_show[0]['min_offset']) / 24.0))
            log.debug('Using an airdate offset of {min_offset_show} as we found show(s) with an airdate'
                      ' offset configured.', {'min_offset_show': min_offset_show[0]['min_offset']})

        cur_time = datetime.now(app_timezone)

        cur_date = (
            date.today() + timedelta(days=1 if network_dict else 2) + timedelta(days=additional_search_offset)
        ).toordinal()

        episodes_from_db = main_db_con.select(
            'SELECT indexer, showid, airdate, season, episode '
            'FROM tv_episodes '
            'WHERE status = ? AND (airdate <= ? and airdate > 1)',
            [common.UNAIRED, cur_date]
        )

        new_releases = []
        series_obj = None

        for db_episode in episodes_from_db:
            indexer_id = db_episode['indexer']
            series_id = db_episode['showid']
            try:
                if not series_obj or series_id != series_obj.indexerid:
                    series_obj = Show.find_by_id(app.showList, indexer_id, series_id)

                # for when there is orphaned series in the database but not loaded into our show list
                if not series_obj or series_obj.paused:
                    continue

            except MultipleShowObjectsException:
                log.info('ERROR: expected to find a single show matching {id}',
                         {'id': series_id})
                continue

            cur_ep = series_obj.get_episode(db_episode['season'], db_episode['episode'])

            if series_obj.airs and series_obj.network:
                # This is how you assure it is always converted to local time
                show_air_time = parse_date_time(db_episode['airdate'], series_obj.airs, series_obj.network)
                end_time = show_air_time.astimezone(app_timezone) + timedelta(minutes=try_int(series_obj.runtime, 60))

                if series_obj.airdate_offset != 0:
                    log.debug(
                        '{show}: Applying an airdate offset for the episode: {episode} of {offset} hours',
                        {'show': series_obj.name, 'episode': cur_ep.pretty_name(), 'offset': series_obj.airdate_offset})

                # filter out any episodes that haven't finished airing yet
                if end_time + timedelta(hours=series_obj.airdate_offset) > cur_time:
                    continue

            with cur_ep.lock:
                cur_ep.status = series_obj.default_ep_status if cur_ep.season else common.SKIPPED
                log.info(
                    'Setting status ({status}) for show airing today: {name} {special}', {
                        'name': cur_ep.pretty_name(),
                        'status': common.statusStrings[cur_ep.status],
                        'special': '(specials are not supported)' if not cur_ep.season else '',
                    }
                )
                new_releases.append(cur_ep.get_sql())

        if new_releases:
            main_db_con = DBConnection()
            main_db_con.mass_action(new_releases)

        # queue a daily search
        app.search_queue_scheduler.action.add_item(
            DailySearchQueueItem(scheduler_start_time, force=force)
        )

        self.amActive = False
Beispiel #4
0
    def run(self, force=False):  # pylint:disable=too-many-branches
        """
        Run the daily searcher, queuing selected episodes for search.

        :param force: Force search
        """
        if self.amActive:
            log.debug('Daily search is still running, not starting it again')
            return
        elif app.forced_search_queue_scheduler.action.is_forced_search_in_progress(
        ) and not force:
            log.warning(
                'Manual search is running. Unable to start Daily search')
            return

        self.amActive = True

        if not network_dict:
            update_network_dict()

        cur_time = datetime.now(app_timezone)
        cur_date = (date.today() +
                    timedelta(days=1 if network_dict else 2)).toordinal()

        main_db_con = DBConnection()
        episodes_from_db = main_db_con.select(
            b'SELECT indexer, showid, airdate, season, episode '
            b'FROM tv_episodes '
            b'WHERE status = ? AND (airdate <= ? and airdate > 1)',
            [common.UNAIRED, cur_date])

        new_releases = []
        series_obj = None

        for db_episode in episodes_from_db:
            indexer_id = db_episode[b'indexer']
            series_id = db_episode[b'showid']
            try:
                if not series_obj or series_id != series_obj.indexerid:
                    series_obj = Show.find_by_id(app.showList, indexer_id,
                                                 series_id)

                # for when there is orphaned series in the database but not loaded into our show list
                if not series_obj or series_obj.paused:
                    continue

            except MultipleShowObjectsException:
                log.info('ERROR: expected to find a single show matching {id}',
                         {'id': series_id})
                continue

            if series_obj.airs and series_obj.network:
                # This is how you assure it is always converted to local time
                show_air_time = parse_date_time(db_episode[b'airdate'],
                                                series_obj.airs,
                                                series_obj.network)
                end_time = show_air_time.astimezone(app_timezone) + timedelta(
                    minutes=try_int(series_obj.runtime, 60))

                # filter out any episodes that haven't finished airing yet,
                if end_time > cur_time:
                    continue

            cur_ep = series_obj.get_episode(db_episode[b'season'],
                                            db_episode[b'episode'])
            with cur_ep.lock:
                cur_ep.status = series_obj.default_ep_status if cur_ep.season else common.SKIPPED
                log.info(
                    'Setting status ({status}) for show airing today: {name} {special}',
                    {
                        'name':
                        cur_ep.pretty_name(),
                        'status':
                        common.statusStrings[cur_ep.status],
                        'special':
                        '(specials are not supported)'
                        if not cur_ep.season else '',
                    })
                new_releases.append(cur_ep.get_sql())

        if new_releases:
            main_db_con = DBConnection()
            main_db_con.mass_action(new_releases)

        # queue episode for daily search
        app.search_queue_scheduler.action.add_item(
            DailySearchQueueItem(force=force))

        self.amActive = False
Beispiel #5
0
    def run(self, force=False):
        """
        Run the daily searcher, queuing selected episodes for search.

        :param force: Force search
        """
        if self.amActive:
            log.debug('Daily search is still running, not starting it again')
            return
        elif app.forced_search_queue_scheduler.action.is_forced_search_in_progress(
        ) and not force:
            log.warning(
                'Manual search is running. Unable to start Daily search')
            return

        self.amActive = True
        # Let's keep track of the exact time the scheduler kicked in,
        # as we need to compare to this time for each provider.
        scheduler_start_time = int(time())

        if not network_dict:
            update_network_dict()

        # The tvshows airdate_offset field is used to configure a search offset for specific shows.
        # This way we can search/accept results early or late, depending on the value.
        main_db_con = DBConnection()
        min_offset_show = main_db_con.select(
            'SELECT COUNT(*) as offsets, MIN(airdate_offset) AS min_offset '
            'FROM tv_shows '
            'WHERE paused = 0 AND airdate_offset < 0')
        additional_search_offset = 0
        if min_offset_show and min_offset_show[0]['offsets'] > 0:
            additional_search_offset = int(
                ceil(abs(min_offset_show[0]['min_offset']) / 24.0))
            log.debug(
                'Using an airdate offset of {min_offset_show} as we found show(s) with an airdate'
                ' offset configured.',
                {'min_offset_show': min_offset_show[0]['min_offset']})

        cur_time = datetime.now(app_timezone)

        cur_date = (date.today() + timedelta(days=1 if network_dict else 2) +
                    timedelta(days=additional_search_offset)).toordinal()

        episodes_from_db = main_db_con.select(
            'SELECT indexer, showid, airdate, season, episode '
            'FROM tv_episodes '
            'WHERE status = ? AND (airdate <= ? and airdate > 1)',
            [common.UNAIRED, cur_date])

        new_releases = []
        series_obj = None

        for db_episode in episodes_from_db:
            indexer_id = db_episode['indexer']
            series_id = db_episode['showid']
            try:
                if not series_obj or series_id != series_obj.indexerid:
                    series_obj = Show.find_by_id(app.showList, indexer_id,
                                                 series_id)

                # for when there is orphaned series in the database but not loaded into our show list
                if not series_obj or series_obj.paused:
                    continue

            except MultipleShowObjectsException:
                log.info('ERROR: expected to find a single show matching {id}',
                         {'id': series_id})
                continue

            cur_ep = series_obj.get_episode(db_episode['season'],
                                            db_episode['episode'])

            if series_obj.airs and series_obj.network:
                # This is how you assure it is always converted to local time
                show_air_time = parse_date_time(db_episode['airdate'],
                                                series_obj.airs,
                                                series_obj.network)
                end_time = show_air_time.astimezone(app_timezone) + timedelta(
                    minutes=try_int(series_obj.runtime, 60))

                if series_obj.airdate_offset != 0:
                    log.debug(
                        '{show}: Applying an airdate offset for the episode: {episode} of {offset} hours',
                        {
                            'show': series_obj.name,
                            'episode': cur_ep.pretty_name(),
                            'offset': series_obj.airdate_offset
                        })

                # filter out any episodes that haven't finished airing yet
                if end_time + timedelta(
                        hours=series_obj.airdate_offset) > cur_time:
                    continue

            with cur_ep.lock:
                cur_ep.status = series_obj.default_ep_status if cur_ep.season else common.SKIPPED
                log.info(
                    'Setting status ({status}) for show airing today: {name} {special}',
                    {
                        'name':
                        cur_ep.pretty_name(),
                        'status':
                        common.statusStrings[cur_ep.status],
                        'special':
                        '(specials are not supported)'
                        if not cur_ep.season else '',
                    })
                new_releases.append(cur_ep.get_sql())

        if new_releases:
            main_db_con = DBConnection()
            main_db_con.mass_action(new_releases)

        # queue a daily search
        app.search_queue_scheduler.action.add_item(
            DailySearchQueueItem(scheduler_start_time, force=force))

        self.amActive = False
Beispiel #6
0
    def run(self, force=False):

        self.amActive = True
        refresh_shows = []  # A list of shows, that need to be refreshed
        season_updates = []  # A list of show seasons that have passed their next_update timestamp
        update_max_weeks = 12

        network_timezones.update_network_dict()

        # Refresh the exceptions_cache from db.
        refresh_exceptions_cache()

        logger.info(u'Started periodic show updates')

        # Cache for the indexers list of updated show
        indexer_updated_shows = {}
        # Cache for the last indexer update timestamp
        last_updates = {}

        # Loop through the list of shows, and per show evaluate if we can use the .get_last_updated_seasons()
        for show in app.showList:
            if show.paused:
                logger.info(u'The show {show} is paused, not updating it.', show=show.name)
                continue

            indexer_api_params = indexerApi(show.indexer).api_params.copy()
            try:
                indexer_api = indexerApi(show.indexer).indexer(**indexer_api_params)
            except IndexerUnavailable:
                logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                               u'connectivity issues. While trying to look for show updates on show: {show}',
                               indexer_name=indexerApi(show.indexer).name, show=show.name)
                continue

            # Get the lastUpdate timestamp for this indexer.
            if indexerApi(show.indexer).name not in last_updates:
                last_updates[indexerApi(show.indexer).name] = \
                    self.update_cache.get_last_indexer_update(indexerApi(show.indexer).name)
            last_update = last_updates[indexerApi(show.indexer).name]

            # Get a list of updated shows from the indexer, since last update.
            # Use the list, to limit the shows for which are requested for the last updated seasons.
            if last_update and last_update > time.time() - (604800 * update_max_weeks):
                if show.indexer not in indexer_updated_shows:
                    try:
                        indexer_updated_shows[show.indexer] = indexer_api.get_last_updated_series(
                            last_update, update_max_weeks
                        )
                    except IndexerUnavailable:
                        logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                                       u'connectivity issues while trying to look for show updates on show: {show}',
                                       indexer_name=indexerApi(show.indexer).name, show=show.name)
                        continue
                    except IndexerException as error:
                        logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                                       u'issues while trying to get updates for show {show}. Cause: {cause!r}',
                                       indexer_name=indexerApi(show.indexer).name, show=show.name, cause=error)
                        continue
                    except RequestException as error:
                        logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                                       u'issues while trying to get updates for show {show}. Cause: {cause!r}',
                                       indexer_name=indexerApi(show.indexer).name, show=show.name, cause=error)

                        if isinstance(error, HTTPError):
                            if error.response.status_code == 503:
                                logger.warning(u'API Service offline: '
                                               u'This service is temporarily offline, try again later.')
                            elif error.response.status_code == 429:
                                logger.warning(u'Your request count (#) is over the allowed limit of (40).')
                        continue
                    except Exception as error:
                        logger.exception(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                                         u'issues while trying to get updates for show {show}. Cause: {cause!r}.',
                                         indexer_name=indexerApi(show.indexer).name, show=show.name, cause=error)
                        continue

                # If the current show is not in the list, move on to the next.
                # Only do this for shows, if the indexer has had a successful update run within the last 12 weeks.
                if all([isinstance(indexer_updated_shows[show.indexer], list),
                        show.indexerid not in indexer_updated_shows.get(show.indexer)]):
                    logger.debug(u'Skipping show update for {show}. As the show is not '
                                 u'in the indexers {indexer_name} list with updated '
                                 u'shows within the last {weeks} weeks.', show=show.name,
                                 indexer_name=indexerApi(show.indexer).name, weeks=update_max_weeks)
                    continue

            # These are the criteria for performing a full show refresh.
            if any([not hasattr(indexer_api, 'get_last_updated_seasons'),
                    not last_update or last_update < time.time() - 604800 * update_max_weeks]):
                # no entry in lastUpdate, or last update was too long ago,
                # let's refresh the show for this indexer
                logger.debug(u'Trying to update {show}. Your lastUpdate for {indexer_name} is older then {weeks} weeks,'
                             u" or the indexer doesn't support per season updates. Doing a full update.",
                             show=show.name, indexer_name=indexerApi(show.indexer).name,
                             weeks=update_max_weeks)
                refresh_shows.append(show)

            # Else fall back to per season updates.
            elif hasattr(indexer_api, 'get_last_updated_seasons'):
                # Get updated seasons and add them to the season update list.
                try:
                    updated_seasons = indexer_api.get_last_updated_seasons([show.indexerid], last_update,
                                                                           update_max_weeks)
                except IndexerUnavailable:
                    logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                                   u'connectivity issues while trying to look for showupdates on show: {show}',
                                   indexer_name=indexerApi(show.indexer).name, show=show.name)
                    continue
                except IndexerException as error:
                    logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                                   u'issues while trying to get updates for show {show}. Cause: {cause!r}',
                                   indexer_name=indexerApi(show.indexer).name, show=show.name, cause=error)
                    continue
                except Exception as error:
                    logger.exception(u'Problem running show_updater, Indexer {indexer_name} seems to be having '
                                     u'issues while trying to get updates for show {show}. Cause: {cause!r}',
                                     indexer_name=indexerApi(show.indexer).name, show=show.name, cause=error)
                    continue

                if updated_seasons[show.indexerid]:
                    logger.info(u'{show_name}: Adding the following seasons for update to queue: {seasons}',
                                show_name=show.name, seasons=updated_seasons[show.indexerid])
                    for season in updated_seasons[show.indexerid]:
                        season_updates.append((show.indexer, show, season))

        pi_list = []

        # Full refreshes
        for show in refresh_shows:
            # If the cur_show is not 'paused' then add to the show_queue_scheduler
            if not show.paused:
                logger.info(u'Full update on show: {show}', show=show.name)
                try:
                    pi_list.append(app.show_queue_scheduler.action.updateShow(show))
                except (CantUpdateShowException, CantRefreshShowException) as e:
                    logger.warning(u'Automatic update failed. Error: {error}', error=e)
                except Exception as e:
                    logger.error(u'Automatic update failed: Error: {error}', error=e)
            else:
                logger.info(u'Show update skipped, show: {show} is paused.', show=show.name)

        # Only update expired season
        for show in season_updates:
            # If the cur_show is not 'paused' then add to the show_queue_scheduler
            if not show[1].paused:
                logger.info(u'Updating season {season} for show: {show}.', season=show[2], show=show[1].name)
                try:
                    pi_list.append(app.show_queue_scheduler.action.updateShow(show[1], season=show[2]))
                except CantUpdateShowException as e:
                    logger.warning(u'Automatic update failed. Error: {error}', error=e)
                except Exception as e:
                    logger.error(u'Automatic update failed: Error: {error}', error=e)
            else:
                logger.info(u'Show update skipped, show: {show} is paused.', show=show[1].name)

        ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator('Daily Update', pi_list))

        # Only refresh updated shows that have been updated using the season updates.
        # The full refreshed shows, are updated from the queueItem.
        for show in set(show[1] for show in season_updates):
            if not show.paused:
                try:
                    app.show_queue_scheduler.action.refreshShow(show, True)
                except CantRefreshShowException as e:
                    logger.warning(u'Show refresh on show {show_name} failed. Error: {error}',
                                   show_name=show.name, error=e)
                except Exception as e:
                    logger.error(u'Show refresh on show {show_name} failed: Unexpected Error: {error}',
                                 show_name=show.name, error=e)
            else:
                logger.info(u'Show refresh skipped, show: {show_name} is paused.', show_name=show.name)

        if refresh_shows or season_updates:
            for indexer in set([show.indexer for show in refresh_shows] + [s[1].indexer for s in season_updates]):
                indexer_api = indexerApi(indexer)
                self.update_cache.set_last_indexer_update(indexer_api.name)
                logger.info(u'Updated lastUpdate timestamp for {indexer_name}', indexer_name=indexer_api.name)
            logger.info(u'Completed scheduling updates on shows')
        else:
            logger.info(u'Completed but there was nothing to update')

        self.amActive = False
Beispiel #7
0
    def run(self, force=False):

        self.amActive = True
        refresh_shows = []  # A list of shows, that need to be refreshed
        season_updates = []  # A list of show seasons that have passed their next_update timestamp
        update_max_weeks = 12

        network_timezones.update_network_dict()

        # Refresh the exceptions_cache from db.
        refresh_exceptions_cache()

        logger.info('Started periodic show updates')

        # Cache for the indexers list of updated show
        indexer_updated_shows = {}
        # Cache for the last indexer update timestamp
        last_updates = {}

        # Loop through the list of shows, and per show evaluate if we can use the .get_last_updated_seasons()
        for show in app.showList:
            if show.paused:
                logger.info('The show {show} is paused, not updating it.', show=show.name)
                continue

            indexer_name = indexerApi(show.indexer).name
            indexer_api_params = indexerApi(show.indexer).api_params.copy()
            try:
                indexer_api = indexerApi(show.indexer).indexer(**indexer_api_params)
            except IndexerUnavailable:
                logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
                               'connectivity issues. While trying to look for show updates on show: {show}',
                               indexer_name=indexer_name, show=show.name)
                continue

            # Get the lastUpdate timestamp for this indexer.
            if indexer_name not in last_updates:
                last_indexer_update = self.update_cache.get_last_indexer_update(indexer_name)
                if not last_indexer_update:
                    last_updates[indexer_name] = int(time.time() - 86400)  # 1 day ago
                elif last_indexer_update < time.time() - 604800 * update_max_weeks:
                    last_updates[indexer_name] = int(time.time() - 604800)  # 1 week ago
                else:
                    last_updates[indexer_name] = last_indexer_update

            # Get a list of updated shows from the indexer, since last update.
            if show.indexer not in indexer_updated_shows:
                try:
                    indexer_updated_shows[show.indexer] = indexer_api.get_last_updated_series(
                        last_updates[indexer_name], update_max_weeks
                    )
                except IndexerUnavailable:
                    logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
                                   'connectivity issues while trying to look for show updates on show: {show}',
                                   indexer_name=indexer_name, show=show.name)
                    continue
                except IndexerException as error:
                    logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
                                   'issues while trying to get updates for show {show}. Cause: {cause!r}',
                                   indexer_name=indexer_name, show=show.name, cause=error)
                    continue
                except RequestException as error:
                    logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
                                   'issues while trying to get updates for show {show}. Cause: {cause!r}',
                                   indexer_name=indexer_name, show=show.name, cause=error)

                    if isinstance(error, HTTPError):
                        if error.response.status_code == 503:
                            logger.warning('API Service offline: '
                                           'This service is temporarily offline, try again later.')
                        elif error.response.status_code == 429:
                            logger.warning('Your request count (#) is over the allowed limit of (40).')
                    continue
                except Exception as error:
                    logger.exception('Problem running show_updater, Indexer {indexer_name} seems to be having '
                                     'issues while trying to get updates for show {show}. Cause: {cause!r}.',
                                     indexer_name=indexer_name, show=show.name, cause=error)
                    continue

            # Update shows that were updated in the last X weeks
            # or were not updated within the last X weeks
            if show.indexerid not in indexer_updated_shows.get(show.indexer, []):
                if show.last_update_indexer > time.time() - 604800 * update_max_weeks:
                    logger.debug('Skipping show update for {show}. Show was not in the '
                                 'indexers {indexer_name} list with updated shows and it '
                                 'was updated within the last {weeks} weeks.', show=show.name,
                                 indexer_name=indexer_name, weeks=update_max_weeks)
                    continue

            # If indexer doesn't have season updates.
            if not hasattr(indexer_api, 'get_last_updated_seasons'):
                logger.debug('Adding the following show for full update to queue: {show}', show=show.name)
                refresh_shows.append(show)

            # Else fall back to per season updates.
            elif hasattr(indexer_api, 'get_last_updated_seasons'):
                # Get updated seasons and add them to the season update list.
                try:
                    updated_seasons = indexer_api.get_last_updated_seasons(
                        [show.indexerid], show.last_update_indexer, update_max_weeks)
                except IndexerUnavailable:
                    logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
                                   'connectivity issues while trying to look for show updates for show: {show}',
                                   indexer_name=indexer_name, show=show.name)
                    continue
                except IndexerException as error:
                    logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
                                   'issues while trying to get updates for show {show}. Cause: {cause!r}',
                                   indexer_name=indexer_name, show=show.name, cause=error)
                    continue
                except Exception as error:
                    logger.exception('Problem running show_updater, Indexer {indexer_name} seems to be having '
                                     'issues while trying to get updates for show {show}. Cause: {cause!r}',
                                     indexer_name=indexer_name, show=show.name, cause=error)
                    continue

                if updated_seasons[show.indexerid]:
                    logger.info('{show_name}: Adding the following seasons for update to queue: {seasons}',
                                show_name=show.name, seasons=updated_seasons[show.indexerid])
                    season_updates.append((show.indexer, show, updated_seasons[show.indexerid]))
                elif show.indexerid in indexer_updated_shows.get(show.indexer, []):
                    # This show was marked to have an update, but it didn't get a season update. Let's fully
                    # update the show anyway.
                    logger.debug('Could not detect a season update, but an update is required. \n'
                                 'Adding the following show for full update to queue: {show}', show=show.name)
                    refresh_shows.append(show)

        pi_list = []
        # Full refreshes
        for show in refresh_shows:
            logger.info('Full update on show: {show}', show=show.name)
            try:
                pi_list.append(app.show_queue_scheduler.action.updateShow(show))
            except (CantUpdateShowException, CantRefreshShowException) as e:
                logger.warning('Automatic update failed. Error: {error}', error=e)
            except Exception as e:
                logger.error('Automatic update failed: Error: {error}', error=e)

        # Only update expired season
        for show in season_updates:
            logger.info('Updating season {season} for show: {show}.', season=show[2], show=show[1].name)
            try:
                pi_list.append(app.show_queue_scheduler.action.updateShow(show[1], season=show[2]))
            except CantUpdateShowException as e:
                logger.warning('Automatic update failed. Error: {error}', error=e)
            except Exception as e:
                logger.error('Automatic update failed: Error: {error}', error=e)

        ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator('Daily Update', pi_list))

        # Only refresh updated shows that have been updated using the season updates.
        # The full refreshed shows, are updated from the queueItem.
        for show in set(show[1] for show in season_updates):
            try:
                app.show_queue_scheduler.action.refreshShow(show, True)
            except CantRefreshShowException as e:
                logger.warning('Show refresh on show {show_name} failed. Error: {error}',
                               show_name=show.name, error=e)
            except Exception as e:
                logger.error('Show refresh on show {show_name} failed: Unexpected Error: {error}',
                             show_name=show.name, error=e)

        if refresh_shows or season_updates:
            for indexer in set([s.indexer for s in refresh_shows] + [s[1].indexer for s in season_updates]):
                indexer_api = indexerApi(indexer)
                self.update_cache.set_last_indexer_update(indexer_api.name)
                logger.info('Updated lastUpdate timestamp for {indexer_name}', indexer_name=indexer_api.name)
            logger.info('Completed scheduling updates on shows')
        else:
            logger.info('Completed scheduling updates on shows, but there was nothing to update')

        self.amActive = False