New: Reset Quality Definitions to default

(cherry picked from commit d5fff15f32fdb49768dcadd94c760678e650c884)
pull/7767/head
Qstick 2 years ago
parent 850bfdcf82
commit ae8245c3c5

@ -22,7 +22,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';
@ -143,7 +143,7 @@ function AppRoutes(props) {
<Route
path="/settings/quality"
component={Quality}
component={QualityConnector}
/>
<Route

@ -15,6 +15,7 @@ export const REFRESH_MOVIE = 'RefreshMovie';
export const RENAME_FILES = 'RenameFiles';
export const RENAME_MOVIE = 'RenameMovie';
export const RESET_API_KEY = 'ResetApiKey';
export const RESET_QUALITY_DEFINITIONS = 'ResetQualityDefinitions';
export const RSS_SYNC = 'RssSync';
export const MOVIE_SEARCH = 'MoviesSearch';
export const IMPORT_LIST_SYNC = 'ImportListSync';

@ -9,6 +9,7 @@ import { fetchCommands, finishCommand, updateCommand } from 'Store/Actions/comma
import { fetchMovies } from 'Store/Actions/movieActions';
import { fetchQueue, fetchQueueDetails } from 'Store/Actions/queueActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { fetchQualityDefinitions } from 'Store/Actions/settingsActions';
import { fetchHealth } from 'Store/Actions/systemActions';
import { fetchTagDetails, fetchTags } from 'Store/Actions/tagActions';
import { repopulatePage } from 'Utilities/pagePopulator';
@ -46,6 +47,7 @@ const mapDispatchToProps = {
dispatchUpdateItem: updateItem,
dispatchRemoveItem: removeItem,
dispatchFetchHealth: fetchHealth,
dispatchFetchQualityDefinitions: fetchQualityDefinitions,
dispatchFetchQueue: fetchQueue,
dispatchFetchQueueDetails: fetchQueueDetails,
dispatchFetchRootFolders: fetchRootFolders,
@ -216,6 +218,10 @@ class SignalRConnector extends Component {
}
};
handleQualitydefinition = () => {
this.props.dispatchFetchQualityDefinitions();
};
handleQueue = () => {
if (this.props.isQueuePopulated) {
this.props.dispatchFetchQueue();
@ -335,6 +341,7 @@ SignalRConnector.propTypes = {
dispatchUpdateItem: PropTypes.func.isRequired,
dispatchRemoveItem: PropTypes.func.isRequired,
dispatchFetchHealth: PropTypes.func.isRequired,
dispatchFetchQualityDefinitions: PropTypes.func.isRequired,
dispatchFetchQueue: PropTypes.func.isRequired,
dispatchFetchQueueDetails: PropTypes.func.isRequired,
dispatchFetchRootFolders: PropTypes.func.isRequired,

@ -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 {
<SettingsToolbarConnector
isSaving={isSaving}
hasPendingChanges={hasPendingChanges}
additionalButtons={
<Fragment>
<PageToolbarSeparator />
<PageToolbarButton
label={translate('ResetDefinitions')}
iconName={icons.REFRESH}
isSpinning={isResettingQualityDefinitions}
onPress={this.onResetQualityDefinitionsPress}
/>
</Fragment>
}
onSavePress={this.onSavePress}
/>
@ -61,9 +88,18 @@ class Quality extends Component {
onChildStateChange={this.onChildStateChange}
/>
</PageContentBody>
<ResetQualityDefinitionsModal
isOpen={this.state.isConfirmQualityDefinitionResetModalOpen}
onModalClose={this.onCloseResetQualityDefinitionsModal}
/>
</PageContent>
);
}
}
Quality.propTypes = {
isResettingQualityDefinitions: PropTypes.bool.isRequired
};
export default Quality;

@ -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 (
<Quality
{...this.props}
/>
);
}
}
QualityConnector.propTypes = {
isResettingQualityDefinitions: PropTypes.bool.isRequired
};
export default connect(createMapStateToProps)(QualityConnector);

@ -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 (
<Modal
isOpen={isOpen}
size={sizes.MEDIUM}
onModalClose={onModalClose}
>
<ResetQualityDefinitionsModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
ResetQualityDefinitionsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default ResetQualityDefinitionsModal;

@ -0,0 +1,104 @@
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 translate from 'Utilities/String/translate';
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 (
<ModalContent
onModalClose={onModalClose}
>
<ModalHeader>
{translate('ResetQualityDefinitions')}
</ModalHeader>
<ModalBody>
<div className={styles.messageContainer}>
{translate('AreYouSureYouWantToResetQualityDefinitions')}
</div>
<FormGroup>
<FormLabel>{translate('ResetTitles')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="resetDefinitionTitles"
value={resetDefinitionTitles}
helpText={translate('ResetTitlesHelpText')}
onChange={this.onResetDefinitionTitlesChange}
/>
</FormGroup>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>
{translate('Cancel')}
</Button>
<Button
kind={kinds.DANGER}
onPress={this.onResetQualityDefinitionsConfirmed}
isDisabled={isResettingQualityDefinitions}
>
{translate('Reset')}
</Button>
</ModalFooter>
</ModalContent>
);
}
}
ResetQualityDefinitionsModalContent.propTypes = {
onResetQualityDefinitions: PropTypes.func.isRequired,
isResettingQualityDefinitions: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default ResetQualityDefinitionsModalContent;

@ -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 (
<ResetQualityDefinitionsModalContent
{...this.props}
onResetQualityDefinitions={this.onResetQualityDefinitions}
/>
);
}
}
ResetQualityDefinitionsModalContentConnector.propTypes = {
onModalClose: PropTypes.func.isRequired,
isResettingQualityDefinitions: PropTypes.bool.isRequired,
executeCommand: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(ResetQualityDefinitionsModalContentConnector);

@ -66,6 +66,7 @@
"AreYouSureYouWantToRemoveSelectedItemFromQueue": "Are you sure you want to remove 1 item from the queue?",
"AreYouSureYouWantToRemoveSelectedItemsFromQueue": "Are you sure you want to remove {0} items from the queue?",
"AreYouSureYouWantToRemoveTheSelectedItemsFromBlocklist": "Are you sure you want to remove the selected items from the blocklist?",
"AreYouSureYouWantToResetQualityDefinitions": "Are you sure you want to reset quality definitions?",
"AreYouSureYouWantToResetYourAPIKey": "Are you sure you want to reset your API Key?",
"AsAllDayHelpText": "Events will appear as all-day events in your calendar",
"AudioInfo": "Audio Info",
@ -846,6 +847,10 @@
"RescanMovieFolderAfterRefresh": "Rescan Movie Folder after Refresh",
"Reset": "Reset",
"ResetAPIKey": "Reset API Key",
"ResetDefinitions": "Reset Definitions",
"ResetQualityDefinitions": "Reset Quality Definitions",
"ResetTitles": "Reset Titles",
"ResetTitlesHelpText": "Reset definition titles as well as values",
"Restart": "Restart",
"RestartNow": "Restart Now",
"RestartRadarr": "Restart Radarr",

@ -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;
}
}
}

@ -1,10 +1,12 @@
using System;
using System;
using System.Collections.Generic;
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<ApplicationStartedEvent>
public class QualityDefinitionService : IQualityDefinitionService, IExecute<ResetQualityDefinitionsCommand>, IHandle<ApplicationStartedEvent>
{
private readonly IQualityDefinitionRepository _repo;
private readonly ICached<Dictionary<Quality, QualityDefinition>> _cache;
@ -105,5 +107,28 @@ namespace NzbDrone.Core.Qualities
InsertMissingDefinitions();
}
public void Execute(ResetQualityDefinitionsCommand message)
{
List<QualityDefinition> updateList = new List<QualityDefinition>();
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();
}
}
}

@ -1,7 +1,10 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Qualities;
using NzbDrone.SignalR;
using Radarr.Http;
using Radarr.Http.REST;
using Radarr.Http.REST.Attributes;
@ -9,11 +12,12 @@ using Radarr.Http.REST.Attributes;
namespace Radarr.Api.V3.Qualities
{
[V3ApiController]
public class QualityDefinitionController : RestController<QualityDefinitionResource>
public class QualityDefinitionController : RestControllerWithSignalR<QualityDefinitionResource, QualityDefinition>, IHandle<CommandExecutedEvent>
{
private readonly IQualityDefinitionService _qualityDefinitionService;
public QualityDefinitionController(IQualityDefinitionService qualityDefinitionService)
public QualityDefinitionController(IQualityDefinitionService qualityDefinitionService, IBroadcastSignalRMessage signalRBroadcaster)
: base(signalRBroadcaster)
{
_qualityDefinitionService = qualityDefinitionService;
}
@ -40,7 +44,7 @@ namespace Radarr.Api.V3.Qualities
[HttpPut("update")]
public object UpdateMany([FromBody] List<QualityDefinitionResource> resource)
{
//Read from request
// Read from request
var qualityDefinitions = resource
.ToModel()
.ToList();
@ -50,5 +54,14 @@ namespace Radarr.Api.V3.Qualities
return Accepted(_qualityDefinitionService.All()
.ToResource());
}
[NonAction]
public void Handle(CommandExecutedEvent message)
{
if (message.Command.Name == "ResetQualityDefinitions")
{
BroadcastResourceChange(ModelAction.Sync);
}
}
}
}

Loading…
Cancel
Save