diff --git a/CHANGELOG.md b/CHANGELOG.md index c7b697d946..da269d14a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Updated `guessit` to version 3.0.0 ([#4244](https://github.com/pymedusa/Medusa/pull/4244)) - Updated the API v2 endpoint to handle concurrent requests ([#4970](https://github.com/pymedusa/Medusa/pull/4970)) - Converted some of the show header to Vue ([#5087](https://github.com/pymedusa/Medusa/pull/5087)) +- Converted "Add Show" options into a Vue SFC ([#4848](https://github.com/pymedusa/Medusa/pull/4848)) #### Fixes - Fixed many release name parsing issues as a result of updating `guessit` ([#4244](https://github.com/pymedusa/Medusa/pull/4244)) diff --git a/medusa/common.py b/medusa/common.py index a0e988d0aa..c1c6af6ace 100644 --- a/medusa/common.py +++ b/medusa/common.py @@ -260,6 +260,22 @@ def split_quality(quality): return sorted(allowed_qualities), sorted(preferred_qualities) + @staticmethod + def is_valid_combined_quality(quality): + """ + Check quality value to make sure it is a valid combined quality. + + :param quality: Quality to check + :type quality: int + :return: True if valid, False if not + """ + for cur_qual in Quality.qualityStrings: + if cur_qual & quality: + quality -= cur_qual + if cur_qual << 16 & quality: + quality -= cur_qual << 16 + return quality == 0 + @staticmethod def name_quality(name, anime=False, extend=True): """ diff --git a/medusa/server/api/v2/config.py b/medusa/server/api/v2/config.py index c768317a52..7c5370ac38 100644 --- a/medusa/server/api/v2/config.py +++ b/medusa/server/api/v2/config.py @@ -17,6 +17,7 @@ logger, ws, ) +from medusa.common import IGNORED, Quality, SKIPPED, WANTED from medusa.helper.mappings import NonEmptyDict from medusa.indexers.indexer_config import get_indexer_config from medusa.logger.adapters.style import BraceAdapter @@ -51,6 +52,11 @@ def theme_name_setter(object, name, value): config.change_theme(value) +def season_folders_validator(value): + """Validate default season folders setting.""" + return not (app.NAMING_FORCE_FOLDERS and value is False) + + class ConfigHandler(BaseRequestHandler): """Config request handler.""" @@ -116,6 +122,15 @@ class ConfigHandler(BaseRequestHandler): 'backlogOverview.period': StringField(app, 'BACKLOG_PERIOD'), 'backlogOverview.status': StringField(app, 'BACKLOG_STATUS'), 'rootDirs': ListField(app, 'ROOT_DIRS'), + + 'showDefaults.status': EnumField(app, 'STATUS_DEFAULT', (SKIPPED, WANTED, IGNORED), int), + 'showDefaults.statusAfter': EnumField(app, 'STATUS_DEFAULT_AFTER', (SKIPPED, WANTED, IGNORED), int), + 'showDefaults.quality': IntegerField(app, 'QUALITY_DEFAULT', validator=Quality.is_valid_combined_quality), + 'showDefaults.subtitles': BooleanField(app, 'SUBTITLES_DEFAULT', validator=lambda v: app.USE_SUBTITLES, converter=bool), + 'showDefaults.seasonFolders': BooleanField(app, 'SEASON_FOLDERS_DEFAULT', validator=season_folders_validator, converter=bool), + 'showDefaults.anime': BooleanField(app, 'ANIME_DEFAULT', converter=bool), + 'showDefaults.scene': BooleanField(app, 'SCENE_DEFAULT', converter=bool), + 'postProcessing.showDownloadDir': StringField(app, 'TV_DOWNLOAD_DIR'), 'postProcessing.processAutomatically': BooleanField(app, 'PROCESS_AUTOMATICALLY'), 'postProcessing.processMethod': StringField(app, 'PROCESS_METHOD'), @@ -297,6 +312,15 @@ def data_main(): section_data['subtitles']['enabled'] = bool(app.USE_SUBTITLES) section_data['recentShows'] = app.SHOWS_RECENT + section_data['showDefaults'] = {} + section_data['showDefaults']['status'] = app.STATUS_DEFAULT + section_data['showDefaults']['statusAfter'] = app.STATUS_DEFAULT_AFTER + section_data['showDefaults']['quality'] = app.QUALITY_DEFAULT + section_data['showDefaults']['subtitles'] = bool(app.SUBTITLES_DEFAULT) + section_data['showDefaults']['seasonFolders'] = bool(app.SEASON_FOLDERS_DEFAULT) + section_data['showDefaults']['anime'] = bool(app.ANIME_DEFAULT) + section_data['showDefaults']['scene'] = bool(app.SCENE_DEFAULT) + section_data['news'] = NonEmptyDict() section_data['news']['lastRead'] = app.NEWS_LAST_READ section_data['news']['latest'] = app.NEWS_LATEST diff --git a/tests/apiv2/test_config.py b/tests/apiv2/test_config.py index 1267edd38f..b60a4bcbf0 100644 --- a/tests/apiv2/test_config.py +++ b/tests/apiv2/test_config.py @@ -78,6 +78,15 @@ def config_main(monkeypatch, app_config): config_data['subtitles']['enabled'] = bool(app.USE_SUBTITLES) config_data['recentShows'] = app.SHOWS_RECENT + config_data['showDefaults'] = {} + config_data['showDefaults']['status'] = app.STATUS_DEFAULT + config_data['showDefaults']['statusAfter'] = app.STATUS_DEFAULT_AFTER + config_data['showDefaults']['quality'] = app.QUALITY_DEFAULT + config_data['showDefaults']['subtitles'] = bool(app.SUBTITLES_DEFAULT) + config_data['showDefaults']['seasonFolders'] = bool(app.SEASON_FOLDERS_DEFAULT) + config_data['showDefaults']['anime'] = bool(app.ANIME_DEFAULT) + config_data['showDefaults']['scene'] = bool(app.SCENE_DEFAULT) + config_data['news'] = NonEmptyDict() config_data['news']['lastRead'] = app.NEWS_LAST_READ config_data['news']['latest'] = app.NEWS_LATEST diff --git a/tests/test_common.py b/tests/test_common.py index d19a033a15..514dfbdaf7 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -461,3 +461,33 @@ def test_wanted_quality(): # Then assert actual is True + + +@pytest.mark.parametrize('p', [ + { # p0 - Invalid combined quality + 'quality': -4, + 'expected': False + }, + { # p1 - Valid 'allowed' quality + 'quality': Quality.HDTV, + 'expected': True + }, + { # p2 - Valid 'allowed' quality + valid 'preferred' quality + 'quality': Quality.combine_qualities([Quality.HDTV], [Quality.HDWEBDL]), + 'expected': True + }, + { # p3 - Valid 'allowed' quality + **invalid** 'preferred' quality + 'quality': Quality.combine_qualities([Quality.HDTV], [-4]), + 'expected': False + }, +]) +def test_is_valid_combined_quality(p): + # Given + quality = p['quality'] + expected = p['expected'] + + # When + actual = Quality.is_valid_combined_quality(quality) + + # Then + assert expected == actual diff --git a/themes-default/slim/src/components/add-show-options.vue b/themes-default/slim/src/components/add-show-options.vue new file mode 100644 index 0000000000..1d41f596cb --- /dev/null +++ b/themes-default/slim/src/components/add-show-options.vue @@ -0,0 +1,338 @@ + + + diff --git a/themes-default/slim/src/components/anidb-release-group-ui.vue b/themes-default/slim/src/components/anidb-release-group-ui.vue index 5eeb192665..5d25eb85b3 100644 --- a/themes-default/slim/src/components/anidb-release-group-ui.vue +++ b/themes-default/slim/src/components/anidb-release-group-ui.vue @@ -159,6 +159,11 @@ export default { this.$emit('change', this.allReleaseGroups); }, deep: true + }, + allGroups: { + handler(newValue) { + this.createIndexedObjects(newValue, 'releasegroups'); + } } } }; diff --git a/themes-default/slim/src/components/config-toggle-slider.vue b/themes-default/slim/src/components/config-toggle-slider.vue index bd26dea60f..cb4c1a1826 100644 --- a/themes-default/slim/src/components/config-toggle-slider.vue +++ b/themes-default/slim/src/components/config-toggle-slider.vue @@ -6,7 +6,7 @@ {{ label }}
- +

{{ explanation }}

@@ -30,6 +30,10 @@ export default { type: Boolean, default: null }, + disabled: { + type: Boolean, + default: false + }, explanations: { type: Array, default: () => [] @@ -44,6 +48,9 @@ export default { this.localChecked = this.checked; }, watch: { + checked() { + this.localChecked = this.checked; + }, localChecked() { this.$emit('update', this.localChecked); } diff --git a/themes-default/slim/src/components/index.js b/themes-default/slim/src/components/index.js index 7c0db9a27e..b598256977 100644 --- a/themes-default/slim/src/components/index.js +++ b/themes-default/slim/src/components/index.js @@ -1,4 +1,5 @@ export { default as AddRecommended } from './add-recommended.vue'; +export { default as AddShowOptions } from './add-show-options.vue'; export { default as AddShows } from './add-shows.vue'; export { default as AnidbReleaseGroupUi } from './anidb-release-group-ui.vue'; export { default as AppHeader } from './app-header.vue'; diff --git a/themes-default/slim/src/index.js b/themes-default/slim/src/index.js index 4bb6b49713..1975648fc8 100644 --- a/themes-default/slim/src/index.js +++ b/themes-default/slim/src/index.js @@ -19,6 +19,7 @@ import router from './router'; import { isDevelopment } from './utils'; import { apiRoute, apiv1, api, webRoot, apiKey } from './api'; import { + AddShowOptions, AnidbReleaseGroupUi, AppHeader, AppLink, @@ -75,6 +76,7 @@ if (window) { // Push pages that load via a vue file but still use `el` for mounting window.components = []; + window.components.push(AddShowOptions); window.components.push(AnidbReleaseGroupUi); window.components.push(AppHeader); window.components.push(AppLink); diff --git a/themes-default/slim/src/store/modules/config.js b/themes-default/slim/src/store/modules/config.js index 8bdf6afb2c..9f5949b931 100644 --- a/themes-default/slim/src/store/modules/config.js +++ b/themes-default/slim/src/store/modules/config.js @@ -252,7 +252,16 @@ const state = { os: null, anonRedirect: null, logDir: null, - recentShows: [] + recentShows: [], + showDefaults: { + status: null, + statusAfter: null, + quality: null, + subtitles: null, + seasonFolders: null, + anime: null, + scene: null + } }; const mutations = { diff --git a/themes-default/slim/src/utils.js b/themes-default/slim/src/utils.js index a8070207ec..43e3dc6dff 100644 --- a/themes-default/slim/src/utils.js +++ b/themes-default/slim/src/utils.js @@ -1,5 +1,20 @@ const isDevelopment = process.env.NODE_ENV === 'development'; +/** + * Calculate the combined value of the selected qualities. + * @param {number[]} allowedQualities - Array of allowed qualities. + * @param {number[]} preferredQualities - Array of preferred qualities. + * @returns {number} - An unsigned integer. + */ +const combineQualities = (allowedQualities, preferredQualities) => { + const reducer = (accumulator, currentValue) => accumulator | currentValue; + const allowed = allowedQualities.reduce(reducer, 0); + const preferred = preferredQualities.reduce(reducer, 0); + + return (allowed | (preferred << 16)) >>> 0; // Unsigned int +}; + export { + combineQualities, isDevelopment }; diff --git a/themes-default/slim/static/js/blackwhite.js b/themes-default/slim/static/js/blackwhite.js index e2b11fb54e..e986815442 100644 --- a/themes-default/slim/static/js/blackwhite.js +++ b/themes-default/slim/static/js/blackwhite.js @@ -1,38 +1,3 @@ -function generateBlackWhiteList() { // eslint-disable-line no-unused-vars - let realvalues = []; - - $('#white option').each((i, selected) => { - realvalues[i] = $(selected).val(); - }); - $('#whitelist').val(realvalues.join(',')); - - realvalues = []; - $('#black option').each((i, selected) => { - realvalues[i] = $(selected).val(); - }); - $('#blacklist').val(realvalues.join(',')); -} - -function updateBlackWhiteList(showName) { // eslint-disable-line no-unused-vars - $('#pool').children().remove(); - - $('#blackwhitelist').show(); - if (showName) { - $.getJSON('home/fetch_releasegroups', { - series_name: showName // eslint-disable-line camelcase - }, data => { - if (data.result === 'success') { - $.each(data.groups, (i, group) => { - const option = $('