Automatic History cleanup setting

pull/30/head
Qstick 3 years ago
parent 9fab7d8328
commit fd27018caa

@ -7,7 +7,7 @@ import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
import { fetchCustomFilters } from 'Store/Actions/customFilterActions';
import { fetchIndexers } from 'Store/Actions/indexerActions';
import { fetchIndexerStatus } from 'Store/Actions/indexerStatusActions';
import { fetchIndexerCategories, fetchIndexerFlags, fetchLanguages, fetchUISettings } from 'Store/Actions/settingsActions';
import { fetchGeneralSettings, fetchIndexerCategories, fetchIndexerFlags, fetchLanguages, fetchUISettings } from 'Store/Actions/settingsActions';
import { fetchStatus } from 'Store/Actions/systemActions';
import { fetchTags } from 'Store/Actions/tagActions';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
@ -47,6 +47,7 @@ const selectIsPopulated = createSelector(
(state) => state.customFilters.isPopulated,
(state) => state.tags.isPopulated,
(state) => state.settings.ui.isPopulated,
(state) => state.settings.general.isPopulated,
(state) => state.settings.languages.isPopulated,
(state) => state.indexers.isPopulated,
(state) => state.indexerStatus.isPopulated,
@ -57,6 +58,7 @@ const selectIsPopulated = createSelector(
customFiltersIsPopulated,
tagsIsPopulated,
uiSettingsIsPopulated,
generalSettingsIsPopulated,
languagesIsPopulated,
indexersIsPopulated,
indexerStatusIsPopulated,
@ -68,6 +70,7 @@ const selectIsPopulated = createSelector(
customFiltersIsPopulated &&
tagsIsPopulated &&
uiSettingsIsPopulated &&
generalSettingsIsPopulated &&
languagesIsPopulated &&
indexersIsPopulated &&
indexerStatusIsPopulated &&
@ -82,6 +85,7 @@ const selectErrors = createSelector(
(state) => state.customFilters.error,
(state) => state.tags.error,
(state) => state.settings.ui.error,
(state) => state.settings.general.error,
(state) => state.settings.languages.error,
(state) => state.indexers.error,
(state) => state.indexerStatus.error,
@ -92,6 +96,7 @@ const selectErrors = createSelector(
customFiltersError,
tagsError,
uiSettingsError,
generalSettingsError,
languagesError,
indexersError,
indexerStatusError,
@ -103,6 +108,7 @@ const selectErrors = createSelector(
customFiltersError ||
tagsError ||
uiSettingsError ||
generalSettingsError ||
languagesError ||
indexersError ||
indexerStatusError ||
@ -116,6 +122,7 @@ const selectErrors = createSelector(
customFiltersError,
tagsError,
uiSettingsError,
generalSettingsError,
languagesError,
indexersError,
indexerStatusError,
@ -177,6 +184,9 @@ function createMapDispatchToProps(dispatch, props) {
dispatchFetchUISettings() {
dispatch(fetchUISettings());
},
dispatchFetchGeneralSettings() {
dispatch(fetchGeneralSettings());
},
dispatchFetchStatus() {
dispatch(fetchStatus());
},
@ -212,6 +222,7 @@ class PageConnector extends Component {
this.props.dispatchFetchIndexerCategories();
this.props.dispatchFetchIndexerFlags();
this.props.dispatchFetchUISettings();
this.props.dispatchFetchGeneralSettings();
this.props.dispatchFetchStatus();
}
}
@ -237,6 +248,7 @@ class PageConnector extends Component {
dispatchFetchIndexerCategories,
dispatchFetchIndexerFlags,
dispatchFetchUISettings,
dispatchFetchGeneralSettings,
dispatchFetchStatus,
...otherProps
} = this.props;
@ -277,6 +289,7 @@ PageConnector.propTypes = {
dispatchFetchIndexerCategories: PropTypes.func.isRequired,
dispatchFetchIndexerFlags: PropTypes.func.isRequired,
dispatchFetchUISettings: PropTypes.func.isRequired,
dispatchFetchGeneralSettings: PropTypes.func.isRequired,
dispatchFetchStatus: PropTypes.func.isRequired,
onSidebarVisibleChange: PropTypes.func.isRequired
};

@ -13,6 +13,7 @@ import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptions
import TablePager from 'Components/Table/TablePager';
import { align, icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import HistoryOptionsConnector from './HistoryOptionsConnector';
import HistoryRowConnector from './HistoryRowConnector';
class History extends Component {
@ -58,6 +59,7 @@ class History extends Component {
<TableOptionsModalWrapper
{...otherProps}
columns={columns}
optionsComponent={HistoryOptionsConnector}
>
<PageToolbarButton
label={translate('Options')}

@ -0,0 +1,80 @@
import PropTypes from 'prop-types';
import React, { Component, Fragment } from 'react';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import { inputTypes } from 'Helpers/Props';
class HistoryOptions extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
historyCleanupDays: props.historyCleanupDays
};
}
componentDidUpdate(prevProps) {
const {
historyCleanupDays
} = this.props;
if (historyCleanupDays !== prevProps.historyCleanupDays) {
this.setState({
historyCleanupDays
});
}
}
//
// Listeners
onGlobalInputChange = ({ name, value }) => {
const {
dispatchSaveGeneralSettings
} = this.props;
const setting = { [name]: value };
this.setState(setting, () => {
dispatchSaveGeneralSettings(setting);
});
}
//
// Render
render() {
const {
historyCleanupDays
} = this.state;
return (
<Fragment>
<FormGroup>
<FormLabel>History Cleanup</FormLabel>
<FormInputGroup
type={inputTypes.NUMBER}
name="historyCleanupDays"
value={historyCleanupDays}
helpText="Set to 0 to disable automatic cleanup"
helpTextWarning="History items older than the selected number of days will be cleaned up automatically"
onChange={this.onGlobalInputChange}
/>
</FormGroup>
</Fragment>
);
}
}
HistoryOptions.propTypes = {
historyCleanupDays: PropTypes.bool.isRequired,
dispatchSaveGeneralSettings: PropTypes.func.isRequired
};
export default HistoryOptions;

@ -0,0 +1,21 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveGeneralSettings } from 'Store/Actions/settingsActions';
import HistoryOptions from './HistoryOptions';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.general.item,
(generalSettings) => {
return {
...generalSettings
};
}
);
}
const mapDispatchToProps = {
dispatchSaveGeneralSettings: saveGeneralSettings
};
export default connect(createMapStateToProps, mapDispatchToProps)(HistoryOptions);

@ -7,7 +7,7 @@
.indexer {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 80px;
width: 150px;
}
.releaseGroup {

@ -77,28 +77,16 @@ namespace NzbDrone.Core.Configuration
return _repository.Get(key.ToLower()) != null;
}
public bool AutoUnmonitorPreviouslyDownloadedMovies
{
get { return GetValueBoolean("AutoUnmonitorPreviouslyDownloadedMovies"); }
set { SetValue("AutoUnmonitorPreviouslyDownloadedMovies", value); }
}
public int Retention
{
get { return GetValueInt("Retention", 0); }
set { SetValue("Retention", value); }
}
public string RecycleBin
public int HistoryCleanupDays
{
get { return GetValue("RecycleBin", string.Empty); }
set { SetValue("RecycleBin", value); }
}
public int RecycleBinCleanupDays
{
get { return GetValueInt("RecycleBinCleanupDays", 7); }
set { SetValue("RecycleBinCleanupDays", value); }
get { return GetValueInt("HistoryCleanupDays", 365); }
set { SetValue("HistoryCleanupDays", value); }
}
public int RssSyncInterval
@ -202,20 +190,6 @@ namespace NzbDrone.Core.Configuration
set { SetValue("RemoveFailedDownloads", value); }
}
public bool CreateEmptyMovieFolders
{
get { return GetValueBoolean("CreateEmptyMovieFolders", false); }
set { SetValue("CreateEmptyMovieFolders", value); }
}
public bool DeleteEmptyFolders
{
get { return GetValueBoolean("DeleteEmptyFolders", false); }
set { SetValue("DeleteEmptyFolders", value); }
}
public string DownloadClientWorkingFolders
{
get { return GetValue("DownloadClientWorkingFolders", "_UNPACK_|_FAILED_"); }
@ -236,76 +210,6 @@ namespace NzbDrone.Core.Configuration
set { SetValue("DownloadClientHistoryLimit", value); }
}
public bool SkipFreeSpaceCheckWhenImporting
{
get { return GetValueBoolean("SkipFreeSpaceCheckWhenImporting", false); }
set { SetValue("SkipFreeSpaceCheckWhenImporting", value); }
}
public int MinimumFreeSpaceWhenImporting
{
get { return GetValueInt("MinimumFreeSpaceWhenImporting", 100); }
set { SetValue("MinimumFreeSpaceWhenImporting", value); }
}
public bool CopyUsingHardlinks
{
get { return GetValueBoolean("CopyUsingHardlinks", true); }
set { SetValue("CopyUsingHardlinks", value); }
}
public bool EnableMediaInfo
{
get { return GetValueBoolean("EnableMediaInfo", true); }
set { SetValue("EnableMediaInfo", value); }
}
public bool ImportExtraFiles
{
get { return GetValueBoolean("ImportExtraFiles", false); }
set { SetValue("ImportExtraFiles", value); }
}
public string ExtraFileExtensions
{
get { return GetValue("ExtraFileExtensions", "srt"); }
set { SetValue("ExtraFileExtensions", value); }
}
public bool AutoRenameFolders
{
get { return GetValueBoolean("AutoRenameFolders", false); }
set { SetValue("AutoRenameFolders", value); }
}
public RescanAfterRefreshType RescanAfterRefresh
{
get { return GetValueEnum("RescanAfterRefresh", RescanAfterRefreshType.Always); }
set { SetValue("RescanAfterRefresh", value); }
}
public bool SetPermissionsLinux
{
get { return GetValueBoolean("SetPermissionsLinux", false); }
set { SetValue("SetPermissionsLinux", value); }
}
public string FileChmod
{
get { return GetValue("FileChmod", "0644"); }
set { SetValue("FileChmod", value); }
}
public int FirstDayOfWeek
{
get { return GetValueInt("FirstDayOfWeek", (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek); }

@ -22,24 +22,8 @@ namespace NzbDrone.Core.Configuration
bool AutoRedownloadFailed { get; set; }
bool RemoveFailedDownloads { get; set; }
//Media Management
bool AutoUnmonitorPreviouslyDownloadedMovies { get; set; }
string RecycleBin { get; set; }
int RecycleBinCleanupDays { get; set; }
bool CreateEmptyMovieFolders { get; set; }
bool DeleteEmptyFolders { get; set; }
bool SkipFreeSpaceCheckWhenImporting { get; set; }
int MinimumFreeSpaceWhenImporting { get; set; }
bool CopyUsingHardlinks { get; set; }
bool EnableMediaInfo { get; set; }
bool ImportExtraFiles { get; set; }
string ExtraFileExtensions { get; set; }
RescanAfterRefreshType RescanAfterRefresh { get; set; }
bool AutoRenameFolders { get; set; }
//Permissions (Media Management)
bool SetPermissionsLinux { get; set; }
string FileChmod { get; set; }
//History
int HistoryCleanupDays { get; set; }
//Indexers
int Retention { get; set; }

@ -0,0 +1,8 @@
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.History
{
public class CleanUpHistoryCommand : Command
{
}
}

@ -26,6 +26,6 @@ namespace NzbDrone.Core.History
IndexerQuery = 2,
IndexerRss = 3,
IndexerAuth = 4,
IndexerCapabilities = 5
IndexerInfo = 5
}
}

@ -15,6 +15,7 @@ namespace NzbDrone.Core.History
void DeleteForIndexers(List<int> indexerIds);
History MostRecentForIndexer(int indexerId);
List<History> Since(DateTime date, HistoryEventType? eventType);
void Cleanup(int days);
}
public class HistoryRepository : BasicRepository<History>, IHistoryRepository
@ -61,6 +62,11 @@ namespace NzbDrone.Core.History
Delete(c => indexerIds.Contains(c.IndexerId));
}
public void Cleanup(int days)
{
Delete(c => c.Date.AddDays(days) <= DateTime.Now);
}
public History MostRecentForIndexer(int indexerId)
{
return Query(x => x.IndexerId == indexerId)

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Events;
@ -29,14 +30,17 @@ namespace NzbDrone.Core.History
IHandle<ProviderDeletedEvent<IIndexer>>,
IHandle<IndexerQueryEvent>,
IHandle<IndexerDownloadEvent>,
IHandle<IndexerAuthEvent>
IHandle<IndexerAuthEvent>,
IExecute<CleanUpHistoryCommand>
{
private readonly IHistoryRepository _historyRepository;
private readonly IConfigService _configService;
private readonly Logger _logger;
public HistoryService(IHistoryRepository historyRepository, Logger logger)
public HistoryService(IHistoryRepository historyRepository, IConfigService configService, Logger logger)
{
_historyRepository = historyRepository;
_configService = configService;
_logger = logger;
}
@ -85,6 +89,23 @@ namespace NzbDrone.Core.History
return _historyRepository.Since(date, eventType);
}
public void Cleanup()
{
var cleanupDays = _configService.HistoryCleanupDays;
if (cleanupDays == 0)
{
_logger.Info("Automatic cleanup of History is disabled");
return;
}
_logger.Info("Removing items older than {0} days from the history", cleanupDays);
_historyRepository.Cleanup(cleanupDays);
_logger.Debug("History has been cleaned up.");
}
public void Handle(IndexerQueryEvent message)
{
var history = new History
@ -171,5 +192,10 @@ namespace NzbDrone.Core.History
{
_historyRepository.DeleteForIndexers(new List<int> { message.ProviderId });
}
public void Execute(CleanUpHistoryCommand message)
{
Cleanup();
}
}
}

@ -6,6 +6,7 @@ using NzbDrone.Core.Applications;
using NzbDrone.Core.Backup;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.HealthCheck;
using NzbDrone.Core.History;
using NzbDrone.Core.Housekeeping;
using NzbDrone.Core.IndexerVersions;
using NzbDrone.Core.Lifecycle;
@ -61,6 +62,7 @@ namespace NzbDrone.Core.Jobs
new ScheduledTask { Interval = 6 * 60, TypeName = typeof(ApplicationCheckUpdateCommand).FullName },
new ScheduledTask { Interval = 6 * 60, TypeName = typeof(CheckHealthCommand).FullName },
new ScheduledTask { Interval = 24 * 60, TypeName = typeof(HousekeepingCommand).FullName },
new ScheduledTask { Interval = 24 * 60, TypeName = typeof(CleanUpHistoryCommand).FullName },
new ScheduledTask { Interval = 24 * 60, TypeName = typeof(IndexerDefinitionUpdateCommand).FullName },
new ScheduledTask { Interval = 6 * 60, TypeName = typeof(ApplicationIndexerSyncCommand).FullName },
@ -106,40 +108,6 @@ namespace NzbDrone.Core.Jobs
return interval * 60 * 24;
}
private int GetRssSyncInterval()
{
var interval = _configService.RssSyncInterval;
if (interval > 0 && interval < 10)
{
return 10;
}
if (interval < 0)
{
return 0;
}
return interval;
}
private int GetImportListSyncInterval()
{
var interval = _configService.ImportListSyncInterval;
if (interval > 0 && interval < 10)
{
return 10;
}
if (interval < 0)
{
return 0;
}
return interval;
}
public void Handle(CommandExecutedEvent message)
{
var scheduledTask = _scheduledTaskRepository.All().SingleOrDefault(c => c.TypeName == message.Command.Body.GetType().FullName);

@ -40,6 +40,7 @@ namespace Prowlarr.Api.V1.Config
public string BackupFolder { get; set; }
public int BackupInterval { get; set; }
public int BackupRetention { get; set; }
public int HistoryCleanupDays { get; set; }
}
public static class HostConfigResourceMapper
@ -80,7 +81,8 @@ namespace Prowlarr.Api.V1.Config
CertificateValidation = configService.CertificateValidation,
BackupFolder = configService.BackupFolder,
BackupInterval = configService.BackupInterval,
BackupRetention = configService.BackupRetention
BackupRetention = configService.BackupRetention,
HistoryCleanupDays = configService.HistoryCleanupDays
};
}
}

@ -1,27 +0,0 @@
using FluentValidation;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
using Prowlarr.Http;
namespace Prowlarr.Api.V1.Config
{
[V1ApiController("config/mediamanagement")]
public class MediaManagementConfigController : ConfigController<MediaManagementConfigResource>
{
public MediaManagementConfigController(IConfigService configService, PathExistsValidator pathExistsValidator, FileChmodValidator fileChmodValidator)
: base(configService)
{
SharedValidator.RuleFor(c => c.RecycleBinCleanupDays).GreaterThanOrEqualTo(0);
SharedValidator.RuleFor(c => c.FileChmod).SetValidator(fileChmodValidator).When(c => !string.IsNullOrEmpty(c.FileChmod) && (OsInfo.IsLinux || OsInfo.IsOsx));
SharedValidator.RuleFor(c => c.RecycleBin).IsValidPath().SetValidator(pathExistsValidator).When(c => !string.IsNullOrWhiteSpace(c.RecycleBin));
SharedValidator.RuleFor(c => c.MinimumFreeSpaceWhenImporting).GreaterThanOrEqualTo(100);
}
protected override MediaManagementConfigResource ToResource(IConfigService model)
{
return MediaManagementConfigResourceMapper.ToResource(model);
}
}
}

@ -1,54 +0,0 @@
using NzbDrone.Core.Configuration;
using Prowlarr.Http.REST;
namespace Prowlarr.Api.V1.Config
{
public class MediaManagementConfigResource : RestResource
{
public bool AutoUnmonitorPreviouslyDownloadedMovies { get; set; }
public string RecycleBin { get; set; }
public int RecycleBinCleanupDays { get; set; }
public bool CreateEmptyMovieFolders { get; set; }
public bool DeleteEmptyFolders { get; set; }
public RescanAfterRefreshType RescanAfterRefresh { get; set; }
public bool AutoRenameFolders { get; set; }
public bool PathsDefaultStatic { get; set; }
public bool SetPermissionsLinux { get; set; }
public string FileChmod { get; set; }
public bool SkipFreeSpaceCheckWhenImporting { get; set; }
public int MinimumFreeSpaceWhenImporting { get; set; }
public bool CopyUsingHardlinks { get; set; }
public bool ImportExtraFiles { get; set; }
public string ExtraFileExtensions { get; set; }
public bool EnableMediaInfo { get; set; }
}
public static class MediaManagementConfigResourceMapper
{
public static MediaManagementConfigResource ToResource(IConfigService model)
{
return new MediaManagementConfigResource
{
AutoUnmonitorPreviouslyDownloadedMovies = model.AutoUnmonitorPreviouslyDownloadedMovies,
RecycleBin = model.RecycleBin,
RecycleBinCleanupDays = model.RecycleBinCleanupDays,
CreateEmptyMovieFolders = model.CreateEmptyMovieFolders,
DeleteEmptyFolders = model.DeleteEmptyFolders,
RescanAfterRefresh = model.RescanAfterRefresh,
AutoRenameFolders = model.AutoRenameFolders,
SetPermissionsLinux = model.SetPermissionsLinux,
FileChmod = model.FileChmod,
SkipFreeSpaceCheckWhenImporting = model.SkipFreeSpaceCheckWhenImporting,
MinimumFreeSpaceWhenImporting = model.MinimumFreeSpaceWhenImporting,
CopyUsingHardlinks = model.CopyUsingHardlinks,
ImportExtraFiles = model.ImportExtraFiles,
ExtraFileExtensions = model.ExtraFileExtensions,
EnableMediaInfo = model.EnableMediaInfo
};
}
}
}
Loading…
Cancel
Save