def write_ep_file(self, ep_obj): """ Generates and writes ep_obj's metadata under the given path with the given filename root. Uses the episode's name with the extension in _ep_nfo_extension. ep_obj: Episode object for which to create the metadata file_name_path: The file name to use for this metadata. Note that the extension will be automatically added based on _ep_nfo_extension. This should include an absolute path. Note that this method expects that _ep_data will return an ElementTree object. If your _ep_data returns data in another format you'll need to override this method. """ data = self._ep_data(ep_obj) if not data: return False nfo_file_path = self.get_episode_file_path(ep_obj) nfo_file_dir = os.path.dirname(nfo_file_path) if not (nfo_file_path and nfo_file_dir): log.debug( u'Unable to write episode nfo file because episode location is missing.' ) return False try: if not os.path.isdir(nfo_file_dir): log.debug( 'Metadata directory did not exist, creating it at {location}', {'location': nfo_file_dir}) os.makedirs(nfo_file_dir) helpers.chmod_as_parent(nfo_file_dir) log.debug('Writing episode nfo file to {location}', {'location': nfo_file_path}) with io.open(nfo_file_path, 'wb') as nfo_file: # Calling encode directly, b/c often descriptions have wonky characters. data.write(nfo_file, encoding='utf-8', xml_declaration=True) helpers.chmod_as_parent(nfo_file_path) except IOError as e: log.error( 'Unable to write file to {location} - are you sure the folder is writable? {error}', { 'location': nfo_file_path, 'error': ex(e) }) return False return True
def write_ep_file(self, ep_obj): """ Generates and writes ep_obj's metadata under the given path with the given filename root. Uses the episode's name with the extension in _ep_nfo_extension. ep_obj: Episode object for which to create the metadata file_name_path: The file name to use for this metadata. Note that the extension will be automatically added based on _ep_nfo_extension. This should include an absolute path. """ data = self._ep_data(ep_obj) if not data: return False nfo_file_path = self.get_episode_file_path(ep_obj) nfo_file_dir = os.path.dirname(nfo_file_path) if not (nfo_file_path and nfo_file_dir): log.debug( u'Unable to write episode nfo file because episode location is missing.' ) return False try: if not os.path.isdir(nfo_file_dir): log.debug( u'Metadata directory missing, creating it at {location}', {'location': nfo_file_dir}) os.makedirs(nfo_file_dir) helpers.chmod_as_parent(nfo_file_dir) log.debug(u'Writing episode nfo file to {location}', {'location': nfo_file_path}) with io.open(nfo_file_path, 'wb') as nfo_file: # Calling encode directly, b/c often descriptions have wonky characters. nfo_file.write(data.encode('utf-8')) helpers.chmod_as_parent(nfo_file_path) except EnvironmentError as e: log.error( u'Unable to write file to {path} - are you sure the folder is writable? {error}', { 'path': nfo_file_path, 'error': ex(e) }) return False return True
def update_show_indexer_metadata(self, show_obj): if self.show_metadata and show_obj and self._has_show_metadata( show_obj): log.debug( u'Metadata provider {name} updating series indexer info metadata file for {series}', { u'name': self.name, u'series': show_obj.name }) nfo_file_path = self.get_show_file_path(show_obj) try: with io.open(nfo_file_path, u'rb') as xmlFileObj: showXML = etree.ElementTree(file=xmlFileObj) indexerid = showXML.find(u'id') root = showXML.getroot() if indexerid is not None: indexerid.text = str(show_obj.indexerid) else: etree.SubElement(root, u'id').text = str(show_obj.indexerid) # Make it purdy helpers.indent_xml(root) showXML.write(nfo_file_path, encoding=u'UTF-8') helpers.chmod_as_parent(nfo_file_path) return True except etree.ParseError as error: log.warning( u'Received an invalid XML for {series}, try again later. Error: {error}', { u'series': show_obj.name, u'error': error }) except IOError as error: if error.errno == errno.EACCES: log.warning( u'Unable to write metadata file to {location} - verify that the path is writeable', {u'location': nfo_file_path}) else: log.error( u'Unable to write metadata file to {location}. Error: {error!r}', { u'location': nfo_file_path, u'error': error })
def write_ep_file(self, ep_obj): """ Generates and writes ep_obj's metadata under the given path with the given filename root. Uses the episode's name with the extension in _ep_nfo_extension. ep_obj: Episode object for which to create the metadata file_name_path: The file name to use for this metadata. Note that the extension will be automatically added based on _ep_nfo_extension. This should include an absolute path. Note that this method expects that _ep_data will return an ElementTree object. If your _ep_data returns data in another format you'll need to override this method. """ data = self._ep_data(ep_obj) if not data: return False nfo_file_path = self.get_episode_file_path(ep_obj) nfo_file_dir = os.path.dirname(nfo_file_path) if not (nfo_file_path and nfo_file_dir): log.debug(u'Unable to write episode nfo file because episode location is missing.') return False try: if not os.path.isdir(nfo_file_dir): log.debug('Metadata directory did not exist, creating it at {location}', {'location': nfo_file_dir}) os.makedirs(nfo_file_dir) helpers.chmod_as_parent(nfo_file_dir) log.debug('Writing episode nfo file to {location}', {'location': nfo_file_path}) with io.open(nfo_file_path, 'wb') as nfo_file: # Calling encode directly, b/c often descriptions have wonky characters. data.write(nfo_file, encoding='utf-8', xml_declaration=True) helpers.chmod_as_parent(nfo_file_path) except IOError as e: log.error('Unable to write file to {location} - are you sure the folder is writable? {error}', {'location': nfo_file_path, 'error': ex(e)}) return False return True
def write_ep_file(self, ep_obj): """ Generates and writes ep_obj's metadata under the given path with the given filename root. Uses the episode's name with the extension in _ep_nfo_extension. ep_obj: Episode object for which to create the metadata file_name_path: The file name to use for this metadata. Note that the extension will be automatically added based on _ep_nfo_extension. This should include an absolute path. Note that this method expects that _ep_data will return an ElementTree object. If your _ep_data returns data in another format you'll need to override this method. """ data = self._ep_data(ep_obj) if not data: return False nfo_file_path = self.get_episode_file_path(ep_obj) nfo_file_dir = os.path.dirname(nfo_file_path) if not (nfo_file_path and nfo_file_dir): log.debug( u'Unable to write episode nfo file because episode location is missing.' ) return False try: if not os.path.isdir(nfo_file_dir): log.debug( u'Metadata directory missing, creating it at {location}', {u'location': nfo_file_dir}) os.makedirs(nfo_file_dir) helpers.chmod_as_parent(nfo_file_dir) log.debug(u'Writing episode nfo file to {location}', {u'location': nfo_file_path}) nfo_file = io.open(nfo_file_path, u'wb') data.write(nfo_file, encoding=u'UTF-8') nfo_file.close() helpers.chmod_as_parent(nfo_file_path) except IOError as e: exception_handler.handle(e, u'Unable to write file to {location}', location=nfo_file_path) return False return True
def write_show_file(self, show_obj): """ Generates and writes show_obj's metadata under the given path to the filename given by get_show_file_path() show_obj: Series object for which to create the metadata path: An absolute or relative path where we should put the file. Note that the file name will be the default show_file_name. Note that this method expects that _show_data will return an ElementTree object. If your _show_data returns data in another format you'll need to override this method. """ data = self._show_data(show_obj) if not data: return False nfo_file_path = self.get_show_file_path(show_obj) nfo_file_dir = os.path.dirname(nfo_file_path) try: if not os.path.isdir(nfo_file_dir): log.debug( 'Metadata directory did not exist, creating it at {location}', {'location': nfo_file_dir} ) os.makedirs(nfo_file_dir) helpers.chmod_as_parent(nfo_file_dir) log.debug( 'Writing show nfo file to {location}', {'location': nfo_file_dir} ) nfo_file = io.open(nfo_file_path, 'wb') data.write(nfo_file, encoding='utf-8', xml_declaration=True) nfo_file.close() helpers.chmod_as_parent(nfo_file_path) except IOError as error: log.error( 'Unable to write file to {location} - are you sure the folder is writable? {error}', {'location': nfo_file_path, 'error': ex(error)} ) return False return True
def write_ep_file(self, ep_obj): """ Add episode information to the .plexmatch file. The episode hint:value pairs are used to match an episode filename to a specific episode. Uses the format of: ep: S01E12: /Season 01/Episode 12 - Finale Part 2.mkv :param ep_obj: Episode object for which to create the metadata """ # Parse existing .flexmatch data flexmatch_file_path = self.get_show_file_path(ep_obj.series) flexmatch_file_dir = os.path.dirname(flexmatch_file_path) with open(flexmatch_file_path) as f: current_content = f.readlines() data = self._ep_data(current_content, ep_obj) if not data: return False if not (flexmatch_file_path and flexmatch_file_dir): log.debug( 'Unable to write episode flexmatch file because episode location is missing.' ) return False try: if not os.path.isdir(flexmatch_file_dir): log.debug( 'Metadata directory missing, creating it at {location}', {'location': flexmatch_file_dir}) os.makedirs(flexmatch_file_dir) helpers.chmod_as_parent(flexmatch_file_dir) log.debug('Writing episode flexmatch file to {location}', {'location': flexmatch_file_path}) with open(flexmatch_file_path, 'w') as outfile: outfile.write('\n'.join(data)) helpers.chmod_as_parent(flexmatch_file_path) except IOError: log.error('Unable to write file to {location}', {'location': flexmatch_file_path}) return False return True
def write_show_file(self, show_obj): """ Generates and writes show_obj's metadata under the given path to the filename given by get_show_file_path() show_obj: Series object for which to create the metadata path: An absolute or relative path where we should put the file. Note that the file name will be the default show_file_name. Note that this method expects that _show_data will return an ElementTree object. If your _show_data returns data in another format you'll need to override this method. """ data = self._show_data(show_obj) if not data: return False nfo_file_path = self.get_show_file_path(show_obj) nfo_file_dir = os.path.dirname(nfo_file_path) try: if not os.path.isdir(nfo_file_dir): log.debug( 'Metadata directory did not exist, creating it at {location}', {'location': nfo_file_dir}) os.makedirs(nfo_file_dir) helpers.chmod_as_parent(nfo_file_dir) log.debug('Writing show nfo file to {location}', {'location': nfo_file_dir}) nfo_file = io.open(nfo_file_path, 'wb') data.write(nfo_file, encoding='utf-8', xml_declaration=True) nfo_file.close() helpers.chmod_as_parent(nfo_file_path) except IOError as error: log.error( 'Unable to write file to {location} - are you sure the folder is writable? {error}', { 'location': nfo_file_path, 'error': ex(error) }) return False return True
def dump_html(data): """Dump html data.""" dump_name = os.path.join(app.CACHE_DIR, 'custom_torrent.html') try: file_out = io.open(dump_name, 'wb') file_out.write(data) file_out.close() helpers.chmod_as_parent(dump_name) except IOError as error: log.error('Unable to save the file: {0}', error) return False log.info('Saved custom_torrent html dump {0} ', dump_name) return True
def save_subs(tv_episode, video, found_subtitles, video_path=None): """Save subtitles. :param tv_episode: the episode to download subtitles :type tv_episode: sickbeard.tv.Episode :param video: :type video: subliminal.Video :param found_subtitles: :type found_subtitles: list of subliminal.Subtitle :param video_path: the video path. If none, the episode location will be used :type video_path: str :return: a sorted list of the opensubtitles codes for the downloaded subtitles :rtype: list of str """ video_path = video_path or tv_episode.location show_name = tv_episode.series.name season = tv_episode.season episode = tv_episode.episode episode_name = tv_episode.name show_indexerid = tv_episode.series.indexerid subtitles_dir = get_subtitles_dir(video_path) saved_subtitles = save_subtitles(video, found_subtitles, directory=subtitles_dir, single=not app.SUBTITLES_MULTI, encoding='utf-8') for subtitle in saved_subtitles: logger.info(u'Found subtitle for %s in %s provider with language %s', os.path.basename(video_path), subtitle.provider_name, subtitle.language.opensubtitles) subtitle_path = compute_subtitle_path(subtitle, video_path, subtitles_dir) helpers.chmod_as_parent(subtitle_path) helpers.fix_set_group_id(subtitle_path) if app.SUBTITLES_EXTRA_SCRIPTS and is_media_file(video_path): subtitle_path = compute_subtitle_path(subtitle, video_path, subtitles_dir) run_subs_extra_scripts(video_path=video_path, subtitle_path=subtitle_path, subtitle_language=subtitle.language, show_name=show_name, season=season, episode=episode, episode_name=episode_name, show_indexerid=show_indexerid) if app.SUBTITLES_HISTORY: logger.debug(u'Logging to history downloaded subtitle from provider %s and language %s', subtitle.provider_name, subtitle.language.opensubtitles) history.log_subtitle(tv_episode, subtitle) # Refresh the subtitles property if tv_episode.location: tv_episode.refresh_subtitles() return sorted({subtitle.language.opensubtitles for subtitle in saved_subtitles})
def _download_result(result): """ Download a result to the appropriate black hole folder. :param result: SearchResult instance to download. :return: boolean, True on success """ res_provider = result.provider if res_provider is None: log.error( u'Invalid provider name - this is a coding error, report it please' ) return False # nzbs with an URL can just be downloaded from the provider if result.result_type == u'nzb': new_result = res_provider.download_result(result) # if it's an nzb data result elif result.result_type == u'nzbdata': # get the final file path to the nzb file_name = os.path.join(app.NZB_DIR, result.name + u'.nzb') log.info(u'Saving NZB to {0}', file_name) new_result = True # save the data to disk try: with open(file_name, u'w') as file_out: file_out.write(result.extra_info[0]) chmod_as_parent(file_name) except EnvironmentError as e: log.error(u'Error trying to save NZB to black hole: {0}', ex(e)) new_result = False elif result.result_type == u'torrent': new_result = res_provider.download_result(result) else: log.error( u'Invalid provider type - this is a coding error, report it please' ) new_result = False return new_result
def write_show_file(self, show_obj): """ Generates and writes show_obj's metadata under the given path to the filename given by get_show_file_path() show_obj: Series object for which to create the metadata path: An absolute or relative path where we should put the file. Note that the file name will be the default show_file_name. Note that this method expects that _show_data will return an ElementTree object. If your _show_data returns data in another format you'll need to override this method. """ data = self._show_data(show_obj) if not data: return False nfo_file_path = self.get_show_file_path(show_obj) nfo_file_dir = os.path.dirname(nfo_file_path) try: if not os.path.isdir(nfo_file_dir): log.debug( u'Metadata directory missing, creating it at {location}', {u'location': nfo_file_dir}) os.makedirs(nfo_file_dir) helpers.chmod_as_parent(nfo_file_dir) log.debug(u'Writing show nfo file to {location}', {u'location': nfo_file_path}) nfo_file = io.open(nfo_file_path, u'wb') data.write(nfo_file, encoding=u'UTF-8') nfo_file.close() helpers.chmod_as_parent(nfo_file_path) except IOError as e: exception_handler.handle(e, u'Unable to write file to {location}', location=nfo_file_path) return False return True
def write_show_file(self, show_obj): """ Generate and write show_obj's metadata under the given path to the filename given by get_show_file_path(). show_obj: Series object for which to create the metadata Note that this method expects that _show_data will return a string, which will be written to a text file. """ data = self._show_data(show_obj) if not data: return False flexmatch_file_path = self.get_show_file_path(show_obj) flexmatch_file_dir = os.path.dirname(flexmatch_file_path) try: if not os.path.isdir(flexmatch_file_dir): log.debug( 'Metadata directory did not exist, creating it at {location}', {'location': flexmatch_file_dir}) os.makedirs(flexmatch_file_dir) helpers.chmod_as_parent(flexmatch_file_dir) log.debug('Writing show flexmatch file to {location}', {'location': flexmatch_file_dir}) flexmatch_file = io.open(flexmatch_file_path, 'wb') flexmatch_file.write(data.encode('utf-8')) flexmatch_file.close() helpers.chmod_as_parent(flexmatch_file_path) except IOError as error: log.error( 'Unable to write file to {location} - are you sure the folder is writable? {error}', { 'location': flexmatch_file_path, 'error': error }) return False return True
def _download_result(result): """ Download a result to the appropriate black hole folder. :param result: SearchResult instance to download. :return: boolean, True on success """ res_provider = result.provider if res_provider is None: log.error(u'Invalid provider name - this is a coding error, report it please') return False # nzbs with an URL can just be downloaded from the provider if result.result_type == u'nzb': new_result = res_provider.download_result(result) # if it's an nzb data result elif result.result_type == u'nzbdata': # get the final file path to the nzb file_name = os.path.join(app.NZB_DIR, result.name + u'.nzb') log.info(u'Saving NZB to {0}', file_name) new_result = True # save the data to disk try: with open(file_name, u'w') as file_out: file_out.write(result.extra_info[0]) chmod_as_parent(file_name) except EnvironmentError as e: log.error(u'Error trying to save NZB to black hole: {0}', ex(e)) new_result = False elif result.result_type == u'torrent': new_result = res_provider.download_result(result) else: log.error(u'Invalid provider type - this is a coding error, report it please') new_result = False return new_result
def _write_image(self, image_data, image_path, obj=None): """ Saves the data in image_data to the location image_path. Returns True/False to represent success or failure. image_data: binary image data to write to file image_path: file location to save the image to """ # don't bother overwriting it if os.path.isfile(image_path): log.debug(u'Image already exists, not downloading') return False image_dir = os.path.dirname(image_path) if not image_data: log.debug( u'Unable to retrieve image to save in {location}, skipping', {u'location': image_path}) return False try: if not os.path.isdir(image_dir): log.debug( u'Metadata directory missing, creating it at {location}', {u'location': image_path}) os.makedirs(image_dir) helpers.chmod_as_parent(image_dir) outFile = io.open(image_path, u'wb') outFile.write(image_data) outFile.close() helpers.chmod_as_parent(image_path) except IOError as e: exception_handler.handle(e, u'Unable to write image to {location}', location=image_path) return False return True
def get_subtitles_dir(video_path): """Return the correct subtitles directory based on the user configuration. If the directory doesn't exist, it will be created :param video_path: the video path :type video_path: str :return: the subtitles directory :rtype: str """ if not app.SUBTITLES_DIR: return os.path.dirname(video_path) if os.path.isabs(app.SUBTITLES_DIR): return _decode(app.SUBTITLES_DIR) new_subtitles_path = os.path.join(os.path.dirname(video_path), app.SUBTITLES_DIR) if helpers.make_dir(new_subtitles_path): helpers.chmod_as_parent(new_subtitles_path) else: logger.warning(u'Unable to create subtitles folder %s', new_subtitles_path) return new_subtitles_path
def addNewShow(self, whichSeries=None, indexer_lang=None, rootDir=None, defaultStatus=None, quality_preset=None, allowed_qualities=None, preferred_qualities=None, season_folders=None, subtitles=None, fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None, anime=None, scene=None, blacklist=None, whitelist=None, defaultStatusAfter=None): """ Receive tvdb id, dir, and other options and create a show from them. If extra show dirs are provided then it forwards back to newShow, if not it goes to /home. """ provided_indexer = providedIndexer indexer_lang = app.INDEXER_DEFAULT_LANGUAGE if not indexer_lang else indexer_lang # grab our list of other dirs if given if not other_shows: other_shows = [] elif not isinstance(other_shows, list): other_shows = [other_shows] def finishAddShow(): # if there are no extra shows then go home if not other_shows: return json_redirect('/home/') # go to add the next show return json_redirect( '/addShows/newShow/', [('show_to_add' if not i else 'other_shows', cur_dir) for i, cur_dir in enumerate(other_shows)]) # if we're skipping then behave accordingly if skipShow: return finishAddShow() # sanity check on our inputs if (not rootDir and not fullShowPath) or not whichSeries: return 'Missing params, no Indexer ID or folder:{series!r} and {root!r}/{path!r}'.format( series=whichSeries, root=rootDir, path=fullShowPath) # figure out what show we're adding and where series_pieces = whichSeries.split('|') if (whichSeries and rootDir) or (whichSeries and fullShowPath and len(series_pieces) > 1): if len(series_pieces) < 6: logger.log( u'Unable to add show due to show selection. Not enough arguments: %s' % (repr(series_pieces)), logger.ERROR) ui.notifications.error( 'Unknown error. Unable to add show due to problem with show selection.' ) return json_redirect('/addShows/existingShows/') indexer = int(series_pieces[1]) indexer_id = int(series_pieces[3]) show_name = series_pieces[4] else: # if no indexer was provided use the default indexer set in General settings if not provided_indexer: provided_indexer = app.INDEXER_DEFAULT indexer = int(provided_indexer) indexer_id = int(whichSeries) show_name = os.path.basename(os.path.normpath(fullShowPath)) # use the whole path if it's given, or else append the show name to the root dir to get the full show path if fullShowPath: show_dir = os.path.normpath(fullShowPath) else: show_dir = os.path.join(rootDir, sanitize_filename(show_name)) # blanket policy - if the dir exists you should have used 'add existing show' numbnuts if os.path.isdir(show_dir) and not fullShowPath: ui.notifications.error( 'Unable to add show', 'Folder {path} exists already'.format(path=show_dir)) return json_redirect('/addShows/existingShows/') # don't create show dir if config says not to if app.ADD_SHOWS_WO_DIR: logger.log( u'Skipping initial creation of {path} due to config.ini setting' .format(path=show_dir)) else: dir_exists = helpers.make_dir(show_dir) if not dir_exists: logger.log( u'Unable to create the folder {path}, can\'t add the show'. format(path=show_dir), logger.ERROR) ui.notifications.error( 'Unable to add show', 'Unable to create the folder {path}, can\'t add the show'. format(path=show_dir)) # Don't redirect to default page because user wants to see the new show return json_redirect('/home/') else: helpers.chmod_as_parent(show_dir) # prepare the inputs for passing along scene = config.checkbox_to_value(scene) anime = config.checkbox_to_value(anime) season_folders = config.checkbox_to_value(season_folders) subtitles = config.checkbox_to_value(subtitles) if whitelist: whitelist = short_group_names(whitelist) if blacklist: blacklist = short_group_names(blacklist) if not allowed_qualities: allowed_qualities = [] if not preferred_qualities or try_int(quality_preset, None): preferred_qualities = [] if not isinstance(allowed_qualities, list): allowed_qualities = [allowed_qualities] if not isinstance(preferred_qualities, list): preferred_qualities = [preferred_qualities] new_quality = Quality.combine_qualities( [int(q) for q in allowed_qualities], [int(q) for q in preferred_qualities]) # add the show app.show_queue_scheduler.action.addShow(indexer, indexer_id, show_dir, int(defaultStatus), new_quality, season_folders, indexer_lang, subtitles, anime, scene, None, blacklist, whitelist, int(defaultStatusAfter)) ui.notifications.message( 'Show added', 'Adding the specified show into {path}'.format(path=show_dir)) return finishAddShow()
def addNewShow(self, whichSeries=None, indexer_lang=None, rootDir=None, defaultStatus=None, quality_preset=None, allowed_qualities=None, preferred_qualities=None, season_folders=None, subtitles=None, fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None, anime=None, scene=None, blacklist=None, whitelist=None, defaultStatusAfter=None): """ Receive tvdb id, dir, and other options and create a show from them. If extra show dirs are provided then it forwards back to newShow, if not it goes to /home. """ provided_indexer = providedIndexer indexer_lang = app.INDEXER_DEFAULT_LANGUAGE if not indexer_lang else indexer_lang # grab our list of other dirs if given if not other_shows: other_shows = [] elif not isinstance(other_shows, list): other_shows = [other_shows] other_shows = decode_shows(other_shows) def finishAddShow(): # if there are no extra shows then go home if not other_shows: return json_response(redirect='/home/') # go to add the next show return json_response( redirect='/addShows/newShow/', params=[ ('show_to_add' if not i else 'other_shows', cur_dir) for i, cur_dir in enumerate(other_shows) ] ) # if we're skipping then behave accordingly if skipShow: return finishAddShow() # sanity check on our inputs if (not rootDir and not fullShowPath) or not whichSeries: error_msg = 'Missing params, no Indexer ID or folder: {series!r} and {root!r}/{path!r}'.format( series=whichSeries, root=rootDir, path=fullShowPath) log.error(error_msg) return json_response( result=False, message=error_msg, redirect='/home/' ) # figure out what show we're adding and where series_pieces = whichSeries.split('|') if (whichSeries and rootDir) or (whichSeries and fullShowPath and len(series_pieces) > 1): if len(series_pieces) < 6: log.error('Unable to add show due to show selection. Not enough arguments: {pieces!r}', {'pieces': series_pieces}) ui.notifications.error('Unknown error. Unable to add show due to problem with show selection.') return json_response( result=False, message='Unable to add show due to show selection. Not enough arguments: {0!r}'.format(series_pieces), redirect='/addShows/existingShows/' ) indexer = int(series_pieces[1]) indexer_id = int(series_pieces[3]) show_name = series_pieces[4] else: # if no indexer was provided use the default indexer set in General settings if not provided_indexer: provided_indexer = app.INDEXER_DEFAULT indexer = int(provided_indexer) indexer_id = int(whichSeries) show_name = os.path.basename(os.path.normpath(fullShowPath)) # use the whole path if it's given, or else append the show name to the root dir to get the full show path if fullShowPath: show_dir = os.path.normpath(fullShowPath) else: show_dir = os.path.join(rootDir, sanitize_filename(show_name)) # blanket policy - if the dir exists you should have used 'add existing show' numbnuts if os.path.isdir(show_dir) and not fullShowPath: ui.notifications.error('Unable to add show', 'Folder {path} exists already'.format(path=show_dir)) return json_response( result=False, message='Unable to add show: Folder {path} exists already'.format(path=show_dir), redirect='/addShows/existingShows/' ) # don't create show dir if config says not to if app.ADD_SHOWS_WO_DIR: log.info('Skipping initial creation of {path} due to config.ini setting', {'path': show_dir}) else: dir_exists = helpers.make_dir(show_dir) if not dir_exists: log.error("Unable to create the folder {path}, can't add the show", {'path': show_dir}) ui.notifications.error('Unable to add show', 'Unable to create the folder {path}, can\'t add the show'.format(path=show_dir)) # Don't redirect to default page because user wants to see the new show return json_response( result=False, message='Unable to add show: Unable to create the folder {path}'.format(path=show_dir), redirect='/home/' ) else: helpers.chmod_as_parent(show_dir) # prepare the inputs for passing along scene = config.checkbox_to_value(scene) anime = config.checkbox_to_value(anime) season_folders = config.checkbox_to_value(season_folders) subtitles = config.checkbox_to_value(subtitles) if whitelist: if not isinstance(whitelist, list): whitelist = [whitelist] whitelist = short_group_names(whitelist) if blacklist: if not isinstance(blacklist, list): blacklist = [blacklist] blacklist = short_group_names(blacklist) if not allowed_qualities: allowed_qualities = [] if not preferred_qualities or try_int(quality_preset, None): preferred_qualities = [] if not isinstance(allowed_qualities, list): allowed_qualities = [allowed_qualities] if not isinstance(preferred_qualities, list): preferred_qualities = [preferred_qualities] new_quality = Quality.combine_qualities([int(q) for q in allowed_qualities], [int(q) for q in preferred_qualities]) # add the show app.show_queue_scheduler.action.addShow(indexer, indexer_id, show_dir, int(defaultStatus), new_quality, season_folders, indexer_lang, subtitles, anime, scene, None, blacklist, whitelist, int(defaultStatusAfter)) ui.notifications.message('Show added', 'Adding the specified show into {path}'.format(path=show_dir)) return finishAddShow()
def run(self): ShowQueueItem.run(self) logger.log(u"Starting to add show {0}".format( "by ShowDir: {0}".format(self.showDir) if self. showDir else u"by Indexer Id: {0}".format(self.indexer_id))) # make sure the Indexer IDs are valid try: l_indexer_api_params = indexerApi(self.indexer).api_params.copy() if self.lang: l_indexer_api_params['language'] = self.lang logger.log(u"" + str(indexerApi(self.indexer).name) + ": " + repr(l_indexer_api_params)) indexer_api = indexerApi( self.indexer).indexer(**l_indexer_api_params) s = indexer_api[self.indexer_id] # Let's try to create the show Dir if it's not provided. This way we force the show dir # to build build using the Indexers provided series name if not self.showDir and self.root_dir: show_name = get_showname_from_indexer(self.indexer, self.indexer_id, self.lang) if show_name: self.showDir = os.path.join(self.root_dir, sanitize_filename(show_name)) dir_exists = make_dir(self.showDir) if not dir_exists: logger.log( u"Unable to create the folder {0}, can't add the show" .format(self.showDir)) return chmod_as_parent(self.showDir) else: logger.log( u"Unable to get a show {0}, can't add the show".format( self.showDir)) return # this usually only happens if they have an NFO in their show dir which gave us a Indexer ID that # has no proper english version of the show if getattr(s, 'seriesname', None) is None: logger.log( u"Show in {0} has no name on {1}, probably searched with the wrong language." .format(self.showDir, indexerApi(self.indexer).name), logger.ERROR) ui.notifications.error( 'Unable to add show', 'Show in {0} has no name on {1}, probably the wrong language. \ Delete .nfo and manually add the correct language.' .format(self.showDir, indexerApi(self.indexer).name)) self._finishEarly() return # if the show has no episodes/seasons if not s: logger.log(u"Show " + str(s['seriesname']) + u" is on " + str(indexerApi(self.indexer).name) + u" but contains no season/episode data.") ui.notifications.error( "Unable to add show", "Show {0} is on {1} but contains no season/episode data.". format(s['seriesname'], indexerApi(self.indexer).name)) self._finishEarly() return # Check if we can already find this show in our current showList. try: check_existing_shows(s, self.indexer) except IndexerShowAllreadyInLibrary as e: logger.log( u"Could not add the show %s, as it already is in your library." u" Error: %s" % (s['seriesname'], e.message), logger.WARNING) ui.notifications.error('Unable to add show', 'reason: {0}'.format(e.message)) self._finishEarly() # Clean up leftover if the newly created directory is empty. delete_empty_folders(self.showDir) return # TODO: Add more specific indexer exceptions, that should provide the user with some accurate feedback. except IndexerShowIncomplete as e: logger.log( u"%s Error while loading information from indexer %s. " u"Error: %s" % (self.indexer_id, indexerApi(self.indexer).name, e.message), logger.WARNING) ui.notifications.error( "Unable to add show", "Unable to look up the show in {0} on {1} using ID {2} " "Reason: {3}".format(self.showDir, indexerApi(self.indexer).name, self.indexer_id, e.message)) self._finishEarly() return except IndexerShowNotFoundInLanguage as e: logger.log( u'{id}: Data retrieved from {indexer} was incomplete. The indexer does not provide ' u'show information in the searched language {language}. Aborting: {error_msg}' .format(id=self.indexer_id, indexer=indexerApi(self.indexer).name, language=e.language, error_msg=e.message), logger.WARNING) ui.notifications.error( 'Error adding show!', 'Unable to add show {indexer_id} on {indexer} with this language: {language}' .format(indexer_id=self.indexer_id, indexer=indexerApi(self.indexer).name, language=e.language)) self._finishEarly() return except Exception as e: logger.log( u"%s Error while loading information from indexer %s. " u"Error: %r" % (self.indexer_id, indexerApi(self.indexer).name, e.message), logger.ERROR) ui.notifications.error( "Unable to add show", "Unable to look up the show in {0} on {1} using ID {2}, not using the NFO. " "Delete .nfo and try adding manually again.".format( self.showDir, indexerApi(self.indexer).name, self.indexer_id)) self._finishEarly() return try: newShow = Series(self.indexer, self.indexer_id, self.lang) newShow.load_from_indexer(indexer_api) self.show = newShow # set up initial values self.show.location = self.showDir self.show.subtitles = self.subtitles if self.subtitles is not None else app.SUBTITLES_DEFAULT self.show.quality = self.quality if self.quality else app.QUALITY_DEFAULT self.show.flatten_folders = self.flatten_folders if self.flatten_folders is not None \ else app.FLATTEN_FOLDERS_DEFAULT self.show.anime = self.anime if self.anime is not None else app.ANIME_DEFAULT self.show.scene = self.scene if self.scene is not None else app.SCENE_DEFAULT self.show.paused = self.paused if self.paused is not None else False # set up default new/missing episode status logger.log( u"Setting all previously aired episodes to the specified status: {status}" .format(status=statusStrings[self.default_status])) self.show.default_ep_status = self.default_status if self.show.anime: self.show.release_groups = BlackAndWhiteList( self.show.indexerid) if self.blacklist: self.show.release_groups.set_black_keywords(self.blacklist) if self.whitelist: self.show.release_groups.set_white_keywords(self.whitelist) # # be smartish about this # if self.show.genre and "talk show" in self.show.genre.lower(): # self.show.air_by_date = 1 # if self.show.genre and "documentary" in self.show.genre.lower(): # self.show.air_by_date = 0 # if self.show.classification and "sports" in self.show.classification.lower(): # self.show.sports = 1 except IndexerException as e: logger.log( u"Unable to add show due to an error with " + indexerApi(self.indexer).name + ": " + e.message, logger.ERROR) if self.show: ui.notifications.error("Unable to add " + str(self.show.name) + " due to an error with " + indexerApi(self.indexer).name + "") else: ui.notifications.error( "Unable to add show due to an error with " + indexerApi(self.indexer).name + "") self._finishEarly() return except MultipleShowObjectsException: logger.log( u"The show in " + self.showDir + " is already in your show list, skipping", logger.WARNING) ui.notifications.error( 'Show skipped', "The show in " + self.showDir + " is already in your show list") self._finishEarly() return except Exception as e: logger.log(u"Error trying to add show: " + e.message, logger.ERROR) logger.log(traceback.format_exc(), logger.DEBUG) self._finishEarly() raise logger.log(u"Retrieving show info from IMDb", logger.DEBUG) try: self.show.load_imdb_info() except ImdbAPIError as e: logger.log(u"Something wrong on IMDb api: " + e.message, logger.INFO) except Exception as e: logger.log(u"Error loading IMDb info: " + e.message, logger.ERROR) try: self.show.save_to_db() except Exception as e: logger.log(u"Error saving the show to the database: " + e.message, logger.ERROR) logger.log(traceback.format_exc(), logger.DEBUG) self._finishEarly() raise # add it to the show list app.showList.append(self.show) try: self.show.load_episodes_from_indexer(tvapi=indexer_api) except Exception as e: logger.log( u"Error with " + indexerApi(self.show.indexer).name + ", not creating episode list: " + e.message, logger.ERROR) logger.log(traceback.format_exc(), logger.DEBUG) # update internal name cache name_cache.build_name_cache(self.show) try: self.show.load_episodes_from_dir() except Exception as e: logger.log(u"Error searching dir for episodes: " + e.message, logger.ERROR) logger.log(traceback.format_exc(), logger.DEBUG) # if they set default ep status to WANTED then run the backlog to search for episodes # FIXME: This needs to be a backlog queue item!!! if self.show.default_ep_status == WANTED: logger.log( u"Launching backlog for this show since its episodes are WANTED" ) app.backlog_search_scheduler.action.search_backlog([self.show]) self.show.write_metadata() self.show.update_metadata() self.show.populate_cache() self.show.flush_episodes() if app.USE_TRAKT: # if there are specific episodes that need to be added by trakt app.trakt_checker_scheduler.action.manage_new_show(self.show) # add show to trakt.tv library if app.TRAKT_SYNC: app.trakt_checker_scheduler.action.add_show_trakt_library( self.show) if app.TRAKT_SYNC_WATCHLIST: logger.log(u"update watchlist") notifiers.trakt_notifier.update_watchlist(show_obj=self.show) # Load XEM data to DB for show scene_numbering.xem_refresh(self.show.indexerid, self.show.indexer, force=True) # check if show has XEM mapping so we can determine if searches # should go by scene numbering or indexer numbering. if not self.scene and scene_numbering.get_xem_numbering_for_show( self.show.indexerid, self.show.indexer): self.show.scene = 1 # After initial add, set to default_status_after. self.show.default_ep_status = self.default_status_after self.finish()
def run(self): ShowQueueItem.run(self) log.info('Starting to add show by {0}', ('show_dir: {0}'.format(self.show_dir) if self.show_dir else 'Indexer Id: {0}'.format(self.indexer_id))) # make sure the Indexer IDs are valid try: l_indexer_api_params = indexerApi(self.indexer).api_params.copy() if self.lang: l_indexer_api_params['language'] = self.lang log.info( '{indexer_name}: {indexer_params!r}', { 'indexer_name': indexerApi(self.indexer).name, 'indexer_params': l_indexer_api_params }) indexer_api = indexerApi( self.indexer).indexer(**l_indexer_api_params) s = indexer_api[self.indexer_id] # Let's try to create the show Dir if it's not provided. This way we force the show dir # to build build using the Indexers provided series name if not self.show_dir and self.root_dir: show_name = get_showname_from_indexer(self.indexer, self.indexer_id, self.lang) if show_name: self.show_dir = os.path.join(self.root_dir, sanitize_filename(show_name)) dir_exists = make_dir(self.show_dir) if not dir_exists: log.info( "Unable to create the folder {0}, can't add the show", self.show_dir) return chmod_as_parent(self.show_dir) else: log.info("Unable to get a show {0}, can't add the show", self.show_dir) return # this usually only happens if they have an NFO in their show dir which gave us a Indexer ID that # has no proper english version of the show if getattr(s, 'seriesname', None) is None: log.error( 'Show in {path} has no name on {indexer}, probably searched with the wrong language.', { 'path': self.show_dir, 'indexer': indexerApi(self.indexer).name }) ui.notifications.error( 'Unable to add show', 'Show in {path} has no name on {indexer}, probably the wrong language.' ' Delete .nfo and manually add the correct language.'. format(path=self.show_dir, indexer=indexerApi(self.indexer).name)) self._finishEarly() return # Check if we can already find this show in our current showList. try: check_existing_shows(s, self.indexer) except IndexerShowAlreadyInLibrary as error: log.warning( 'Could not add the show {series}, as it already is in your library.' ' Error: {error}', { 'series': s['seriesname'], 'error': error }) ui.notifications.error('Unable to add show', 'reason: {0}'.format(error)) self._finishEarly() # Clean up leftover if the newly created directory is empty. delete_empty_folders(self.show_dir) return # TODO: Add more specific indexer exceptions, that should provide the user with some accurate feedback. except IndexerShowNotFound as error: log.warning( '{id}: Unable to look up the show in {path} using id {id} on {indexer}.' ' Delete metadata files from the folder and try adding it again.\n' 'With error: {error}', { 'id': self.indexer_id, 'path': self.show_dir, 'indexer': indexerApi(self.indexer).name, 'error': error }) ui.notifications.error( 'Unable to add show', 'Unable to look up the show in {path} using id {id} on {indexer}.' ' Delete metadata files from the folder and try adding it again.' .format(path=self.show_dir, id=self.indexer_id, indexer=indexerApi(self.indexer).name)) self._finishEarly() return except IndexerShowNotFoundInLanguage as error: log.warning( '{id}: Data retrieved from {indexer} was incomplete. The indexer does not provide' ' show information in the searched language {language}. Aborting: {error_msg}', { 'id': self.indexer_id, 'indexer': indexerApi(self.indexer).name, 'language': error.language, 'error_msg': error }) ui.notifications.error( 'Error adding show!', 'Unable to add show {indexer_id} on {indexer} with this language: {language}' .format(indexer_id=self.indexer_id, indexer=indexerApi(self.indexer).name, language=error.language)) self._finishEarly() return except Exception as error: log.error( '{id}: Error while loading information from indexer {indexer}. Error: {error!r}', { 'id': self.indexer_id, 'indexer': indexerApi(self.indexer).name, 'error': error }) ui.notifications.error( 'Unable to add show', 'Unable to look up the show in {path} on {indexer} using ID {id}.' .format(path=self.show_dir, indexer=indexerApi(self.indexer).name, id=self.indexer_id)) self._finishEarly() return try: newShow = Series(self.indexer, self.indexer_id, self.lang) newShow.load_from_indexer(indexer_api) self.show = newShow # set up initial values self.show.location = self.show_dir self.show.subtitles = self.subtitles if self.subtitles is not None else app.SUBTITLES_DEFAULT self.show.quality = self.quality if self.quality else app.QUALITY_DEFAULT self.show.season_folders = self.season_folders if self.season_folders is not None \ else app.SEASON_FOLDERS_DEFAULT self.show.anime = self.anime if self.anime is not None else app.ANIME_DEFAULT self.show.scene = self.scene if self.scene is not None else app.SCENE_DEFAULT self.show.paused = self.paused if self.paused is not None else False # set up default new/missing episode status log.info( 'Setting all previously aired episodes to the specified status: {status}', {'status': statusStrings[self.default_status]}) self.show.default_ep_status = self.default_status if self.show.anime: self.show.release_groups = BlackAndWhiteList(self.show) if self.blacklist: self.show.release_groups.set_black_keywords(self.blacklist) if self.whitelist: self.show.release_groups.set_white_keywords(self.whitelist) except IndexerException as error: log.error( 'Unable to add show due to an error with {indexer}: {error}', { 'indexer': indexerApi(self.indexer).name, 'error': error }) ui.notifications.error( 'Unable to add {series_name} due to an error with {indexer_name}' .format(series_name=self.show.name if self.show else 'show', indexer_name=indexerApi(self.indexer).name)) self._finishEarly() return except MultipleShowObjectsException: log.warning( 'The show in {show_dir} is already in your show list, skipping', {'show_dir': self.show_dir}) ui.notifications.error( 'Show skipped', 'The show in {show_dir} is already in your show list'.format( show_dir=self.show_dir)) self._finishEarly() return except Exception as error: log.error('Error trying to add show: {0}', error) log.debug(traceback.format_exc()) self._finishEarly() raise log.debug('Retrieving show info from IMDb') try: self.show.load_imdb_info() except ImdbAPIError as error: log.info('Something wrong on IMDb api: {0}', error) except RequestException as error: log.warning('Error loading IMDb info: {0}', error) try: log.debug('{id}: Saving new show to database', {'id': self.show.series_id}) self.show.save_to_db() except Exception as error: log.error('Error saving the show to the database: {0}', error) log.debug(traceback.format_exc()) self._finishEarly() raise # add it to the show list app.showList.append(self.show) try: self.show.load_episodes_from_indexer(tvapi=indexer_api) except Exception as error: log.error( 'Error with {indexer}, not creating episode list: {error}', { 'indexer': indexerApi(self.show.indexer).name, 'error': error }) log.debug(traceback.format_exc()) # update internal name cache name_cache.build_name_cache(self.show) try: self.show.load_episodes_from_dir() except Exception as error: log.error('Error searching dir for episodes: {0}', error) log.debug(traceback.format_exc()) # if they set default ep status to WANTED then run the backlog to search for episodes if self.show.default_ep_status == WANTED: log.info( 'Launching backlog for this show since its episodes are WANTED' ) wanted_segments = self.show.get_wanted_segments() for season, segment in viewitems(wanted_segments): cur_backlog_queue_item = BacklogQueueItem(self.show, segment) app.forced_search_queue_scheduler.action.add_item( cur_backlog_queue_item) log.info('Sending forced backlog for {show} season {season}' ' because some episodes were set to wanted'.format( show=self.show.name, season=season)) self.show.write_metadata() self.show.update_metadata() self.show.populate_cache() self.show.flush_episodes() if app.USE_TRAKT: # if there are specific episodes that need to be added by trakt app.trakt_checker_scheduler.action.manage_new_show(self.show) # add show to trakt.tv library if app.TRAKT_SYNC: app.trakt_checker_scheduler.action.add_show_trakt_library( self.show) if app.TRAKT_SYNC_WATCHLIST: log.info('update watchlist') notifiers.trakt_notifier.update_watchlist(show_obj=self.show) # Load XEM data to DB for show scene_numbering.xem_refresh(self.show, force=True) # check if show has XEM mapping so we can determine if searches # should go by scene numbering or indexer numbering. Warn the user. if not self.scene and scene_numbering.get_xem_numbering_for_show( self.show): log.warning( '{id}: while adding the show {title} we noticed thexem.de has an episode mapping available' '\nyou might want to consider enabling the scene option for this show.', { 'id': self.show.series_id, 'title': self.show.name }) ui.notifications.message( 'consider enabling scene for this show', 'for show {title} you might want to consider enabling the scene option' .format(title=self.show.name)) # After initial add, set to default_status_after. self.show.default_ep_status = self.default_status_after try: log.debug('{id}: Saving new show info to database', {'id': self.show.series_id}) self.show.save_to_db() except Exception as error: log.warning( '{id}: Error saving new show info to database: {error_msg}', { 'id': self.show.series_id, 'error_msg': error }) log.error(traceback.format_exc()) # Send ws update to client ws.Message('showAdded', self.show.to_json(detailed=False)).push() self.finish()
def mass_edit_show( self, show_obj, location=None, allowed_qualities=None, preferred_qualities=None, season_folders=None, paused=None, air_by_date=None, sports=None, dvd_order=None, subtitles=None, anime=None, scene=None, default_ep_status=None ): """A variation of the original `editShow`, where `directCall` is always true.""" allowed_qualities = allowed_qualities or [] preferred_qualities = preferred_qualities or [] errors = 0 do_update_scene_numbering = not (scene == show_obj.scene and anime == show_obj.anime) if not isinstance(allowed_qualities, list): allowed_qualities = [allowed_qualities] if not isinstance(preferred_qualities, list): preferred_qualities = [preferred_qualities] with show_obj.lock: new_quality = Quality.combine_qualities([int(q) for q in allowed_qualities], [int(q) for q in preferred_qualities]) show_obj.quality = new_quality # reversed for now if bool(show_obj.season_folders) != bool(season_folders): show_obj.season_folders = season_folders try: app.show_queue_scheduler.action.refreshShow(show_obj) except CantRefreshShowException as error: errors += 1 log.warning("Unable to refresh show '{show}': {error}", { 'show': show_obj.name, 'error': error }) # Check if we should erase parsed cached results for that show do_erase_parsed_cache = False for item in [('scene', scene), ('anime', anime), ('sports', sports), ('air_by_date', air_by_date), ('dvd_order', dvd_order)]: if getattr(show_obj, item[0]) != item[1]: do_erase_parsed_cache = True # Break if at least one setting was changed break show_obj.paused = paused show_obj.scene = scene show_obj.anime = anime show_obj.sports = sports show_obj.subtitles = subtitles show_obj.air_by_date = air_by_date show_obj.default_ep_status = int(default_ep_status) show_obj.dvd_order = dvd_order # if we change location clear the db of episodes, change it, write to db, and rescan old_location = path.normpath(show_obj._location) new_location = path.normpath(location) if old_location != new_location: changed_location = True log.info('Changing show location to: {new}', {'new': new_location}) if not path.isdir(new_location): if app.CREATE_MISSING_SHOW_DIRS: log.info("Show directory doesn't exist, creating it") try: mkdir(new_location) except OSError as error: errors += 1 changed_location = False log.warning("Unable to create the show directory '{location}'. Error: {msg}", { 'location': new_location, 'msg': error}) else: log.info('New show directory created') helpers.chmod_as_parent(new_location) else: changed_location = False log.warning("New location '{location}' does not exist. " "Enable setting 'Create missing show dirs'", {'location': location}) # Save new location to DB only if we changed it if changed_location: show_obj.location = new_location if changed_location and path.isdir(new_location): try: app.show_queue_scheduler.action.refreshShow(show_obj) except CantRefreshShowException as error: errors += 1 log.warning("Unable to refresh show '{show}'. Error: {error}", { 'show': show_obj.name, 'error': error}) # Save all settings changed while in show_obj.lock show_obj.save_to_db() if do_update_scene_numbering or do_erase_parsed_cache: try: xem_refresh(show_obj) except CantUpdateShowException as error: errors += 1 log.warning("Unable to update scene numbering for show '{show}': {error}", {'show': show_obj.name, 'error': error}) # Must erase cached DB results when toggling scene numbering show_obj.erase_provider_cache() # Erase parsed cached names as we are changing scene numbering show_obj.flush_episodes() show_obj.erase_cached_parse() # Need to refresh show as we updated scene numbering or changed show format try: app.show_queue_scheduler.action.refreshShow(show_obj) except CantRefreshShowException as error: errors += 1 log.warning( "Unable to refresh show '{show}'. Please manually trigger a full show refresh. " 'Error: {error!r}'.format(show=show_obj.name, error=error), {'show': show_obj.name, 'error': error} ) return errors