def find_propers(self, search_date=None): results = [] db = DBConnection() placeholder = ','.join([ str(x) for x in Quality.DOWNLOADED + Quality.SNATCHED + Quality.SNATCHED_BEST ]) sql_results = db.select( 'SELECT s.show_name, s.lang, e.showid, e.season, e.episode, e.status, e.airdate' ' FROM tv_episodes AS e' ' INNER JOIN tv_shows AS s ON (e.showid = s.indexer_id)' ' WHERE e.airdate >= ' + str(search_date.toordinal()) + ' AND e.status IN (' + placeholder + ')') for result in sql_results or []: show = Show.find(sickbeard.showList, int(result['showid'])) if show: episode = show.getEpisode(result['season'], result['episode']) for term in self.proper_strings: search_strings = self._get_episode_search_strings( episode, add_string=term) for item in self.search(search_strings[0]): title, url = self._get_title_and_url(item) results.append( Proper(title, url, datetime.today(), show)) return results
def overall_stats(): db = DBConnection() shows = sickbeard.showList today = str(date.today().toordinal()) downloaded_status = Quality.DOWNLOADED + Quality.ARCHIVED snatched_status = Quality.SNATCHED + Quality.SNATCHED_PROPER + Quality.SNATCHED_BEST total_status = [SKIPPED, WANTED] results = db.select('SELECT airdate, status ' 'FROM tv_episodes ' 'WHERE season > 0 ' 'AND episode > 0 ' 'AND airdate > 1') stats = { 'episodes': { 'downloaded': 0, 'snatched': 0, 'total': 0, }, 'shows': { 'active': len([ show for show in shows if show.paused == 0 and show.status == 'Continuing' ]), 'total': len(shows), }, } for result in results: if result[b'status'] in downloaded_status: stats['episodes']['downloaded'] += 1 stats['episodes']['total'] += 1 elif result[b'status'] in snatched_status: stats['episodes']['snatched'] += 1 stats['episodes']['total'] += 1 elif result[b'airdate'] <= today and result[ b'status'] in total_status: stats['episodes']['total'] += 1 return stats
def find_search_results( self, show, episodes, search_mode, # pylint: disable=too-many-branches,too-many-arguments,too-many-locals,too-many-statements manual_search=False, download_current_quality=False): self._check_auth() self.show = show results = {} items_list = [] searched_scene_season = None for episode in episodes: cache_result = self.cache.search_cache( episode, manual_search=manual_search, down_cur_quality=download_current_quality) if cache_result: if episode.episode not in results: results[episode.episode] = cache_result else: results[episode.episode].extend(cache_result) continue if len( episodes ) > 1 and search_mode == 'sponly' and searched_scene_season == episode.scene_season: continue search_strings = [] searched_scene_season = episode.scene_season if len(episodes) > 1 and search_mode == 'sponly': search_strings = self.get_season_search_strings(episode) elif search_mode == 'eponly': search_strings = self.get_episode_search_strings(episode) for search_string in search_strings: items_list += self.search(search_string, ep_obj=episode) if len(results) == len(episodes): return results if items_list: items = {} unknown_items = [] for item in items_list: quality = self.get_quality(item, anime=show.is_anime) if quality == Quality.UNKNOWN: unknown_items.append(item) elif quality == Quality.NONE: pass # Skipping an HEVC when HEVC is not allowed by settings else: if quality not in items: items[quality] = [] items[quality].append(item) items_list = list( chain(*[ v for (k_, v) in sorted(six.iteritems(items), reverse=True) ])) items_list += unknown_items cl = [] for item in items_list: (title, url) = self._get_title_and_url(item) try: parse_result = NameParser( parse_method=('normal', 'anime')[show.is_anime]).parse(title) except (InvalidNameException, InvalidShowException) as error: logger.log("{0}".format(error), logger.DEBUG) continue show_object = parse_result.show quality = parse_result.quality release_group = parse_result.release_group version = parse_result.version add_cache_entry = False if not (show_object.air_by_date or show_object.sports): if search_mode == 'sponly': if parse_result.episode_numbers: logger.log( 'This is supposed to be a season pack search but the result {0} is not a valid season pack, skipping it' .format(title), logger.DEBUG) add_cache_entry = True elif not [ ep for ep in episodes if parse_result.season_number == (ep.season, ep.scene_season)[ep.show.is_scene] ]: logger.log( 'This season result {0} is for a season we are not searching for, skipping it' .format(title), logger.DEBUG) add_cache_entry = True else: if not all([ # pylint: disable=bad-continuation parse_result.season_number is not None, parse_result.episode_numbers, [ ep for ep in episodes if (ep.season, ep.scene_season)[ep.show.is_scene] == (parse_result.season_number, parse_result.scene_season)[ep.show.is_scene] and (ep.episode, ep.scene_episode)[ep.show.is_scene] in parse_result.episode_numbers ] ]) and not all([ # fallback for anime on absolute numbering parse_result.is_anime, parse_result.ab_episode_numbers is not None, [ ep for ep in episodes if ep.show.is_anime and ep. absolute_number in parse_result.ab_episode_numbers ] ]): logger.log( 'The result {0} doesn\'t seem to match an episode that we are currently trying to snatch, skipping it' .format(title), logger.DEBUG) add_cache_entry = True if not add_cache_entry: actual_season = parse_result.season_number actual_episodes = parse_result.episode_numbers else: same_day_special = False if not parse_result.is_air_by_date: logger.log( 'This is supposed to be a date search but the result {0} didn\'t parse as one, skipping it' .format(title), logger.DEBUG) add_cache_entry = True else: air_date = parse_result.air_date.toordinal() db = DBConnection() sql_results = db.select( 'SELECT season, episode FROM tv_episodes WHERE showid = ? AND airdate = ?', [show_object.indexerid, air_date]) if len(sql_results) == 2: if int(sql_results[0][b'season']) == 0 and int( sql_results[1][b'season']) != 0: actual_season = int(sql_results[1][b'season']) actual_episodes = [int(sql_results[1][b'episode'])] same_day_special = True elif int(sql_results[1][b'season']) == 0 and int( sql_results[0][b'season']) != 0: actual_season = int(sql_results[0][b'season']) actual_episodes = [int(sql_results[0][b'episode'])] same_day_special = True elif len(sql_results) != 1: logger.log( 'Tried to look up the date for the episode {0} but the database didn\'t give proper results, skipping it' .format(title), logger.WARNING) add_cache_entry = True if not add_cache_entry and not same_day_special: actual_season = int(sql_results[0][b'season']) actual_episodes = [int(sql_results[0][b'episode'])] if add_cache_entry: logger.log( 'Adding item from search to cache: {0}'.format(title), logger.DEBUG) # pylint: disable=protected-access # Access to a protected member of a client class ci = self.cache._add_cache_entry(title, url, parse_result=parse_result) if ci is not None: cl.append(ci) continue episode_wanted = True for episode_number in actual_episodes: if not show_object.wantEpisode(actual_season, episode_number, quality, manual_search, download_current_quality): episode_wanted = False break if not episode_wanted: logger.log('Ignoring result {0}.'.format(title), logger.DEBUG) continue logger.log('Found result {0} at {1}'.format(title, url), logger.DEBUG) episode_object = [] for current_episode in actual_episodes: episode_object.append( show_object.getEpisode(actual_season, current_episode)) result = self.get_result(episode_object) result.show = show_object result.url = url result.name = title result.quality = quality result.release_group = release_group result.version = version result.content = None result.size = self._get_size(item) if len(episode_object) == 1: episode_number = episode_object[0].episode logger.log('Single episode result.', logger.DEBUG) elif len(episode_object) > 1: episode_number = MULTI_EP_RESULT logger.log( 'Separating multi-episode result to check for later - result contains episodes: {0}' .format(parse_result.episode_numbers), logger.DEBUG) elif len(episode_object) == 0: episode_number = SEASON_RESULT logger.log('Separating full season result to check for later', logger.DEBUG) if episode_number not in results: results[episode_number] = [result] else: results[episode_number].append(result) if cl: # pylint: disable=protected-access # Access to a protected member of a client class cache_db = self.cache._get_db() cache_db.mass_action(cl) return results
def __init__(self): self.db = DBConnection()
def get_coming_episodes(categories, sort, group, paused=sickbeard.COMING_EPS_DISPLAY_PAUSED): """ :param categories: The categories of coming episodes. See ``ComingEpisodes.categories`` :param sort: The sort to apply to the coming episodes. See ``ComingEpisodes.sorts`` :param group: ``True`` to group the coming episodes by category, ``False`` otherwise :param paused: ``True`` to include paused shows, ``False`` otherwise :return: The list of coming episodes """ categories = ComingEpisodes._get_categories(categories) sort = ComingEpisodes._get_sort(sort) today = date.today().toordinal() recently = ( date.today() - timedelta(days=sickbeard.COMING_EPS_MISSED_RANGE)).toordinal() next_week = (date.today() + timedelta(days=7)).toordinal() db = DBConnection(row_type='dict') fields_to_select = ', '.join([ 'airdate', 'airs', 'e.description as description', 'episode', 'imdb_id', 'e.indexer', 'indexer_id', 'e.location', 'name', 'network', 'paused', 'quality', 'runtime', 'season', 'show_name', 'showid', 'e.status as epstatus', 's.status' ]) status_list = [WANTED, UNAIRED] + SNATCHED sql_l = [] for show_obj in sickbeard.showList: next_air_date = show_obj.nextEpisode() sql_l.append([ 'SELECT DISTINCT {0} '.format(fields_to_select) + 'FROM tv_episodes e, tv_shows s ' 'WHERE showid = ? ' 'AND airdate <= ? ' 'AND airdate >= ? ' 'AND s.indexer_id = e.showid ' 'AND e.status IN (' + ','.join(['?'] * len(status_list)) + ')', [show_obj.indexerid, next_air_date or today, recently] + status_list ]) results = [] for sql_i in sql_l: if results: results += db.select(*sql_i) else: results = db.select(*sql_i) for index, item in enumerate(results): results[index][b'localtime'] = sbdatetime.convert_to_setting( parse_date_time(item[b'airdate'], item[b'airs'], item[b'network'])) results[index][b'snatchedsort'] = int( not results[index][b'epstatus'] in SNATCHED) results.sort(key=ComingEpisodes.sorts[sort]) if not group: return results grouped_results = ComingEpisodes._get_categories_map(categories) for result in results: if result[b'paused'] and not paused: continue result[b'airs'] = str(result[b'airs']).replace( 'am', ' AM').replace('pm', ' PM').replace(' ', ' ') result[b'airdate'] = result[b'localtime'].toordinal() if result[b'epstatus'] in SNATCHED: if result[b'location']: continue else: category = 'snatched' elif result[b'airdate'] < today: category = 'missed' elif result[b'airdate'] >= next_week: category = 'later' elif result[b'airdate'] == today: category = 'today' else: category = 'soon' if len(categories) > 0 and category not in categories: continue if not result[b'network']: result[b'network'] = '' result[b'quality'] = get_quality_string(result[b'quality']) result[b'airs'] = sbdatetime.sbftime( result[b'localtime'], t_preset=timeFormat).lstrip('0').replace(' 0', ' ') result[b'weekday'] = 1 + result[b'localtime'].weekday() result[b'tvdbid'] = result[b'indexer_id'] result[b'airdate'] = sbdatetime.sbfdate(result[b'localtime'], d_preset=dateFormat) result[b'localtime'] = result[b'localtime'].toordinal() grouped_results[category].append(result) return grouped_results
def get_coming_episodes(categories, sort, group, paused=sickbeard.COMING_EPS_DISPLAY_PAUSED): """ :param categories: The categories of coming episodes. See ``ComingEpisodes.categories`` :param sort: The sort to apply to the coming episodes. See ``ComingEpisodes.sorts`` :param group: ``True`` to group the coming episodes by category, ``False`` otherwise :param paused: ``True`` to include paused shows, ``False`` otherwise :return: The list of coming episodes """ categories = ComingEpisodes._get_categories(categories) sort = ComingEpisodes._get_sort(sort) today = date.today().toordinal() next_week = (date.today() + timedelta(days=7)).toordinal() recently = (date.today() - timedelta(days=sickbeard.COMING_EPS_MISSED_RANGE)).toordinal() qualities_list = Quality.DOWNLOADED + Quality.SNATCHED + Quality.SNATCHED_BEST + Quality.SNATCHED_PROPER + Quality.ARCHIVED + [IGNORED] db = DBConnection() fields_to_select = ', '.join( ['airdate', 'airs', 'description', 'episode', 'imdb_id', 'e.indexer', 'indexer_id', 'name', 'network', 'paused', 'quality', 'runtime', 'season', 'show_name', 'showid', 's.status'] ) results = db.select( 'SELECT %s ' % fields_to_select + 'FROM tv_episodes e, tv_shows s ' 'WHERE season != 0 ' 'AND airdate >= ? ' 'AND airdate < ? ' 'AND s.indexer_id = e.showid ' 'AND e.status NOT IN (' + ','.join(['?'] * len(qualities_list)) + ')', [today, next_week] + qualities_list ) done_shows_list = [int(result['showid']) for result in results] placeholder = ','.join(['?'] * len(done_shows_list)) placeholder2 = ','.join(['?'] * len(Quality.DOWNLOADED + Quality.SNATCHED + Quality.SNATCHED_BEST + Quality.SNATCHED_PROPER)) results += db.select( 'SELECT %s ' % fields_to_select + 'FROM tv_episodes e, tv_shows s ' 'WHERE season != 0 ' 'AND showid NOT IN (' + placeholder + ') ' 'AND s.indexer_id = e.showid ' 'AND airdate = (SELECT airdate ' 'FROM tv_episodes inner_e ' 'WHERE inner_e.season != 0 ' 'AND inner_e.showid = e.showid ' 'AND inner_e.airdate >= ? ' 'ORDER BY inner_e.airdate ASC LIMIT 1) ' 'AND e.status NOT IN (' + placeholder2 + ')', done_shows_list + [next_week] + Quality.DOWNLOADED + Quality.SNATCHED + Quality.SNATCHED_BEST + Quality.SNATCHED_PROPER ) results += db.select( 'SELECT %s ' % fields_to_select + 'FROM tv_episodes e, tv_shows s ' 'WHERE season != 0 ' 'AND s.indexer_id = e.showid ' 'AND airdate < ? ' 'AND airdate >= ? ' 'AND e.status IN (?,?) ' 'AND e.status NOT IN (' + ','.join(['?'] * len(qualities_list)) + ')', [today, recently, WANTED, UNAIRED] + qualities_list ) results = [dict(result) for result in results] for index, item in enumerate(results): results[index]['localtime'] = sbdatetime.convert_to_setting( parse_date_time(item['airdate'], item['airs'], item['network'])) results.sort(ComingEpisodes.sorts[sort]) if not group: return results grouped_results = ComingEpisodes._get_categories_map(categories) for result in results: if result['paused'] and not paused: continue result['airs'] = str(result['airs']).replace('am', ' AM').replace('pm', ' PM').replace(' ', ' ') result['airdate'] = result['localtime'].toordinal() if result['airdate'] < today: category = 'missed' elif result['airdate'] >= next_week: category = 'later' elif result['airdate'] == today: category = 'today' else: category = 'soon' if len(categories) > 0 and category not in categories: continue if not result['network']: result['network'] = '' result['quality'] = get_quality_string(result['quality']) result['airs'] = sbdatetime.sbftime(result['localtime'], t_preset=timeFormat).lstrip('0').replace(' 0', ' ') result['weekday'] = 1 + date.fromordinal(result['airdate']).weekday() result['tvdbid'] = result['indexer_id'] result['airdate'] = sbdatetime.sbfdate(result['localtime'], d_preset=dateFormat) result['localtime'] = result['localtime'].toordinal() grouped_results[category].append(result) return grouped_results
def find_search_results(self, show, episodes, search_mode, manual_search=False, download_current_quality=False): # pylint: disable=too-many-branches,too-many-arguments,too-many-locals,too-many-statements self._check_auth() self.show = show results = {} items_list = [] searched_scene_season = None for episode in episodes: cache_result = self.cache.searchCache(episode, manualSearch=manual_search, downCurQuality=download_current_quality) if cache_result: if episode.episode not in results: results[episode.episode] = cache_result else: results[episode.episode].extend(cache_result) continue if len(episodes) > 1 and search_mode == 'sponly' and searched_scene_season == episode.scene_season: continue search_strings = [] searched_scene_season = episode.scene_season if len(episodes) > 1 and search_mode == 'sponly': search_strings = self._get_season_search_strings(episode) elif search_mode == 'eponly': search_strings = self._get_episode_search_strings(episode) first = search_strings and isinstance(search_strings[0], dict) and 'rid' in search_strings[0] if first: logger.log(u'First search_string has rid', logger.DEBUG) for search_string in search_strings: items_list += self.search(search_string, ep_obj=episode) if first: first = False if items_list: logger.log(u'First search_string had rid, and returned results, skipping query by string', logger.DEBUG) break logger.log(u'First search_string had rid, but returned no results, searching with string query', logger.DEBUG) if len(results) == len(episodes): return results if items_list: items = {} unknown_items = [] for item in items_list: quality = self.get_quality(item, anime=show.is_anime) if quality == Quality.UNKNOWN: unknown_items.append(item) else: if quality not in items: items[quality] = [] items[quality].append(item) items_list = list(chain(*[v for (_, v) in sorted(items.iteritems(), reverse=True)])) items_list += unknown_items cl = [] for item in items_list: (title, url) = self._get_title_and_url(item) try: parser = NameParser(parse_method=('normal', 'anime')[show.is_anime]) parse_result = parser.parse(title) except InvalidNameException: logger.log(u'Unable to parse the filename %s into a valid episode' % title, logger.DEBUG) continue except InvalidShowException: logger.log(u'Unable to parse the filename %s into a valid show' % title, logger.DEBUG) continue show_object = parse_result.show quality = parse_result.quality release_group = parse_result.release_group version = parse_result.version add_cache_entry = False if not (show_object.air_by_date or show_object.sports): if search_mode == 'sponly': if len(parse_result.episode_numbers): logger.log( u'This is supposed to be a season pack search but the result %s is not a valid season pack, skipping it' % title, logger.DEBUG ) add_cache_entry = True if len(parse_result.episode_numbers) and \ (parse_result.season_number not in set([ep.season for ep in episodes]) or not [ep for ep in episodes if ep.scene_episode in parse_result.episode_numbers]): logger.log( u'The result %s doesn\'t seem to be a valid episode that we are trying to snatch, ignoring' % title, logger.DEBUG) add_cache_entry = True else: if not len(parse_result.episode_numbers) and parse_result.season_number and not [ep for ep in episodes if ep.season == parse_result.season_number and ep.episode in parse_result.episode_numbers]: logger.log( u'The result %s doesn\'t seem to be a valid season that we are trying to snatch, ignoring' % title, logger.DEBUG) add_cache_entry = True elif len(parse_result.episode_numbers) and not [ep for ep in episodes if ep.season == parse_result.season_number and ep.episode in parse_result.episode_numbers]: logger.log( u'The result %s doesn\'t seem to be a valid episode that we are trying to snatch, ignoring' % title, logger.DEBUG) add_cache_entry = True if not add_cache_entry: actual_season = parse_result.season_number actual_episodes = parse_result.episode_numbers else: same_day_special = False if not parse_result.is_air_by_date: logger.log( u'This is supposed to be a date search but the result %s didn\'t parse as one, skipping it' % title, logger.DEBUG) add_cache_entry = True else: air_date = parse_result.air_date.toordinal() db = DBConnection() sql_results = db.select( 'SELECT season, episode FROM tv_episodes WHERE showid = ? AND airdate = ?', [show_object.indexerid, air_date] ) if len(sql_results) == 2: if int(sql_results[0]['season']) == 0 and int(sql_results[1]['season']) != 0: actual_season = int(sql_results[1]['season']) actual_episodes = [int(sql_results[1]['episode'])] same_day_special = True elif int(sql_results[1]['season']) == 0 and int(sql_results[0]['season']) != 0: actual_season = int(sql_results[0]['season']) actual_episodes = [int(sql_results[0]['episode'])] same_day_special = True elif len(sql_results) != 1: logger.log( u'Tried to look up the date for the episode %s but the database didn\'t give proper results, skipping it' % title, logger.WARNING) add_cache_entry = True if not add_cache_entry and not same_day_special: actual_season = int(sql_results[0]['season']) actual_episodes = [int(sql_results[0]['episode'])] if add_cache_entry: logger.log(u'Adding item from search to cache: %s' % title, logger.DEBUG) # pylint: disable=protected-access # Access to a protected member of a client class ci = self.cache._addCacheEntry(title, url, parse_result=parse_result) if ci is not None: cl.append(ci) continue episode_wanted = True for episode_number in actual_episodes: if not show_object.wantEpisode(actual_season, episode_number, quality, manual_search, download_current_quality): episode_wanted = False break if not episode_wanted: logger.log(u'Ignoring result %s because we don\'t want an episode that is %s' % ( title, Quality.qualityStrings[quality]), logger.INFO) continue logger.log(u'Found result %s at %s' % (title, url), logger.DEBUG) episode_object = [] for current_episode in actual_episodes: episode_object.append(show_object.getEpisode(actual_season, current_episode)) result = self.get_result(episode_object) result.show = show_object result.url = url result.name = title result.quality = quality result.release_group = release_group result.version = version result.content = None result.size = self._get_size(item) if len(episode_object) == 1: episode_number = episode_object[0].episode logger.log(u'Single episode result.', logger.DEBUG) elif len(episode_object) > 1: episode_number = MULTI_EP_RESULT logger.log(u'Separating multi-episode result to check for later - result contains episodes: %s' % str( parse_result.episode_numbers), logger.DEBUG) elif len(episode_object) == 0: episode_number = SEASON_RESULT logger.log(u'Separating full season result to check for later', logger.DEBUG) if episode_number not in results: results[episode_number] = [result] else: results[episode_number].append(result) if len(cl) > 0: # pylint: disable=protected-access # Access to a protected member of a client class db = self.cache._getDB() db.mass_action(cl) return results