diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.css b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.css index 23e22b6dc..ced336990 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.css +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.css @@ -13,3 +13,9 @@ .labelIcon { margin-left: 8px; } + +.message { + composes: alert from '~Components/Alert.css'; + + margin-bottom: 30px; +} diff --git a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js index 3398a6d5a..3f7657a48 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js +++ b/frontend/src/Settings/ImportLists/ImportLists/EditImportListModalContent.js @@ -20,6 +20,7 @@ import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import Popover from 'Components/Tooltip/Popover'; import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props'; +import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan'; import translate from 'Utilities/String/translate'; import styles from './EditImportListModalContent.css'; @@ -74,6 +75,7 @@ function EditImportListModalContent(props) { id, name, enableAutomaticAdd, + minRefreshInterval, shouldMonitor, shouldMonitorExisting, shouldSearch, @@ -118,6 +120,13 @@ function EditImportListModalContent(props) { } + + {translate('ListWillRefreshEveryInterp', [formatShortTimeSpan(minRefreshInterval.value)])} + +
diff --git a/frontend/src/Settings/ImportLists/ImportLists/ImportList.js b/frontend/src/Settings/ImportLists/ImportLists/ImportList.js index 41dff3fa5..639d84f58 100644 --- a/frontend/src/Settings/ImportLists/ImportLists/ImportList.js +++ b/frontend/src/Settings/ImportLists/ImportLists/ImportList.js @@ -4,6 +4,7 @@ import Card from 'Components/Card'; import Label from 'Components/Label'; import ConfirmModal from 'Components/Modal/ConfirmModal'; import { kinds } from 'Helpers/Props'; +import formatShortTimeSpan from 'Utilities/Date/formatShortTimeSpan'; import translate from 'Utilities/String/translate'; import EditImportListModalConnector from './EditImportListModalConnector'; import styles from './ImportList.css'; @@ -56,6 +57,7 @@ class ImportList extends Component { id, name, enableAutomaticAdd, + minRefreshInterval, shouldSearch } = this.props; @@ -85,6 +87,15 @@ class ImportList extends Component { } +
+ +
+ 0) { + return `${hours} hour(s)`; + } + + if (minutes > 0) { + return `${minutes} minute(s)`; + } + + return `${seconds} second(s)`; +} + +export default formatShortTimeSpan; diff --git a/src/NzbDrone.Core/Datastore/Migration/029_list_sync_time.cs b/src/NzbDrone.Core/Datastore/Migration/029_list_sync_time.cs new file mode 100644 index 000000000..75d3084f6 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/029_list_sync_time.cs @@ -0,0 +1,16 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(029)] + public class list_sync_time : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Delete.Column("LastSyncListInfo").FromTable("ImportListStatus"); + + Alter.Table("ImportListStatus").AddColumn("LastInfoSync").AsDateTime().Nullable(); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index e43791951..d7e42341a 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -75,8 +75,9 @@ namespace NzbDrone.Core.Datastore Mapper.Entity("ImportLists").RegisterModel() .Ignore(x => x.ImplementationName) - .Ignore(i => i.Enable) - .Ignore(i => i.ListType); + .Ignore(i => i.ListType) + .Ignore(i => i.MinRefreshInterval) + .Ignore(i => i.Enable); Mapper.Entity("Notifications").RegisterModel() .Ignore(x => x.ImplementationName) diff --git a/src/NzbDrone.Core/ImportLists/FetchAndParseImportListService.cs b/src/NzbDrone.Core/ImportLists/FetchAndParseImportListService.cs index 2e9e62c69..19e353976 100644 --- a/src/NzbDrone.Core/ImportLists/FetchAndParseImportListService.cs +++ b/src/NzbDrone.Core/ImportLists/FetchAndParseImportListService.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using NLog; -using NzbDrone.Common.Extensions; using NzbDrone.Common.TPL; using NzbDrone.Core.Parser.Model; @@ -18,11 +17,13 @@ namespace NzbDrone.Core.ImportLists public class FetchAndParseImportListService : IFetchAndParseImportList { private readonly IImportListFactory _importListFactory; + private readonly IImportListStatusService _importListStatusService; private readonly Logger _logger; - public FetchAndParseImportListService(IImportListFactory importListFactory, Logger logger) + public FetchAndParseImportListService(IImportListFactory importListFactory, IImportListStatusService importListStatusService, Logger logger) { _importListFactory = importListFactory; + _importListStatusService = importListStatusService; _logger = logger; } @@ -46,6 +47,13 @@ namespace NzbDrone.Core.ImportLists foreach (var importList in importLists) { 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(() => { @@ -59,6 +67,8 @@ namespace NzbDrone.Core.ImportLists result.AddRange(importListReports); } + + _importListStatusService.UpdateListSyncStatus(importList.Definition.Id); } catch (Exception e) { @@ -90,6 +100,14 @@ namespace NzbDrone.Core.ImportLists 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(); var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None); diff --git a/src/NzbDrone.Core/ImportLists/Goodreads/Bookshelf/GoodreadsBookshelf.cs b/src/NzbDrone.Core/ImportLists/Goodreads/Bookshelf/GoodreadsBookshelf.cs index ffc5d1f2a..3ab0ce6e3 100644 --- a/src/NzbDrone.Core/ImportLists/Goodreads/Bookshelf/GoodreadsBookshelf.cs +++ b/src/NzbDrone.Core/ImportLists/Goodreads/Bookshelf/GoodreadsBookshelf.cs @@ -24,6 +24,7 @@ namespace NzbDrone.Core.ImportLists.Goodreads } public override string Name => "Goodreads Bookshelves"; + public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(12); public override IList Fetch() { diff --git a/src/NzbDrone.Core/ImportLists/Goodreads/Lists/GoodreadsListImportList.cs b/src/NzbDrone.Core/ImportLists/Goodreads/Lists/GoodreadsListImportList.cs index 3ba3cb27e..02e4a6cf4 100644 --- a/src/NzbDrone.Core/ImportLists/Goodreads/Lists/GoodreadsListImportList.cs +++ b/src/NzbDrone.Core/ImportLists/Goodreads/Lists/GoodreadsListImportList.cs @@ -19,6 +19,7 @@ namespace NzbDrone.Core.ImportLists.Goodreads public override string Name => "Goodreads List"; public override ImportListType ListType => ImportListType.Goodreads; + public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(12); public GoodreadsListImportList(IProvideListInfo listInfo, IImportListStatusService importListStatusService, diff --git a/src/NzbDrone.Core/ImportLists/Goodreads/OwnedBooks/GoodreadsOwnedBooks.cs b/src/NzbDrone.Core/ImportLists/Goodreads/OwnedBooks/GoodreadsOwnedBooks.cs index 9770be666..65c2f9d9a 100644 --- a/src/NzbDrone.Core/ImportLists/Goodreads/OwnedBooks/GoodreadsOwnedBooks.cs +++ b/src/NzbDrone.Core/ImportLists/Goodreads/OwnedBooks/GoodreadsOwnedBooks.cs @@ -27,6 +27,7 @@ namespace NzbDrone.Core.ImportLists.Goodreads } public override string Name => "Goodreads Owned Books"; + public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(12); public override IList Fetch() { diff --git a/src/NzbDrone.Core/ImportLists/Goodreads/Series/GoodreadsSeriesImportList.cs b/src/NzbDrone.Core/ImportLists/Goodreads/Series/GoodreadsSeriesImportList.cs index 7352a10ba..5276e9873 100644 --- a/src/NzbDrone.Core/ImportLists/Goodreads/Series/GoodreadsSeriesImportList.cs +++ b/src/NzbDrone.Core/ImportLists/Goodreads/Series/GoodreadsSeriesImportList.cs @@ -18,6 +18,7 @@ namespace NzbDrone.Core.ImportLists.Goodreads public override string Name => "Goodreads Series"; public override ImportListType ListType => ImportListType.Goodreads; + public override TimeSpan MinRefreshInterval => TimeSpan.FromHours(12); public GoodreadsSeriesImportList(IProvideSeriesInfo seriesInfo, IImportListStatusService importListStatusService, diff --git a/src/NzbDrone.Core/ImportLists/IImportList.cs b/src/NzbDrone.Core/ImportLists/IImportList.cs index f9fde642b..4970c5261 100644 --- a/src/NzbDrone.Core/ImportLists/IImportList.cs +++ b/src/NzbDrone.Core/ImportLists/IImportList.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.ThingiProvider; @@ -7,6 +8,7 @@ namespace NzbDrone.Core.ImportLists public interface IImportList : IProvider { ImportListType ListType { get; } + TimeSpan MinRefreshInterval { get; } IList Fetch(); } } diff --git a/src/NzbDrone.Core/ImportLists/ImportListBase.cs b/src/NzbDrone.Core/ImportLists/ImportListBase.cs index 25b999f58..6e8c917e6 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListBase.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListBase.cs @@ -5,6 +5,7 @@ using FluentValidation.Results; using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Indexers; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.ThingiProvider; @@ -23,6 +24,8 @@ namespace NzbDrone.Core.ImportLists public abstract ImportListType ListType { get; } + public abstract TimeSpan MinRefreshInterval { get; } + public ImportListBase(IImportListStatusService importListStatusService, IConfigService configService, IParsingService parsingService, Logger logger) { _importListStatusService = importListStatusService; diff --git a/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs b/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs index 9f83c9560..ecf335c63 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListDefinition.cs @@ -1,3 +1,4 @@ +using System; using NzbDrone.Core.Books; using NzbDrone.Core.ThingiProvider; @@ -18,6 +19,7 @@ namespace NzbDrone.Core.ImportLists public ImportListStatus Status { get; set; } public ImportListType ListType { get; set; } + public TimeSpan MinRefreshInterval { get; set; } } public enum ImportListMonitorType diff --git a/src/NzbDrone.Core/ImportLists/ImportListFactory.cs b/src/NzbDrone.Core/ImportLists/ImportListFactory.cs index aed9ad44d..211ccb413 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListFactory.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListFactory.cs @@ -41,6 +41,7 @@ namespace NzbDrone.Core.ImportLists base.SetProviderCharacteristics(provider, definition); definition.ListType = provider.ListType; + definition.MinRefreshInterval = provider.MinRefreshInterval; } public List AutomaticAddEnabled(bool filterBlockedImportLists = true) diff --git a/src/NzbDrone.Core/ImportLists/ImportListStatus.cs b/src/NzbDrone.Core/ImportLists/ImportListStatus.cs index 139d2b97b..69892e1b5 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListStatus.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListStatus.cs @@ -1,3 +1,4 @@ +using System; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.ThingiProvider.Status; @@ -5,6 +6,6 @@ namespace NzbDrone.Core.ImportLists { public class ImportListStatus : ProviderStatusBase { - public ImportListItemInfo LastSyncListInfo { get; set; } + public DateTime LastInfoSync { get; set; } } } diff --git a/src/NzbDrone.Core/ImportLists/ImportListStatusService.cs b/src/NzbDrone.Core/ImportLists/ImportListStatusService.cs index 362359dca..e543d0a7e 100644 --- a/src/NzbDrone.Core/ImportLists/ImportListStatusService.cs +++ b/src/NzbDrone.Core/ImportLists/ImportListStatusService.cs @@ -1,16 +1,16 @@ +using System; using NLog; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Parser.Model; using NzbDrone.Core.ThingiProvider.Status; namespace NzbDrone.Core.ImportLists { public interface IImportListStatusService : IProviderStatusServiceBase { - ImportListItemInfo GetLastSyncListInfo(int importListId); + DateTime GetLastSyncListInfo(int importListId); - void UpdateListSyncStatus(int importListId, ImportListItemInfo listItemInfo); + void UpdateListSyncStatus(int importListId); } public class ImportListStatusService : ProviderStatusServiceBase, 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) { var status = GetProviderStatus(importListId); - status.LastSyncListInfo = listItemInfo; + status.LastInfoSync = DateTime.UtcNow; _providerStatusRepository.Upsert(status); } diff --git a/src/NzbDrone.Core/ImportLists/LazyLibrarian/LazyLibrarianImport.cs b/src/NzbDrone.Core/ImportLists/LazyLibrarian/LazyLibrarianImport.cs index c56825a6c..aff4c20a4 100644 --- a/src/NzbDrone.Core/ImportLists/LazyLibrarian/LazyLibrarianImport.cs +++ b/src/NzbDrone.Core/ImportLists/LazyLibrarian/LazyLibrarianImport.cs @@ -1,3 +1,4 @@ +using System; using NLog; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; @@ -10,7 +11,7 @@ namespace NzbDrone.Core.ImportLists.LazyLibrarianImport public override string Name => "LazyLibrarian"; public override ImportListType ListType => ImportListType.Other; - + public override TimeSpan MinRefreshInterval => TimeSpan.FromMinutes(15); public override int PageSize => 1000; public LazyLibrarianImport(IHttpClient httpClient, IImportListStatusService importListStatusService, IConfigService configService, IParsingService parsingService, Logger logger) diff --git a/src/NzbDrone.Core/ImportLists/Readarr/ReadarrImport.cs b/src/NzbDrone.Core/ImportLists/Readarr/ReadarrImport.cs index 20f38edb9..36310c578 100644 --- a/src/NzbDrone.Core/ImportLists/Readarr/ReadarrImport.cs +++ b/src/NzbDrone.Core/ImportLists/Readarr/ReadarrImport.cs @@ -17,6 +17,7 @@ namespace NzbDrone.Core.ImportLists.Readarr public override string Name => "Readarr"; public override ImportListType ListType => ImportListType.Program; + public override TimeSpan MinRefreshInterval => TimeSpan.FromMinutes(15); public ReadarrImport(IReadarrV1Proxy readarrV1Proxy, IImportListStatusService importListStatusService, diff --git a/src/NzbDrone.Core/Jobs/TaskManager.cs b/src/NzbDrone.Core/Jobs/TaskManager.cs index 09177a88b..647a0e6ce 100644 --- a/src/NzbDrone.Core/Jobs/TaskManager.cs +++ b/src/NzbDrone.Core/Jobs/TaskManager.cs @@ -116,7 +116,7 @@ namespace NzbDrone.Core.Jobs new ScheduledTask { - Interval = 24 * 60, // TODO: Add a setting? + Interval = 5, TypeName = typeof(ImportListSyncCommand).FullName }, diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index bbebb6ad9..c2bea533e 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -401,6 +401,8 @@ "LaunchBrowserHelpText": " Open a web browser and navigate to Readarr homepage on app start.", "Level": "Level", "LibraryHelpText": "Calibre content server library name. Leave blank for default.", + "ListRefreshInterval": "List Refresh Interval", + "ListWillRefreshEveryInterp": "List will refresh every {0}", "Lists": "Lists", "ListsSettingsSummary": "Import Lists", "Loading": "loading", diff --git a/src/Readarr.Api.V1/ImportLists/ImportListResource.cs b/src/Readarr.Api.V1/ImportLists/ImportListResource.cs index d7eb46eea..ebac520e9 100644 --- a/src/Readarr.Api.V1/ImportLists/ImportListResource.cs +++ b/src/Readarr.Api.V1/ImportLists/ImportListResource.cs @@ -1,3 +1,4 @@ +using System; using NzbDrone.Core.Books; using NzbDrone.Core.ImportLists; @@ -15,6 +16,7 @@ namespace Readarr.Api.V1.ImportLists public int MetadataProfileId { get; set; } public ImportListType ListType { get; set; } public int ListOrder { get; set; } + public TimeSpan MinRefreshInterval { get; set; } } public class ImportListResourceMapper : ProviderResourceMapper @@ -38,6 +40,7 @@ namespace Readarr.Api.V1.ImportLists resource.MetadataProfileId = definition.MetadataProfileId; resource.ListType = definition.ListType; resource.ListOrder = (int)definition.ListType; + resource.MinRefreshInterval = definition.MinRefreshInterval; return resource; } @@ -60,6 +63,7 @@ namespace Readarr.Api.V1.ImportLists definition.ProfileId = resource.QualityProfileId; definition.MetadataProfileId = resource.MetadataProfileId; definition.ListType = resource.ListType; + definition.MinRefreshInterval = resource.MinRefreshInterval; return definition; }