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
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)
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
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
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
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
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