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>(); var resource = Request.Body.FromJson<ArtistEditorResource>();
foreach (var artistId in resource.ArtistIds) _artistService.DeleteArtists(resource.ArtistIds, resource.DeleteFiles);
{
_artistService.DeleteArtist(artistId, resource.DeleteFiles);
}
return new object(); return new object();
} }

View file

@ -29,7 +29,7 @@ namespace Lidarr.Api.V1.Artist
IHandle<TrackFileDeletedEvent>, IHandle<TrackFileDeletedEvent>,
IHandle<ArtistUpdatedEvent>, IHandle<ArtistUpdatedEvent>,
IHandle<ArtistEditedEvent>, IHandle<ArtistEditedEvent>,
IHandle<ArtistDeletedEvent>, IHandle<ArtistsDeletedEvent>,
IHandle<ArtistRenamedEvent>, IHandle<ArtistRenamedEvent>,
IHandle<MediaCoversUpdatedEvent> IHandle<MediaCoversUpdatedEvent>
{ {
@ -285,9 +285,12 @@ namespace Lidarr.Api.V1.Artist
BroadcastResourceChange(ModelAction.Updated, GetArtistResource(message.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) public void Handle(ArtistRenamedEvent message)

View file

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

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Music; using NzbDrone.Core.Music;
@ -9,7 +9,8 @@ namespace NzbDrone.Core.Blacklisting
{ {
List<Blacklist> BlacklistedByTitle(int artistId, string sourceTitle); List<Blacklist> BlacklistedByTitle(int artistId, string sourceTitle);
List<Blacklist> BlacklistedByTorrentInfoHash(int artistId, string torrentInfoHash); 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 public class BlacklistRepository : BasicRepository<Blacklist>, IBlacklistRepository
@ -29,9 +30,14 @@ namespace NzbDrone.Core.Blacklisting
return Query(e => e.ArtistId == artistId && e.TorrentInfoHash.Contains(torrentInfoHash)); 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); 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>, IExecute<ClearBlacklistCommand>,
IHandle<DownloadFailedEvent>, IHandle<DownloadFailedEvent>,
IHandleAsync<ArtistDeletedEvent> IHandleAsync<ArtistsDeletedEvent>
{ {
private readonly IBlacklistRepository _blacklistRepository; private readonly IBlacklistRepository _blacklistRepository;
@ -161,11 +161,9 @@ namespace NzbDrone.Core.Blacklisting
_blacklistRepository.Insert(blacklist); _blacklistRepository.Insert(blacklist);
} }
public void HandleAsync(ArtistDeletedEvent message) public void HandleAsync(ArtistsDeletedEvent message)
{ {
var blacklisted = _blacklistRepository.BlacklistedByArtist(message.Artist.Id); _blacklistRepository.DeleteForArtists(message.Artists.Select(x => x.Id).ToList());
_blacklistRepository.DeleteMany(blacklisted);
} }
} }
} }

View file

@ -6,7 +6,7 @@ namespace NzbDrone.Core.Download.Pending
{ {
public interface IPendingReleaseRepository : IBasicRepository<PendingRelease> public interface IPendingReleaseRepository : IBasicRepository<PendingRelease>
{ {
void DeleteByArtistId(int artistId); void DeleteByArtistIds(List<int> artistIds);
List<PendingRelease> AllByArtistId(int artistId); List<PendingRelease> AllByArtistId(int artistId);
List<PendingRelease> WithoutFallback(); 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) public List<PendingRelease> AllByArtistId(int artistId)

View file

@ -31,7 +31,7 @@ namespace NzbDrone.Core.Download.Pending
} }
public class PendingReleaseService : IPendingReleaseService, public class PendingReleaseService : IPendingReleaseService,
IHandle<ArtistDeletedEvent>, IHandle<ArtistsDeletedEvent>,
IHandle<AlbumGrabbedEvent>, IHandle<AlbumGrabbedEvent>,
IHandle<RssSyncCompleteEvent> IHandle<RssSyncCompleteEvent>
{ {
@ -425,9 +425,9 @@ namespace NzbDrone.Core.Download.Pending
return 1; 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) public void Handle(AlbumGrabbedEvent message)

View file

@ -8,7 +8,7 @@ namespace NzbDrone.Core.Extras.Files
public interface IExtraFileRepository<TExtraFile> : IBasicRepository<TExtraFile> public interface IExtraFileRepository<TExtraFile> : IBasicRepository<TExtraFile>
where TExtraFile : ExtraFile, new() where TExtraFile : ExtraFile, new()
{ {
void DeleteForArtist(int artistId); void DeleteForArtists(List<int> artistIds);
void DeleteForAlbum(int artistId, int albumId); void DeleteForAlbum(int artistId, int albumId);
void DeleteForTrackFile(int trackFileId); void DeleteForTrackFile(int trackFileId);
List<TExtraFile> GetFilesByArtist(int artistId); 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) 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>, public abstract class ExtraFileService<TExtraFile> : IExtraFileService<TExtraFile>,
IHandleAsync<ArtistDeletedEvent>, IHandleAsync<ArtistsDeletedEvent>,
IHandle<TrackFileDeletedEvent> IHandle<TrackFileDeletedEvent>
where TExtraFile : ExtraFile, new() where TExtraFile : ExtraFile, new()
{ {
@ -95,10 +95,9 @@ namespace NzbDrone.Core.Extras.Files
_repository.DeleteMany(ids); _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.DeleteForArtists(message.Artists.Select(x => x.Id).ToList());
_repository.DeleteForArtist(message.Artist.Id);
} }
public void Handle(TrackFileDeletedEvent message) public void Handle(TrackFileDeletedEvent message)

View file

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

View file

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

View file

@ -39,7 +39,7 @@ namespace NzbDrone.Core.History
IHandle<TrackFileDeletedEvent>, IHandle<TrackFileDeletedEvent>,
IHandle<TrackFileRenamedEvent>, IHandle<TrackFileRenamedEvent>,
IHandle<TrackFileRetaggedEvent>, IHandle<TrackFileRetaggedEvent>,
IHandle<ArtistDeletedEvent>, IHandle<ArtistsDeletedEvent>,
IHandle<DownloadIgnoredEvent> IHandle<DownloadIgnoredEvent>
{ {
private readonly IHistoryRepository _historyRepository; 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) public void Handle(DownloadIgnoredEvent message)

View file

@ -18,7 +18,7 @@ namespace NzbDrone.Core.ImportLists.Exclusions
} }
public class ImportListExclusionService : IImportListExclusionService, public class ImportListExclusionService : IImportListExclusionService,
IHandleAsync<ArtistDeletedEvent>, IHandleAsync<ArtistsDeletedEvent>,
IHandleAsync<AlbumDeletedEvent> IHandleAsync<AlbumDeletedEvent>
{ {
private readonly IImportListExclusionRepository _repo; private readonly IImportListExclusionRepository _repo;
@ -72,27 +72,32 @@ namespace NzbDrone.Core.ImportLists.Exclusions
return _repo.All().ToList(); return _repo.All().ToList();
} }
public void HandleAsync(ArtistDeletedEvent message) public void HandleAsync(ArtistsDeletedEvent message)
{ {
if (!message.AddImportListExclusion) if (!message.AddImportListExclusion)
{ {
return; 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 _repo.InsertMany(importExclusions);
{
ForeignId = message.Artist.ForeignArtistId,
Name = message.Artist.Name
};
_repo.Insert(importExclusion);
} }
public void HandleAsync(AlbumDeletedEvent message) public void HandleAsync(AlbumDeletedEvent message)

View file

@ -25,7 +25,7 @@ namespace NzbDrone.Core.MediaCover
public class MediaCoverService : public class MediaCoverService :
IHandleAsync<ArtistRefreshCompleteEvent>, IHandleAsync<ArtistRefreshCompleteEvent>,
IHandleAsync<ArtistDeletedEvent>, IHandleAsync<ArtistsDeletedEvent>,
IHandleAsync<AlbumAddedEvent>, IHandleAsync<AlbumAddedEvent>,
IHandleAsync<AlbumDeletedEvent>, IHandleAsync<AlbumDeletedEvent>,
IMapCoversToLocal IMapCoversToLocal
@ -293,12 +293,15 @@ namespace NzbDrone.Core.MediaCover
_eventAggregator.PublishEvent(new MediaCoversUpdatedEvent(message.Artist, updated)); _eventAggregator.PublishEvent(new MediaCoversUpdatedEvent(message.Artist, updated));
} }
public void HandleAsync(ArtistDeletedEvent message) public void HandleAsync(ArtistsDeletedEvent message)
{ {
var path = GetArtistCoverPath(message.Artist.Id); foreach (var artist in message.Artists)
if (_diskProvider.FolderExists(path))
{ {
_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, public class MediaFileDeletionService : IDeleteMediaFiles,
IHandleAsync<ArtistDeletedEvent>, IHandleAsync<ArtistsDeletedEvent>,
IHandleAsync<AlbumDeletedEvent>, IHandleAsync<AlbumDeletedEvent>,
IHandle<TrackFileDeletedEvent> IHandle<TrackFileDeletedEvent>
{ {
@ -99,36 +99,39 @@ namespace NzbDrone.Core.MediaFiles
_mediaFileService.Delete(trackFile, DeleteMediaFileReason.Manual); _mediaFileService.Delete(trackFile, DeleteMediaFileReason.Manual);
} }
public void HandleAsync(ArtistDeletedEvent message) public void HandleAsync(ArtistsDeletedEvent message)
{ {
if (message.DeleteFiles) if (message.DeleteFiles)
{ {
var artist = message.Artist; var artists = message.Artists;
var allArtists = _artistService.AllArtistPaths(); 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); _recycleBinProvider.DeleteFolder(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 (_diskProvider.FolderExists(message.Artist.Path))
{
_recycleBinProvider.DeleteFolder(message.Artist.Path);
} }
} }
} }

View file

@ -1,16 +1,17 @@
using System.Collections.Generic;
using NzbDrone.Common.Messaging; using NzbDrone.Common.Messaging;
namespace NzbDrone.Core.Music.Events 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 DeleteFiles { get; private set; }
public bool AddImportListExclusion { 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; DeleteFiles = deleteFiles;
AddImportListExclusion = addImportListExclusion; AddImportListExclusion = addImportListExclusion;
} }

View file

@ -42,7 +42,7 @@ namespace NzbDrone.Core.Music
} }
public class AlbumService : IAlbumService, public class AlbumService : IAlbumService,
IHandle<ArtistDeletedEvent> IHandle<ArtistsDeletedEvent>
{ {
private readonly IAlbumRepository _albumRepository; private readonly IAlbumRepository _albumRepository;
private readonly IEventAggregator _eventAggregator; 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); DeleteMany(albums);
} }
} }

View file

@ -22,6 +22,7 @@ namespace NzbDrone.Core.Music
Artist FindByNameInexact(string title); Artist FindByNameInexact(string title);
List<Artist> GetCandidates(string title); List<Artist> GetCandidates(string title);
void DeleteArtist(int artistId, bool deleteFiles, bool addImportListExclusion = false); void DeleteArtist(int artistId, bool deleteFiles, bool addImportListExclusion = false);
void DeleteArtists(List<int> artistIds, bool deleteFiles, bool addImportListExclusion = false);
List<Artist> GetAllArtists(); List<Artist> GetAllArtists();
List<Artist> AllForTag(int tagId); List<Artist> AllForTag(int tagId);
Artist UpdateArtist(Artist artist, bool publishUpdatedEvent = true); Artist UpdateArtist(Artist artist, bool publishUpdatedEvent = true);
@ -80,7 +81,21 @@ namespace NzbDrone.Core.Music
_cache.Clear(); _cache.Clear();
var artist = _artistRepository.Get(artistId); var artist = _artistRepository.Get(artistId);
_artistRepository.Delete(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) public Artist FindById(string foreignArtistId)