2017-09-16 02:49:38 +00:00
|
|
|
using NLog;
|
2017-05-07 16:58:24 +00:00
|
|
|
using NzbDrone.Common.Extensions;
|
2018-11-11 04:50:21 +00:00
|
|
|
using NzbDrone.Core.Configuration;
|
2017-05-07 16:58:24 +00:00
|
|
|
using NzbDrone.Common.Instrumentation.Extensions;
|
|
|
|
using NzbDrone.Core.Exceptions;
|
|
|
|
using NzbDrone.Core.MediaFiles;
|
|
|
|
using NzbDrone.Core.Messaging.Commands;
|
|
|
|
using NzbDrone.Core.Messaging.Events;
|
2017-07-08 12:08:24 +00:00
|
|
|
using NzbDrone.Core.MetadataSource;
|
2017-05-07 16:58:24 +00:00
|
|
|
using NzbDrone.Core.Music.Commands;
|
|
|
|
using NzbDrone.Core.Music.Events;
|
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.IO;
|
|
|
|
using System.Linq;
|
2019-03-01 22:26:36 +00:00
|
|
|
using NzbDrone.Core.ImportLists.Exclusions;
|
2019-07-09 20:14:05 +00:00
|
|
|
using NzbDrone.Common.EnsureThat;
|
|
|
|
using NzbDrone.Core.History;
|
2017-05-07 16:58:24 +00:00
|
|
|
|
|
|
|
namespace NzbDrone.Core.Music
|
|
|
|
{
|
2019-07-09 20:14:05 +00:00
|
|
|
public class RefreshArtistService : RefreshEntityServiceBase<Artist, Album>, IExecute<RefreshArtistCommand>
|
2017-05-07 16:58:24 +00:00
|
|
|
{
|
|
|
|
private readonly IProvideArtistInfo _artistInfo;
|
|
|
|
private readonly IArtistService _artistService;
|
2018-01-18 02:28:47 +00:00
|
|
|
private readonly IAlbumService _albumService;
|
2017-06-18 02:27:01 +00:00
|
|
|
private readonly IRefreshAlbumService _refreshAlbumService;
|
2017-05-07 16:58:24 +00:00
|
|
|
private readonly IEventAggregator _eventAggregator;
|
2019-07-09 20:14:05 +00:00
|
|
|
private readonly IMediaFileService _mediaFileService;
|
|
|
|
private readonly IHistoryService _historyService;
|
2017-05-07 16:58:24 +00:00
|
|
|
private readonly IDiskScanService _diskScanService;
|
2017-05-07 22:50:07 +00:00
|
|
|
private readonly ICheckIfArtistShouldBeRefreshed _checkIfArtistShouldBeRefreshed;
|
2018-11-11 04:50:21 +00:00
|
|
|
private readonly IConfigService _configService;
|
2019-03-01 22:26:36 +00:00
|
|
|
private readonly IImportListExclusionService _importListExclusionService;
|
2017-05-07 16:58:24 +00:00
|
|
|
private readonly Logger _logger;
|
|
|
|
|
|
|
|
public RefreshArtistService(IProvideArtistInfo artistInfo,
|
|
|
|
IArtistService artistService,
|
2019-07-25 20:42:42 +00:00
|
|
|
IArtistMetadataService artistMetadataService,
|
2018-01-18 02:28:47 +00:00
|
|
|
IAlbumService albumService,
|
2017-06-18 02:27:01 +00:00
|
|
|
IRefreshAlbumService refreshAlbumService,
|
2017-05-07 16:58:24 +00:00
|
|
|
IEventAggregator eventAggregator,
|
2019-07-09 20:14:05 +00:00
|
|
|
IMediaFileService mediaFileService,
|
|
|
|
IHistoryService historyService,
|
2017-05-07 16:58:24 +00:00
|
|
|
IDiskScanService diskScanService,
|
2017-05-07 22:50:07 +00:00
|
|
|
ICheckIfArtistShouldBeRefreshed checkIfArtistShouldBeRefreshed,
|
2018-11-11 04:50:21 +00:00
|
|
|
IConfigService configService,
|
2019-03-01 22:26:36 +00:00
|
|
|
IImportListExclusionService importListExclusionService,
|
2017-05-07 16:58:24 +00:00
|
|
|
Logger logger)
|
2019-07-25 20:42:42 +00:00
|
|
|
: base(logger, artistMetadataService)
|
2017-05-07 16:58:24 +00:00
|
|
|
{
|
|
|
|
_artistInfo = artistInfo;
|
|
|
|
_artistService = artistService;
|
2018-01-18 02:28:47 +00:00
|
|
|
_albumService = albumService;
|
2017-06-18 02:27:01 +00:00
|
|
|
_refreshAlbumService = refreshAlbumService;
|
2017-05-07 16:58:24 +00:00
|
|
|
_eventAggregator = eventAggregator;
|
2019-07-09 20:14:05 +00:00
|
|
|
_mediaFileService = mediaFileService;
|
|
|
|
_historyService = historyService;
|
2017-05-07 16:58:24 +00:00
|
|
|
_diskScanService = diskScanService;
|
2017-05-07 22:50:07 +00:00
|
|
|
_checkIfArtistShouldBeRefreshed = checkIfArtistShouldBeRefreshed;
|
2018-11-11 04:50:21 +00:00
|
|
|
_configService = configService;
|
2019-03-01 22:26:36 +00:00
|
|
|
_importListExclusionService = importListExclusionService;
|
2017-05-07 16:58:24 +00:00
|
|
|
_logger = logger;
|
|
|
|
}
|
2019-07-09 20:14:05 +00:00
|
|
|
|
|
|
|
protected override RemoteData GetRemoteData(Artist local, List<Artist> remote)
|
2017-05-07 16:58:24 +00:00
|
|
|
{
|
2019-07-09 20:14:05 +00:00
|
|
|
var result = new RemoteData();
|
2017-05-07 16:58:24 +00:00
|
|
|
try
|
|
|
|
{
|
2019-07-09 20:14:05 +00:00
|
|
|
result.Entity = _artistInfo.GetArtistInfo(local.Metadata.Value.ForeignArtistId, local.MetadataProfileId);
|
|
|
|
result.Metadata = new List<ArtistMetadata> { result.Entity.Metadata.Value };
|
2017-05-07 16:58:24 +00:00
|
|
|
}
|
|
|
|
catch (ArtistNotFoundException)
|
|
|
|
{
|
2019-07-09 20:14:05 +00:00
|
|
|
_logger.Error($"Could not find artist with id {local.Metadata.Value.ForeignArtistId}");
|
2017-05-07 16:58:24 +00:00
|
|
|
}
|
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override bool ShouldDelete(Artist local)
|
|
|
|
{
|
|
|
|
return !_mediaFileService.GetFilesByArtist(local.Id).Any();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void LogProgress(Artist local)
|
|
|
|
{
|
|
|
|
_logger.ProgressInfo("Updating Info for {0}", local.Name);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override bool IsMerge(Artist local, Artist remote)
|
|
|
|
{
|
|
|
|
return local.ArtistMetadataId != remote.Metadata.Value.Id;
|
|
|
|
}
|
2019-03-15 12:10:45 +00:00
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
protected override UpdateResult UpdateEntity(Artist local, Artist remote)
|
|
|
|
{
|
|
|
|
UpdateResult result = UpdateResult.None;
|
|
|
|
|
|
|
|
if(!local.Metadata.Value.Equals(remote.Metadata.Value))
|
2017-05-07 16:58:24 +00:00
|
|
|
{
|
2019-07-09 20:14:05 +00:00
|
|
|
result = UpdateResult.UpdateTags;
|
2017-05-07 16:58:24 +00:00
|
|
|
}
|
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
local.CleanName = remote.CleanName;
|
|
|
|
local.SortName = remote.SortName;
|
|
|
|
local.LastInfoSync = DateTime.UtcNow;
|
2017-05-07 16:58:24 +00:00
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2019-07-09 20:14:05 +00:00
|
|
|
local.Path = new DirectoryInfo(local.Path).FullName;
|
|
|
|
local.Path = local.Path.GetActualCasing();
|
2017-05-07 16:58:24 +00:00
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
2019-07-09 20:14:05 +00:00
|
|
|
_logger.Warn(e, "Couldn't update artist path for " + local.Path);
|
2017-05-07 16:58:24 +00:00
|
|
|
}
|
2018-01-28 06:27:33 +00:00
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override UpdateResult MoveEntity(Artist local, Artist remote)
|
|
|
|
{
|
|
|
|
_logger.Debug($"Updating MusicBrainz id for {local} to {remote}");
|
|
|
|
|
|
|
|
// We are moving from one metadata to another (will already have been poplated)
|
|
|
|
local.ArtistMetadataId = remote.Metadata.Value.Id;
|
|
|
|
local.Metadata = remote.Metadata.Value;
|
2018-01-28 06:27:33 +00:00
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
// Update list exclusion if one exists
|
|
|
|
var importExclusion = _importListExclusionService.FindByForeignId(local.Metadata.Value.ForeignArtistId);
|
2018-01-28 06:27:33 +00:00
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
if (importExclusion != null)
|
2018-01-28 06:27:33 +00:00
|
|
|
{
|
2019-07-09 20:14:05 +00:00
|
|
|
importExclusion.ForeignId = remote.Metadata.Value.ForeignArtistId;
|
|
|
|
_importListExclusionService.Update(importExclusion);
|
|
|
|
}
|
2018-01-28 06:27:33 +00:00
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
// Do the standard update
|
|
|
|
UpdateEntity(local, remote);
|
|
|
|
|
|
|
|
// We know we need to update tags as artist id has changed
|
|
|
|
return UpdateResult.UpdateTags;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override UpdateResult MergeEntity(Artist local, Artist target, Artist remote)
|
|
|
|
{
|
|
|
|
_logger.Warn($"Artist {local} was replaced with {remote} because the original was a duplicate.");
|
|
|
|
|
|
|
|
// Update list exclusion if one exists
|
|
|
|
var importExclusionLocal = _importListExclusionService.FindByForeignId(local.Metadata.Value.ForeignArtistId);
|
|
|
|
|
|
|
|
if (importExclusionLocal != null)
|
|
|
|
{
|
|
|
|
var importExclusionTarget = _importListExclusionService.FindByForeignId(target.Metadata.Value.ForeignArtistId);
|
|
|
|
if (importExclusionTarget == null)
|
2018-01-28 06:27:33 +00:00
|
|
|
{
|
2019-07-09 20:14:05 +00:00
|
|
|
importExclusionLocal.ForeignId = remote.Metadata.Value.ForeignArtistId;
|
|
|
|
_importListExclusionService.Update(importExclusionLocal);
|
2018-01-28 06:27:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
// move any albums over to the new artist and remove the local artist
|
|
|
|
var albums = _albumService.GetAlbumsByArtist(local.Id);
|
|
|
|
albums.ForEach(x => x.ArtistMetadataId = target.ArtistMetadataId);
|
|
|
|
_albumService.UpdateMany(albums);
|
|
|
|
_artistService.DeleteArtist(local.Id, false);
|
2019-01-12 16:56:13 +00:00
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
// Update history entries to new id
|
|
|
|
var items = _historyService.GetByArtist(local.Id, null);
|
|
|
|
items.ForEach(x => x.ArtistId = target.Id);
|
|
|
|
_historyService.UpdateMany(items);
|
2019-03-15 12:10:45 +00:00
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
// We know we need to update tags as artist id has changed
|
|
|
|
return UpdateResult.UpdateTags;
|
|
|
|
}
|
2018-01-28 06:27:33 +00:00
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
protected override Artist GetEntityByForeignId(Artist local)
|
|
|
|
{
|
|
|
|
return _artistService.FindById(local.ForeignArtistId);
|
|
|
|
}
|
2018-03-20 01:56:05 +00:00
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
protected override void SaveEntity(Artist local)
|
|
|
|
{
|
|
|
|
_artistService.UpdateArtist(local);
|
|
|
|
}
|
2019-06-08 19:13:58 +00:00
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
protected override void DeleteEntity(Artist local, bool deleteFiles)
|
|
|
|
{
|
|
|
|
_artistService.DeleteArtist(local.Id, true);
|
|
|
|
}
|
2019-06-08 19:13:58 +00:00
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
protected override List<Album> GetRemoteChildren(Artist remote)
|
|
|
|
{
|
|
|
|
return remote.Albums.Value.DistinctBy(m => m.ForeignAlbumId).ToList();
|
|
|
|
}
|
2019-06-08 19:13:58 +00:00
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
protected override List<Album> GetLocalChildren(Artist entity, List<Album> remoteChildren)
|
|
|
|
{
|
|
|
|
return _albumService.GetAlbumsForRefresh(entity.ArtistMetadataId,
|
|
|
|
remoteChildren.Select(x => x.ForeignAlbumId)
|
|
|
|
.Concat(remoteChildren.SelectMany(x => x.OldForeignAlbumIds)));
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override Tuple<Album, List<Album> > GetMatchingExistingChildren(List<Album> existingChildren, Album remote)
|
|
|
|
{
|
|
|
|
var existingChild = existingChildren.SingleOrDefault(x => x.ForeignAlbumId == remote.ForeignAlbumId);
|
|
|
|
var mergeChildren = existingChildren.Where(x => remote.OldForeignAlbumIds.Contains(x.ForeignAlbumId)).ToList();
|
|
|
|
return Tuple.Create(existingChild, mergeChildren);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void PrepareNewChild(Album child, Artist entity)
|
|
|
|
{
|
|
|
|
child.Artist = entity;
|
|
|
|
child.ArtistMetadata = entity.Metadata.Value;
|
|
|
|
child.ArtistMetadataId = entity.Metadata.Value.Id;
|
|
|
|
child.Added = DateTime.UtcNow;
|
|
|
|
child.LastInfoSync = DateTime.MinValue;
|
|
|
|
child.ProfileId = entity.QualityProfileId;
|
|
|
|
child.Monitored = entity.Monitored;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void PrepareExistingChild(Album local, Album remote, Artist entity)
|
|
|
|
{
|
|
|
|
local.Artist = entity;
|
|
|
|
local.ArtistMetadata = entity.Metadata.Value;
|
|
|
|
local.ArtistMetadataId = entity.Metadata.Value.Id;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void AddChildren(List<Album> children)
|
|
|
|
{
|
|
|
|
_albumService.InsertMany(children);
|
2017-05-07 16:58:24 +00:00
|
|
|
}
|
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
protected override bool RefreshChildren(SortedChildren localChildren, List<Album> remoteChildren, bool forceChildRefresh, bool forceUpdateFileTags)
|
2018-01-28 06:27:33 +00:00
|
|
|
{
|
2019-07-09 20:14:05 +00:00
|
|
|
// we always want to end up refreshing the albums since we don't get have proper data
|
|
|
|
Ensure.That(localChildren.UpToDate.Count, () => localChildren.UpToDate.Count).IsLessThanOrEqualTo(0);
|
|
|
|
return _refreshAlbumService.RefreshAlbumInfo(localChildren.All, remoteChildren, forceChildRefresh, forceUpdateFileTags);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void PublishEntityUpdatedEvent(Artist entity)
|
|
|
|
{
|
|
|
|
_eventAggregator.PublishEvent(new ArtistUpdatedEvent(entity));
|
|
|
|
}
|
2018-01-28 06:27:33 +00:00
|
|
|
|
2019-07-19 12:40:31 +00:00
|
|
|
protected virtual void PublishRefreshCompleteEvent(Artist entity)
|
|
|
|
{
|
|
|
|
_eventAggregator.PublishEvent(new ArtistRefreshCompleteEvent(entity));
|
|
|
|
}
|
|
|
|
|
2019-07-09 20:14:05 +00:00
|
|
|
protected override void PublishChildrenUpdatedEvent(Artist entity, List<Album> newChildren, List<Album> updateChildren)
|
|
|
|
{
|
|
|
|
_eventAggregator.PublishEvent(new AlbumInfoRefreshedEvent(entity, newChildren, updateChildren));
|
2018-01-28 06:27:33 +00:00
|
|
|
}
|
|
|
|
|
2019-06-08 19:13:58 +00:00
|
|
|
private void RescanArtist(Artist artist, bool isNew, CommandTrigger trigger, bool infoUpdated)
|
2018-08-08 00:57:15 +00:00
|
|
|
{
|
2018-11-11 04:50:21 +00:00
|
|
|
var rescanAfterRefresh = _configService.RescanAfterRefresh;
|
|
|
|
var shouldRescan = true;
|
|
|
|
|
|
|
|
if (isNew)
|
|
|
|
{
|
2019-06-08 19:13:58 +00:00
|
|
|
_logger.Trace("Forcing rescan of {0}. Reason: New artist", artist);
|
2018-11-11 04:50:21 +00:00
|
|
|
shouldRescan = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (rescanAfterRefresh == RescanAfterRefreshType.Never)
|
|
|
|
{
|
2019-06-08 19:13:58 +00:00
|
|
|
_logger.Trace("Skipping rescan of {0}. Reason: never recan after refresh", artist);
|
2018-11-11 04:50:21 +00:00
|
|
|
shouldRescan = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (rescanAfterRefresh == RescanAfterRefreshType.AfterManual && trigger != CommandTrigger.Manual)
|
|
|
|
{
|
2019-06-08 19:13:58 +00:00
|
|
|
_logger.Trace("Skipping rescan of {0}. Reason: not after automatic scans", artist);
|
2018-11-11 04:50:21 +00:00
|
|
|
shouldRescan = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!shouldRescan)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-08-08 00:57:15 +00:00
|
|
|
try
|
|
|
|
{
|
2019-06-08 19:13:58 +00:00
|
|
|
// If some metadata has been updated then rescan unmatched files.
|
|
|
|
// Otherwise only scan files that haven't been seen before.
|
|
|
|
var filter = infoUpdated ? FilterFilesType.Matched : FilterFilesType.Known;
|
|
|
|
_diskScanService.Scan(artist, filter);
|
2018-08-08 00:57:15 +00:00
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
|
|
|
_logger.Error(e, "Couldn't rescan artist {0}", artist);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-07 16:58:24 +00:00
|
|
|
public void Execute(RefreshArtistCommand message)
|
|
|
|
{
|
2018-11-11 04:50:21 +00:00
|
|
|
var trigger = message.Trigger;
|
|
|
|
var isNew = message.IsNewArtist;
|
2017-05-07 16:58:24 +00:00
|
|
|
|
|
|
|
if (message.ArtistId.HasValue)
|
|
|
|
{
|
|
|
|
var artist = _artistService.GetArtist(message.ArtistId.Value);
|
2019-06-08 19:13:58 +00:00
|
|
|
bool updated = false;
|
2018-08-08 00:57:15 +00:00
|
|
|
try
|
|
|
|
{
|
2019-07-09 20:14:05 +00:00
|
|
|
updated = RefreshEntityInfo(artist, null, true, false);
|
2019-06-08 19:13:58 +00:00
|
|
|
RescanArtist(artist, isNew, trigger, updated);
|
2018-08-08 00:57:15 +00:00
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
|
|
|
_logger.Error(e, "Couldn't refresh info for {0}", artist);
|
2019-06-08 19:13:58 +00:00
|
|
|
RescanArtist(artist, isNew, trigger, updated);
|
2018-08-08 00:57:15 +00:00
|
|
|
throw;
|
|
|
|
}
|
2017-05-07 16:58:24 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-06-13 02:02:17 +00:00
|
|
|
var allArtists = _artistService.GetAllArtists().OrderBy(c => c.Name).ToList();
|
2017-05-07 16:58:24 +00:00
|
|
|
|
|
|
|
foreach (var artist in allArtists)
|
|
|
|
{
|
2018-05-28 07:49:34 +00:00
|
|
|
var manualTrigger = message.Trigger == CommandTrigger.Manual;
|
2018-11-11 04:50:21 +00:00
|
|
|
|
2018-05-28 07:49:34 +00:00
|
|
|
if (manualTrigger || _checkIfArtistShouldBeRefreshed.ShouldRefresh(artist))
|
2017-05-07 16:58:24 +00:00
|
|
|
{
|
2019-06-08 19:13:58 +00:00
|
|
|
bool updated = false;
|
2017-05-07 16:58:24 +00:00
|
|
|
try
|
|
|
|
{
|
2019-07-09 20:14:05 +00:00
|
|
|
updated = RefreshEntityInfo(artist, null, manualTrigger, false);
|
2017-05-07 16:58:24 +00:00
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
|
|
|
_logger.Error(e, "Couldn't refresh info for {0}", artist);
|
|
|
|
}
|
2018-11-11 04:50:21 +00:00
|
|
|
|
2019-06-08 19:13:58 +00:00
|
|
|
RescanArtist(artist, false, trigger, updated);
|
2017-05-07 16:58:24 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-08-08 00:57:15 +00:00
|
|
|
_logger.Info("Skipping refresh of artist: {0}", artist.Name);
|
2019-06-08 19:13:58 +00:00
|
|
|
RescanArtist(artist, false, trigger, false);
|
2017-05-07 16:58:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|