New: Refresh only selected or filtered artists

(cherry picked from commit bf90c3cbddffca4f7ad07c3ae51fa705988cd80b)

Closes #3717
This commit is contained in:
Stevie Robinson 2023-05-21 23:05:30 +02:00 committed by Bogdan
parent 0e7b368c3a
commit cbb3cb78f9
10 changed files with 125 additions and 36 deletions

View File

@ -0,0 +1,8 @@
import { CustomFilter } from './AppState';
interface ClientSideCollectionAppState {
totalItems: number;
customFilters: CustomFilter[];
}
export default ClientSideCollectionAppState;

View File

@ -2,7 +2,7 @@ import React, { useCallback, useMemo, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { SelectProvider } from 'App/SelectContext';
import NoArtist from 'Artist/NoArtist';
import { REFRESH_ARTIST, 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';
@ -29,6 +29,7 @@ import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import getErrorMessage from 'Utilities/Object/getErrorMessage';
import translate from 'Utilities/String/translate';
import ArtistIndexFooter from './ArtistIndexFooter';
import ArtistIndexRefreshArtistsButton from './ArtistIndexRefreshArtistsButton';
import ArtistIndexBanners from './Banners/ArtistIndexBanners';
import ArtistIndexBannerOptionsModal from './Banners/Options/ArtistIndexBannerOptionsModal';
import ArtistIndexFilterMenu from './Menus/ArtistIndexFilterMenu';
@ -83,9 +84,6 @@ const ArtistIndex = withScrollPosition((props: ArtistIndexProps) => {
view,
} = useSelector(createArtistClientSideCollectionItemsSelector('artistIndex'));
const isRefreshingArtist = useSelector(
createCommandExecutingSelector(REFRESH_ARTIST)
);
const isRssSyncExecuting = useSelector(
createCommandExecutingSelector(RSS_SYNC)
);
@ -96,14 +94,6 @@ const ArtistIndex = withScrollPosition((props: ArtistIndexProps) => {
const [jumpToCharacter, setJumpToCharacter] = useState<string | null>(null);
const [isSelectMode, setIsSelectMode] = useState(false);
const onRefreshArtistPress = useCallback(() => {
dispatch(
executeCommand({
name: REFRESH_ARTIST,
})
);
}, [dispatch]);
const onRssSyncPress = useCallback(() => {
dispatch(
executeCommand({
@ -217,13 +207,9 @@ const ArtistIndex = withScrollPosition((props: ArtistIndexProps) => {
<PageContent>
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label={translate('UpdateAll')}
iconName={icons.REFRESH}
spinningName={icons.REFRESH}
isSpinning={isRefreshingArtist}
isDisabled={hasNoArtist}
onPress={onRefreshArtistPress}
<ArtistIndexRefreshArtistsButton
isSelectMode={isSelectMode}
selectedFilterKey={selectedFilterKey}
/>
<PageToolbarButton

View File

@ -0,0 +1,74 @@
import React, { useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useSelect } from 'App/SelectContext';
import ArtistAppState, { ArtistIndexAppState } from 'App/State/ArtistAppState';
import ClientSideCollectionAppState from 'App/State/ClientSideCollectionAppState';
import { REFRESH_ARTIST } from 'Commands/commandNames';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import { icons } from 'Helpers/Props';
import { executeCommand } from 'Store/Actions/commandActions';
import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import translate from 'Utilities/String/translate';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
interface ArtistIndexRefreshArtistsButtonProps {
isSelectMode: boolean;
selectedFilterKey: string;
}
function ArtistIndexRefreshArtistsButton(
props: ArtistIndexRefreshArtistsButtonProps
) {
const isRefreshing = useSelector(
createCommandExecutingSelector(REFRESH_ARTIST)
);
const {
items,
totalItems,
}: ArtistAppState & ArtistIndexAppState & ClientSideCollectionAppState =
useSelector(createArtistClientSideCollectionItemsSelector('artistIndex'));
const dispatch = useDispatch();
const { isSelectMode, selectedFilterKey } = props;
const [selectState] = useSelect();
const { selectedState } = selectState;
const selectedArtistIds = useMemo(() => {
return getSelectedIds(selectedState);
}, [selectedState]);
const artistsToRefresh =
isSelectMode && selectedArtistIds.length > 0
? selectedArtistIds
: items.map((m) => m.id);
let refreshLabel = translate('UpdateAll');
if (selectedArtistIds.length > 0) {
refreshLabel = translate('UpdateSelected');
} else if (selectedFilterKey !== 'all') {
refreshLabel = translate('UpdateFiltered');
}
const onPress = useCallback(() => {
dispatch(
executeCommand({
name: REFRESH_ARTIST,
artistIds: artistsToRefresh,
})
);
}, [dispatch, artistsToRefresh]);
return (
<PageToolbarButton
label={refreshLabel}
isSpinning={isRefreshing}
isDisabled={!totalItems}
iconName={icons.REFRESH}
onPress={onPress}
/>
);
}
export default ArtistIndexRefreshArtistsButton;

View File

@ -119,7 +119,7 @@ namespace NzbDrone.Core.Test.MusicTests
GivenAlbumsForRefresh(_albums);
AllowArtistUpdate();
Subject.Execute(new RefreshArtistCommand(_artist.Id));
Subject.Execute(new RefreshArtistCommand(new List<int> { _artist.Id }));
VerifyEventNotPublished<ArtistUpdatedEvent>();
VerifyEventPublished<ArtistRefreshCompleteEvent>();
@ -140,7 +140,7 @@ namespace NzbDrone.Core.Test.MusicTests
GivenAlbumsForRefresh(new List<Album>());
AllowArtistUpdate();
Subject.Execute(new RefreshArtistCommand(_artist.Id));
Subject.Execute(new RefreshArtistCommand(new List<int> { _artist.Id }));
VerifyEventPublished<ArtistUpdatedEvent>();
VerifyEventPublished<ArtistRefreshCompleteEvent>();
@ -163,7 +163,7 @@ namespace NzbDrone.Core.Test.MusicTests
GivenAlbumsForRefresh(_albums);
AllowArtistUpdate();
Subject.Execute(new RefreshArtistCommand(_artist.Id));
Subject.Execute(new RefreshArtistCommand(new List<int> { _artist.Id }));
Mocker.GetMock<IMonitorNewAlbumService>()
.Verify(x => x.ShouldMonitorNewAlbum(newAlbum, _albums, _artist.MonitorNewItems), Times.Once());
@ -175,7 +175,7 @@ namespace NzbDrone.Core.Test.MusicTests
Mocker.GetMock<IArtistService>()
.Setup(x => x.DeleteArtist(It.IsAny<int>(), It.IsAny<bool>(), It.IsAny<bool>()));
Subject.Execute(new RefreshArtistCommand(_artist.Id));
Subject.Execute(new RefreshArtistCommand(new List<int> { _artist.Id }));
Mocker.GetMock<IArtistService>()
.Verify(v => v.UpdateArtist(It.IsAny<Artist>(), It.IsAny<bool>()), Times.Never());
@ -193,7 +193,7 @@ namespace NzbDrone.Core.Test.MusicTests
GivenArtistFiles();
GivenAlbumsForRefresh(new List<Album>());
Subject.Execute(new RefreshArtistCommand(_artist.Id));
Subject.Execute(new RefreshArtistCommand(new List<int> { _artist.Id }));
Mocker.GetMock<IArtistService>()
.Verify(v => v.UpdateArtist(It.IsAny<Artist>(), It.IsAny<bool>()), Times.Never());
@ -238,7 +238,7 @@ namespace NzbDrone.Core.Test.MusicTests
.Setup(x => x.UpdateArtist(It.IsAny<Artist>(), It.IsAny<bool>()))
.Returns((Artist a, bool updated) => a);
Subject.Execute(new RefreshArtistCommand(_artist.Id));
Subject.Execute(new RefreshArtistCommand(new List<int> { _artist.Id }));
Mocker.GetMock<IArtistService>()
.Verify(v => v.UpdateArtist(It.Is<Artist>(s => s.ArtistMetadataId == 100 && s.ForeignArtistId == newArtistInfo.ForeignArtistId), It.IsAny<bool>()),
@ -298,7 +298,7 @@ namespace NzbDrone.Core.Test.MusicTests
.Setup(x => x.UpdateArtist(It.IsAny<Artist>(), It.IsAny<bool>()))
.Returns((Artist a, bool updated) => a);
Subject.Execute(new RefreshArtistCommand(_artist.Id));
Subject.Execute(new RefreshArtistCommand(new List<int> { _artist.Id }));
// the retained artist gets updated
Mocker.GetMock<IArtistService>()

View File

@ -1089,12 +1089,13 @@
"Unmonitored": "Unmonitored",
"UnmonitoredHelpText": "Include unmonitored albums in the iCal feed",
"UnmonitoredOnly": "Unmonitored Only",
"UpdateAll": "Update all",
"UpdateAll": "Update All",
"UpdateAutomaticallyHelpText": "Automatically download and install updates. You will still be able to install from System: Updates",
"UpdateAvailable": "New update is available",
"UpdateCheckStartupNotWritableMessage": "Cannot install update because startup folder '{0}' is not writable by the user '{1}'.",
"UpdateCheckStartupTranslocationMessage": "Cannot install update because startup folder '{0}' is in an App Translocation folder.",
"UpdateCheckUINotWritableMessage": "Cannot install update because UI folder '{0}' is not writable by the user '{1}'.",
"UpdateFiltered": "Update Filtered",
"UpdateMechanismHelpText": "Use Lidarr's built-in updater or a script",
"UpdateMonitoring": "Update Monitoring",
"UpdateScriptPathHelpText": "Path to a custom script that takes an extracted update package and handle the remainder of the update process",

View File

@ -1,24 +1,41 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.Music.Commands
{
public class RefreshArtistCommand : Command
{
public int? ArtistId { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public int ArtistId
{
get => 0;
set
{
if (ArtistIds.Empty())
{
ArtistIds.Add(value);
}
}
}
public List<int> ArtistIds { get; set; }
public bool IsNewArtist { get; set; }
public RefreshArtistCommand()
{
ArtistIds = new List<int>();
}
public RefreshArtistCommand(int? artistId, bool isNewArtist = false)
public RefreshArtistCommand(List<int> artistIds, bool isNewArtist = false)
{
ArtistId = artistId;
ArtistIds = artistIds;
IsNewArtist = isNewArtist;
}
public override bool SendUpdatesToClient => true;
public override bool UpdateScheduledTask => !ArtistId.HasValue;
public override bool UpdateScheduledTask => ArtistIds.Empty();
}
}

View File

@ -1,3 +1,4 @@
using System.Collections.Generic;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Music.Commands;
@ -25,7 +26,7 @@ namespace NzbDrone.Core.Music
if (_checkIfArtistShouldBeRefreshed.ShouldRefresh(artist))
{
_commandQueueManager.Push(new RefreshArtistCommand(artist.Id));
_commandQueueManager.Push(new RefreshArtistCommand(new List<int> { artist.Id }));
}
else
{

View File

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
@ -22,7 +23,7 @@ namespace NzbDrone.Core.Music
{
if (message.DoRefresh)
{
_commandQueueManager.Push(new RefreshArtistCommand(message.Artist.Id, true));
_commandQueueManager.Push(new RefreshArtistCommand(new List<int> { message.Artist.Id }, true));
}
else
{

View File

@ -1,3 +1,4 @@
using System.Collections.Generic;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Music.Commands;
@ -19,7 +20,7 @@ namespace NzbDrone.Core.Music
// Refresh Artist is we change AlbumType Preferences
if (message.Artist.MetadataProfileId != message.OldArtist.MetadataProfileId)
{
_commandQueueManager.Push(new RefreshArtistCommand(message.Artist.Id, false));
_commandQueueManager.Push(new RefreshArtistCommand(new List<int> { message.Artist.Id }, false));
}
}
}

View File

@ -352,9 +352,9 @@ namespace NzbDrone.Core.Music
var trigger = message.Trigger;
var isNew = message.IsNewArtist;
if (message.ArtistId.HasValue)
if (message.ArtistIds.Any())
{
RefreshSelectedArtists(new List<int> { message.ArtistId.Value }, isNew, trigger);
RefreshSelectedArtists(message.ArtistIds, isNew, trigger);
}
else
{