# Author: Nic Wolfe # URL: http://code.google.com/p/sickbeard/ # # This file is part of SickGear. # # SickGear is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # SickGear is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with SickGear. If not, see . from __future__ import with_statement import datetime import os import traceback # noinspection PyPep8Naming import encodingKludge as ek import exceptions_helper from exceptions_helper import ex import sickbeard from . import logger, ui, db, generic_queue, name_cache from .anime import BlackAndWhiteList from .common import SKIPPED, WANTED, UNAIRED, Quality, statusStrings from .helpers import should_delete_episode from .indexermapper import map_indexers_to_show from .indexers.indexer_config import TVINFO_TVDB, TVINFO_TVRAGE from .indexers.indexer_exceptions import check_exception_type, ExceptionTuples from .name_parser.parser import NameParser from .tv import TVShow # noinspection PyUnreachableCode if False: from typing import AnyStr, Dict, List class ShowQueue(generic_queue.GenericQueue): def __init__(self): generic_queue.GenericQueue.__init__(self) self.queue_name = 'SHOWQUEUE' def _isInQueue(self, show_obj, actions): # type: (TVShow, tuple) -> bool """ :param show_obj: show object :param actions: :return: """ with self.lock: return show_obj in [x.show_obj for x in self.queue if x.action_id in actions] def _isBeingSomethinged(self, show_obj, actions): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :param actions: :return: :rtype: bool """ with self.lock: return None is not self.currentItem \ and show_obj == self.currentItem.show_obj \ and self.currentItem.action_id in actions def isInUpdateQueue(self, show_obj): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :return: :rtype: bool """ return self._isInQueue(show_obj, (ShowQueueActions.UPDATE, ShowQueueActions.FORCEUPDATE)) def isInRefreshQueue(self, show_obj): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :return: :rtype: bool """ return self._isInQueue(show_obj, (ShowQueueActions.REFRESH,)) def isInRenameQueue(self, show_obj): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :return: :rtype: bool """ return self._isInQueue(show_obj, (ShowQueueActions.RENAME,)) def isInSubtitleQueue(self, show_obj): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :return: :rtype: bool """ return self._isInQueue(show_obj, (ShowQueueActions.SUBTITLE,)) def isBeingAdded(self, show_obj): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :return: :rtype: bool """ return self._isBeingSomethinged(show_obj, (ShowQueueActions.ADD,)) def isBeingUpdated(self, show_obj): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :return: :rtype: bool """ return self._isBeingSomethinged(show_obj, (ShowQueueActions.UPDATE, ShowQueueActions.FORCEUPDATE)) def isBeingRefreshed(self, show_obj): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :return: :rtype: bool """ return self._isBeingSomethinged(show_obj, (ShowQueueActions.REFRESH,)) def isBeingRenamed(self, show_obj): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :return: :rtype: bool """ return self._isBeingSomethinged(show_obj, (ShowQueueActions.RENAME,)) def isBeingSubtitled(self, show_obj): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :return: :rtype: bool """ return self._isBeingSomethinged(show_obj, (ShowQueueActions.SUBTITLE,)) def isShowUpdateRunning(self): """ :return: :rtype: bool """ with self.lock: for x in self.queue + [self.currentItem]: if isinstance(x, ShowQueueItem) and x.scheduled_update: return True return False def _getLoadingShowList(self): """ :return: :rtype: List """ with self.lock: return [x for x in self.queue + [self.currentItem] if None is not x and x.isLoading] def queue_length(self): # type: (...) -> Dict[AnyStr, List[AnyStr, Dict]] """ :return: """ length = dict(add=[], update=[], forceupdate=[], forceupdateweb=[], refresh=[], rename=[], subtitle=[]) with self.lock: for cur_item in [self.currentItem] + self.queue: # type: ShowQueueItem if not cur_item: continue result_item = dict(name=cur_item.show_name, scheduled_update=cur_item.scheduled_update) if isinstance(cur_item, QueueItemAdd): length['add'].append(result_item) else: result_item.update(dict( tvid=cur_item.show_obj.tvid, prodid=cur_item.show_obj.prodid, tvid_prodid=cur_item.show_obj.tvid_prodid, # legacy keys for api responses indexer=cur_item.show_obj.tvid, indexerid=cur_item.show_obj.prodid)) if isinstance(cur_item, QueueItemUpdate): update_type = 'Normal' if isinstance(cur_item, QueueItemForceUpdate): update_type = 'Forced' elif isinstance(cur_item, QueueItemForceUpdateWeb): update_type = 'Forced Web' result_item.update(dict(update_type=update_type)) length['update'].append(result_item) elif isinstance(cur_item, QueueItemRefresh): length['refresh'].append(result_item) elif isinstance(cur_item, QueueItemRename): length['rename'].append(result_item) elif isinstance(cur_item, QueueItemSubtitle): length['subtitle'].append(result_item) return length loadingShowList = property(_getLoadingShowList) def updateShow(self, show_obj, force=False, web=False, scheduled_update=False, priority=generic_queue.QueuePriorities.NORMAL, **kwargs): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :param force: :type force: bool :param web: :type web: bool :param scheduled_update: :type scheduled_update: bool :param priority: :type priority: int :param kwargs: :return: :rtype: QueueItemUpdate or QueueItemForceUpdateWeb or QueueItemForceUpdate """ if self.isBeingAdded(show_obj): raise exceptions_helper.CantUpdateException( 'Show is still being added, wait until it is finished before you update.') if self.isBeingUpdated(show_obj): raise exceptions_helper.CantUpdateException( 'This show is already being updated, can\'t update again until it\'s done.') if self.isInUpdateQueue(show_obj): raise exceptions_helper.CantUpdateException( 'This show is already being updated, can\'t update again until it\'s done.') if not force: queue_item_obj = QueueItemUpdate( show_obj, scheduled_update=scheduled_update, **kwargs) elif web: queue_item_obj = QueueItemForceUpdateWeb( show_obj, scheduled_update=scheduled_update, priority=priority, **kwargs) else: queue_item_obj = QueueItemForceUpdate( show_obj, scheduled_update=scheduled_update, **kwargs) self.add_item(queue_item_obj) return queue_item_obj def refreshShow(self, show_obj, force=False, scheduled_update=False, after_update=False, priority=generic_queue.QueuePriorities.HIGH, force_image_cache=False, **kwargs): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :param force: :type force: bool :param scheduled_update: :type scheduled_update: bool :param after_update: :type after_update: :param priority: :type priority: int :param force_image_cache: :type force_image_cache: bool :param kwargs: :return: :rtype: QueueItemRefresh """ if self.isBeingRefreshed(show_obj) and not force: raise exceptions_helper.CantRefreshException('This show is already being refreshed, not refreshing again.') if ((not after_update and self.isBeingUpdated(show_obj)) or self.isInUpdateQueue(show_obj)) and not force: logger.log('Skipping this refresh as there is already an update queued or' ' in progress and a refresh is done at the end of an update anyway.', logger.DEBUG) return queue_item_obj = QueueItemRefresh(show_obj, force=force, scheduled_update=scheduled_update, priority=priority, force_image_cache=force_image_cache, **kwargs) self.add_item(queue_item_obj) return queue_item_obj def renameShowEpisodes(self, show_obj): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :return: :rtype: QueueItemRename """ queue_item_obj = QueueItemRename(show_obj) self.add_item(queue_item_obj) return queue_item_obj def download_subtitles(self, show_obj): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :return: :rtype: QueueItemSubtitle """ queue_item_obj = QueueItemSubtitle(show_obj) self.add_item(queue_item_obj) return queue_item_obj def addShow(self, tvid, prodid, show_dir, default_status=None, quality=None, flatten_folders=None, lang='en', subtitles=None, anime=None, scene=None, paused=None, blacklist=None, whitelist=None, wanted_begin=None, wanted_latest=None, prune=None, tag=None, new_show=False, show_name=None, upgrade_once=False): """ :param tvid: tvid :type tvid: int :param prodid: prodid :type prodid: int or long :param show_dir: show dir :type show_dir: AnyStr :param default_status: :type default_status: int or None :param quality: :type quality: None or int :param flatten_folders: :type flatten_folders: int or None :param lang: :type lang: AnyStr :param subtitles: :type subtitles: int or None :param anime: :type anime: int or None :param scene: :type scene: int or None :param paused: :type paused: None or int :param blacklist: :type blacklist: AnyStr or None :param whitelist: :type whitelist: AnyStr or None :param wanted_begin: :type wanted_begin: int or None :param wanted_latest: :type wanted_latest: int or None :param prune: :type prune: int or None :param tag: :type tag: AnyStr or None :param new_show: :type new_show: AnyStr or None :param show_name: :type show_name: AnyStr or None :param upgrade_once: :type upgrade_once: bool or None :return: :rtype: QueueItemAdd """ queue_item_obj = QueueItemAdd(tvid, prodid, show_dir, default_status, quality, flatten_folders, lang, subtitles, anime, scene, paused, blacklist, whitelist, wanted_begin, wanted_latest, prune, tag, new_show=new_show, show_name=show_name, upgrade_once=upgrade_once) self.add_item(queue_item_obj) return queue_item_obj class ShowQueueActions(object): REFRESH = 1 ADD = 2 UPDATE = 3 FORCEUPDATE = 4 RENAME = 5 SUBTITLE = 6 names = {REFRESH: 'Refresh', ADD: 'Add', UPDATE: 'Update', FORCEUPDATE: 'Force Update', RENAME: 'Rename', SUBTITLE: 'Subtitle'} class ShowQueueItem(generic_queue.QueueItem): """ Represents an item in the queue waiting to be executed Can be either: - show being added (may or may not be associated with a show object) - show being refreshed - show being updated - show being force updated - show being subtitled """ def __init__(self, action_id, show_obj, scheduled_update=False): """ :param action_id: :type action_id: :param show_obj: show object :type show_obj: sickbeard.tv.TVShow or None :param scheduled_update: :type scheduled_update: bool """ generic_queue.QueueItem.__init__(self, ShowQueueActions.names[action_id], action_id) self.show_obj = show_obj # type: sickbeard.tv.TVShow self.scheduled_update = scheduled_update # type: bool def isInQueue(self): """ :rtype: bool """ return self in sickbeard.showQueueScheduler.action.queue + [ sickbeard.showQueueScheduler.action.currentItem] def _getName(self): """ :rtype: AnyStr """ if self.show_obj: return self.show_obj.name return '' def _isLoading(self): return False show_name = property(_getName) isLoading = property(_isLoading) class QueueItemAdd(ShowQueueItem): def __init__(self, tvid, prodid, show_dir, default_status, quality, flatten_folders, lang, subtitles, anime, scene, paused, blacklist, whitelist, default_wanted_begin, default_wanted_latest, prune, tag, scheduled_update=False, new_show=False, show_name=None, upgrade_once=False): """ :param tvid: tvid :type tvid: int :param prodid: prodid :type prodid: int or long :param show_dir: :type show_dir: AnyStr :param default_status: :type default_status: :param quality: :type quality: int :param flatten_folders: :type flatten_folders: :param lang: :type lang: :param subtitles: :type subtitles: :param anime: :type anime: :param scene: :type scene: :param paused: :type paused: :param blacklist: :type blacklist: :param whitelist: :type whitelist: :param default_wanted_begin: :type default_wanted_begin: :param default_wanted_latest: :type default_wanted_latest: :param prune: :type prune: :param tag: :type tag: :param scheduled_update: :type scheduled_update: :param new_show: :type new_show: :param show_name: :type show_name: AnyStr or None :param upgrade_once: :type upgrade_once: """ self.tvid = tvid # type: int self.prodid = prodid # type: int self.showDir = show_dir # type: AnyStr self.default_status = default_status self.default_wanted_begin = default_wanted_begin self.default_wanted_latest = default_wanted_latest self.quality = quality # type: int self.upgrade_once = upgrade_once self.flatten_folders = flatten_folders self.lang = lang self.subtitles = subtitles self.anime = anime self.scene = scene self.paused = paused self.blacklist = blacklist self.whitelist = whitelist self.prune = prune self.tag = tag self.new_show = new_show self.showname = show_name # type: AnyStr or None self.show_obj = None # this will initialize self.show_obj to None ShowQueueItem.__init__(self, ShowQueueActions.ADD, self.show_obj, scheduled_update) self.priority = generic_queue.QueuePriorities.VERYHIGH def _getName(self): """ :return: the show name if there is a show object created, if not returns the dir that the show is being added to. :rtype: AnyStr """ if None is not self.showname: return self.showname if None is self.show_obj: return self.showDir return self.show_obj.name show_name = property(_getName) def _isLoading(self): """ :return: True if we've gotten far enough to have a show object, or False if we still only know the folder name. :rtype: bool """ if None is self.show_obj: return True return False isLoading = property(_isLoading) # if they gave a number to start or number to end as wanted, then change those eps to it def _get_wanted(self, db_obj, wanted_max, latest): # type (...) -> int latest_season = 0 actual = 0 upgradable = 0 process_sql = True wanted_updates = [] if latest: # find season number with latest aired episode latest_result = db_obj.select('SELECT MAX(season) AS latest_season FROM tv_episodes WHERE season > 0 ' 'AND indexer = ? AND showid = ? AND status != ?', [self.show_obj.tvid, self.show_obj.prodid, UNAIRED]) if latest_result and None is not latest_result[0]['latest_season']: latest_season = int(latest_result[0]['latest_season']) else: process_sql = False if process_sql: if -1 == wanted_max: compare_op = '' # equal, we only want the specific season else: compare_op = ('>', '<')[latest] # less/more then equal season base_sql = 'SELECT indexerid, season, episode, status FROM tv_episodes WHERE indexer = ?' \ ' AND showid = ? AND season != 0 AND season %s= ? AND status != ?' \ ' ORDER BY season%s, episode%s%s' % \ (compare_op, ('', ' DESC')[latest], ('', ' DESC')[latest], (' LIMIT ?', '')[-1 == wanted_max]) selected_res = db_obj.select(base_sql, [self.show_obj.tvid, self.show_obj.prodid, (1, latest_season)[latest], UNAIRED] + ([wanted_max], [])[-1 == wanted_max]) selected_ids = [] for sr in selected_res: if sr['status'] in [SKIPPED]: selected_ids.append(sr['indexerid']) wanted_updates.append({'season': sr['season'], 'episode': sr['episode'], 'status': sr['status']}) elif sr['status'] not in [WANTED]: cur_status, cur_quality = Quality.splitCompositeStatus(int(sr['status'])) if sickbeard.WANTEDLIST_CACHE.get_wantedlist( self.quality, self.upgrade_once, cur_quality, cur_status, unaired=(sickbeard.SEARCH_UNAIRED and not sickbeard.UNAIRED_RECENT_SEARCH_ONLY)): upgradable += 1 if selected_ids: update = 'UPDATE [tv_episodes] SET status = ? WHERE indexer = ? AND indexerid IN (%s)' % \ ','.join(['?'] * len(selected_ids)) db_obj.action(update, [WANTED, self.show_obj.tvid] + selected_ids) # noinspection SqlResolve result = db_obj.select('SELECT changes() as last FROM [tv_episodes]') for cur_result in result: actual = cur_result['last'] break action_log = 'didn\'t find any episodes that need to be set wanted' if actual: action_log = ('updated %s %s episodes > %s' % ( (((('%s of %s' % (actual, wanted_max)), ('%s of max %s limited' % (actual, wanted_max)))[10 == wanted_max]), ('max %s available' % actual))[-1 == wanted_max], ('first season', 'latest')[latest], ', '.join([ ('S%02dE%02d=%s' % (a['season'], a['episode'], statusStrings[a['status']])) for a in wanted_updates ]) )) logger.log('Get wanted ' + action_log) return actual + upgradable def run(self): ShowQueueItem.run(self) logger.log('Starting to add show %s' % self.showDir) # make sure the TV info source IDs are valid try: tvinfo_config = sickbeard.TVInfoAPI(self.tvid).api_params.copy() if self.lang: tvinfo_config['language'] = self.lang logger.log(u'' + str(sickbeard.TVInfoAPI(self.tvid).name) + ': ' + repr(tvinfo_config)) t = sickbeard.TVInfoAPI(self.tvid).setup(**tvinfo_config) s = t[self.prodid, False] if getattr(t, 'show_not_found', False): logger.log('Show %s was not found on %s, maybe show was deleted' % (self.show_name, sickbeard.TVInfoAPI(self.tvid).name), logger.ERROR) self._finishEarly() return # this usually only happens if they have an NFO in their show dir # which gave us a TV info source ID that has no proper english version of the show if None is getattr(s, 'seriesname', None): logger.log('Show in %s has no name on %s, probably the wrong language used to search with.' % (self.showDir, sickbeard.TVInfoAPI(self.tvid).name), logger.ERROR) ui.notifications.error('Unable to add show', 'Show in %s has no name on %s, probably the wrong language.' ' Delete .nfo and add manually in the correct language.' % (self.showDir, sickbeard.TVInfoAPI(self.tvid).name)) self._finishEarly() return except (BaseException, Exception): logger.log('Unable to find show ID:%s on TV info: %s' % (self.prodid, sickbeard.TVInfoAPI(self.tvid).name), logger.ERROR) ui.notifications.error('Unable to add show', 'Unable to look up the show in %s on %s using ID %s, not using the NFO.' ' Delete .nfo and try adding manually again.' % (self.showDir, sickbeard.TVInfoAPI(self.tvid).name, self.prodid)) self._finishEarly() return try: new_show_obj = TVShow(self.tvid, self.prodid, self.lang) new_show_obj.load_from_tvinfo() self.show_obj = new_show_obj # set up initial values self.show_obj.location = self.showDir self.show_obj.subtitles = self.subtitles if None is not self.subtitles else sickbeard.SUBTITLES_DEFAULT self.show_obj.quality = self.quality if self.quality else sickbeard.QUALITY_DEFAULT self.show_obj.upgrade_once = self.upgrade_once self.show_obj.flatten_folders = self.flatten_folders if None is not self.flatten_folders \ else sickbeard.FLATTEN_FOLDERS_DEFAULT self.show_obj.anime = self.anime if None is not self.anime else sickbeard.ANIME_DEFAULT self.show_obj.scene = self.scene if None is not self.scene else sickbeard.SCENE_DEFAULT self.show_obj.paused = self.paused if None is not self.paused else False self.show_obj.prune = self.prune if None is not self.prune else 0 self.show_obj.tag = self.tag if None is not self.tag else 'Show List' if self.show_obj.anime: self.show_obj.release_groups = BlackAndWhiteList(self.show_obj.tvid, self.show_obj.prodid, self.show_obj.tvid_prodid) if self.blacklist: self.show_obj.release_groups.set_black_keywords(self.blacklist) if self.whitelist: self.show_obj.release_groups.set_white_keywords(self.whitelist) # be smartish about this if self.show_obj.genre and 'talk show' in self.show_obj.genre.lower(): self.show_obj.air_by_date = 1 if self.show_obj.genre and 'documentary' in self.show_obj.genre.lower(): self.show_obj.air_by_date = 0 if self.show_obj.classification and 'sports' in self.show_obj.classification.lower(): self.show_obj.sports = 1 except Exception as e: if check_exception_type(e, ExceptionTuples.tvinfo_exception): logger.log( 'Unable to add show due to an error with %s: %s' % (sickbeard.TVInfoAPI(self.tvid).name, ex(e)), logger.ERROR) if self.show_obj: ui.notifications.error('Unable to add %s due to an error with %s' % (self.show_obj.name, sickbeard.TVInfoAPI(self.tvid).name)) else: ui.notifications.error( 'Unable to add show due to an error with %s' % sickbeard.TVInfoAPI(self.tvid).name) self._finishEarly() return elif check_exception_type(e, exceptions_helper.MultipleShowObjectsException): logger.log('The show in %s is already in your show list, skipping' % self.showDir, logger.ERROR) ui.notifications.error('Show skipped', 'The show in %s is already in your show list' % self.showDir) self._finishEarly() return else: logger.log('Error trying to add show: %s' % ex(e), logger.ERROR) logger.log(traceback.format_exc(), logger.ERROR) self._finishEarly() raise self.show_obj.load_imdb_info() try: self.show_obj.save_to_db() except (BaseException, Exception) as e: logger.log('Error saving the show to the database: %s' % ex(e), logger.ERROR) logger.log(traceback.format_exc(), logger.ERROR) self._finishEarly() raise # add it to the show list sickbeard.showList.append(self.show_obj) try: self.show_obj.load_episodes_from_tvinfo() except (BaseException, Exception) as e: logger.log( 'Error with %s, not creating episode list: %s' % (sickbeard.TVInfoAPI(self.show_obj.tvid).name, ex(e)), logger.ERROR) logger.log(traceback.format_exc(), logger.ERROR) try: self.show_obj.load_episodes_from_dir() except (BaseException, Exception) as e: logger.log('Error searching directory for episodes: %s' % ex(e), logger.ERROR) logger.log(traceback.format_exc(), logger.ERROR) # if they gave a custom status then change all the eps to it my_db = db.DBConnection() if self.default_status != SKIPPED: logger.log('Setting all episodes to the specified default status: %s' % sickbeard.common.statusStrings[self.default_status]) my_db.action('UPDATE tv_episodes' ' SET status = ?' ' WHERE status = ?' ' AND indexer = ? AND showid = ?' ' AND season != 0', [self.default_status, SKIPPED, self.show_obj.tvid, self.show_obj.prodid]) items_wanted = self._get_wanted(my_db, self.default_wanted_begin, latest=False) items_wanted += self._get_wanted(my_db, self.default_wanted_latest, latest=True) self.show_obj.write_metadata() self.show_obj.update_metadata() self.show_obj.populate_cache() self.show_obj.flush_episodes() # load ids _ = self.show_obj.ids # if sickbeard.USE_TRAKT: # # if there are specific episodes that need to be added by trakt # sickbeard.traktCheckerScheduler.action.manageNewShow(self.show_obj) # # # add show to trakt.tv library # if sickbeard.TRAKT_SYNC: # sickbeard.traktCheckerScheduler.action.addShowToTraktLibrary(self.show_obj) # Load XEM data to DB for show sickbeard.scene_numbering.xem_refresh(self.show_obj.tvid, self.show_obj.prodid, force=True) if self.show_obj.scene: # enable/disable scene flag based on if show has an explicit _scene_ mapping at XEM self.show_obj.scene = sickbeard.scene_numbering.has_xem_scene_mapping( self.show_obj.tvid, self.show_obj.prodid) # if "scene" numbering is disabled during add show, output availability to log if None is not self.scene and not self.show_obj.scene and \ self.show_obj.prodid in sickbeard.scene_exceptions.xem_ids_list[self.show_obj.tvid]: logger.log('No scene number mappings found at TheXEM. Therefore, episode scene numbering disabled, ' 'edit show and enable it to manually add custom numbers for search and media processing.') try: self.show_obj.save_to_db() except (BaseException, Exception) as e: logger.log('Error saving the show to the database: %s' % ex(e), logger.ERROR) logger.log(traceback.format_exc(), logger.ERROR) self._finishEarly() raise # update internal name cache name_cache.buildNameCache(self.show_obj) self.show_obj.load_episodes_from_db() if self.show_obj.tvid in (TVINFO_TVRAGE, TVINFO_TVDB): # noinspection SqlResolve oh = my_db.select('SELECT resource FROM history WHERE indexer = 0 AND showid = ?', [self.show_obj.prodid]) if oh: found = False for o in oh: np = NameParser(file_name=True, indexer_lookup=False, try_scene_exceptions=True) try: pr = np.parse(o['resource']) except (BaseException, Exception): continue if pr.show_obj.tvid == self.show_obj.tvid and pr.show_obj.prodid == self.show_obj.prodid: found = True break if found: my_db.action('UPDATE history SET indexer = ? WHERE indexer = 0 AND showid = ?', [self.show_obj.tvid, self.show_obj.prodid]) msg = ' the specified show into ' + self.showDir # if started with WANTED eps then run the backlog if WANTED == self.default_status or items_wanted: logger.log('Launching backlog for this show since episodes are WANTED') sickbeard.backlogSearchScheduler.action.search_backlog([self.show_obj]) ui.notifications.message('Show added/search', 'Adding and searching for episodes of' + msg) else: ui.notifications.message('Show added', 'Adding' + msg) self.finish() def _finishEarly(self): if None is not self.show_obj: self.show_obj.delete_show() if self.new_show: # if we adding a new show, delete the empty folder that was already created try: ek.ek(os.rmdir, self.showDir) except (BaseException, Exception): pass self.finish() class QueueItemRefresh(ShowQueueItem): def __init__(self, show_obj=None, force=False, scheduled_update=False, priority=generic_queue.QueuePriorities.HIGH, force_image_cache=False, **kwargs): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :param force: :type force: bool :param scheduled_update: :type scheduled_update: bool :param priority: :type priority: int :param force_image_cache: :type force_image_cache: bool :param kwargs: """ ShowQueueItem.__init__(self, ShowQueueActions.REFRESH, show_obj, scheduled_update) # do refreshes first because they're quick self.priority = priority # type: int # force refresh certain items self.force = force # type: bool self.force_image_cache = force_image_cache # type: bool self.kwargs = kwargs def run(self): ShowQueueItem.run(self) logger.log('Performing refresh on %s' % self.show_obj.name) self.show_obj.refresh_dir() self.show_obj.write_metadata() # if self.force: # self.show_obj.update_metadata() self.show_obj.populate_cache(self.force_image_cache) # Load XEM data to DB for show if self.show_obj.prodid in sickbeard.scene_exceptions.xem_ids_list[self.show_obj.tvid]: sickbeard.scene_numbering.xem_refresh(self.show_obj.tvid, self.show_obj.prodid) if 'pausestatus_after' in self.kwargs and None is not self.kwargs['pausestatus_after']: self.show_obj.paused = self.kwargs['pausestatus_after'] self.inProgress = False class QueueItemRename(ShowQueueItem): def __init__(self, show_obj=None, scheduled_update=False): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :param scheduled_update: :type scheduled_update: bool """ ShowQueueItem.__init__(self, ShowQueueActions.RENAME, show_obj, scheduled_update) def run(self): ShowQueueItem.run(self) logger.log('Performing rename on %s' % self.show_obj.name) try: _ = self.show_obj.location except exceptions_helper.ShowDirNotFoundException: logger.log('Can\'t perform rename on %s when the show directory is missing.' % self.show_obj.name, logger.WARNING) return ep_obj_rename_list = [] ep_obj_list = self.show_obj.get_all_episodes(has_location=True) for cur_ep_obj in ep_obj_list: # Only want to rename if we have a location if cur_ep_obj.location: if cur_ep_obj.related_ep_obj: # do we have one of multi-episodes in the rename list already have_already = False for cur_related_ep_obj in cur_ep_obj.related_ep_obj + [cur_ep_obj]: if cur_related_ep_obj in ep_obj_rename_list: have_already = True break if not have_already: ep_obj_rename_list.append(cur_ep_obj) else: ep_obj_rename_list.append(cur_ep_obj) for cur_ep_obj in ep_obj_rename_list: cur_ep_obj.rename() self.inProgress = False class QueueItemSubtitle(ShowQueueItem): def __init__(self, show_obj=None, scheduled_update=False): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :param scheduled_update: :type scheduled_update: bool """ ShowQueueItem.__init__(self, ShowQueueActions.SUBTITLE, show_obj, scheduled_update) def run(self): ShowQueueItem.run(self) logger.log('Downloading subtitles for %s' % self.show_obj.name) self.show_obj.download_subtitles() self.inProgress = False class QueueItemUpdate(ShowQueueItem): def __init__(self, show_obj=None, scheduled_update=False, **kwargs): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :param scheduled_update: :type scheduled_update: bool :param kwargs: """ ShowQueueItem.__init__(self, ShowQueueActions.UPDATE, show_obj, scheduled_update) self.force = False # type: bool self.force_web = False # type: bool self.kwargs = kwargs def run(self): ShowQueueItem.run(self) last_update = datetime.date.fromordinal(self.show_obj.last_update_indexer) if not sickbeard.TVInfoAPI(self.show_obj.tvid).config['active']: logger.log('TV info source %s is marked inactive, aborting update for show %s and continue with refresh.' % (sickbeard.TVInfoAPI(self.show_obj.tvid).config['name'], self.show_obj.name)) sickbeard.showQueueScheduler.action.refreshShow(self.show_obj, self.force, self.scheduled_update, after_update=True) return logger.log('Beginning update of %s' % self.show_obj.name) logger.log('Retrieving show info from %s' % sickbeard.TVInfoAPI(self.show_obj.tvid).name, logger.DEBUG) try: result = self.show_obj.load_from_tvinfo(cache=not self.force) if None is not result: return except Exception as e: if check_exception_type(e, ExceptionTuples.tvinfo_error): logger.log('Unable to contact %s, aborting: %s' % (sickbeard.TVInfoAPI(self.show_obj.tvid).name, ex(e)), logger.WARNING) return elif check_exception_type(e, ExceptionTuples.tvinfo_attributenotfound): logger.log('Data retrieved from %s was incomplete, aborting: %s' % (sickbeard.TVInfoAPI(self.show_obj.tvid).name, ex(e)), logger.ERROR) return else: raise e if self.force_web: self.show_obj.load_imdb_info() try: self.show_obj.save_to_db() except (BaseException, Exception) as e: logger.log('Error saving the show to the database: %s' % ex(e), logger.ERROR) logger.log(traceback.format_exc(), logger.ERROR) # get episode list from DB logger.log('Loading all episodes from the database', logger.DEBUG) db_ep_obj_list = self.show_obj.load_episodes_from_db(update=True) # get episode list from TVDB logger.log('Loading all episodes from %s' % sickbeard.TVInfoAPI(self.show_obj.tvid).name, logger.DEBUG) try: tvinfo_ep_list = self.show_obj.load_episodes_from_tvinfo(cache=not self.force, update=True) except Exception as e: if check_exception_type(e, ExceptionTuples.tvinfo_exception): logger.log('Unable to get info from %s, the show info will not be refreshed: %s' % (sickbeard.TVInfoAPI(self.show_obj.tvid).name, ex(e)), logger.ERROR) tvinfo_ep_list = None else: raise e if None is tvinfo_ep_list: logger.log('No data returned from %s, unable to update episodes for show: %s' % (sickbeard.TVInfoAPI(self.show_obj.tvid).name, self.show_obj.name), logger.ERROR) elif not tvinfo_ep_list or 0 == len(tvinfo_ep_list): logger.log('No episodes returned from %s for show: %s' % (sickbeard.TVInfoAPI(self.show_obj.tvid).name, self.show_obj.name), logger.WARNING) else: # for each ep we found on TVDB delete it from the DB list for cur_season in tvinfo_ep_list: for cur_episode in tvinfo_ep_list[cur_season]: logger.log('Removing %sx%s from the DB list' % (cur_season, cur_episode), logger.DEBUG) if cur_season in db_ep_obj_list and cur_episode in db_ep_obj_list[cur_season]: del db_ep_obj_list[cur_season][cur_episode] # for the remaining episodes in the DB list just delete them from the DB for cur_season in db_ep_obj_list: for cur_episode in db_ep_obj_list[cur_season]: ep_obj = self.show_obj.get_episode(cur_season, cur_episode) status = sickbeard.common.Quality.splitCompositeStatus(ep_obj.status)[0] if should_delete_episode(status): logger.log('Permanently deleting episode %sx%s from the database' % (cur_season, cur_episode), logger.MESSAGE) try: ep_obj.delete_episode() except exceptions_helper.EpisodeDeletedException: pass else: logger.log('Not deleting episode %sx%s from the database because status is: %s' % (cur_season, cur_episode, statusStrings[status]), logger.MESSAGE) # update indexer mapper once a month (using the day of the first ep as random date) update_over_month = (datetime.date.today() - last_update).days > 31 if (self.scheduled_update or update_over_month) and tvinfo_ep_list.get(1, {}).get(1, False): first_ep_airdate = self.show_obj.get_episode(1, 1, no_create=True).airdate day = (first_ep_airdate.day, 28)[28 < first_ep_airdate.day] if datetime.date.today().day == day or update_over_month or \ -8 < (datetime.date.today() - first_ep_airdate).days < 31: map_indexers_to_show(self.show_obj, force=True) if self.priority != generic_queue.QueuePriorities.NORMAL: self.kwargs['priority'] = self.priority sickbeard.showQueueScheduler.action.refreshShow(self.show_obj, self.force, self.scheduled_update, after_update=True, force_image_cache=self.force_web, **self.kwargs) class QueueItemForceUpdate(QueueItemUpdate): def __init__(self, show_obj=None, scheduled_update=False, **kwargs): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :param scheduled_update: :type scheduled_update: bool :param kwargs: """ ShowQueueItem.__init__(self, ShowQueueActions.FORCEUPDATE, show_obj, scheduled_update) self.force = True # type: bool self.force_web = False # type: bool self.kwargs = kwargs class QueueItemForceUpdateWeb(QueueItemUpdate): def __init__(self, show_obj=None, scheduled_update=False, priority=generic_queue.QueuePriorities.NORMAL, **kwargs): """ :param show_obj: show object :type show_obj: sickbeard.tv.TVShow :param scheduled_update: :type scheduled_update: bool :param priority: :type priority: int :param kwargs: """ ShowQueueItem.__init__(self, ShowQueueActions.FORCEUPDATE, show_obj, scheduled_update) self.force = True # type: bool self.force_web = True # type: bool self.priority = priority # type: int self.kwargs = kwargs