def patch(self, series_slug, episode_slug=None, path_param=None): """Patch episode.""" series_identifier = SeriesIdentifier.from_slug(series_slug) if not series_identifier: return self._bad_request('Invalid series slug') series = Series.find_by_identifier(series_identifier) if not series: return self._not_found('Series not found') data = json_decode(self.request.body) # Multi-patch request if not episode_slug: return self._patch_multi(series, data) episode_number = EpisodeNumber.from_slug(episode_slug) if not episode_number: return self._bad_request('Invalid episode number') episode = Episode.find_by_series_and_episode(series, episode_number) if not episode: return self._not_found('Episode not found') accepted = self._patch_episode(episode, data) return self._ok(data=accepted)
def delete(self, series_slug, episode_slug, **kwargs): """Delete the episode.""" if not series_slug: return self._method_not_allowed( 'Deleting multiple series are not allowed') identifier = SeriesIdentifier.from_slug(series_slug) if not identifier: return self._bad_request('Invalid series identifier') series = Series.find_by_identifier(identifier) if not series: return self._not_found('Series not found') episode_number = EpisodeNumber.from_slug(episode_slug) if not episode_number: return self._bad_request('Invalid episode number') episode = Episode.find_by_series_and_episode(series, episode_number) if not episode: return self._not_found('Episode not found') try: episode.delete_episode() except EpisodeDeletedException: return self._no_content() else: return self._conflict('Unable to delete episode')
def get_show_from_slug(slug): identifier = SeriesIdentifier.from_slug(slug) if not identifier: raise ChangeIndexerException(f'Could not create identifier with slug {slug}') show = Series.find_by_identifier(identifier) return show
def get_results(self, show_slug=None, season=None, episode=None): """Get cached results for this provider.""" cache_db_con = self._get_db() param = [] where = [] if show_slug: show = SeriesIdentifier.from_slug(show_slug) where += ['indexer', 'indexerid'] param += [show.indexer.id, show.id] if season: where += ['season'] param += [season] if episode: where += ['episodes'] param += ['|{0}|'.format(episode)] base_sql = 'SELECT * FROM [{name}]'.format(name=self.provider_id) base_params = [] if where and param: base_sql += ' WHERE ' base_sql += ' AND '.join([item + ' = ? ' for item in where]) base_params += param results = cache_db_con.select( base_sql, base_params ) return results
def http_post(self, series_slug=None, path_param=None): """Add a new series.""" if series_slug is not None: return self._bad_request('Series slug should not be specified') data = json_decode(self.request.body) if not data or 'id' not in data: return self._bad_request('Invalid series data') ids = {k: v for k, v in viewitems(data['id']) if k != 'imdb'} if len(ids) != 1: return self._bad_request('Only 1 indexer identifier should be specified') identifier = SeriesIdentifier.from_slug('{slug}{id}'.format(slug=list(ids)[0], id=list(itervalues(ids))[0])) if not identifier: return self._bad_request('Invalid series identifier') series = Series.find_by_identifier(identifier) if series: return self._conflict('Series already exist added') series = Series.from_identifier(identifier) if not Series.save_series(series): return self._not_found('Series not found in the specified indexer') return self._created(series.to_json(), identifier=identifier.slug)
def post(self, series_slug=None, path_param=None): """Add a new series.""" if series_slug is not None: return self._bad_request('Series slug should not be specified') data = json_decode(self.request.body) if not data or 'id' not in data: return self._bad_request('Invalid series data') ids = {k: v for k, v in viewitems(data['id']) if k != 'imdb'} if len(ids) != 1: return self._bad_request( 'Only 1 indexer identifier should be specified') identifier = SeriesIdentifier.from_slug('{slug}{id}'.format( slug=list(ids)[0], id=list(itervalues(ids))[0])) if not identifier: return self._bad_request('Invalid series identifier') series = Series.find_by_identifier(identifier) if series: return self._conflict('Series already exist added') series = Series.from_identifier(identifier) if not Series.save_series(series): return self._not_found('Series not found in the specified indexer') return self._created(series.to_json(), identifier=identifier.slug)
def resource_get_subtitle_missed(self): """Return a list of episodes which are missing a specific subtitle language.""" language = self.get_argument('language' '').strip() main_db_con = db.DBConnection() results = main_db_con.select( 'SELECT show_name, tv_shows.show_id, tv_shows.indexer, ' 'tv_shows.indexer_id AS indexer_id, tv_episodes.subtitles subtitles, ' 'tv_episodes.episode AS episode, tv_episodes.season AS season, ' 'tv_episodes.name AS name ' 'FROM tv_episodes, tv_shows ' 'WHERE tv_shows.subtitles = 1 ' 'AND tv_episodes.status = ? ' 'AND tv_episodes.season != 0 ' "AND tv_episodes.location != '' " 'AND tv_episodes.showid = tv_shows.indexer_id ' 'AND tv_episodes.indexer = tv_shows.indexer ' 'ORDER BY show_name', [DOWNLOADED]) subtitle_status = {} for cur_status_result in results: cur_indexer = int(cur_status_result['indexer']) cur_series_id = int(cur_status_result['indexer_id']) show_slug = SeriesIdentifier.from_id(cur_indexer, cur_series_id).slug subtitles = cur_status_result['subtitles'].split(',') if language == 'all': if not frozenset(wanted_languages()).difference(subtitles): continue elif language in subtitles: continue if show_slug not in subtitle_status: subtitle_status[show_slug] = { 'selected': True, 'slug': show_slug, 'name': cur_status_result['show_name'], 'episodes': [], 'showEpisodes': False } subtitle_status[show_slug]['episodes'].append({ 'episode': cur_status_result['episode'], 'season': cur_status_result['season'], 'selected': True, 'slug': str( RelativeNumber(cur_status_result['season'], cur_status_result['episode'])), 'name': cur_status_result['name'], 'subtitles': subtitles }) return self._ok(data=subtitle_status)
def http_post(self, identifier, **kwargs): """Add an alias.""" if identifier is not None: return self._bad_request('Alias id should not be specified') data = json_decode(self.request.body) if not data or not all([data.get('series'), data.get('name'), data.get('type')]) or 'id' in data or data['type'] != 'local': return self._bad_request('Invalid request body') series_identifier = SeriesIdentifier.from_slug(data.get('series')) if not series_identifier: return self._bad_request('Invalid series') cache_db_con = db.DBConnection('cache.db') last_changes = cache_db_con.connection.total_changes cursor = cache_db_con.action('INSERT INTO scene_exceptions' ' (indexer, indexer_id, show_name, season, custom) ' ' values (?,?,?,?,1)', [series_identifier.indexer.id, series_identifier.id, data['name'], data.get('season')]) if cache_db_con.connection.total_changes - last_changes <= 0: return self._conflict('Unable to create alias') data['id'] = cursor.lastrowid return self._created(data=data, identifier=data['id'])
def get(self, series_slug, identifier): """Query series information. :param series_slug: series slug. E.g.: tvdb1234 :param identifier: """ series_identifier = SeriesIdentifier.from_slug(series_slug) if not series_identifier: return self._bad_request('Invalid series slug') series = Series.find_by_identifier(series_identifier) if not series: return self._not_found('Series not found') if identifier == 'backlogged': # TODO: revisit allowed_qualities = self._parse( self.get_argument('allowed', default=None), str) allowed_qualities = list(map( int, allowed_qualities.split(','))) if allowed_qualities else [] preferred_qualities = self._parse( self.get_argument('preferred', default=None), str) preferred_qualities = list(map(int, preferred_qualities.split( ','))) if preferred_qualities else [] new, existing = series.get_backlogged_episodes( allowed_qualities=allowed_qualities, preferred_qualities=preferred_qualities) data = {'new': new, 'existing': existing} return self._ok(data=data) return self._bad_request('Invalid request')
def http_put(self, identifier, **kwargs): """Update alias information.""" identifier = self._parse(identifier) if not identifier: return self._not_found('Invalid alias id') data = json_decode(self.request.body) if not data or not all([data.get('id'), data.get('series'), data.get('name'), data.get('type')]) or data['id'] != identifier: return self._bad_request('Invalid request body') series_identifier = SeriesIdentifier.from_slug(data.get('series')) if not series_identifier: return self._bad_request('Invalid series') cache_db_con = db.DBConnection('cache.db') last_changes = cache_db_con.connection.total_changes cache_db_con.action('UPDATE scene_exceptions' ' set indexer = ?' ', indexer_id = ?' ', show_name = ?' ', season = ?' ', custom = 1' ' WHERE exception_id = ?', [series_identifier.indexer.id, series_identifier.id, data['name'], data.get('season'), identifier]) if cache_db_con.connection.total_changes - last_changes != 1: return self._not_found('Alias not found') return self._no_content()
def http_patch(self, series_slug, episode_slug=None, path_param=None): """Patch episode.""" series_identifier = SeriesIdentifier.from_slug(series_slug) if not series_identifier: return self._bad_request('Invalid series slug') series = Series.find_by_identifier(series_identifier) if not series: return self._not_found('Series not found') data = json_decode(self.request.body) # Multi-patch request if not episode_slug: return self._patch_multi(series, data) episode_number = EpisodeNumber.from_slug(episode_slug) if not episode_number: return self._bad_request('Invalid episode number') episode = Episode.find_by_series_and_episode(series, episode_number) if not episode: return self._not_found('Episode not found') accepted = self._patch_episode(episode, data) return self._ok(data=accepted)
def get(self, series_slug, path_param=None): """Query series information. :param series_slug: series slug. E.g.: tvdb1234 :param path_param: """ arg_paused = self._parse_boolean(self.get_argument('paused', default=None)) def filter_series(current): return arg_paused is None or current.paused == arg_paused if not series_slug: detailed = self._parse_boolean(self.get_argument('detailed', default=False)) data = [s.to_json(detailed=detailed) for s in Series.find_series(predicate=filter_series)] return self._paginate(data, sort='title') identifier = SeriesIdentifier.from_slug(series_slug) if not identifier: return self._bad_request('Invalid series slug') series = Series.find_by_identifier(identifier, predicate=filter_series) if not series: return self._not_found('Series not found') detailed = self._parse_boolean(self.get_argument('detailed', default=True)) data = series.to_json(detailed=detailed) if path_param: if path_param not in data: return self._bad_request("Invalid path parameter'{0}'".format(path_param)) data = data[path_param] return self._ok(data)
def http_delete(self, series_slug, episode_slug, **kwargs): """Delete the episode.""" if not series_slug: return self._method_not_allowed('Deleting multiple series are not allowed') identifier = SeriesIdentifier.from_slug(series_slug) if not identifier: return self._bad_request('Invalid series identifier') series = Series.find_by_identifier(identifier) if not series: return self._not_found('Series not found') episode_number = EpisodeNumber.from_slug(episode_slug) if not episode_number: return self._bad_request('Invalid episode number') episode = Episode.find_by_series_and_episode(series, episode_number) if not episode: return self._not_found('Episode not found') try: episode.delete_episode() except EpisodeDeletedException: return self._no_content() else: return self._conflict('Unable to delete episode')
def http_get(self, series_slug, identifier): """Query series information. :param series_slug: series slug. E.g.: tvdb1234 :param identifier: """ series_identifier = SeriesIdentifier.from_slug(series_slug) if not series_identifier: return self._bad_request('Invalid series slug') series = Series.find_by_identifier(series_identifier) if not series: return self._not_found('Series not found') if identifier == 'backlogged': # TODO: revisit allowed_qualities = self._parse(self.get_argument('allowed', default=None), str) allowed_qualities = list(map(int, allowed_qualities.split(','))) if allowed_qualities else [] preferred_qualities = self._parse(self.get_argument('preferred', default=None), str) preferred_qualities = list(map(int, preferred_qualities.split(','))) if preferred_qualities else [] new, existing = series.get_backlogged_episodes(allowed_qualities=allowed_qualities, preferred_qualities=preferred_qualities) data = {'new': new, 'existing': existing} return self._ok(data=data) return self._bad_request('Invalid request')
def post(self, identifier, **kwargs): """Add an alias.""" if identifier is not None: return self._bad_request('Alias id should not be specified') data = json_decode(self.request.body) if not data or not all( [data.get('series'), data.get('name'), data.get('type')]) or 'id' in data or data['type'] != 'local': return self._bad_request('Invalid request body') series_identifier = SeriesIdentifier.from_slug(data.get('series')) if not series_identifier: return self._bad_request('Invalid series') cache_db_con = db.DBConnection('cache.db') last_changes = cache_db_con.connection.total_changes cursor = cache_db_con.action( b'INSERT INTO scene_exceptions' b' (indexer, indexer_id, show_name, season, custom) ' b' values (?,?,?,?,1)', [ series_identifier.indexer.id, series_identifier.id, data['name'], data.get('season') ]) if cache_db_con.connection.total_changes - last_changes <= 0: return self._conflict('Unable to create alias') data['id'] = cursor.lastrowid return self._created(data=data, identifier=data['id'])
def _search_manual(self, data): """Queue a manual search for results for the provided episodes or season. :param data: :return: """ if not data.get('showSlug'): return self._bad_request('For a manual search you need to provide a show slug') if not data.get('episodes') and not data.get('season'): return self._bad_request('For a manual 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) season_segments = self._get_season_segments(series, data) for segments in ({'segment': episode_segments, 'manual_search_type': 'episode'}, {'segment': season_segments, 'manual_search_type': 'season'}): for segment in itervalues(segments['segment']): cur_manual_search_queue_item = ManualSearchQueueItem(series, segment, manual_search_type=segments['manual_search_type']) app.forced_search_queue_scheduler.action.add_item(cur_manual_search_queue_item) if not episode_segments and not season_segments: return self._not_found('Could not find any episode for show {show}. Did you provide the correct format?' .format(show=series.name)) return self._accepted('Manual search for {0} started'.format(data['showSlug']))
def http_patch(self, series_slug, path_param=None): """Patch series.""" if not series_slug: return self._method_not_allowed('Patching multiple series is not allowed') identifier = SeriesIdentifier.from_slug(series_slug) if not identifier: return self._bad_request('Invalid series identifier') series = Series.find_by_identifier(identifier) if not series: return self._not_found('Series not found') data = json_decode(self.request.body) indexer_id = data.get('id', {}).get(identifier.indexer.slug) if indexer_id is not None and indexer_id != identifier.id: return self._bad_request('Conflicting series identifier') accepted = {} ignored = {} patches = { 'config.aliases': ListField(series, 'aliases'), 'config.defaultEpisodeStatus': StringField(series, 'default_ep_status_name'), 'config.dvdOrder': BooleanField(series, 'dvd_order'), 'config.seasonFolders': BooleanField(series, 'season_folders'), 'config.anime': BooleanField(series, 'anime'), 'config.scene': BooleanField(series, 'scene'), 'config.sports': BooleanField(series, 'sports'), 'config.paused': BooleanField(series, 'paused'), 'config.location': StringField(series, 'location'), 'config.airByDate': BooleanField(series, 'air_by_date'), 'config.subtitlesEnabled': BooleanField(series, 'subtitles'), 'config.release.requiredWords': ListField(series, 'release_required_words'), 'config.release.ignoredWords': ListField(series, 'release_ignore_words'), 'config.release.blacklist': ListField(series, 'blacklist'), 'config.release.whitelist': ListField(series, 'whitelist'), 'config.release.requiredWordsExclude': BooleanField(series, 'rls_require_exclude'), 'config.release.ignoredWordsExclude': BooleanField(series, 'rls_ignore_exclude'), 'language': StringField(series, 'lang'), 'config.qualities.allowed': ListField(series, 'qualities_allowed'), 'config.qualities.preferred': ListField(series, 'qualities_preferred'), 'config.qualities.combined': IntegerField(series, 'quality'), 'config.airdateOffset': IntegerField(series, 'airdate_offset'), } for key, value in iter_nested_items(data): patch_field = patches.get(key) if patch_field and patch_field.patch(series, value): set_nested_value(accepted, key, value) else: set_nested_value(ignored, key, value) # Save patched attributes in db. series.save_to_db() if ignored: log.warning('Series patch ignored {items!r}', {'items': ignored}) return self._ok(data=accepted)
def backlogShow(self, showslug): identifier = SeriesIdentifier.from_slug(showslug) series_obj = Series.find_by_identifier(identifier) if series_obj: app.backlog_search_scheduler.action.search_backlog([series_obj]) return self.redirect('/manage/backlogOverview/')
def patch(self, series_slug, path_param=None): """Patch series.""" if not series_slug: return self._method_not_allowed('Patching multiple series is not allowed') identifier = SeriesIdentifier.from_slug(series_slug) if not identifier: return self._bad_request('Invalid series identifier') series = Series.find_by_identifier(identifier) if not series: return self._not_found('Series not found') data = json_decode(self.request.body) indexer_id = data.get('id', {}).get(identifier.indexer.slug) if indexer_id is not None and indexer_id != identifier.id: return self._bad_request('Conflicting series identifier') accepted = {} ignored = {} patches = { 'config.aliases': ListField(series, 'aliases'), 'config.defaultEpisodeStatus': StringField(series, 'default_ep_status_name'), 'config.dvdOrder': BooleanField(series, 'dvd_order'), 'config.seasonFolders': BooleanField(series, 'season_folders'), 'config.anime': BooleanField(series, 'anime'), 'config.scene': BooleanField(series, 'scene'), 'config.sports': BooleanField(series, 'sports'), 'config.paused': BooleanField(series, 'paused'), 'config.location': StringField(series, '_location'), 'config.airByDate': BooleanField(series, 'air_by_date'), 'config.subtitlesEnabled': BooleanField(series, 'subtitles'), 'config.release.requiredWords': ListField(series, 'release_required_words'), 'config.release.ignoredWords': ListField(series, 'release_ignore_words'), 'config.release.blacklist': ListField(series, 'blacklist'), 'config.release.whitelist': ListField(series, 'whitelist'), 'language': StringField(series, 'lang'), 'config.qualities.allowed': ListField(series, 'qualities_allowed'), 'config.qualities.preferred': ListField(series, 'qualities_preferred'), 'config.qualities.combined': IntegerField(series, 'quality'), } for key, value in iter_nested_items(data): patch_field = patches.get(key) if patch_field and patch_field.patch(series, value): set_nested_value(accepted, key, value) else: set_nested_value(ignored, key, value) # Save patched attributes in db. series.save_to_db() if ignored: log.warning('Series patch ignored {items!r}', {'items': ignored}) self._ok(data=accepted)
def get(self, series_slug, path_param): """ Get history records. History records can be specified using a show slug. """ sql_base = """ SELECT rowid, date, action, quality, provider, version, proper_tags, manually_searched, resource, size, indexer_id, showid, season, episode FROM history """ params = [] arg_page = self._get_page() arg_limit = self._get_limit(default=50) if series_slug is not None: series_identifier = SeriesIdentifier.from_slug(series_slug) if not series_identifier: return self._bad_request('Invalid series') sql_base += ' WHERE indexer_id = ? AND showid = ?' params += [series_identifier.indexer.id, series_identifier.id] sql_base += ' ORDER BY date DESC' results = db.DBConnection().select(sql_base, params) def data_generator(): """Read and paginate history records.""" start = arg_limit * (arg_page - 1) for item in results[start:start + arg_limit]: d = {} d['id'] = item['rowid'] d['series'] = SeriesIdentifier.from_id(item['indexer_id'], item['showid']).slug d['status'] = item['action'] d['actionDate'] = item['date'] d['resource'] = basename(item['resource']) d['size'] = item['size'] d['properTags'] = item['proper_tags'] d['statusName'] = statusStrings.get(item['action']) d['season'] = item['season'] d['episode'] = item['episode'] d['manuallySearched'] = bool(item['manually_searched']) d['provider'] = item['provider'] yield d if not results: return self._not_found('History data not found') return self._paginate(data_generator=data_generator)
def resource_get_episode_status(self): """Return a list of episodes with a specific status.""" status = self.get_argument('status' '').strip() status_list = [int(status)] if status_list: if status_list[0] == SNATCHED: status_list = [SNATCHED, SNATCHED_PROPER, SNATCHED_BEST] else: status_list = [] main_db_con = db.DBConnection() status_results = main_db_con.select( 'SELECT show_name, tv_shows.indexer, tv_shows.show_id, tv_shows.indexer_id AS indexer_id, ' 'tv_episodes.season AS season, tv_episodes.episode AS episode, tv_episodes.name as name ' 'FROM tv_episodes, tv_shows ' 'WHERE season != 0 ' 'AND tv_episodes.showid = tv_shows.indexer_id ' 'AND tv_episodes.indexer = tv_shows.indexer ' 'AND tv_episodes.status IN ({statuses}) '.format( statuses=','.join(['?'] * len(status_list))), status_list) episode_status = {} for cur_status_result in status_results: cur_indexer = int(cur_status_result['indexer']) cur_series_id = int(cur_status_result['indexer_id']) show_slug = SeriesIdentifier.from_id(cur_indexer, cur_series_id).slug if show_slug not in episode_status: episode_status[show_slug] = { 'selected': True, 'slug': show_slug, 'name': cur_status_result['show_name'], 'episodes': [], 'showEpisodes': False } episode_status[show_slug]['episodes'].append({ 'episode': cur_status_result['episode'], 'season': cur_status_result['season'], 'selected': True, 'slug': str( RelativeNumber(cur_status_result['season'], cur_status_result['episode'])), 'name': cur_status_result['name'] }) return self._ok(data={'episodeStatus': episode_status})
def resource_search_missing_subtitles(self): """ Search missing subtitles for multiple episodes at once. example: Pass the following structure: language: 'all', // Or a three letter language code. shows: [ { 'slug': 'tvdb1234', 'episodes': ['s01e01', 's02e03', 's10e10'] }, ] """ data = json_decode(self.request.body) language = data.get('language', 'all') shows = data.get('shows', []) if language != 'all' and language not in subtitle_code_filter(): return self._bad_request( 'You need to provide a valid subtitle code') for show in shows: # Loop through the shows. Each show should have an array of episode slugs series_identifier = SeriesIdentifier.from_slug(show.get('slug')) if not series_identifier: log.warning( 'Could not create a show identifier with slug {slug}', {'slug': show.get('slug')}) continue series = Series.find_by_identifier(series_identifier) if not series: log.warning( 'Could not match to a show in the library with slug {slug}', {'slug': show.get('slug')}) continue for episode_slug in show.get('episodes', []): episode_number = EpisodeNumber.from_slug(episode_slug) if not episode_number: log.warning('Bad episode number from slug {slug}', {'slug': episode_slug}) continue episode = Episode.find_by_series_and_episode( series, episode_number) if not episode: log.warning('Episode not found with slug {slug}', {'slug': episode_slug}) episode.download_subtitles( lang=language if language != 'all' else None) return self._ok()
def _search_failed(self, data): """Queue a failed search. :param data: :return: """ statuses = {} if not data.get('showSlug'): return self._bad_request( 'For a failed search you need to provide a show slug') if not data.get('episodes'): return self._bad_request( 'For a failed search you need to provide a list of episodes') 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') season_segments = defaultdict(list) for episode_slug in data['episodes']: episode_number = EpisodeNumber.from_slug(episode_slug) if not episode_number: statuses[episode_slug] = {'status': 400} continue episode = Episode.find_by_series_and_episode( series, episode_number) if not episode: statuses[episode_slug] = {'status': 404} continue season_segments[episode.season].append(episode) if not season_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(season_segments): cur_failed_queue_item = FailedQueueItem(series, segment) app.forced_search_queue_scheduler.action.add_item( cur_failed_queue_item) return self._accepted('Failed search for {0} started'.format( data['showSlug']))
def togglePause(self, showslug=None): # @TODO: Replace with PUT to update the state var /api/v2/show/{id} identifier = SeriesIdentifier.from_slug(showslug) error, series_obj = Show.pause(identifier.indexer.slug, identifier.id) if error is not None: return self._genericMessage('Error', error) ui.notifications.message('{show} has been {state}'.format( show=series_obj.name, state='paused' if series_obj.paused else 'resumed')) return self.redirect( '/home/displayShow?showslug={series_obj.slug}'.format( series_obj=series_obj))
def data_generator(): """Read and paginate history records.""" start = arg_limit * (arg_page - 1) for item in results[start:start + arg_limit]: provider = {} release_group = None release_name = None file_name = None subtitle_language = None if item['action'] in (SNATCHED, FAILED): provider.update({ 'id': GenericProvider.make_id(item['provider']), 'name': item['provider'] }) release_name = item['resource'] if item['action'] == DOWNLOADED: release_group = item['provider'] file_name = item['resource'] if item['action'] == SUBTITLED: subtitle_language = item['resource'] if item['action'] == SUBTITLED: subtitle_language = item['resource'] yield { 'id': item['rowid'], 'series': SeriesIdentifier.from_id(item['indexer_id'], item['showid']).slug, 'status': item['action'], 'statusName': statusStrings.get(item['action']), 'actionDate': item['date'], 'quality': item['quality'], 'resource': basename(item['resource']), 'size': item['size'], 'properTags': item['proper_tags'], 'season': item['season'], 'episode': item['episode'], 'manuallySearched': bool(item['manually_searched']), 'infoHash': item['info_hash'], 'provider': provider, 'release_name': release_name, 'releaseGroup': release_group, 'fileName': file_name, 'subtitleLanguage': subtitle_language }
def searchEpisodeSubtitles(self, showslug=None, season=None, episode=None, lang=None): # retrieve the episode object and fail if we can't get one series_obj = Series.find_by_identifier( SeriesIdentifier.from_slug(showslug)) ep_obj = series_obj.get_episode(season, episode) if not ep_obj: return json.dumps({ 'result': 'failure', }) try: if lang: logger.log( 'Manual re-downloading subtitles for {show} with language {lang}' .format(show=ep_obj.series.name, lang=lang)) new_subtitles = ep_obj.download_subtitles(lang=lang) except Exception as error: return json.dumps({ 'result': 'failure', 'description': 'Error while downloading subtitles: {error}'.format( error=error) }) if new_subtitles: new_languages = [ subtitles.name_from_code(code) for code in new_subtitles ] description = 'New subtitles downloaded: {languages}'.format( languages=', '.join(new_languages)) result = 'success' else: new_languages = [] description = 'No subtitles downloaded' result = 'failure' ui.notifications.message(ep_obj.series.name, description) return json.dumps({ 'result': result, 'subtitles': ep_obj.subtitles, 'languages': new_languages, 'description': description })
def get(self, series_slug, identifier, *args, **kwargs): """Get an asset.""" series_identifier = SeriesIdentifier.from_slug(series_slug) if not series_identifier: return self._bad_request('Invalid series slug') series = Series.find_by_identifier(series_identifier) if not series: return self._not_found('Series not found') asset_type = identifier or 'banner' asset = series.get_asset(asset_type) if not asset: return self._not_found('Asset not found') self._ok(stream=asset.media, content_type=asset.media_type)
def get(self, series_slug, episode_slug, path_param): """Query episode information. :param series_slug: series slug. E.g.: tvdb1234 :param episode_number: :param path_param: """ series_identifier = SeriesIdentifier.from_slug(series_slug) if not series_identifier: return self._bad_request('Invalid series slug') series = Series.find_by_identifier(series_identifier) if not series: return self._not_found('Series not found') if not episode_slug: detailed = self._parse_boolean( self.get_argument('detailed', default=False)) season = self._parse(self.get_argument('season', None), int) data = [ e.to_json(detailed=detailed) for e in series.get_all_episodes(season=season) ] return self._paginate(data, sort='airDate') episode_number = EpisodeNumber.from_slug(episode_slug) if not episode_number: return self._bad_request('Invalid episode number') episode = Episode.find_by_series_and_episode(series, episode_number) if not episode: return self._not_found('Episode not found') detailed = self._parse_boolean( self.get_argument('detailed', default=True)) data = episode.to_json(detailed=detailed) if path_param: if path_param == 'metadata': data = episode.metadata() if episode.is_location_valid( ) else {} elif path_param in data: data = data[path_param] else: return self._bad_request( "Invalid path parameter '{0}'".format(path_param)) return self._ok(data=data)
def get(self, identifier): """Collect ran, running and queued searches for a specific show. :param identifier: """ if not identifier: return self._bad_request( 'You need to add the show slug to the route') series = SeriesIdentifier.from_slug(identifier) if not series: return self._bad_request('Invalid series slug') series_obj = Series.find_by_identifier(series) if not series_obj: return self._not_found('Series not found') return {'results': collect_episodes_from_search_thread(series_obj)}
def delete(self, series_slug, path_param=None): """Delete the series.""" if not series_slug: return self._method_not_allowed('Deleting multiple series are not allowed') identifier = SeriesIdentifier.from_slug(series_slug) if not identifier: return self._bad_request('Invalid series identifier') series = Series.find_by_identifier(identifier) if not series: return self._not_found('Series not found') remove_files = self._parse_boolean(self.get_argument('remove-files', default=None)) if not series.delete(remove_files): return self._conflict('Unable to delete series') return self._no_content()
def test_series_identifier(p): # Given slug = p['slug'] expected = p['expected'] # When actual = SeriesIdentifier.from_slug(slug) # Then if expected is None: assert actual is None else: assert actual assert expected == actual assert expected.id == actual.id assert expected.indexer == actual.indexer assert expected.id != actual assert expected.indexer != actual
def http_delete(self, series_slug, path_param=None): """Delete the series.""" if not series_slug: return self._method_not_allowed('Deleting multiple series are not allowed') identifier = SeriesIdentifier.from_slug(series_slug) if not identifier: return self._bad_request('Invalid series identifier') series = Series.find_by_identifier(identifier) if not series: return self._not_found('Series not found') remove_files = self._parse_boolean(self.get_argument('remove-files', default=None)) if not series.delete(remove_files): return self._conflict('Unable to delete series') return self._no_content()
def patch(self, series_slug, path_param=None): """Patch series.""" if not series_slug: return self._method_not_allowed( 'Patching multiple series is not allowed') identifier = SeriesIdentifier.from_slug(series_slug) if not identifier: return self._bad_request('Invalid series identifier') series = Series.find_by_identifier(identifier) if not series: return self._not_found('Series not found') data = json_decode(self.request.body) indexer_id = data.get('id', {}).get(identifier.indexer.slug) if indexer_id is not None and indexer_id != identifier.id: return self._bad_request('Conflicting series identifier') accepted = {} ignored = {} patches = { 'config.dvdOrder': BooleanField(series, 'dvd_order'), 'config.flattenFolders': BooleanField(series, 'flatten_folders'), 'config.scene': BooleanField(series, 'scene'), 'config.paused': BooleanField(series, 'paused'), 'config.location': StringField(series, '_location'), 'config.airByDate': BooleanField(series, 'air_by_date'), 'config.subtitlesEnabled': BooleanField(series, 'subtitles') } for key, value in iter_nested_items(data): patch_field = patches.get(key) if patch_field and patch_field.patch(series, value): set_nested_value(accepted, key, value) else: set_nested_value(ignored, key, value) # Save patched attributes in db. series.save_to_db() if ignored: log.warning('Series patch ignored %r', ignored) self._ok(data=accepted)
def subtitleShow(self, showslug=None): if showslug is None: return self._genericMessage('Error', 'Invalid show ID') identifier = SeriesIdentifier.from_slug(showslug) series_obj = Series.find_by_identifier(identifier) if series_obj is None: return self._genericMessage('Error', 'Unable to find the specified show') # search and download subtitles app.show_queue_scheduler.action.download_subtitles(series_obj) time.sleep(cpu_presets[app.CPU_PRESET]) return self.redirect( '/home/displayShow?showslug={series_obj.slug}'.format( series_obj=series_obj))
def refreshShow(self, showslug=None): # @TODO: Replace with status=refresh from PATCH /api/v2/show/{id} identifier = SeriesIdentifier.from_slug(showslug) error, series_obj = Show.refresh(identifier.indexer.slug, identifier.id) # This is a show validation error if error is not None and series_obj is None: return self._genericMessage('Error', error) # This is a refresh error if error is not None: ui.notifications.error('Unable to refresh this show.', error) time.sleep(cpu_presets[app.CPU_PRESET]) return self.redirect( '/home/displayShow?showslug={series_obj.slug}'.format( series_obj=series_obj))
def data_generator(): """Read history data and normalize key/value pairs.""" for item in results: d = {} d['id'] = item['rowid'] d['series'] = SeriesIdentifier.from_id(item['indexer_id'], item['showid']).slug d['status'] = item['action'] d['actionDate'] = item['date'] d['resource'] = basename(item['resource']) d['size'] = item['size'] d['properTags'] = item['proper_tags'] d['statusName'] = statusStrings.get(item['action']) d['season'] = item['season'] d['episode'] = item['episode'] d['manuallySearched'] = bool(item['manually_searched']) d['provider'] = item['provider'] yield d
def http_get(self, series_slug, identifier, *args, **kwargs): """Get an asset.""" series_identifier = SeriesIdentifier.from_slug(series_slug) if not series_identifier: return self._bad_request('Invalid series slug') series = Series.find_by_identifier(series_identifier) if not series: return self._not_found('Series not found') asset_type = identifier or 'banner' asset = series.get_asset(asset_type, fallback=False) if not asset: return self._not_found('Asset not found') media = asset.media if not media: return self._not_found('{kind} not found'.format(kind=asset_type.capitalize())) return self._ok(stream=media, content_type=asset.media_type)
def http_get(self, series_slug, identifier, *args, **kwargs): """Get an asset.""" series_identifier = SeriesIdentifier.from_slug(series_slug) if not series_identifier: return self._bad_request('Invalid series slug') series = Series.find_by_identifier(series_identifier) if not series: return self._not_found('Series not found') asset_type = identifier or 'banner' asset = series.get_asset(asset_type, fallback=False) if not asset: return self._not_found('Asset not found') media = asset.media if not media: return self._not_found( '{kind} not found'.format(kind=asset_type.capitalize())) return self._ok(stream=media, content_type=asset.media_type)
def http_get(self, series_slug, episode_slug, path_param): """Query episode information. :param series_slug: series slug. E.g.: tvdb1234 :param episode_number: :param path_param: """ series_identifier = SeriesIdentifier.from_slug(series_slug) if not series_identifier: return self._bad_request('Invalid series slug') series = Series.find_by_identifier(series_identifier) if not series: return self._not_found('Series not found') if not episode_slug: detailed = self._parse_boolean(self.get_argument('detailed', default=False)) season = self._parse(self.get_argument('season', None), int) data = [e.to_json(detailed=detailed) for e in series.get_all_episodes(season=season)] return self._paginate(data, sort='airDate') episode_number = EpisodeNumber.from_slug(episode_slug) if not episode_number: return self._bad_request('Invalid episode number') episode = Episode.find_by_series_and_episode(series, episode_number) if not episode: return self._not_found('Episode not found') detailed = self._parse_boolean(self.get_argument('detailed', default=True)) data = episode.to_json(detailed=detailed) if path_param: if path_param == 'metadata': data = episode.metadata() if episode.is_location_valid() else {} elif path_param in data: data = data[path_param] else: return self._bad_request("Invalid path parameter '{0}'".format(path_param)) return self._ok(data=data)
def getSeasonSceneExceptions(self, showslug=None): """Get show name scene exceptions per season :param indexer: The shows indexer :param indexer_id: The shows indexer_id :return: A json with the scene exceptions per season. """ identifier = SeriesIdentifier.from_slug(showslug) series_obj = Series.find_by_identifier(identifier) return json.dumps({ 'seasonExceptions': { season: list(exception_name) for season, exception_name in iteritems( get_all_scene_exceptions(series_obj)) }, 'xemNumbering': { tvdb_season_ep[0]: anidb_season_ep[0] for (tvdb_season_ep, anidb_season_ep) in iteritems( get_xem_numbering_for_show(series_obj, refresh_data=False)) } })
def http_post(self, series_slug): """Query series information. :param series_slug: series slug. E.g.: tvdb1234 """ series_identifier = SeriesIdentifier.from_slug(series_slug) if not series_identifier: return self._bad_request('Invalid series slug') series = Series.find_by_identifier(series_identifier) if not series: return self._not_found('Series not found') data = json_decode(self.request.body) if not data or not all([data.get('type')]) or len(data) != 1: return self._bad_request('Invalid request body') if data['type'] == 'ARCHIVE_EPISODES': if series.set_all_episodes_archived(final_status_only=True): return self._created() return self._no_content() return self._bad_request('Invalid operation')
def http_get(self, series_slug, path_param=None): """Query series information. :param series_slug: series slug. E.g.: tvdb1234 :param path_param: """ arg_paused = self._parse_boolean(self.get_argument('paused', default=None)) def filter_series(current): return arg_paused is None or current.paused == arg_paused if not series_slug: detailed = self._parse_boolean(self.get_argument('detailed', default=False)) fetch = self._parse_boolean(self.get_argument('fetch', default=False)) data = [ s.to_json(detailed=detailed, fetch=fetch) for s in Series.find_series(predicate=filter_series) ] return self._paginate(data, sort='title') identifier = SeriesIdentifier.from_slug(series_slug) if not identifier: return self._bad_request('Invalid series slug') series = Series.find_by_identifier(identifier, predicate=filter_series) if not series: return self._not_found('Series not found') detailed = self._parse_boolean(self.get_argument('detailed', default=True)) fetch = self._parse_boolean(self.get_argument('fetch', default=False)) data = series.to_json(detailed=detailed, fetch=fetch) if path_param: if path_param not in data: return self._bad_request("Invalid path parameter '{0}'".format(path_param)) data = data[path_param] return self._ok(data)
def get_coming_episodes(categories, sort, group, paused=app.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=app.COMING_EPS_MISSED_RANGE)).toordinal() status_list = [DOWNLOADED, SNATCHED, SNATCHED_BEST, SNATCHED_PROPER, ARCHIVED, IGNORED] db = DBConnection() fields_to_select = ', '.join( ['airdate', 'airs', 'e.description as description', 'episode', 'imdb_id', 'e.indexer', 'indexer_id', 'name', 'network', 'paused', 's.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 = e.indexer ' 'AND s.indexer_id = e.showid ' 'AND e.status NOT IN (' + ','.join(['?'] * len(status_list)) + ')', [today, next_week] + status_list ) done_shows_list = [int(result['showid']) for result in results] placeholder = ','.join(['?'] * len(done_shows_list)) placeholder2 = ','.join(['?'] * len([DOWNLOADED, SNATCHED, SNATCHED_BEST, SNATCHED_PROPER])) # FIXME: This inner join is not multi indexer friendly. 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.indexer = e.indexer ' 'AND inner_e.airdate >= ? ' 'ORDER BY inner_e.airdate ASC LIMIT 1) ' 'AND e.status NOT IN (' + placeholder2 + ')', done_shows_list + [next_week] + [DOWNLOADED, SNATCHED, SNATCHED_BEST, 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(status_list)) + ')', [today, recently, WANTED, UNAIRED] + status_list ) for index, item in enumerate(results): item['series_slug'] = str(SeriesIdentifier.from_id(int(item['indexer']), item['indexer_id'])) results[index]['localtime'] = sbdatetime.convert_to_setting( parse_date_time(item['airdate'], item['airs'], item['network'])) results.sort(key=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 massEdit(self, toEdit=None): t = PageTemplate(rh=self, filename='manage_massEdit.mako') if not toEdit: return self.redirect('/manage/') series_slugs = toEdit.split('|') show_list = [] show_names = [] for slug in series_slugs: identifier = SeriesIdentifier.from_slug(slug) series_obj = Series.find_by_identifier(identifier) if series_obj: show_list.append(series_obj) show_names.append(series_obj.name) season_folders_all_same = True last_season_folders = None paused_all_same = True last_paused = None default_ep_status_all_same = True last_default_ep_status = None anime_all_same = True last_anime = None sports_all_same = True last_sports = None quality_all_same = True last_quality = None subtitles_all_same = True last_subtitles = None scene_all_same = True last_scene = None air_by_date_all_same = True last_air_by_date = None dvd_order_all_same = True last_dvd_order = None root_dir_list = [] for cur_show in show_list: cur_root_dir = os.path.dirname(cur_show._location) # pylint: disable=protected-access if cur_root_dir not in root_dir_list: root_dir_list.append(cur_root_dir) # if we know they're not all the same then no point even bothering if paused_all_same: # if we had a value already and this value is different then they're not all the same if last_paused not in (None, cur_show.paused): paused_all_same = False else: last_paused = cur_show.paused if default_ep_status_all_same: if last_default_ep_status not in (None, cur_show.default_ep_status): default_ep_status_all_same = False else: last_default_ep_status = cur_show.default_ep_status if anime_all_same: # if we had a value already and this value is different then they're not all the same if last_anime not in (None, cur_show.is_anime): anime_all_same = False else: last_anime = cur_show.anime if season_folders_all_same: if last_season_folders not in (None, cur_show.season_folders): season_folders_all_same = False else: last_season_folders = cur_show.season_folders if quality_all_same: if last_quality not in (None, cur_show.quality): quality_all_same = False else: last_quality = cur_show.quality if subtitles_all_same: if last_subtitles not in (None, cur_show.subtitles): subtitles_all_same = False else: last_subtitles = cur_show.subtitles if scene_all_same: if last_scene not in (None, cur_show.scene): scene_all_same = False else: last_scene = cur_show.scene if sports_all_same: if last_sports not in (None, cur_show.sports): sports_all_same = False else: last_sports = cur_show.sports if air_by_date_all_same: if last_air_by_date not in (None, cur_show.air_by_date): air_by_date_all_same = False else: last_air_by_date = cur_show.air_by_date if dvd_order_all_same: if last_dvd_order not in (None, cur_show.dvd_order): dvd_order_all_same = False else: last_dvd_order = cur_show.dvd_order default_ep_status_value = last_default_ep_status if default_ep_status_all_same else None paused_value = last_paused if paused_all_same else None anime_value = last_anime if anime_all_same else None season_folders_value = last_season_folders if season_folders_all_same else None quality_value = last_quality if quality_all_same else None subtitles_value = last_subtitles if subtitles_all_same else None scene_value = last_scene if scene_all_same else None sports_value = last_sports if sports_all_same else None air_by_date_value = last_air_by_date if air_by_date_all_same else None dvd_order_value = last_dvd_order if dvd_order_all_same else None root_dir_list = root_dir_list return t.render(showList=toEdit, showNames=show_names, default_ep_status_value=default_ep_status_value, dvd_order_value=dvd_order_value, paused_value=paused_value, anime_value=anime_value, season_folders_value=season_folders_value, quality_value=quality_value, subtitles_value=subtitles_value, scene_value=scene_value, sports_value=sports_value, air_by_date_value=air_by_date_value, root_dir_list=root_dir_list)
def massEditSubmit(self, paused=None, default_ep_status=None, dvd_order=None, anime=None, sports=None, scene=None, season_folders=None, quality_preset=None, subtitles=None, air_by_date=None, allowed_qualities=None, preferred_qualities=None, toEdit=None, *args, **kwargs): allowed_qualities = allowed_qualities or [] preferred_qualities = preferred_qualities or [] dir_map = {} for cur_arg in kwargs: if not cur_arg.startswith('orig_root_dir_'): continue which_index = cur_arg.replace('orig_root_dir_', '') end_dir = kwargs['new_root_dir_{index}'.format(index=which_index)] dir_map[kwargs[cur_arg]] = end_dir series_slugs = toEdit.split('|') if toEdit else [] errors = 0 for series_slug in series_slugs: identifier = SeriesIdentifier.from_slug(series_slug) series_obj = Series.find_by_identifier(identifier) if not series_obj: continue cur_root_dir = os.path.dirname(series_obj._location) cur_show_dir = os.path.basename(series_obj._location) if cur_root_dir in dir_map and cur_root_dir != dir_map[cur_root_dir]: new_show_dir = os.path.join(dir_map[cur_root_dir], cur_show_dir) logger.log(u'For show {show.name} changing dir from {show._location} to {location}'.format (show=series_obj, location=new_show_dir)) else: new_show_dir = series_obj._location if paused == 'keep': new_paused = series_obj.paused else: new_paused = True if paused == 'enable' else False new_paused = 'on' if new_paused else 'off' if default_ep_status == 'keep': new_default_ep_status = series_obj.default_ep_status else: new_default_ep_status = default_ep_status if anime == 'keep': new_anime = series_obj.anime else: new_anime = True if anime == 'enable' else False new_anime = 'on' if new_anime else 'off' if sports == 'keep': new_sports = series_obj.sports else: new_sports = True if sports == 'enable' else False new_sports = 'on' if new_sports else 'off' if scene == 'keep': new_scene = series_obj.is_scene else: new_scene = True if scene == 'enable' else False new_scene = 'on' if new_scene else 'off' if air_by_date == 'keep': new_air_by_date = series_obj.air_by_date else: new_air_by_date = True if air_by_date == 'enable' else False new_air_by_date = 'on' if new_air_by_date else 'off' if dvd_order == 'keep': new_dvd_order = series_obj.dvd_order else: new_dvd_order = True if dvd_order == 'enable' else False new_dvd_order = 'on' if new_dvd_order else 'off' if season_folders == 'keep': new_season_folders = series_obj.season_folders else: new_season_folders = True if season_folders == 'enable' else False new_season_folders = 'on' if new_season_folders else 'off' if subtitles == 'keep': new_subtitles = series_obj.subtitles else: new_subtitles = True if subtitles == 'enable' else False new_subtitles = 'on' if new_subtitles else 'off' if quality_preset == 'keep': allowed_qualities, preferred_qualities = series_obj.current_qualities elif try_int(quality_preset, None): preferred_qualities = [] exceptions_list = [] errors += self.editShow(identifier.indexer.slug, identifier.id, new_show_dir, allowed_qualities, preferred_qualities, exceptions_list, defaultEpStatus=new_default_ep_status, season_folders=new_season_folders, paused=new_paused, sports=new_sports, dvd_order=new_dvd_order, subtitles=new_subtitles, anime=new_anime, scene=new_scene, air_by_date=new_air_by_date, directCall=True) if errors: ui.notifications.error('Errors', '{num} error{s} while saving changes. Please check logs'.format (num=errors, s='s' if errors > 1 else '')) return self.redirect('/manage/')
def massUpdate(self, toUpdate=None, toRefresh=None, toRename=None, toDelete=None, toRemove=None, toMetadata=None, toSubtitle=None, toImageUpdate=None): to_update = toUpdate.split('|') if toUpdate else [] to_refresh = toRefresh.split('|') if toRefresh else [] to_rename = toRename.split('|') if toRename else [] to_subtitle = toSubtitle.split('|') if toSubtitle else [] to_delete = toDelete.split('|') if toDelete else [] to_remove = toRemove.split('|') if toRemove else [] to_metadata = toMetadata.split('|') if toMetadata else [] to_image_update = toImageUpdate.split('|') if toImageUpdate else [] errors = [] refreshes = [] updates = [] renames = [] subtitles = [] image_update = [] for slug in set(to_update + to_refresh + to_rename + to_subtitle + to_delete + to_remove + to_metadata + to_image_update): identifier = SeriesIdentifier.from_slug(slug) series_obj = Series.find_by_identifier(identifier) if not series_obj: continue if slug in to_delete + to_remove: app.show_queue_scheduler.action.removeShow(series_obj, slug in to_delete) continue # don't do anything else if it's being deleted or removed if slug in to_update: try: app.show_queue_scheduler.action.updateShow(series_obj) updates.append(series_obj.name) except CantUpdateShowException as msg: errors.append('Unable to update show: {error}'.format(error=msg)) elif slug in to_refresh: # don't bother refreshing shows that were updated try: app.show_queue_scheduler.action.refreshShow(series_obj) refreshes.append(series_obj.name) except CantRefreshShowException as msg: errors.append('Unable to refresh show {show.name}: {error}'.format (show=series_obj, error=msg)) if slug in to_rename: app.show_queue_scheduler.action.renameShowEpisodes(series_obj) renames.append(series_obj.name) if slug in to_subtitle: app.show_queue_scheduler.action.download_subtitles(series_obj) subtitles.append(series_obj.name) if slug in to_image_update: image_cache.replace_images(series_obj) if errors: ui.notifications.error('Errors encountered', '<br />\n'.join(errors)) message = '' if updates: message += '\nUpdates: {0}'.format(len(updates)) if refreshes: message += '\nRefreshes: {0}'.format(len(refreshes)) if renames: message += '\nRenames: {0}'.format(len(renames)) if subtitles: message += '\nSubtitles: {0}'.format(len(subtitles)) if image_update: message += '\nImage updates: {0}'.format(len(image_update)) if message: ui.notifications.message('Queued actions:', message) return self.redirect('/manage/')
def http_get(self, identifier, path_param): """Query scene_exception information.""" cache_db_con = db.DBConnection('cache.db') sql_base = ('SELECT ' ' exception_id, ' ' indexer, ' ' indexer_id, ' ' show_name, ' ' season, ' ' custom ' 'FROM scene_exceptions ') sql_where = [] params = [] if identifier is not None: sql_where.append('exception_id') params += [identifier] else: series_slug = self.get_query_argument('series', None) series_identifier = SeriesIdentifier.from_slug(series_slug) if series_slug and not series_identifier: return self._bad_request('Invalid series') season = self._parse(self.get_query_argument('season', None)) exception_type = self.get_query_argument('type', None) if exception_type and exception_type not in ('local', ): return self._bad_request('Invalid type') if series_identifier: sql_where.append('indexer') sql_where.append('indexer_id') params += [series_identifier.indexer.id, series_identifier.id] if season is not None: sql_where.append('season') params += [season] if exception_type == 'local': sql_where.append('custom') params += [1] if sql_where: sql_base += ' WHERE ' + ' AND '.join([where + ' = ? ' for where in sql_where]) sql_results = cache_db_con.select(sql_base, params) data = [] for item in sql_results: d = {} d['id'] = item['exception_id'] d['series'] = SeriesIdentifier.from_id(item['indexer'], item['indexer_id']).slug d['name'] = item['show_name'] d['season'] = item['season'] if item['season'] >= 0 else None d['type'] = 'local' if item['custom'] else None data.append(d) if not identifier: return self._paginate(data, sort='id') if not data: return self._not_found('Alias not found') data = data[0] if path_param: if path_param not in data: return self._bad_request('Invalid path parameter') data = data[path_param] return self._ok(data=data)