From bf90c3cbddffca4f7ad07c3ae51fa705988cd80b Mon Sep 17 00:00:00 2001 From: Stevie Robinson Date: Sun, 21 May 2023 23:05:30 +0200 Subject: [PATCH] New: refresh only selected or filtered series Closes #5611 --- frontend/src/Series/Index/SeriesIndex.tsx | 24 ++---- .../Index/SeriesIndexRefreshSeriesButton.tsx | 73 +++++++++++++++++++ .../TvTests/RefreshSeriesServiceFixture.cs | 26 +++---- .../Tv/Commands/RefreshSeriesCommand.cs | 25 ++++++- src/NzbDrone.Core/Tv/RefreshSeriesService.cs | 39 +++++----- src/NzbDrone.Core/Tv/SeriesAddedHandler.cs | 5 +- src/NzbDrone.Core/Tv/SeriesEditedService.cs | 3 +- .../IntegrationTestBase.cs | 2 +- 8 files changed, 139 insertions(+), 58 deletions(-) create mode 100644 frontend/src/Series/Index/SeriesIndexRefreshSeriesButton.tsx diff --git a/frontend/src/Series/Index/SeriesIndex.tsx b/frontend/src/Series/Index/SeriesIndex.tsx index 6149b1e92..43bef9a64 100644 --- a/frontend/src/Series/Index/SeriesIndex.tsx +++ b/frontend/src/Series/Index/SeriesIndex.tsx @@ -9,7 +9,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { SelectProvider } from 'App/SelectContext'; import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState'; import SeriesAppState, { SeriesIndexAppState } from 'App/State/SeriesAppState'; -import { REFRESH_SERIES, RSS_SYNC } from 'Commands/commandNames'; +import { RSS_SYNC } from 'Commands/commandNames'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; @@ -49,6 +49,7 @@ import SeriesIndexSelectFooter from './Select/SeriesIndexSelectFooter'; import SeriesIndexSelectModeButton from './Select/SeriesIndexSelectModeButton'; import SeriesIndexSelectModeMenuItem from './Select/SeriesIndexSelectModeMenuItem'; import SeriesIndexFooter from './SeriesIndexFooter'; +import SeriesIndexRefreshSeriesButton from './SeriesIndexRefreshSeriesButton'; import SeriesIndexTable from './Table/SeriesIndexTable'; import SeriesIndexTableOptions from './Table/SeriesIndexTableOptions'; import styles from './SeriesIndex.css'; @@ -86,9 +87,6 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => { }: SeriesAppState & SeriesIndexAppState & ClientSideCollectionAppState = useSelector(createSeriesClientSideCollectionItemsSelector('seriesIndex')); - const isRefreshingSeries = useSelector( - createCommandExecutingSelector(REFRESH_SERIES) - ); const isRssSyncExecuting = useSelector( createCommandExecutingSelector(RSS_SYNC) ); @@ -106,14 +104,6 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => { dispatch(fetchQueueDetails({ all: true })); }, [dispatch]); - const onRefreshSeriesPress = useCallback(() => { - dispatch( - executeCommand({ - name: REFRESH_SERIES, - }) - ); - }, [dispatch]); - const onRssSyncPress = useCallback(() => { dispatch( executeCommand({ @@ -227,13 +217,9 @@ const SeriesIndex = withScrollPosition((props: SeriesIndexProps) => { - { + return getSelectedIds(selectedState); + }, [selectedState]); + + const seriesToRefresh = + isSelectMode && selectedSeriesIds.length > 0 + ? selectedSeriesIds + : items.map((m) => m.id); + + let refreshLabel = 'Update All'; + + if (selectedSeriesIds.length > 0) { + refreshLabel = 'Update Selected'; + } else if (selectedFilterKey !== 'all') { + refreshLabel = 'Update Filtered'; + } + + const onPress = useCallback(() => { + dispatch( + executeCommand({ + name: REFRESH_SERIES, + seriesIds: seriesToRefresh, + }) + ); + }, [dispatch, seriesToRefresh]); + + return ( + + ); +} + +export default SeriesIndexRefreshSeriesButton; diff --git a/src/NzbDrone.Core.Test/TvTests/RefreshSeriesServiceFixture.cs b/src/NzbDrone.Core.Test/TvTests/RefreshSeriesServiceFixture.cs index 67cd1dd77..7a154d5fa 100644 --- a/src/NzbDrone.Core.Test/TvTests/RefreshSeriesServiceFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/RefreshSeriesServiceFixture.cs @@ -68,7 +68,7 @@ namespace NzbDrone.Core.Test.TvTests GivenNewSeriesInfo(newSeriesInfo); - Subject.Execute(new RefreshSeriesCommand(_series.Id)); + Subject.Execute(new RefreshSeriesCommand(new List { _series.Id })); Mocker.GetMock() .Verify(v => v.UpdateSeries(It.Is(s => s.Seasons.Count == 2 && s.Seasons.Single(season => season.SeasonNumber == 2).Monitored == true), It.IsAny(), It.IsAny())); @@ -85,7 +85,7 @@ namespace NzbDrone.Core.Test.TvTests GivenNewSeriesInfo(newSeriesInfo); - Subject.Execute(new RefreshSeriesCommand(_series.Id)); + Subject.Execute(new RefreshSeriesCommand(new List { _series.Id })); Mocker.GetMock() .Verify(v => v.UpdateSeries(It.Is(s => s.Seasons.Count == 2 && s.Seasons.Single(season => season.SeasonNumber == 2).Monitored == false), It.IsAny(), It.IsAny())); @@ -101,7 +101,7 @@ namespace NzbDrone.Core.Test.TvTests GivenNewSeriesInfo(series); - Subject.Execute(new RefreshSeriesCommand(_series.Id)); + Subject.Execute(new RefreshSeriesCommand(new List { _series.Id })); Mocker.GetMock() .Verify(v => v.UpdateSeries(It.Is(s => s.Seasons.Count == 2 && s.Seasons.Single(season => season.SeasonNumber == 0).Monitored == false), It.IsAny(), It.IsAny())); @@ -115,7 +115,7 @@ namespace NzbDrone.Core.Test.TvTests GivenNewSeriesInfo(newSeriesInfo); - Subject.Execute(new RefreshSeriesCommand(_series.Id)); + Subject.Execute(new RefreshSeriesCommand(new List { _series.Id })); Mocker.GetMock() .Verify(v => v.UpdateSeries(It.Is(s => s.TvRageId == newSeriesInfo.TvRageId), It.IsAny(), It.IsAny())); @@ -129,7 +129,7 @@ namespace NzbDrone.Core.Test.TvTests GivenNewSeriesInfo(newSeriesInfo); - Subject.Execute(new RefreshSeriesCommand(_series.Id)); + Subject.Execute(new RefreshSeriesCommand(new List { _series.Id })); Mocker.GetMock() .Verify(v => v.UpdateSeries(It.Is(s => s.TvMazeId == newSeriesInfo.TvMazeId), It.IsAny(), It.IsAny())); @@ -138,7 +138,7 @@ namespace NzbDrone.Core.Test.TvTests [Test] public void should_log_error_if_tvdb_id_not_found() { - Subject.Execute(new RefreshSeriesCommand(_series.Id)); + Subject.Execute(new RefreshSeriesCommand(new List { _series.Id })); Mocker.GetMock() .Verify(v => v.UpdateSeries(It.Is(s => s.Status == SeriesStatusType.Deleted), It.IsAny(), It.IsAny()), Times.Once()); @@ -149,7 +149,7 @@ namespace NzbDrone.Core.Test.TvTests [Test] public void should_mark_as_deleted_if_tvdb_id_not_found() { - Subject.Execute(new RefreshSeriesCommand(_series.Id)); + Subject.Execute(new RefreshSeriesCommand(new List { _series.Id })); Mocker.GetMock() .Verify(v => v.UpdateSeries(It.Is(s => s.Status == SeriesStatusType.Deleted), It.IsAny(), It.IsAny()), Times.Once()); @@ -162,7 +162,7 @@ namespace NzbDrone.Core.Test.TvTests { _series.Status = SeriesStatusType.Deleted; - Subject.Execute(new RefreshSeriesCommand(_series.Id)); + Subject.Execute(new RefreshSeriesCommand(new List { _series.Id })); Mocker.GetMock() .Verify(v => v.UpdateSeries(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); @@ -178,7 +178,7 @@ namespace NzbDrone.Core.Test.TvTests GivenNewSeriesInfo(newSeriesInfo); - Subject.Execute(new RefreshSeriesCommand(_series.Id)); + Subject.Execute(new RefreshSeriesCommand(new List { _series.Id })); Mocker.GetMock() .Verify(v => v.UpdateSeries(It.Is(s => s.TvdbId == newSeriesInfo.TvdbId), It.IsAny(), It.IsAny())); @@ -204,7 +204,7 @@ namespace NzbDrone.Core.Test.TvTests GivenNewSeriesInfo(newSeriesInfo); - Subject.Execute(new RefreshSeriesCommand(_series.Id)); + Subject.Execute(new RefreshSeriesCommand(new List { _series.Id })); Mocker.GetMock() .Verify(v => v.UpdateSeries(It.Is(s => s.Seasons.Count == 2), It.IsAny(), It.IsAny())); @@ -224,7 +224,7 @@ namespace NzbDrone.Core.Test.TvTests GivenNewSeriesInfo(newSeriesInfo); - Subject.Execute(new RefreshSeriesCommand(_series.Id)); + Subject.Execute(new RefreshSeriesCommand(new List { _series.Id })); Mocker.GetMock() .Verify(v => v.UpdateSeries(It.Is(s => s.Seasons.Count == 2), It.IsAny(), It.IsAny())); @@ -237,7 +237,7 @@ namespace NzbDrone.Core.Test.TvTests .Setup(s => s.GetSeriesInfo(_series.Id)) .Throws(new IOException()); - Assert.Throws(() => Subject.Execute(new RefreshSeriesCommand(_series.Id))); + Assert.Throws(() => Subject.Execute(new RefreshSeriesCommand(new List { _series.Id }))); Mocker.GetMock() .Verify(v => v.Scan(_series), Times.Once()); @@ -252,7 +252,7 @@ namespace NzbDrone.Core.Test.TvTests .Setup(s => s.GetSeriesInfo(_series.Id)) .Throws(new SeriesNotFoundException(_series.Id)); - Subject.Execute(new RefreshSeriesCommand(_series.Id)); + Subject.Execute(new RefreshSeriesCommand(new List { _series.Id })); Mocker.GetMock() .Verify(v => v.Scan(_series), Times.Never()); diff --git a/src/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs b/src/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs index 5cedb151e..da6e853dc 100644 --- a/src/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs +++ b/src/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs @@ -1,25 +1,42 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Tv.Commands { public class RefreshSeriesCommand : Command { - public int? SeriesId { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public int SeriesId + { + get => 0; + set + { + if (SeriesIds.Empty()) + { + SeriesIds.Add(value); + } + } + } + + public List SeriesIds { get; set; } public bool IsNewSeries { get; set; } public RefreshSeriesCommand() { + SeriesIds = new List(); } - public RefreshSeriesCommand(int? seriesId, bool isNewSeries = false) + public RefreshSeriesCommand(List seriesIds, bool isNewSeries = false) { - SeriesId = seriesId; + SeriesIds = seriesIds; IsNewSeries = isNewSeries; } public override bool SendUpdatesToClient => true; - public override bool UpdateScheduledTask => !SeriesId.HasValue; + public override bool UpdateScheduledTask => SeriesIds.Empty(); public override bool IsLongRunning => true; } diff --git a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs index 189223d1e..a3c74b5e5 100644 --- a/src/NzbDrone.Core/Tv/RefreshSeriesService.cs +++ b/src/NzbDrone.Core/Tv/RefreshSeriesService.cs @@ -232,26 +232,29 @@ namespace NzbDrone.Core.Tv var isNew = message.IsNewSeries; _eventAggregator.PublishEvent(new SeriesRefreshStartingEvent(trigger == CommandTrigger.Manual)); - if (message.SeriesId.HasValue) + if (message.SeriesIds.Any()) { - var series = _seriesService.GetSeries(message.SeriesId.Value); - - try - { - series = RefreshSeriesInfo(message.SeriesId.Value); - UpdateTags(series); - RescanSeries(series, isNew, trigger); - } - catch (SeriesNotFoundException) + foreach (var seriesId in message.SeriesIds) { - _logger.Error("Series '{0}' (tvdbid {1}) was not found, it may have been removed from TheTVDB.", series.Title, series.TvdbId); - } - catch (Exception e) - { - _logger.Error(e, "Couldn't refresh info for {0}", series); - UpdateTags(series); - RescanSeries(series, isNew, trigger); - throw; + var series = _seriesService.GetSeries(seriesId); + + try + { + series = RefreshSeriesInfo(seriesId); + UpdateTags(series); + RescanSeries(series, isNew, trigger); + } + catch (SeriesNotFoundException) + { + _logger.Error("Series '{0}' (tvdbid {1}) was not found, it may have been removed from TheTVDB.", series.Title, series.TvdbId); + } + catch (Exception e) + { + _logger.Error(e, "Couldn't refresh info for {0}", series); + UpdateTags(series); + RescanSeries(series, isNew, trigger); + throw; + } } } else diff --git a/src/NzbDrone.Core/Tv/SeriesAddedHandler.cs b/src/NzbDrone.Core/Tv/SeriesAddedHandler.cs index 788fe59d8..5fb32259f 100644 --- a/src/NzbDrone.Core/Tv/SeriesAddedHandler.cs +++ b/src/NzbDrone.Core/Tv/SeriesAddedHandler.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; @@ -18,12 +19,12 @@ namespace NzbDrone.Core.Tv public void Handle(SeriesAddedEvent message) { - _commandQueueManager.Push(new RefreshSeriesCommand(message.Series.Id, true)); + _commandQueueManager.Push(new RefreshSeriesCommand(new List { message.Series.Id }, true)); } public void Handle(SeriesImportedEvent message) { - _commandQueueManager.PushMany(message.SeriesIds.Select(s => new RefreshSeriesCommand(s, true)).ToList()); + _commandQueueManager.PushMany(message.SeriesIds.Select(s => new RefreshSeriesCommand(new List { s }, true)).ToList()); } } } diff --git a/src/NzbDrone.Core/Tv/SeriesEditedService.cs b/src/NzbDrone.Core/Tv/SeriesEditedService.cs index c28641495..5a002534e 100644 --- a/src/NzbDrone.Core/Tv/SeriesEditedService.cs +++ b/src/NzbDrone.Core/Tv/SeriesEditedService.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv.Commands; @@ -18,7 +19,7 @@ namespace NzbDrone.Core.Tv { if (message.Series.SeriesType != message.OldSeries.SeriesType) { - _commandQueueManager.Push(new RefreshSeriesCommand(message.Series.Id, false)); + _commandQueueManager.Push(new RefreshSeriesCommand(new List { message.Series.Id }, false)); } } } diff --git a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs index ca2ee7a47..a1c086790 100644 --- a/src/NzbDrone.Integration.Test/IntegrationTestBase.cs +++ b/src/NzbDrone.Integration.Test/IntegrationTestBase.cs @@ -305,7 +305,7 @@ namespace NzbDrone.Integration.Test Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllText(path, "Fake Episode"); - Commands.PostAndWait(new RefreshSeriesCommand(series.Id)); + Commands.PostAndWait(new RefreshSeriesCommand(new List { series.Id })); Commands.WaitAll();