New: Rework List sync interval logic

* New: Rework List sync interval logic

Fixes #5011
This commit is contained in:
Qstick 2022-12-07 21:09:42 -06:00 committed by GitHub
parent 7675b4bc3b
commit c522cd120d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 121 additions and 15 deletions

View File

@ -7,3 +7,9 @@
.labelIcon { .labelIcon {
margin-left: 8px; margin-left: 8px;
} }
.message {
composes: alert from '~Components/Alert.css';
margin-bottom: 30px;
}

View File

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import SeriesMonitoringOptionsPopoverContent from 'AddSeries/SeriesMonitoringOptionsPopoverContent'; import SeriesMonitoringOptionsPopoverContent from 'AddSeries/SeriesMonitoringOptionsPopoverContent';
import SeriesTypePopoverContent from 'AddSeries/SeriesTypePopoverContent'; import SeriesTypePopoverContent from 'AddSeries/SeriesTypePopoverContent';
import Alert from 'Components/Alert';
import Form from 'Components/Form/Form'; import Form from 'Components/Form/Form';
import FormGroup from 'Components/Form/FormGroup'; import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup'; import FormInputGroup from 'Components/Form/FormInputGroup';
@ -17,6 +18,7 @@ import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader'; import ModalHeader from 'Components/Modal/ModalHeader';
import Popover from 'Components/Tooltip/Popover'; import Popover from 'Components/Tooltip/Popover';
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props'; import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
import styles from './EditImportListModalContent.css'; import styles from './EditImportListModalContent.css';
function EditImportListModalContent(props) { function EditImportListModalContent(props) {
@ -42,6 +44,7 @@ function EditImportListModalContent(props) {
id, id,
name, name,
enableAutomaticAdd, enableAutomaticAdd,
minRefreshInterval,
shouldMonitor, shouldMonitor,
rootFolderPath, rootFolderPath,
qualityProfileId, qualityProfileId,
@ -73,6 +76,14 @@ function EditImportListModalContent(props) {
{ {
!isFetching && !error ? !isFetching && !error ?
<Form {...otherProps}> <Form {...otherProps}>
<Alert
kind={kinds.INFO}
className={styles.message}
>
{`List will refresh every ${formatShortTimeSpan(minRefreshInterval.value)}`}
</Alert>
<FormGroup> <FormGroup>
<FormLabel>Name</FormLabel> <FormLabel>Name</FormLabel>

View File

@ -4,6 +4,7 @@ import Card from 'Components/Card';
import Label from 'Components/Label'; import Label from 'Components/Label';
import ConfirmModal from 'Components/Modal/ConfirmModal'; import ConfirmModal from 'Components/Modal/ConfirmModal';
import { kinds } from 'Helpers/Props'; import { kinds } from 'Helpers/Props';
import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan';
import EditImportListModalConnector from './EditImportListModalConnector'; import EditImportListModalConnector from './EditImportListModalConnector';
import styles from './ImportList.css'; import styles from './ImportList.css';
@ -54,7 +55,8 @@ class ImportList extends Component {
const { const {
id, id,
name, name,
enableAutomaticAdd enableAutomaticAdd,
minRefreshInterval
} = this.props; } = this.props;
return ( return (
@ -77,6 +79,12 @@ class ImportList extends Component {
</div> </div>
<div className={styles.enabled}>
<Label kind={kinds.INFO} title='List Refresh Interval'>
{`Refresh: ${formatShortTimeSpan(minRefreshInterval)}`}
</Label>
</div>
<EditImportListModalConnector <EditImportListModalConnector
id={id} id={id}
isOpen={this.state.isEditImportListModalOpen} isOpen={this.state.isEditImportListModalOpen}
@ -102,6 +110,7 @@ ImportList.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
enableAutomaticAdd: PropTypes.bool.isRequired, enableAutomaticAdd: PropTypes.bool.isRequired,
minRefreshInterval: PropTypes.string.isRequired,
onConfirmDeleteImportList: PropTypes.func.isRequired onConfirmDeleteImportList: PropTypes.func.isRequired
}; };

View File

@ -0,0 +1,25 @@
import moment from 'moment';
function formatShortTimeSpan(timeSpan) {
if (!timeSpan) {
return '';
}
const duration = moment.duration(timeSpan);
const hours = Math.floor(duration.asHours());
const minutes = Math.floor(duration.asMinutes());
const seconds = Math.floor(duration.asSeconds());
if (hours > 0) {
return `${hours} hour(s)`;
}
if (minutes > 0) {
return `${minutes} minute(s)`;
}
return `${seconds} second(s)`;
}
export default formatShortTimeSpan;

View File

@ -0,0 +1,17 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using NzbDrone.Core.Languages;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(178)]
public class list_sync_time : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Delete.Column("LastSyncListInfo").FromTable("ImportListStatus");
Alter.Table("ImportListStatus").AddColumn("LastInfoSync").AsDateTime().Nullable();
}
}
}

View File

@ -79,6 +79,7 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<ImportListDefinition>("ImportLists").RegisterModel() Mapper.Entity<ImportListDefinition>("ImportLists").RegisterModel()
.Ignore(x => x.ImplementationName) .Ignore(x => x.ImplementationName)
.Ignore(i => i.ListType) .Ignore(i => i.ListType)
.Ignore(i => i.MinRefreshInterval)
.Ignore(i => i.Enable); .Ignore(i => i.Enable);
Mapper.Entity<NotificationDefinition>("Notifications").RegisterModel() Mapper.Entity<NotificationDefinition>("Notifications").RegisterModel()

View File

@ -1,9 +1,7 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
@ -16,6 +14,8 @@ namespace NzbDrone.Core.ImportLists.Custom
private readonly ICustomImportProxy _customProxy; private readonly ICustomImportProxy _customProxy;
public override string Name => "Custom List"; public override string Name => "Custom List";
public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(6);
public override ImportListType ListType => ImportListType.Advanced; public override ImportListType ListType => ImportListType.Advanced;
public CustomImport(ICustomImportProxy customProxy, public CustomImport(ICustomImportProxy customProxy,

View File

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using NLog; using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.TPL; using NzbDrone.Common.TPL;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -18,11 +17,13 @@ namespace NzbDrone.Core.ImportLists
public class FetchAndParseImportListService : IFetchAndParseImportList public class FetchAndParseImportListService : IFetchAndParseImportList
{ {
private readonly IImportListFactory _importListFactory; private readonly IImportListFactory _importListFactory;
private readonly IImportListStatusService _importListStatusService;
private readonly Logger _logger; private readonly Logger _logger;
public FetchAndParseImportListService(IImportListFactory importListFactory, Logger logger) public FetchAndParseImportListService(IImportListFactory importListFactory, IImportListStatusService importListStatusService, Logger logger)
{ {
_importListFactory = importListFactory; _importListFactory = importListFactory;
_importListStatusService = importListStatusService;
_logger = logger; _logger = logger;
} }
@ -46,6 +47,13 @@ namespace NzbDrone.Core.ImportLists
foreach (var importList in importLists) foreach (var importList in importLists)
{ {
var importListLocal = importList; var importListLocal = importList;
var importListStatus = _importListStatusService.GetLastSyncListInfo(importListLocal.Definition.Id);
if (DateTime.UtcNow < (importListStatus + importListLocal.MinRefreshInterval))
{
_logger.Trace("Skipping refresh of Import List {0} due to minimum refresh inverval", importListLocal.Definition.Name);
continue;
}
var task = taskFactory.StartNew(() => var task = taskFactory.StartNew(() =>
{ {
@ -59,6 +67,8 @@ namespace NzbDrone.Core.ImportLists
result.AddRange(importListReports); result.AddRange(importListReports);
} }
_importListStatusService.UpdateListSyncStatus(importList.Definition.Id);
} }
catch (Exception e) catch (Exception e)
{ {
@ -90,6 +100,14 @@ namespace NzbDrone.Core.ImportLists
return result; return result;
} }
var importListStatus = _importListStatusService.GetLastSyncListInfo(importList.Definition.Id);
if (DateTime.UtcNow < (importListStatus + importList.MinRefreshInterval))
{
_logger.Trace("Skipping refresh of Import List {0} due to minimum refresh inverval", importList.Definition.Name);
return result;
}
var taskList = new List<Task>(); var taskList = new List<Task>();
var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None); var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
@ -7,6 +8,7 @@ namespace NzbDrone.Core.ImportLists
public interface IImportList : IProvider public interface IImportList : IProvider
{ {
ImportListType ListType { get; } ImportListType ListType { get; }
TimeSpan MinRefreshInterval { get; }
IList<ImportListItemInfo> Fetch(); IList<ImportListItemInfo> Fetch();
} }
} }

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using NLog; using NLog;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
@ -12,6 +13,7 @@ namespace NzbDrone.Core.ImportLists.Imdb
public override string Name => "IMDb Lists"; public override string Name => "IMDb Lists";
public override ImportListType ListType => ImportListType.Other; public override ImportListType ListType => ImportListType.Other;
public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(12);
public ImdbListImport(IHttpClient httpClient, public ImdbListImport(IHttpClient httpClient,
IImportListStatusService importListStatusService, IImportListStatusService importListStatusService,

View File

@ -5,6 +5,7 @@ using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
@ -23,6 +24,8 @@ namespace NzbDrone.Core.ImportLists
public abstract ImportListType ListType { get; } public abstract ImportListType ListType { get; }
public abstract TimeSpan MinRefreshInterval { get; }
public ImportListBase(IImportListStatusService importListStatusService, IConfigService configService, IParsingService parsingService, Logger logger) public ImportListBase(IImportListStatusService importListStatusService, IConfigService configService, IParsingService parsingService, Logger logger)
{ {
_importListStatusService = importListStatusService; _importListStatusService = importListStatusService;

View File

@ -1,3 +1,4 @@
using System;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
@ -16,5 +17,6 @@ namespace NzbDrone.Core.ImportLists
public ImportListStatus Status { get; set; } public ImportListStatus Status { get; set; }
public ImportListType ListType { get; set; } public ImportListType ListType { get; set; }
public TimeSpan MinRefreshInterval { get; set; }
} }
} }

View File

@ -41,6 +41,7 @@ namespace NzbDrone.Core.ImportLists
base.SetProviderCharacteristics(provider, definition); base.SetProviderCharacteristics(provider, definition);
definition.ListType = provider.ListType; definition.ListType = provider.ListType;
definition.MinRefreshInterval = provider.MinRefreshInterval;
} }
public List<IImportList> AutomaticAddEnabled(bool filterBlockedImportLists = true) public List<IImportList> AutomaticAddEnabled(bool filterBlockedImportLists = true)

View File

@ -1,3 +1,4 @@
using System;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider.Status; using NzbDrone.Core.ThingiProvider.Status;
@ -5,6 +6,6 @@ namespace NzbDrone.Core.ImportLists
{ {
public class ImportListStatus : ProviderStatusBase public class ImportListStatus : ProviderStatusBase
{ {
public ImportListItemInfo LastSyncListInfo { get; set; } public DateTime LastInfoSync { get; set; }
} }
} }

View File

@ -1,16 +1,16 @@
using System;
using NLog; using NLog;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider.Status; using NzbDrone.Core.ThingiProvider.Status;
namespace NzbDrone.Core.ImportLists namespace NzbDrone.Core.ImportLists
{ {
public interface IImportListStatusService : IProviderStatusServiceBase<ImportListStatus> public interface IImportListStatusService : IProviderStatusServiceBase<ImportListStatus>
{ {
ImportListItemInfo GetLastSyncListInfo(int importListId); DateTime GetLastSyncListInfo(int importListId);
void UpdateListSyncStatus(int importListId, ImportListItemInfo listItemInfo); void UpdateListSyncStatus(int importListId);
} }
public class ImportListStatusService : ProviderStatusServiceBase<IImportList, ImportListStatus>, IImportListStatusService public class ImportListStatusService : ProviderStatusServiceBase<IImportList, ImportListStatus>, IImportListStatusService
@ -20,18 +20,18 @@ namespace NzbDrone.Core.ImportLists
{ {
} }
public ImportListItemInfo GetLastSyncListInfo(int importListId) public DateTime GetLastSyncListInfo(int importListId)
{ {
return GetProviderStatus(importListId).LastSyncListInfo; return GetProviderStatus(importListId).LastInfoSync;
} }
public void UpdateListSyncStatus(int importListId, ImportListItemInfo listItemInfo) public void UpdateListSyncStatus(int importListId)
{ {
lock (_syncRoot) lock (_syncRoot)
{ {
var status = GetProviderStatus(importListId); var status = GetProviderStatus(importListId);
status.LastSyncListInfo = listItemInfo; status.LastInfoSync = DateTime.UtcNow;
_providerStatusRepository.Upsert(status); _providerStatusRepository.Upsert(status);
} }

View File

@ -15,7 +15,9 @@ namespace NzbDrone.Core.ImportLists.Plex
public class PlexImport : HttpImportListBase<PlexListSettings> public class PlexImport : HttpImportListBase<PlexListSettings>
{ {
public readonly IPlexTvService _plexTvService; public readonly IPlexTvService _plexTvService;
public override ImportListType ListType => ImportListType.Plex; public override ImportListType ListType => ImportListType.Plex;
public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(6);
public PlexImport(IPlexTvService plexTvService, public PlexImport(IPlexTvService plexTvService,
IHttpClient httpClient, IHttpClient httpClient,

View File

@ -17,6 +17,7 @@ namespace NzbDrone.Core.ImportLists.Sonarr
public override string Name => "Sonarr"; public override string Name => "Sonarr";
public override ImportListType ListType => ImportListType.Program; public override ImportListType ListType => ImportListType.Program;
public override TimeSpan MinRefreshInterval => TimeSpan.FromMinutes(5);
public SonarrImport(ISonarrV3Proxy sonarrV3Proxy, public SonarrImport(ISonarrV3Proxy sonarrV3Proxy,
IImportListStatusService importListStatusService, IImportListStatusService importListStatusService,

View File

@ -14,6 +14,7 @@ namespace NzbDrone.Core.ImportLists.Trakt
where TSettings : TraktSettingsBase<TSettings>, new() where TSettings : TraktSettingsBase<TSettings>, new()
{ {
public override ImportListType ListType => ImportListType.Trakt; public override ImportListType ListType => ImportListType.Trakt;
public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(12);
public const string OAuthUrl = "https://trakt.tv/oauth/authorize"; public const string OAuthUrl = "https://trakt.tv/oauth/authorize";
public const string RedirectUri = "https://auth.servarr.com/v1/trakt_sonarr/auth"; public const string RedirectUri = "https://auth.servarr.com/v1/trakt_sonarr/auth";

View File

@ -117,7 +117,7 @@ namespace NzbDrone.Core.Jobs
new ScheduledTask new ScheduledTask
{ {
Interval = 24 * 60, Interval = 5,
TypeName = typeof(ImportListSyncCommand).FullName TypeName = typeof(ImportListSyncCommand).FullName
}, },

View File

@ -1,3 +1,4 @@
using System;
using NzbDrone.Core.ImportLists; using NzbDrone.Core.ImportLists;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
@ -13,6 +14,7 @@ namespace Sonarr.Api.V3.ImportLists
public bool SeasonFolder { get; set; } public bool SeasonFolder { get; set; }
public ImportListType ListType { get; set; } public ImportListType ListType { get; set; }
public int ListOrder { get; set; } public int ListOrder { get; set; }
public TimeSpan MinRefreshInterval { get; set; }
} }
public class ImportListResourceMapper : ProviderResourceMapper<ImportListResource, ImportListDefinition> public class ImportListResourceMapper : ProviderResourceMapper<ImportListResource, ImportListDefinition>
@ -34,6 +36,7 @@ namespace Sonarr.Api.V3.ImportLists
resource.SeasonFolder = definition.SeasonFolder; resource.SeasonFolder = definition.SeasonFolder;
resource.ListType = definition.ListType; resource.ListType = definition.ListType;
resource.ListOrder = (int)definition.ListType; resource.ListOrder = (int)definition.ListType;
resource.MinRefreshInterval = definition.MinRefreshInterval;
return resource; return resource;
} }
@ -54,6 +57,7 @@ namespace Sonarr.Api.V3.ImportLists
definition.SeriesType = resource.SeriesType; definition.SeriesType = resource.SeriesType;
definition.SeasonFolder = resource.SeasonFolder; definition.SeasonFolder = resource.SeasonFolder;
definition.ListType = resource.ListType; definition.ListType = resource.ListType;
definition.MinRefreshInterval = resource.MinRefreshInterval;
return definition; return definition;
} }