def set_episode_to_wanted(show, season, episode): """Set an episode to wanted, only if it is currently skipped.""" # Episode must be loaded from DB to get current status and not default blank episode status ep_obj = show.get_episode(season, episode) if ep_obj: with ep_obj.lock: if ep_obj.status != SKIPPED or ep_obj.airdate == datetime.date.fromordinal(1): log.info("Not setting episode '{show}' {ep} to WANTED because current status is not SKIPPED " "or it doesn't have a valid airdate", {'show': show.name, 'ep': episode_num(season, episode)}) return log.info("Setting episode '{show}' {ep} to wanted", { 'show': show.name, 'ep': episode_num(season, episode) }) # figure out what segment the episode is in and remember it so we can backlog it ep_obj.status = WANTED # As we created the episode and updated the status, need to save to DB ep_obj.save_to_db() cur_backlog_queue_item = BacklogQueueItem(show, [ep_obj]) app.search_queue_scheduler.action.add_item(cur_backlog_queue_item) log.info("Starting backlog search for '{show}' {ep} because some episodes were set to wanted", { 'show': show.name, 'ep': episode_num(season, episode) })
def search_backlog(self, which_shows=None): """Run the backlog search for given shows.""" if self.amActive: log.debug(u'Backlog is still running, not starting it again') return if app.forced_search_queue_scheduler.action.is_forced_search_in_progress(): log.warning(u'Manual search is running. Unable to start Backlog Search') return self.amActive = True self.amPaused = False if which_shows: show_list = which_shows else: show_list = app.showList self._get_last_backlog() cur_date = datetime.date.today().toordinal() from_date = datetime.date.fromordinal(1) if not which_shows and self.forced: log.info(u'Running limited backlog search on missed episodes from last {0} days', app.BACKLOG_DAYS) from_date = datetime.date.today() - datetime.timedelta(days=app.BACKLOG_DAYS) else: log.info(u'Running full backlog search on missed episodes for selected shows') # go through non air-by-date shows and see if they need any episodes for series_obj in show_list: if series_obj.paused: continue segments = self._get_segments(series_obj, from_date) for season, segment in iteritems(segments): self.currentSearchInfo = {'title': '{series_name} Season {season}'.format(series_name=series_obj.name, season=season)} backlog_queue_item = BacklogQueueItem(series_obj, segment) app.search_queue_scheduler.action.add_item(backlog_queue_item) # @UndefinedVariable if not segments: log.debug(u'Nothing needs to be downloaded for {0!r}, skipping', series_obj.name) # don't consider this an actual backlog search if we only did recent eps # or if we only did certain shows if from_date == datetime.date.fromordinal(1) and not which_shows: self._set_last_backlog(cur_date) self.amActive = False self._reset_pi()
def _search_backlog(self, data=None): """Queue a backlog search for results for the provided episodes or season. :param data: :return: :example: Start a backlog search for show slug tvdb1234 with episodes s01e01, s01e02, s03e03. route: `apiv2/search/backlog` { showSlug: "tvdb1234", episodes: [ "s01e01", "s01e02", "s03e03", ] } """ if not data: # Trigger a full backlog search if app.backlog_search_scheduler.forceRun(): return self._accepted('Full backlog search started') return self._bad_request('Triggering a full backlog search failed') if not data.get('showSlug'): return self._bad_request('You need to provide a show slug') if not data.get('episodes') and not data.get('season'): return self._bad_request( 'For a backlog search you need to provide a list of episodes or seasons' ) identifier = SeriesIdentifier.from_slug(data['showSlug']) if not identifier: return self._bad_request('Invalid series slug') series = Series.find_by_identifier(identifier) if not series: return self._not_found('Series not found') episode_segments = self._get_episode_segments(series, data) # If a season is passed, we transform it to a list of episode objects. And merge it with the episode_segment. # This because the backlog search has its own logic for searching per episode or season packs. And falling back # between them, if configured. if data.get('season'): for season_slug in data['season']: episode_season = int(season_slug[1:]) episodes = series.get_all_episodes(episode_season) for episode in episodes: if episode not in episode_segments[episode_season]: episode_segments[episode_season].append(episode) if not episode_segments: return self._not_found( 'Could not find any episode for show {show}. Did you provide the correct format?' .format(show=series.name)) for segment in itervalues(episode_segments): cur_backlog_queue_item = BacklogQueueItem(series, segment) app.forced_search_queue_scheduler.action.add_item( cur_backlog_queue_item) return self._accepted('Backlog search for {0} started'.format( data['showSlug']))
def run(self): ShowQueueItem.run(self) log.info('Starting to add show by {0}', ('show_dir: {0}'.format(self.show_dir) if self.show_dir else 'Indexer Id: {0}'.format(self.indexer_id))) # make sure the Indexer IDs are valid try: l_indexer_api_params = indexerApi(self.indexer).api_params.copy() if self.lang: l_indexer_api_params['language'] = self.lang log.info( '{indexer_name}: {indexer_params!r}', { 'indexer_name': indexerApi(self.indexer).name, 'indexer_params': l_indexer_api_params }) indexer_api = indexerApi( self.indexer).indexer(**l_indexer_api_params) s = indexer_api[self.indexer_id] # Let's try to create the show Dir if it's not provided. This way we force the show dir # to build build using the Indexers provided series name if not self.show_dir and self.root_dir: show_name = get_showname_from_indexer(self.indexer, self.indexer_id, self.lang) if show_name: self.show_dir = os.path.join(self.root_dir, sanitize_filename(show_name)) dir_exists = make_dir(self.show_dir) if not dir_exists: log.info( "Unable to create the folder {0}, can't add the show", self.show_dir) return chmod_as_parent(self.show_dir) else: log.info("Unable to get a show {0}, can't add the show", self.show_dir) return # this usually only happens if they have an NFO in their show dir which gave us a Indexer ID that # has no proper english version of the show if getattr(s, 'seriesname', None) is None: log.error( 'Show in {path} has no name on {indexer}, probably searched with the wrong language.', { 'path': self.show_dir, 'indexer': indexerApi(self.indexer).name }) ui.notifications.error( 'Unable to add show', 'Show in {path} has no name on {indexer}, probably the wrong language.' ' Delete .nfo and manually add the correct language.'. format(path=self.show_dir, indexer=indexerApi(self.indexer).name)) self._finishEarly() return # Check if we can already find this show in our current showList. try: check_existing_shows(s, self.indexer) except IndexerShowAlreadyInLibrary as error: log.warning( 'Could not add the show {series}, as it already is in your library.' ' Error: {error}', { 'series': s['seriesname'], 'error': error }) ui.notifications.error('Unable to add show', 'reason: {0}'.format(error)) self._finishEarly() # Clean up leftover if the newly created directory is empty. delete_empty_folders(self.show_dir) return # TODO: Add more specific indexer exceptions, that should provide the user with some accurate feedback. except IndexerShowNotFound as error: log.warning( '{id}: Unable to look up the show in {path} using id {id} on {indexer}.' ' Delete metadata files from the folder and try adding it again.\n' 'With error: {error}', { 'id': self.indexer_id, 'path': self.show_dir, 'indexer': indexerApi(self.indexer).name, 'error': error }) ui.notifications.error( 'Unable to add show', 'Unable to look up the show in {path} using id {id} on {indexer}.' ' Delete metadata files from the folder and try adding it again.' .format(path=self.show_dir, id=self.indexer_id, indexer=indexerApi(self.indexer).name)) self._finishEarly() return except IndexerShowNotFoundInLanguage as error: log.warning( '{id}: Data retrieved from {indexer} was incomplete. The indexer does not provide' ' show information in the searched language {language}. Aborting: {error_msg}', { 'id': self.indexer_id, 'indexer': indexerApi(self.indexer).name, 'language': error.language, 'error_msg': error }) ui.notifications.error( 'Error adding show!', 'Unable to add show {indexer_id} on {indexer} with this language: {language}' .format(indexer_id=self.indexer_id, indexer=indexerApi(self.indexer).name, language=error.language)) self._finishEarly() return except Exception as error: log.error( '{id}: Error while loading information from indexer {indexer}. Error: {error!r}', { 'id': self.indexer_id, 'indexer': indexerApi(self.indexer).name, 'error': error }) ui.notifications.error( 'Unable to add show', 'Unable to look up the show in {path} on {indexer} using ID {id}.' .format(path=self.show_dir, indexer=indexerApi(self.indexer).name, id=self.indexer_id)) self._finishEarly() return try: newShow = Series(self.indexer, self.indexer_id, self.lang) newShow.load_from_indexer(indexer_api) self.show = newShow # set up initial values self.show.location = self.show_dir self.show.subtitles = self.subtitles if self.subtitles is not None else app.SUBTITLES_DEFAULT self.show.quality = self.quality if self.quality else app.QUALITY_DEFAULT self.show.season_folders = self.season_folders if self.season_folders is not None \ else app.SEASON_FOLDERS_DEFAULT self.show.anime = self.anime if self.anime is not None else app.ANIME_DEFAULT self.show.scene = self.scene if self.scene is not None else app.SCENE_DEFAULT self.show.paused = self.paused if self.paused is not None else False # set up default new/missing episode status log.info( 'Setting all previously aired episodes to the specified status: {status}', {'status': statusStrings[self.default_status]}) self.show.default_ep_status = self.default_status if self.show.anime: self.show.release_groups = BlackAndWhiteList(self.show) if self.blacklist: self.show.release_groups.set_black_keywords(self.blacklist) if self.whitelist: self.show.release_groups.set_white_keywords(self.whitelist) except IndexerException as error: log.error( 'Unable to add show due to an error with {indexer}: {error}', { 'indexer': indexerApi(self.indexer).name, 'error': error }) ui.notifications.error( 'Unable to add {series_name} due to an error with {indexer_name}' .format(series_name=self.show.name if self.show else 'show', indexer_name=indexerApi(self.indexer).name)) self._finishEarly() return except MultipleShowObjectsException: log.warning( 'The show in {show_dir} is already in your show list, skipping', {'show_dir': self.show_dir}) ui.notifications.error( 'Show skipped', 'The show in {show_dir} is already in your show list'.format( show_dir=self.show_dir)) self._finishEarly() return except Exception as error: log.error('Error trying to add show: {0}', error) log.debug(traceback.format_exc()) self._finishEarly() raise log.debug('Retrieving show info from IMDb') try: self.show.load_imdb_info() except ImdbAPIError as error: log.info('Something wrong on IMDb api: {0}', error) except RequestException as error: log.warning('Error loading IMDb info: {0}', error) try: log.debug('{id}: Saving new show to database', {'id': self.show.series_id}) self.show.save_to_db() except Exception as error: log.error('Error saving the show to the database: {0}', error) log.debug(traceback.format_exc()) self._finishEarly() raise # add it to the show list app.showList.append(self.show) try: self.show.load_episodes_from_indexer(tvapi=indexer_api) except Exception as error: log.error( 'Error with {indexer}, not creating episode list: {error}', { 'indexer': indexerApi(self.show.indexer).name, 'error': error }) log.debug(traceback.format_exc()) # update internal name cache name_cache.build_name_cache(self.show) try: self.show.load_episodes_from_dir() except Exception as error: log.error('Error searching dir for episodes: {0}', error) log.debug(traceback.format_exc()) # if they set default ep status to WANTED then run the backlog to search for episodes if self.show.default_ep_status == WANTED: log.info( 'Launching backlog for this show since its episodes are WANTED' ) wanted_segments = self.show.get_wanted_segments() for season, segment in viewitems(wanted_segments): cur_backlog_queue_item = BacklogQueueItem(self.show, segment) app.forced_search_queue_scheduler.action.add_item( cur_backlog_queue_item) log.info('Sending forced backlog for {show} season {season}' ' because some episodes were set to wanted'.format( show=self.show.name, season=season)) self.show.write_metadata() self.show.update_metadata() self.show.populate_cache() self.show.flush_episodes() if app.USE_TRAKT: # if there are specific episodes that need to be added by trakt app.trakt_checker_scheduler.action.manage_new_show(self.show) # add show to trakt.tv library if app.TRAKT_SYNC: app.trakt_checker_scheduler.action.add_show_trakt_library( self.show) if app.TRAKT_SYNC_WATCHLIST: log.info('update watchlist') notifiers.trakt_notifier.update_watchlist(show_obj=self.show) # Load XEM data to DB for show scene_numbering.xem_refresh(self.show, force=True) # check if show has XEM mapping so we can determine if searches # should go by scene numbering or indexer numbering. Warn the user. if not self.scene and scene_numbering.get_xem_numbering_for_show( self.show): log.warning( '{id}: while adding the show {title} we noticed thexem.de has an episode mapping available' '\nyou might want to consider enabling the scene option for this show.', { 'id': self.show.series_id, 'title': self.show.name }) ui.notifications.message( 'consider enabling scene for this show', 'for show {title} you might want to consider enabling the scene option' .format(title=self.show.name)) # After initial add, set to default_status_after. self.show.default_ep_status = self.default_status_after try: log.debug('{id}: Saving new show info to database', {'id': self.show.series_id}) self.show.save_to_db() except Exception as error: log.warning( '{id}: Error saving new show info to database: {error_msg}', { 'id': self.show.series_id, 'error_msg': error }) log.error(traceback.format_exc()) # Send ws update to client ws.Message('showAdded', self.show.to_json(detailed=False)).push() self.finish()