New: Speed up Bulk Artist Deletions

This commit is contained in:
Qstick 2021-01-24 14:41:48 -05:00
commit b9f91db3ae
18 changed files with 120 additions and 85 deletions

View file

@ -95,10 +95,7 @@ namespace Lidarr.Api.V1.Artist
{
var resource = Request.Body.FromJson<ArtistEditorResource>();
foreach (var artistId in resource.ArtistIds)
{
_artistService.DeleteArtist(artistId, resource.DeleteFiles);
}
_artistService.DeleteArtists(resource.ArtistIds, resource.DeleteFiles);
return new object();
}

View file

@ -29,7 +29,7 @@ namespace Lidarr.Api.V1.Artist
IHandle<TrackFileDeletedEvent>,
IHandle<ArtistUpdatedEvent>,
IHandle<ArtistEditedEvent>,
IHandle<ArtistDeletedEvent>,
IHandle<ArtistsDeletedEvent>,
IHandle<ArtistRenamedEvent>,
IHandle<MediaCoversUpdatedEvent>
{
@ -285,9 +285,12 @@ namespace Lidarr.Api.V1.Artist
BroadcastResourceChange(ModelAction.Updated, GetArtistResource(message.Artist));
}
public void Handle(ArtistDeletedEvent message)
public void Handle(ArtistsDeletedEvent message)
{
BroadcastResourceChange(ModelAction.Deleted, message.Artist.ToResource());
foreach (var artist in message.Artists)
{
BroadcastResourceChange(ModelAction.Deleted, artist.ToResource());
}
}
public void Handle(ArtistRenamedEvent message)

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Cache;
using NzbDrone.Core.MediaFiles.Events;
@ -16,7 +16,7 @@ namespace NzbDrone.Core.ArtistStats
public class ArtistStatisticsService : IArtistStatisticsService,
IHandle<ArtistUpdatedEvent>,
IHandle<ArtistDeletedEvent>,
IHandle<ArtistsDeletedEvent>,
IHandle<AlbumAddedEvent>,
IHandle<AlbumDeletedEvent>,
IHandle<AlbumImportedEvent>,
@ -76,10 +76,14 @@ namespace NzbDrone.Core.ArtistStats
}
[EventHandleOrder(EventHandleOrder.First)]
public void Handle(ArtistDeletedEvent message)
public void Handle(ArtistsDeletedEvent message)
{
_cache.Remove("AllArtists");
_cache.Remove(message.Artist.Id.ToString());
foreach (var artist in message.Artists)
{
_cache.Remove(artist.Id.ToString());
}
}
[EventHandleOrder(EventHandleOrder.First)]

View file

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Music;
@ -9,7 +9,8 @@ namespace NzbDrone.Core.Blacklisting
{
List<Blacklist> BlacklistedByTitle(int artistId, string sourceTitle);
List<Blacklist> BlacklistedByTorrentInfoHash(int artistId, string torrentInfoHash);
List<Blacklist> BlacklistedByArtist(int artistId);
List<Blacklist> BlacklistedByArtists(List<int> artistIds);
void DeleteForArtists(List<int> artistIds);
}
public class BlacklistRepository : BasicRepository<Blacklist>, IBlacklistRepository
@ -29,9 +30,14 @@ namespace NzbDrone.Core.Blacklisting
return Query(e => e.ArtistId == artistId && e.TorrentInfoHash.Contains(torrentInfoHash));
}
public List<Blacklist> BlacklistedByArtist(int artistId)
public List<Blacklist> BlacklistedByArtists(List<int> artistIds)
{
return Query(b => b.ArtistId == artistId);
return Query(x => artistIds.Contains(x.ArtistId));
}
public void DeleteForArtists(List<int> artistIds)
{
Delete(x => artistIds.Contains(x.ArtistId));
}
protected override SqlBuilder PagedBuilder() => new SqlBuilder().Join<Blacklist, Artist>((b, m) => b.ArtistId == m.Id);

View file

@ -24,7 +24,7 @@ namespace NzbDrone.Core.Blacklisting
IExecute<ClearBlacklistCommand>,
IHandle<DownloadFailedEvent>,
IHandleAsync<ArtistDeletedEvent>
IHandleAsync<ArtistsDeletedEvent>
{
private readonly IBlacklistRepository _blacklistRepository;
@ -161,11 +161,9 @@ namespace NzbDrone.Core.Blacklisting
_blacklistRepository.Insert(blacklist);
}
public void HandleAsync(ArtistDeletedEvent message)
public void HandleAsync(ArtistsDeletedEvent message)
{
var blacklisted = _blacklistRepository.BlacklistedByArtist(message.Artist.Id);
_blacklistRepository.DeleteMany(blacklisted);
_blacklistRepository.DeleteForArtists(message.Artists.Select(x => x.Id).ToList());
}
}
}

View file

@ -6,7 +6,7 @@ namespace NzbDrone.Core.Download.Pending
{
public interface IPendingReleaseRepository : IBasicRepository<PendingRelease>
{
void DeleteByArtistId(int artistId);
void DeleteByArtistIds(List<int> artistIds);
List<PendingRelease> AllByArtistId(int artistId);
List<PendingRelease> WithoutFallback();
}
@ -18,9 +18,9 @@ namespace NzbDrone.Core.Download.Pending
{
}
public void DeleteByArtistId(int artistId)
public void DeleteByArtistIds(List<int> artistIds)
{
Delete(artistId);
Delete(x => artistIds.Contains(x.ArtistId));
}
public List<PendingRelease> AllByArtistId(int artistId)

View file

@ -31,7 +31,7 @@ namespace NzbDrone.Core.Download.Pending
}
public class PendingReleaseService : IPendingReleaseService,
IHandle<ArtistDeletedEvent>,
IHandle<ArtistsDeletedEvent>,
IHandle<AlbumGrabbedEvent>,
IHandle<RssSyncCompleteEvent>
{
@ -425,9 +425,9 @@ namespace NzbDrone.Core.Download.Pending
return 1;
}
public void Handle(ArtistDeletedEvent message)
public void Handle(ArtistsDeletedEvent message)
{
_repository.DeleteByArtistId(message.Artist.Id);
_repository.DeleteByArtistIds(message.Artists.Select(x => x.Id).ToList());
}
public void Handle(AlbumGrabbedEvent message)

View file

@ -8,7 +8,7 @@ namespace NzbDrone.Core.Extras.Files
public interface IExtraFileRepository<TExtraFile> : IBasicRepository<TExtraFile>
where TExtraFile : ExtraFile, new()
{
void DeleteForArtist(int artistId);
void DeleteForArtists(List<int> artistIds);
void DeleteForAlbum(int artistId, int albumId);
void DeleteForTrackFile(int trackFileId);
List<TExtraFile> GetFilesByArtist(int artistId);
@ -25,9 +25,9 @@ namespace NzbDrone.Core.Extras.Files
{
}
public void DeleteForArtist(int artistId)
public void DeleteForArtists(List<int> artistIds)
{
Delete(c => c.ArtistId == artistId);
Delete(c => artistIds.Contains(c.ArtistId));
}
public void DeleteForAlbum(int artistId, int albumId)

View file

@ -26,7 +26,7 @@ namespace NzbDrone.Core.Extras.Files
}
public abstract class ExtraFileService<TExtraFile> : IExtraFileService<TExtraFile>,
IHandleAsync<ArtistDeletedEvent>,
IHandleAsync<ArtistsDeletedEvent>,
IHandle<TrackFileDeletedEvent>
where TExtraFile : ExtraFile, new()
{
@ -95,10 +95,9 @@ namespace NzbDrone.Core.Extras.Files
_repository.DeleteMany(ids);
}
public void HandleAsync(ArtistDeletedEvent message)
public void HandleAsync(ArtistsDeletedEvent message)
{
_logger.Debug("Deleting Extra from database for artist: {0}", message.Artist);
_repository.DeleteForArtist(message.Artist.Id);
_repository.DeleteForArtists(message.Artists.Select(x => x.Id).ToList());
}
public void Handle(TrackFileDeletedEvent message)

View file

@ -8,7 +8,7 @@ using NzbDrone.Core.RootFolders;
namespace NzbDrone.Core.HealthCheck.Checks
{
[CheckOn(typeof(ArtistDeletedEvent))]
[CheckOn(typeof(ArtistsDeletedEvent))]
[CheckOn(typeof(ArtistMovedEvent))]
[CheckOn(typeof(TrackImportedEvent), CheckOnCondition.FailedOnly)]
[CheckOn(typeof(TrackImportFailedEvent), CheckOnCondition.SuccessfulOnly)]

View file

@ -16,7 +16,7 @@ namespace NzbDrone.Core.History
List<History> GetByArtist(int artistId, HistoryEventType? eventType);
List<History> GetByAlbum(int albumId, HistoryEventType? eventType);
List<History> FindDownloadHistory(int idArtistId, QualityModel quality);
void DeleteForArtist(int artistId);
void DeleteForArtists(List<int> artistIds);
List<History> Since(DateTime date, HistoryEventType? eventType);
}
@ -97,9 +97,9 @@ namespace NzbDrone.Core.History
allowed.Contains(h.EventType));
}
public void DeleteForArtist(int artistId)
public void DeleteForArtists(List<int> artistIds)
{
Delete(c => c.ArtistId == artistId);
Delete(c => artistIds.Contains(c.ArtistId));
}
protected override SqlBuilder PagedBuilder() => new SqlBuilder()

View file

@ -39,7 +39,7 @@ namespace NzbDrone.Core.History
IHandle<TrackFileDeletedEvent>,
IHandle<TrackFileRenamedEvent>,
IHandle<TrackFileRetaggedEvent>,
IHandle<ArtistDeletedEvent>,
IHandle<ArtistsDeletedEvent>,
IHandle<DownloadIgnoredEvent>
{
private readonly IHistoryRepository _historyRepository;
@ -365,9 +365,9 @@ namespace NzbDrone.Core.History
}
}
public void Handle(ArtistDeletedEvent message)
public void Handle(ArtistsDeletedEvent message)
{
_historyRepository.DeleteForArtist(message.Artist.Id);
_historyRepository.DeleteForArtists(message.Artists.Select(x => x.Id).ToList());
}
public void Handle(DownloadIgnoredEvent message)

View file

@ -18,7 +18,7 @@ namespace NzbDrone.Core.ImportLists.Exclusions
}
public class ImportListExclusionService : IImportListExclusionService,
IHandleAsync<ArtistDeletedEvent>,
IHandleAsync<ArtistsDeletedEvent>,
IHandleAsync<AlbumDeletedEvent>
{
private readonly IImportListExclusionRepository _repo;
@ -72,27 +72,32 @@ namespace NzbDrone.Core.ImportLists.Exclusions
return _repo.All().ToList();
}
public void HandleAsync(ArtistDeletedEvent message)
public void HandleAsync(ArtistsDeletedEvent message)
{
if (!message.AddImportListExclusion)
{
return;
}
var existingExclusion = _repo.FindByForeignId(message.Artist.ForeignArtistId);
var importExclusions = new List<ImportListExclusion>();
if (existingExclusion != null)
foreach (var artist in message.Artists)
{
return;
var existingExclusion = _repo.FindByForeignId(artist.ForeignArtistId);
if (existingExclusion != null)
{
return;
}
importExclusions.Add(new ImportListExclusion
{
ForeignId = artist.ForeignArtistId,
Name = artist.Name
});
}
var importExclusion = new ImportListExclusion
{
ForeignId = message.Artist.ForeignArtistId,
Name = message.Artist.Name
};
_repo.Insert(importExclusion);
_repo.InsertMany(importExclusions);
}
public void HandleAsync(AlbumDeletedEvent message)

View file

@ -25,7 +25,7 @@ namespace NzbDrone.Core.MediaCover
public class MediaCoverService :
IHandleAsync<ArtistRefreshCompleteEvent>,
IHandleAsync<ArtistDeletedEvent>,
IHandleAsync<ArtistsDeletedEvent>,
IHandleAsync<AlbumAddedEvent>,
IHandleAsync<AlbumDeletedEvent>,
IMapCoversToLocal
@ -293,12 +293,15 @@ namespace NzbDrone.Core.MediaCover
_eventAggregator.PublishEvent(new MediaCoversUpdatedEvent(message.Artist, updated));
}
public void HandleAsync(ArtistDeletedEvent message)
public void HandleAsync(ArtistsDeletedEvent message)
{
var path = GetArtistCoverPath(message.Artist.Id);
if (_diskProvider.FolderExists(path))
foreach (var artist in message.Artists)
{
_diskProvider.DeleteFolder(path, true);
var path = GetArtistCoverPath(artist.Id);
if (_diskProvider.FolderExists(path))
{
_diskProvider.DeleteFolder(path, true);
}
}
}

View file

@ -21,7 +21,7 @@ namespace NzbDrone.Core.MediaFiles
}
public class MediaFileDeletionService : IDeleteMediaFiles,
IHandleAsync<ArtistDeletedEvent>,
IHandleAsync<ArtistsDeletedEvent>,
IHandleAsync<AlbumDeletedEvent>,
IHandle<TrackFileDeletedEvent>
{
@ -99,36 +99,39 @@ namespace NzbDrone.Core.MediaFiles
_mediaFileService.Delete(trackFile, DeleteMediaFileReason.Manual);
}
public void HandleAsync(ArtistDeletedEvent message)
public void HandleAsync(ArtistsDeletedEvent message)
{
if (message.DeleteFiles)
{
var artist = message.Artist;
var artists = message.Artists;
var allArtists = _artistService.AllArtistPaths();
foreach (var s in allArtists)
foreach (var artist in artists)
{
if (s.Key == artist.Id)
foreach (var s in allArtists)
{
continue;
if (s.Key == artist.Id)
{
continue;
}
if (artist.Path.IsParentPath(s.Value))
{
_logger.Error("Artist path: '{0}' is a parent of another artist, not deleting files.", artist.Path);
return;
}
if (artist.Path.PathEquals(s.Value))
{
_logger.Error("Artist path: '{0}' is the same as another artist, not deleting files.", artist.Path);
return;
}
}
if (artist.Path.IsParentPath(s.Value))
if (_diskProvider.FolderExists(artist.Path))
{
_logger.Error("Artist path: '{0}' is a parent of another artist, not deleting files.", artist.Path);
return;
_recycleBinProvider.DeleteFolder(artist.Path);
}
if (artist.Path.PathEquals(s.Value))
{
_logger.Error("Artist path: '{0}' is the same as another artist, not deleting files.", artist.Path);
return;
}
}
if (_diskProvider.FolderExists(message.Artist.Path))
{
_recycleBinProvider.DeleteFolder(message.Artist.Path);
}
}
}

View file

@ -1,16 +1,17 @@
using System.Collections.Generic;
using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.Music.Events
{
public class ArtistDeletedEvent : IEvent
public class ArtistsDeletedEvent : IEvent
{
public Artist Artist { get; private set; }
public List<Artist> Artists { get; private set; }
public bool DeleteFiles { get; private set; }
public bool AddImportListExclusion { get; private set; }
public ArtistDeletedEvent(Artist artist, bool deleteFiles, bool addImportListExclusion)
public ArtistsDeletedEvent(List<Artist> artists, bool deleteFiles, bool addImportListExclusion)
{
Artist = artist;
Artists = artists;
DeleteFiles = deleteFiles;
AddImportListExclusion = addImportListExclusion;
}

View file

@ -42,7 +42,7 @@ namespace NzbDrone.Core.Music
}
public class AlbumService : IAlbumService,
IHandle<ArtistDeletedEvent>
IHandle<ArtistsDeletedEvent>
{
private readonly IAlbumRepository _albumRepository;
private readonly IEventAggregator _eventAggregator;
@ -289,9 +289,10 @@ namespace NzbDrone.Core.Music
}
}
public void Handle(ArtistDeletedEvent message)
public void Handle(ArtistsDeletedEvent message)
{
var albums = GetAlbumsByArtistMetadataId(message.Artist.ArtistMetadataId);
//TODO Do this in one call instead of one for each artist?
var albums = message.Artists.SelectMany(x => GetAlbumsByArtistMetadataId(x.ArtistMetadataId)).ToList();
DeleteMany(albums);
}
}

View file

@ -22,6 +22,7 @@ namespace NzbDrone.Core.Music
Artist FindByNameInexact(string title);
List<Artist> GetCandidates(string title);
void DeleteArtist(int artistId, bool deleteFiles, bool addImportListExclusion = false);
void DeleteArtists(List<int> artistIds, bool deleteFiles, bool addImportListExclusion = false);
List<Artist> GetAllArtists();
List<Artist> AllForTag(int tagId);
Artist UpdateArtist(Artist artist, bool publishUpdatedEvent = true);
@ -80,7 +81,21 @@ namespace NzbDrone.Core.Music
_cache.Clear();
var artist = _artistRepository.Get(artistId);
_artistRepository.Delete(artistId);
_eventAggregator.PublishEvent(new ArtistDeletedEvent(artist, deleteFiles, addImportListExclusion));
_eventAggregator.PublishEvent(new ArtistsDeletedEvent(new List<Artist> { artist }, deleteFiles, addImportListExclusion));
}
public void DeleteArtists(List<int> artistIds, bool deleteFiles, bool addExclusion = false)
{
var artistsToDelete = _artistRepository.Get(artistIds).ToList();
_artistRepository.DeleteMany(artistIds);
_eventAggregator.PublishEvent(new ArtistsDeletedEvent(artistsToDelete, deleteFiles, addExclusion));
foreach (var artist in artistsToDelete)
{
_logger.Info("Deleted artist {0}", artist);
}
}
public Artist FindById(string foreignArtistId)