From a431a1ce955f0e2d3d33b7f40086c159423bcf7a Mon Sep 17 00:00:00 2001 From: Qstick Date: Sun, 5 Jun 2022 22:50:46 -0500 Subject: [PATCH] New: Reset Quality Definitions to default Closes #2843 (cherry picked from commit d5fff15f32fdb49768dcadd94c760678e650c884) --- frontend/src/App/AppRoutes.js | 4 +- frontend/src/Commands/commandNames.js | 1 + frontend/src/Components/SignalRConnector.js | 8 +- frontend/src/Settings/Quality/Quality.js | 40 ++++++- .../src/Settings/Quality/QualityConnector.js | 38 +++++++ .../Reset/ResetQualityDefinitionsModal.js | 33 ++++++ .../ResetQualityDefinitionsModalContent.css | 3 + .../ResetQualityDefinitionsModalContent.js | 103 ++++++++++++++++++ ...QualityDefinitionsModalContentConnector.js | 54 +++++++++ .../Qualities/QualityDefinitionController.cs | 17 ++- .../ResetQualityDefinitionsCommand.cs | 14 +++ .../Qualities/QualityDefinitionService.cs | 27 ++++- 12 files changed, 334 insertions(+), 8 deletions(-) create mode 100644 frontend/src/Settings/Quality/QualityConnector.js create mode 100644 frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModal.js create mode 100644 frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.css create mode 100644 frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.js create mode 100644 frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContentConnector.js create mode 100644 src/NzbDrone.Core/Qualities/Commands/ResetQualityDefinitionsCommand.cs diff --git a/frontend/src/App/AppRoutes.js b/frontend/src/App/AppRoutes.js index 9e5ec55f2..0285559a8 100644 --- a/frontend/src/App/AppRoutes.js +++ b/frontend/src/App/AppRoutes.js @@ -21,7 +21,7 @@ import MediaManagementConnector from 'Settings/MediaManagement/MediaManagementCo import MetadataSettings from 'Settings/Metadata/MetadataSettings'; import NotificationSettings from 'Settings/Notifications/NotificationSettings'; import Profiles from 'Settings/Profiles/Profiles'; -import Quality from 'Settings/Quality/Quality'; +import QualityConnector from 'Settings/Quality/QualityConnector'; import Settings from 'Settings/Settings'; import TagSettings from 'Settings/Tags/TagSettings'; import UISettingsConnector from 'Settings/UI/UISettingsConnector'; @@ -164,7 +164,7 @@ function AppRoutes(props) { { + this.props.dispatchFetchQualityDefinitions(); + }; + handleQueue = () => { if (this.props.isQueuePopulated) { this.props.dispatchFetchQueue(); @@ -382,6 +387,7 @@ SignalRConnector.propTypes = { dispatchRemoveItem: PropTypes.func.isRequired, dispatchFetchArtist: PropTypes.func.isRequired, dispatchFetchHealth: PropTypes.func.isRequired, + dispatchFetchQualityDefinitions: PropTypes.func.isRequired, dispatchFetchQueue: PropTypes.func.isRequired, dispatchFetchQueueDetails: PropTypes.func.isRequired, dispatchFetchRootFolders: PropTypes.func.isRequired, diff --git a/frontend/src/Settings/Quality/Quality.js b/frontend/src/Settings/Quality/Quality.js index 8ba72489f..b7dfafe99 100644 --- a/frontend/src/Settings/Quality/Quality.js +++ b/frontend/src/Settings/Quality/Quality.js @@ -1,9 +1,14 @@ -import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import React, { Component, Fragment } from 'react'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; +import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; +import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator'; +import { icons } from 'Helpers/Props'; import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector'; import translate from 'Utilities/String/translate'; import QualityDefinitionsConnector from './Definition/QualityDefinitionsConnector'; +import ResetQualityDefinitionsModal from './Reset/ResetQualityDefinitionsModal'; class Quality extends Component { @@ -17,7 +22,8 @@ class Quality extends Component { this.state = { isSaving: false, - hasPendingChanges: false + hasPendingChanges: false, + isConfirmQualityDefinitionResetModalOpen: false }; } @@ -32,6 +38,14 @@ class Quality extends Component { this.setState(payload); }; + onResetQualityDefinitionsPress = () => { + this.setState({ isConfirmQualityDefinitionResetModalOpen: true }); + }; + + onCloseResetQualityDefinitionsModal = () => { + this.setState({ isConfirmQualityDefinitionResetModalOpen: false }); + }; + onSavePress = () => { if (this._saveCallback) { this._saveCallback(); @@ -44,6 +58,7 @@ class Quality extends Component { render() { const { isSaving, + isResettingQualityDefinitions, hasPendingChanges } = this.state; @@ -52,6 +67,18 @@ class Quality extends Component { + + + + + } onSavePress={this.onSavePress} /> @@ -61,9 +88,18 @@ class Quality extends Component { onChildStateChange={this.onChildStateChange} /> + + ); } } +Quality.propTypes = { + isResettingQualityDefinitions: PropTypes.bool.isRequired +}; + export default Quality; diff --git a/frontend/src/Settings/Quality/QualityConnector.js b/frontend/src/Settings/Quality/QualityConnector.js new file mode 100644 index 000000000..8cc9219cb --- /dev/null +++ b/frontend/src/Settings/Quality/QualityConnector.js @@ -0,0 +1,38 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import * as commandNames from 'Commands/commandNames'; +import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; +import Quality from './Quality'; + +function createMapStateToProps() { + return createSelector( + createCommandExecutingSelector(commandNames.RESET_QUALITY_DEFINITIONS), + (isResettingQualityDefinitions) => { + return { + isResettingQualityDefinitions + }; + } + ); +} + +class QualityConnector extends Component { + + // + // Render + + render() { + return ( + + ); + } +} + +QualityConnector.propTypes = { + isResettingQualityDefinitions: PropTypes.bool.isRequired +}; + +export default connect(createMapStateToProps)(QualityConnector); diff --git a/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModal.js b/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModal.js new file mode 100644 index 000000000..ee9caa260 --- /dev/null +++ b/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModal.js @@ -0,0 +1,33 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import Modal from 'Components/Modal/Modal'; +import { sizes } from 'Helpers/Props'; +import ResetQualityDefinitionsModalContentConnector from './ResetQualityDefinitionsModalContentConnector'; + +function ResetQualityDefinitionsModal(props) { + const { + isOpen, + onModalClose, + ...otherProps + } = props; + + return ( + + + + ); +} + +ResetQualityDefinitionsModal.propTypes = { + isOpen: PropTypes.bool.isRequired, + onModalClose: PropTypes.func.isRequired +}; + +export default ResetQualityDefinitionsModal; diff --git a/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.css b/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.css new file mode 100644 index 000000000..99c50adbe --- /dev/null +++ b/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.css @@ -0,0 +1,3 @@ +.messageContainer { + margin-bottom: 20px; +} diff --git a/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.js b/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.js new file mode 100644 index 000000000..7d952ea83 --- /dev/null +++ b/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContent.js @@ -0,0 +1,103 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import FormGroup from 'Components/Form/FormGroup'; +import FormInputGroup from 'Components/Form/FormInputGroup'; +import FormLabel from 'Components/Form/FormLabel'; +import Button from 'Components/Link/Button'; +import ModalBody from 'Components/Modal/ModalBody'; +import ModalContent from 'Components/Modal/ModalContent'; +import ModalFooter from 'Components/Modal/ModalFooter'; +import ModalHeader from 'Components/Modal/ModalHeader'; +import { inputTypes, kinds } from 'Helpers/Props'; +import styles from './ResetQualityDefinitionsModalContent.css'; + +class ResetQualityDefinitionsModalContent extends Component { + + // + // Lifecycle + + constructor(props, context) { + super(props, context); + + this.state = { + resetDefinitionTitles: false + }; + } + + // + // Listeners + + onResetDefinitionTitlesChange = ({ value }) => { + this.setState({ resetDefinitionTitles: value }); + }; + + onResetQualityDefinitionsConfirmed = () => { + const resetDefinitionTitles = this.state.resetDefinitionTitles; + + this.setState({ resetDefinitionTitles: false }); + this.props.onResetQualityDefinitions(resetDefinitionTitles); + }; + + // + // Render + + render() { + const { + onModalClose, + isResettingQualityDefinitions + } = this.props; + + const resetDefinitionTitles = this.state.resetDefinitionTitles; + + return ( + + + Reset Quality Definitions + + + +
+ Are you sure you want to reset quality definitions? +
+ + + Reset Titles + + + + +
+ + + + + + +
+ ); + } +} + +ResetQualityDefinitionsModalContent.propTypes = { + onResetQualityDefinitions: PropTypes.func.isRequired, + isResettingQualityDefinitions: PropTypes.bool.isRequired, + onModalClose: PropTypes.func.isRequired +}; + +export default ResetQualityDefinitionsModalContent; diff --git a/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContentConnector.js b/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContentConnector.js new file mode 100644 index 000000000..645cac1e1 --- /dev/null +++ b/frontend/src/Settings/Quality/Reset/ResetQualityDefinitionsModalContentConnector.js @@ -0,0 +1,54 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import * as commandNames from 'Commands/commandNames'; +import { executeCommand } from 'Store/Actions/commandActions'; +import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; +import ResetQualityDefinitionsModalContent from './ResetQualityDefinitionsModalContent'; + +function createMapStateToProps() { + return createSelector( + createCommandExecutingSelector(commandNames.RESET_QUALITY_DEFINITIONS), + (isResettingQualityDefinitions) => { + return { + isResettingQualityDefinitions + }; + } + ); +} + +const mapDispatchToProps = { + executeCommand +}; + +class ResetQualityDefinitionsModalContentConnector extends Component { + + // + // Listeners + + onResetQualityDefinitions = (resetTitles) => { + this.props.executeCommand({ name: commandNames.RESET_QUALITY_DEFINITIONS, resetTitles }); + this.props.onModalClose(true); + }; + + // + // Render + + render() { + return ( + + ); + } +} + +ResetQualityDefinitionsModalContentConnector.propTypes = { + onModalClose: PropTypes.func.isRequired, + isResettingQualityDefinitions: PropTypes.bool.isRequired, + executeCommand: PropTypes.func.isRequired +}; + +export default connect(createMapStateToProps, mapDispatchToProps)(ResetQualityDefinitionsModalContentConnector); diff --git a/src/Lidarr.Api.V1/Qualities/QualityDefinitionController.cs b/src/Lidarr.Api.V1/Qualities/QualityDefinitionController.cs index 9d7a261cf..6511f7243 100644 --- a/src/Lidarr.Api.V1/Qualities/QualityDefinitionController.cs +++ b/src/Lidarr.Api.V1/Qualities/QualityDefinitionController.cs @@ -4,16 +4,20 @@ using Lidarr.Http; using Lidarr.Http.REST; using Lidarr.Http.REST.Attributes; using Microsoft.AspNetCore.Mvc; +using NzbDrone.Core.Datastore.Events; +using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Qualities; +using NzbDrone.SignalR; namespace Lidarr.Api.V1.Qualities { [V1ApiController] - public class QualityDefinitionController : RestController + public class QualityDefinitionController : RestControllerWithSignalR, IHandle { private readonly IQualityDefinitionService _qualityDefinitionService; - public QualityDefinitionController(IQualityDefinitionService qualityDefinitionService) + public QualityDefinitionController(IQualityDefinitionService qualityDefinitionService, IBroadcastSignalRMessage signalRBroadcaster) + : base(signalRBroadcaster) { _qualityDefinitionService = qualityDefinitionService; } @@ -50,5 +54,14 @@ namespace Lidarr.Api.V1.Qualities return Accepted(_qualityDefinitionService.All() .ToResource()); } + + [NonAction] + public void Handle(CommandExecutedEvent message) + { + if (message.Command.Name == "ResetQualityDefinitions") + { + BroadcastResourceChange(ModelAction.Sync); + } + } } } diff --git a/src/NzbDrone.Core/Qualities/Commands/ResetQualityDefinitionsCommand.cs b/src/NzbDrone.Core/Qualities/Commands/ResetQualityDefinitionsCommand.cs new file mode 100644 index 000000000..d588ef822 --- /dev/null +++ b/src/NzbDrone.Core/Qualities/Commands/ResetQualityDefinitionsCommand.cs @@ -0,0 +1,14 @@ +using NzbDrone.Core.Messaging.Commands; + +namespace NzbDrone.Core.Qualities.Commands +{ + public class ResetQualityDefinitionsCommand : Command + { + public bool ResetTitles { get; set; } + + public ResetQualityDefinitionsCommand(bool resetTitles = false) + { + ResetTitles = resetTitles; + } + } +} diff --git a/src/NzbDrone.Core/Qualities/QualityDefinitionService.cs b/src/NzbDrone.Core/Qualities/QualityDefinitionService.cs index 64f5157f7..968a69407 100644 --- a/src/NzbDrone.Core/Qualities/QualityDefinitionService.cs +++ b/src/NzbDrone.Core/Qualities/QualityDefinitionService.cs @@ -4,7 +4,9 @@ using System.Linq; using NLog; using NzbDrone.Common.Cache; using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Qualities.Commands; namespace NzbDrone.Core.Qualities { @@ -17,7 +19,7 @@ namespace NzbDrone.Core.Qualities QualityDefinition Get(Quality quality); } - public class QualityDefinitionService : IQualityDefinitionService, IHandle + public class QualityDefinitionService : IQualityDefinitionService, IExecute, IHandle { private readonly IQualityDefinitionRepository _repo; private readonly ICached> _cache; @@ -105,5 +107,28 @@ namespace NzbDrone.Core.Qualities InsertMissingDefinitions(); } + + public void Execute(ResetQualityDefinitionsCommand message) + { + List updateList = new List(); + + var allDefinitions = Quality.DefaultQualityDefinitions.OrderBy(d => d.Weight).ToList(); + var existingDefinitions = _repo.All().ToList(); + + foreach (var definition in allDefinitions) + { + var existing = existingDefinitions.SingleOrDefault(d => d.Quality == definition.Quality); + + existing.MinSize = definition.MinSize; + existing.MaxSize = definition.MaxSize; + existing.Title = message.ResetTitles ? definition.Title : existing.Title; + + updateList.Add(existing); + } + + _repo.UpdateMany(updateList); + + _cache.Clear(); + } } }