diff --git a/couchpotato/core/media/show/_base/main.py b/couchpotato/core/media/show/_base/main.py index 1e43a225..71efd8db 100644 --- a/couchpotato/core/media/show/_base/main.py +++ b/couchpotato/core/media/show/_base/main.py @@ -165,12 +165,14 @@ class ShowBase(MediaBase): for season_nr in seasons_info: season_info = seasons_info[season_nr] - season = fireEvent('show.season.add', media.get('_id'), season_info, single = True) + episodes = season_info.get('episodes', {}) + + season = fireEvent('show.season.add', m.get('_id'), season_info, single = True) # Add Episodes - for episode_nr in season_info.get('episodes', {}): + for episode_nr in episodes: - episode_info = season_info['episodes'][episode_nr] + episode_info = episodes[episode_nr] fireEvent('show.episode.add', season.get('_id'), episode_info, single = True) diff --git a/couchpotato/core/media/show/episode.py b/couchpotato/core/media/show/episode.py index db5ff288..ae5fa22e 100644 --- a/couchpotato/core/media/show/episode.py +++ b/couchpotato/core/media/show/episode.py @@ -1,6 +1,5 @@ from couchpotato import get_db from couchpotato.core.event import addEvent, fireEvent, fireEventAsync -from couchpotato.core.helpers.encoding import toUnicode from couchpotato.core.logger import CPLog from couchpotato.core.helpers.variable import tryInt from couchpotato.core.media import MediaBase @@ -10,6 +9,7 @@ log = CPLog(__name__) autoload = 'Episode' + class Episode(MediaBase): def __init__(self): @@ -31,7 +31,7 @@ class Episode(MediaBase): '_t': 'media', 'type': 'episode', 'identifiers': identifiers, - 'parent': parent_id, + 'parent_id': parent_id, 'info': info, # Returned dict by providers } @@ -54,7 +54,7 @@ class Episode(MediaBase): return episode - def update_info(self, media_id = None, info = None, force = False): + def updateInfo(self, media_id = None, info = None, force = False): if not info: info = {} if self.shuttingDown(): diff --git a/couchpotato/core/media/show/providers/info/thetvdb.py b/couchpotato/core/media/show/providers/info/thetvdb.py index e1a8b58f..b34580df 100644 --- a/couchpotato/core/media/show/providers/info/thetvdb.py +++ b/couchpotato/core/media/show/providers/info/thetvdb.py @@ -318,7 +318,7 @@ class TheTVDb(ShowProvider): 'identifiers': { 'thetvdb': show.get('seriesid') if show.get('seriesid') else show[number][1]['seasonid'] }, - 'number': number, + 'number': tryInt(number), 'images': { 'poster': poster, }, @@ -369,8 +369,8 @@ class TheTVDb(ShowProvider): poster = get('filename', []) episode_data = { - 'number': get('episodenumber'), - 'absolute_number': get('absolute_number'), + 'number': tryInt(get('episodenumber')), + 'absolute_number': tryInt(get('absolute_number')), 'identifiers': { 'thetvdb': tryInt(episode['id']) }, diff --git a/couchpotato/core/media/show/searcher.py b/couchpotato/core/media/show/searcher.py index aab7ee9f..31eec197 100644 --- a/couchpotato/core/media/show/searcher.py +++ b/couchpotato/core/media/show/searcher.py @@ -1,4 +1,5 @@ -from couchpotato import Env +import time +from couchpotato import Env, get_db from couchpotato.core.event import addEvent, fireEvent from couchpotato.core.helpers.variable import getTitle, toIterable from couchpotato.core.logger import CPLog @@ -23,22 +24,24 @@ class ShowSearcher(SearcherBase, ShowTypeBase): self.query_condenser = QueryCondenser() - for type in toIterable(self.type): - addEvent('%s.searcher.single' % type, self.single) + addEvent('season.searcher.single', self.singleSeason) + addEvent('episode.searcher.single', self.singleEpisode) addEvent('searcher.correct_release', self.correctRelease) + + def test(): + time.sleep(.2) + db = get_db() + medias = db.get_many('media_by_type', 'show', with_doc = True) + for x in medias: + media = x['doc'] + break + self.single(media) + + addEvent('app.load', test) + def single(self, media, search_protocols = None, manual = False): - show, season, episode = self.getLibraries(media['library']) - - if media['type'] == 'show': - for library in season: - # TODO ideally we shouldn't need to fetch the media for each season library here - m = db.query(Media).filter_by(library_id = library['library_id']).first() - - fireEvent('season.searcher.single', m.to_dict(ShowBase.search_dict)) - - return # Find out search type try: @@ -47,78 +50,123 @@ class ShowSearcher(SearcherBase, ShowTypeBase): except SearchSetupError: return - done_status, available_status, ignored_status, failed_status = fireEvent('status.get', ['done', 'available', 'ignored', 'failed'], single = True) - - if not media['profile'] or media['status_id'] == done_status.get('id'): - log.debug('Episode doesn\'t have a profile or already done, assuming in manage tab.') + if not media['profile_id'] or media['status'] == 'done': + log.debug('Show doesn\'t have a profile or already done, assuming in manage tab.') return - #pre_releases = fireEvent('quality.pre_releases', single = True) + show_title = fireEvent('media.search_query', media, condense = False, single = True) - found_releases = [] - too_early_to_search = [] + fireEvent('notify.frontend', type = 'show.searcher.started.%s' % media['_id'], data = True, message = 'Searching for "%s"' % show_title) - default_title = fireEvent('media.search_query', media['library'], condense = False, single=True) - if not default_title: - log.error('No proper info found for episode, removing it from library to cause it from having more issues.') - #fireEvent('episode.delete', episode['id'], single = True) - return + media = self.extendShow(media) - if not show or not season: - log.error('Unable to find show or season library in database, missing required data for searching') - return + db = get_db() - fireEvent('notify.frontend', type = 'show.searcher.started.%s' % media['id'], data = True, message = 'Searching for "%s"' % default_title) + profile = db.get('id', media['profile_id']) + quality_order = fireEvent('quality.order', single = True) + + seasons = media.get('seasons', {}) + for sx in seasons: + season = seasons.get(sx) + + # Check if full season can be downloaded TODO: add + season_success = self.singleSeason(season, media, profile) + + # Do each episode seperately + if not season_success: + episodes = season.get('episodes', {}) + for ex in episodes: + episode = episodes.get(ex) + + self.singleEpisode(episode, season, media, profile, quality_order, search_protocols) + + + + + fireEvent('notify.frontend', type = 'show.searcher.ended.%s' % media['id'], data = True) + + return ret + + def singleSeason(self, media, show, profile): + + # Check if any episode is already snatched + active = 0 + episodes = media.get('episodes', {}) + for ex in episodes: + episode = episodes.get(ex) + + if episode.get('status') in ['active']: + active += 1 + + if active != len(episodes): + return False + + # Try and search for full season + # TODO: + + return False + + def singleEpisode(self, media, season, show, profile, quality_order, search_protocols = None, manual = False): + + + # TODO: check episode status + + + # TODO: check air date + #if not self.conf('always_search') and not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates, movie['library']['year']): + # too_early_to_search.append(quality_type['quality']['identifier']) + # return ret = False has_better_quality = None + found_releases = [] + too_early_to_search = [] - for quality_type in media['profile']['types']: - # TODO check air date? - #if not self.conf('always_search') and not self.couldBeReleased(quality_type['quality']['identifier'] in pre_releases, release_dates, movie['library']['year']): - # too_early_to_search.append(quality_type['quality']['identifier']) - # continue + releases = fireEvent('release.for_media', media['_id'], single = True) + show_title = getTitle(show) + episode_identifier = '%s S%02d%s' % (show_title, season['info'].get('number', 0), "E%02d" % media['info'].get('number')) + + index = 0 + for q_identifier in profile.get('qualities'): + quality_custom = { + 'quality': q_identifier, + 'finish': profile['finish'][index], + 'wait_for': profile['wait_for'][index], + '3d': profile['3d'][index] if profile.get('3d') else False + } has_better_quality = 0 # See if better quality is available - for release in media['releases']: - if release['quality']['order'] <= quality_type['quality']['order'] and release['status_id'] not in [available_status.get('id'), ignored_status.get('id'), failed_status.get('id')]: + for release in releases: + if quality_order.index(release['quality']) <= quality_order.index(q_identifier) and release['status'] not in ['available', 'ignored', 'failed']: has_better_quality += 1 # Don't search for quality lower then already available. if has_better_quality is 0: - log.info('Search for %s S%02d%s in %s', ( - getTitle(show), - season['season_number'], - "E%02d" % episode['episode_number'] if episode and len(episode) == 1 else "", - quality_type['quality']['label']) - ) - quality = fireEvent('quality.single', identifier = quality_type['quality']['identifier'], single = True) + log.info('Searching for %s in %s', (episode_identifier, q_identifier)) + quality = fireEvent('quality.single', identifier = q_identifier, single = True) + quality['custom'] = quality_custom results = fireEvent('searcher.search', search_protocols, media, quality, single = True) if len(results) == 0: - log.debug('Nothing found for %s in %s', (default_title, quality_type['quality']['label'])) - - # Check if movie isn't deleted while searching - if not db.query(Media).filter_by(id = media.get('id')).first(): - break + log.debug('Nothing found for %s in %s', (episode_identifier, q_identifier)) # Add them to this movie releases list - found_releases += fireEvent('release.create_from_search', results, media, quality_type, single = True) + found_releases += fireEvent('release.create_from_search', results, media, quality, single = True) # Try find a valid result and download it - if fireEvent('release.try_download_result', results, media, quality_type, manual, single = True): + if fireEvent('release.try_download_result', results, media, quality, manual, single = True): ret = True # Remove releases that aren't found anymore - for release in media.get('releases', []): - if release.get('status_id') == available_status.get('id') and release.get('identifier') not in found_releases: + for release in releases: + if release.get('status') == 'available' and release.get('identifier') not in found_releases: fireEvent('release.delete', release.get('id'), single = True) else: - log.info('Better quality (%s) already available or snatched for %s', (quality_type['quality']['label'], default_title)) - fireEvent('media.restatus', media['id']) + log.info('Better quality (%s) already available or snatched for %s', (q_identifier, episode_identifier)) + fireEvent('media.restatus', media['_id']) break # Break if CP wants to shut down @@ -126,21 +174,7 @@ class ShowSearcher(SearcherBase, ShowTypeBase): break if len(too_early_to_search) > 0: - log.info2('Too early to search for %s, %s', (too_early_to_search, default_title)) - elif media['type'] == 'season' and not ret and has_better_quality is 0: - # If nothing was found, start searching for episodes individually - log.info('No season pack found, starting individual episode search') - - for library in episode: - # TODO ideally we shouldn't need to fetch the media for each episode library here - m = db.query(Media).filter_by(library_id = library['library_id']).first() - - fireEvent('episode.searcher.single', m.to_dict(ShowBase.search_dict)) - - - fireEvent('notify.frontend', type = 'show.searcher.ended.%s' % media['id'], data = True) - - return ret + log.info2('Too early to search for %s, %s', (too_early_to_search, episode_identifier)) def correctRelease(self, release = None, media = None, quality = None, **kwargs): @@ -163,28 +197,29 @@ class ShowSearcher(SearcherBase, ShowTypeBase): return False - def getLibraries(self, library): - if 'related_libraries' not in library: - log.warning("'related_libraries' missing from media library, unable to continue searching") - return None, None, None + def extendShow(self, media): - libraries = library['related_libraries'] + db = get_db() - # Show always collapses as there can never be any multiples - show = libraries.get('show', []) - show = show[0] if len(show) else None + seasons = db.get_many('media_children', media['_id'], with_doc = True) - # Season collapses if the subject is a season or episode - season = libraries.get('season', []) - if library['type'] in ['season', 'episode']: - season = season[0] if len(season) else None + media['seasons'] = {} - # Episode collapses if the subject is a episode - episode = libraries.get('episode', []) - if library['type'] == 'episode': - episode = episode[0] if len(episode) else None + for sx in seasons: + season = sx['doc'] - return show, season, episode + # Add episode info + season['episodes'] = {} + episodes = db.get_many('media_children', sx['_id'], with_doc = True) + + for se in episodes: + episode = se['doc'] + season['episodes'][episode['info'].get('number')] = episode + + # Add season to show + media['seasons'][season['info'].get('number', 0)] = season + + return media def searchAll(self): pass diff --git a/couchpotato/core/media/show/season.py b/couchpotato/core/media/show/season.py index 2f248763..9c6f8f07 100644 --- a/couchpotato/core/media/show/season.py +++ b/couchpotato/core/media/show/season.py @@ -17,7 +17,7 @@ class Season(MediaBase): addEvent('media.identifier', self.identifier) addEvent('show.season.add', self.add) - addEvent('show.season.update_info', self.update_info) + addEvent('show.season.update_info', self.updateInfo) def add(self, parent_id, info = None, update_after = True): if not info: info = {} @@ -25,14 +25,16 @@ class Season(MediaBase): identifiers = info.get('identifiers') try: del info['identifiers'] except: pass + try: del info['episodes'] + except: pass # Add Season season_info = { '_t': 'media', 'type': 'season', 'identifiers': identifiers, - 'parent': parent_id, - 'info': info, # Returned dict by providers + 'parent_id': parent_id, + 'info': info, # Returned dict by providers } # Check if season already exists @@ -54,7 +56,7 @@ class Season(MediaBase): return season - def update_info(self, media_id = None, info = None, force = False): + def updateInfo(self, media_id = None, info = None, force = False): if not info: info = {} if self.shuttingDown():