diff --git a/gui/slick/js/core.js b/gui/slick/js/core.js index aeb979d2f0087a5b7d03837a46523d962aca7b6a..91cd9741fc4d5a7c7d8e97198594a588536584e0 100644 --- a/gui/slick/js/core.js +++ b/gui/slick/js/core.js @@ -2859,8 +2859,6 @@ var SICKRAGE = { }); }, editShow: function() { - let allExceptions = []; - $('#location').fileBrowser({title: _('Select Show Location')}); SICKRAGE.common.QualityChooser.init(); @@ -2871,13 +2869,20 @@ var SICKRAGE = { }); */ $('#submit').on('click', function() { - allExceptions = []; + const allExceptions = $('#exceptions_list').find('optgroup').get().map(function(group) { + const season = $(group).data('season'); + const exceptions = $(group).find('option:enabled').get().map(function(option) { + const exception = $(option).val(); + + return encodeURIComponent(exception); + }).join('|'); - $('#exceptions_list option').each(function() { - allExceptions.push($(this).val()); + return [season, exceptions].join(':'); + }).filter(function(item) { + return item; }); - $('#exceptions_list').val(allExceptions); + $('#exceptions').val(allExceptions); if (metaToBool('show.is_anime')) { generateBlackWhiteList(); // eslint-disable-line no-undef @@ -2885,48 +2890,43 @@ var SICKRAGE = { }); $('#addSceneName').on('click', function() { + const season = $('#SceneSeason').find(':selected').data('season'); const sceneEx = $('#SceneName').val(); - const option = $('<option>'); - allExceptions = []; - $('#exceptions_list option').each(function() { - allExceptions.push($(this).val()); - }); + const group = $('#exceptions_list').find('[data-season="' + season + '"]').get()[0]; + const placeholder = $(group).find('option.empty'); - $('#SceneName').val(''); + const exceptions = $(group).find('option:not(.empty)').get().map(function(el) { + return $(el).val(); + }); - if ($.inArray(sceneEx, allExceptions) > -1 || (sceneEx === '')) { + // If we already have the exception or the field is empty return + if (exceptions.indexOf(sceneEx) > -1 || sceneEx.trim() === '') { + $('#SceneName').val(''); return; } - $('#SceneException').show(); - - option.attr('value', sceneEx); - option.html(sceneEx); - return option.appendTo('#exceptions_list'); - }); + const newException = $('<option data-season=' + season + '>').text(sceneEx).val(sceneEx); - $('#removeSceneName').on('click', function(event) { - $('#exceptions_list option:selected').remove(); + $(group).append(newException); + placeholder.remove(); - $(event.currentTarget).toggleSceneException(); + $('#SceneName').val(''); }); - $.fn.toggleSceneException = function() { - allExceptions = []; + $('#removeSceneName').on('click', function() { + const option = $('#exceptions_list').find('option:selected'); + const group = option.closest('optgroup'); - $('#exceptions_list option').each(function() { - allExceptions.push($(this).val()); - }); + if (group.find('option').length < 2) { + const newOption = $('<option disabled class="empty">'); + newOption.text(_('None')); - if (allExceptions === '') { - $('#SceneException').hide(); - } else { - $('#SceneException').show(); + group.append(newOption); } - }; - $(this).toggleSceneException(); + option.remove(); + }); }, postProcess: function() { $('#episodeDir').fileBrowser({ diff --git a/gui/slick/views/editShow.mako b/gui/slick/views/editShow.mako index c692be0322e09f7a6b6f7f35768fdd54ce85417c..62fee40737a4c0b3f338e8c134a9dfb213ca8b5e 100644 --- a/gui/slick/views/editShow.mako +++ b/gui/slick/views/editShow.mako @@ -330,17 +330,38 @@ <div class="col-lg-9 col-md-8 col-sm-7 col-xs-12 component-desc"> <div class="row"> <div class="col-md-12"> - <input type="text" id="SceneName" - class="form-control input-sm input200" autocapitalize="off"/> + <input type="text" id="SceneName" class="form-control input-sm input250" autocapitalize="off"/> + <select id="SceneSeason" class="form-control input-sm" style="width: 95px"> + % for season in range(0, len(seasonResults)): + %if season == 0: + <% season = -1 %> + %endif + <option data-season="${season}">${_('Show') if season == -1 else _('Season ') + str(season)}</option> + %endfor + </select> <input class="btn btn-inline" type="button" value="${_('Add')}" id="addSceneName"/> </div> </div> <div class="row"> <div class="col-md-12"> - <select id="exceptions_list" name="exceptions_list" multiple="multiple" - style="height:99px;width:200px;" title="exceptions_list"> - % for cur_exception in show.exceptions: - <option value="${cur_exception}">${cur_exception}</option> + <input type="hidden" id="exceptions" name="exceptions_list"/> + <select id="exceptions_list" multiple + style="height:200px;" class="form-control input350" title="exceptions_list"> + % for season in range(0, len(seasonResults)): + %if season == 0: + <% season = -1 %> + %endif + <optgroup data-season="${season}" label="${_('Show') if season == -1 else _('Season ') + str(season)}"> + %if season in scene_exceptions: + %for exception in scene_exceptions[season]: + <option ${'disabled' if exception["custom"] == False else ''} value="${exception["show_name"]}"> + ${exception["show_name"]} + </option> + %endfor + % else: + <option class="empty" disabled>${_('None')}</option> + %endif + </optgroup> % endfor </select> <div> @@ -358,6 +379,11 @@ <label>${_('this list appends to the original show name.')}</label> </div> </div> + <div class="row"> + <div class="col-md-12"> + <label>${_('disabled entries come from a central file on github,<br/> if you think something is wrong please make an issue <a href="//github.com/sickrage/sickrage.github.io/issues">here</a>.')}</label> + </div> + </div> </div> </div> diff --git a/sickbeard/scene_exceptions.py b/sickbeard/scene_exceptions.py index c7ca1f5e73b3dac08d6fef6fe107ed934a0cf8d7..8ea0469add82ea96ec720fd38d182ae2b669e79a 100644 --- a/sickbeard/scene_exceptions.py +++ b/sickbeard/scene_exceptions.py @@ -110,13 +110,16 @@ def get_all_scene_exceptions(indexer_id): exceptionsDict = {} cache_db_con = db.DBConnection('cache.db') - exceptions = cache_db_con.select("SELECT show_name,season FROM scene_exceptions WHERE indexer_id = ?", [indexer_id]) + exceptions = cache_db_con.select("SELECT show_name,season,custom FROM scene_exceptions WHERE indexer_id = ?", [indexer_id]) if exceptions: for cur_exception in exceptions: if not cur_exception[b"season"] in exceptionsDict: exceptionsDict[cur_exception[b"season"]] = [] - exceptionsDict[cur_exception[b"season"]].append(cur_exception[b"show_name"]) + exceptionsDict[cur_exception[b"season"]].append({ + "show_name": cur_exception[b"show_name"], + "custom": bool(cur_exception[b"custom"]) + }) return exceptionsDict @@ -271,23 +274,21 @@ def retrieve_exceptions(): # pylint:disable=too-many-locals, too-many-branches xem_exception_dict.clear() -def update_scene_exceptions(indexer_id, scene_exceptions, season=-1): +def update_scene_exceptions(indexer_id, scene_exceptions): """ Given a indexer_id, and a list of all show scene exceptions, update the db. """ cache_db_con = db.DBConnection('cache.db') - cache_db_con.action('DELETE FROM scene_exceptions WHERE indexer_id=? and season=?', [indexer_id, season]) + cache_db_con.action('DELETE FROM scene_exceptions WHERE indexer_id=? and custom=1', [indexer_id]) logger.log("Updating scene exceptions", logger.INFO) - # A change has been made to the scene exception list. Let's clear the cache, to make this visible - if indexer_id in exceptionsCache: - exceptionsCache[indexer_id] = {} - exceptionsCache[indexer_id][season] = scene_exceptions + for season in scene_exceptions: + for cur_exception in scene_exceptions[season]: + cache_db_con.action("INSERT INTO scene_exceptions (indexer_id, show_name, season, custom) VALUES (?,?,?,?)", + [indexer_id, cur_exception["show_name"], season, cur_exception["custom"]]) - for cur_exception in scene_exceptions: - cache_db_con.action("INSERT INTO scene_exceptions (indexer_id, show_name, season) VALUES (?,?,?)", - [indexer_id, cur_exception, season]) + rebuild_exception_cache(indexer_id) def _anidb_exceptions_fetcher(): @@ -342,3 +343,17 @@ def getSceneSeasons(indexer_id): cache_db_con = db.DBConnection('cache.db') seasons = cache_db_con.select("SELECT DISTINCT season FROM scene_exceptions WHERE indexer_id = ?", [indexer_id]) return [cur_exception[b"season"] for cur_exception in seasons] + + +def rebuild_exception_cache(indexer_id): + if indexer_id in exceptionsCache: + exceptionsCache[indexer_id] = {} + + cache_db_con = db.DBConnection('cache.db') + results = cache_db_con.action('SELECT show_name, season FROM scene_exceptions WHERE indexer_id=?', [indexer_id]) + + for result in results: + if result[b"season"] not in exceptionsCache[indexer_id]: + exceptionsCache[indexer_id][result[b"season"]] = [] + + exceptionsCache[indexer_id][result[b"season"]].append(result[b"show_name"]) diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index ce7d98735980fd7bea85115c744d6d0f97f6ad81..5537bf2d3f12a626d9f5a3cba05fd0185a38b896 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -1521,10 +1521,10 @@ class Home(WebRoot): return _("No scene exceptions") out = [] - for season, names in iter(sorted(six.iteritems(exceptionsList))): + for season, object in iter(sorted(six.iteritems(exceptionsList))): if season == -1: season = "*" - out.append("S" + str(season) + ": " + ", ".join(names)) + out.append("S" + str(season) + ": " + ", ".join(object.names)) return "<br>".join(out) def editShow(self, show=None, location=None, anyQualities=None, bestQualities=None, @@ -1551,7 +1551,13 @@ class Home(WebRoot): else: return self._genericMessage(_("Error"), errString) - show_obj.exceptions = sickbeard.scene_exceptions.get_scene_exceptions(show_obj.indexerid) + show_obj.exceptions = sickbeard.scene_exceptions.get_all_scene_exceptions(show_obj.indexerid) + + main_db_con = db.DBConnection() + seasonResults = main_db_con.select( + "SELECT DISTINCT season FROM tv_episodes WHERE showid = ? AND season IS NOT NULL ORDER BY season DESC", + [show_obj.indexerid] + ) if try_int(quality_preset, None): bestQualities = [] @@ -1575,14 +1581,14 @@ class Home(WebRoot): with show_obj.lock: show = show_obj - scene_exceptions = sickbeard.scene_exceptions.get_scene_exceptions(show_obj.indexerid) if show_obj.is_anime: - return t.render(show=show, scene_exceptions=scene_exceptions, groups=groups, whitelist=whitelist, - blacklist=blacklist, title=_('Edit Show'), header=_('Edit Show'), controller="home", action="editShow") + return t.render(show=show, scene_exceptions=show_obj.exceptions, seasonResults=seasonResults, + groups=groups, whitelist=whitelist, blacklist=blacklist, + title=_('Edit Show'), header=_('Edit Show'), controller="home", action="editShow") else: - return t.render(show=show, scene_exceptions=scene_exceptions, title=_('Edit Show'), header=_('Edit Show'), - controller="home", action="editShow") + return t.render(show=show, scene_exceptions=show_obj.exceptions, seasonResults=seasonResults, + title=_('Edit Show'), header=_('Edit Show'), controller="home", action="editShow") season_folders = config.checkbox_to_value(season_folders) dvdorder = config.checkbox_to_value(dvdorder) @@ -1618,18 +1624,28 @@ class Home(WebRoot): if not isinstance(bestQualities, list): bestQualities = [bestQualities] - if not isinstance(exceptions_list, list): - exceptions_list = [exceptions_list] - - # If directCall from mass_edit_update no scene exceptions handling or blackandwhite list handling - if directCall: - do_update_exceptions = False - else: - if set(exceptions_list) == set(show_obj.exceptions): - do_update_exceptions = False + if isinstance(exceptions_list, list): + if len(exceptions_list) > 0: + exceptions_list = exceptions_list[0] else: - do_update_exceptions = True + exceptions_list = None + + # Map custom exceptions + exceptions = {} + if exceptions_list is not None: + for season in exceptions_list.split(','): + (season, shows) = season.split(':') + + show_list = [] + + for cur_show in shows.split('|'): + show_list.append({'show_name': unquote_plus(cur_show), 'custom': True}) + + exceptions[int(season)] = show_list + + # If directCall from mass_edit_update no scene exceptions handling or blackandwhite list handling + if not directCall: with show_obj.lock: if anime: if not show_obj.release_groups: @@ -1710,12 +1726,11 @@ class Home(WebRoot): except CantUpdateShowException as e: errors.append(_("Unable to update show: {error}").format(error=e)) - if do_update_exceptions: - try: - sickbeard.scene_exceptions.update_scene_exceptions(show_obj.indexerid, exceptions_list) # @UndefinedVdexerid) - time.sleep(cpu_presets[sickbeard.CPU_PRESET]) - except CantUpdateShowException as e: - errors.append(_("Unable to force an update on scene exceptions of the show.")) + try: + sickbeard.scene_exceptions.update_scene_exceptions(show_obj.indexerid, exceptions) # @UndefinedVdexerid) + time.sleep(cpu_presets[sickbeard.CPU_PRESET]) + except CantUpdateShowException as e: + errors.append(_("Unable to force an update on scene exceptions of the show.")) if do_update_scene_numbering: try: