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;
|
|
|
|
using System.Text;
|
2019-03-01 22:26:36 +00:00
|
|
|
using NzbDrone.Core.ImportLists.Exclusions;
|
2017-05-07 16:58:24 +00:00
|
|
|
|
|
|
|
namespace NzbDrone.Core.Music
|
|
|
|
{
|
|
|
|
public class RefreshArtistService : IExecute<RefreshArtistCommand>
|
|
|
|
{
|
|
|
|
private readonly IProvideArtistInfo _artistInfo;
|
|
|
|
private readonly IArtistService _artistService;
|
2018-01-28 06:27:33 +00:00
|
|
|
private readonly IAddAlbumService _addAlbumService;
|
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-07-03 18:39:06 +00:00
|
|
|
private readonly IRefreshTrackService _refreshTrackService;
|
2019-03-15 12:10:45 +00:00
|
|
|
private readonly IAudioTagService _audioTagService;
|
2017-05-07 16:58:24 +00:00
|
|
|
private readonly IEventAggregator _eventAggregator;
|
|
|
|
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,
|
2018-01-28 06:27:33 +00:00
|
|
|
IAddAlbumService addAlbumService,
|
2018-01-18 02:28:47 +00:00
|
|
|
IAlbumService albumService,
|
2017-06-18 02:27:01 +00:00
|
|
|
IRefreshAlbumService refreshAlbumService,
|
2017-07-03 18:39:06 +00:00
|
|
|
IRefreshTrackService refreshTrackService,
|
2019-03-15 12:10:45 +00:00
|
|
|
IAudioTagService audioTagService,
|
2017-05-07 16:58:24 +00:00
|
|
|
IEventAggregator eventAggregator,
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
_artistInfo = artistInfo;
|
|
|
|
_artistService = artistService;
|
2018-01-28 06:27:33 +00:00
|
|
|
_addAlbumService = addAlbumService;
|
2018-01-18 02:28:47 +00:00
|
|
|
_albumService = albumService;
|
2017-06-18 02:27:01 +00:00
|
|
|
_refreshAlbumService = refreshAlbumService;
|
2017-07-03 18:39:06 +00:00
|
|
|
_refreshTrackService = refreshTrackService;
|
2019-03-15 12:10:45 +00:00
|
|
|
_audioTagService = audioTagService;
|
2017-05-07 16:58:24 +00:00
|
|
|
_eventAggregator = eventAggregator;
|
|
|
|
_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-06-08 19:13:58 +00:00
|
|
|
private bool RefreshArtistInfo(Artist artist, bool forceAlbumRefresh)
|
2017-05-07 16:58:24 +00:00
|
|
|
{
|
2017-06-13 02:02:17 +00:00
|
|
|
_logger.ProgressInfo("Updating Info for {0}", artist.Name);
|
2019-06-08 19:13:58 +00:00
|
|
|
bool updated = false;
|
2017-05-07 16:58:24 +00:00
|
|
|
|
2018-12-15 00:02:43 +00:00
|
|
|
Artist artistInfo;
|
2017-05-07 16:58:24 +00:00
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2018-12-15 00:02:43 +00:00
|
|
|
artistInfo = _artistInfo.GetArtistInfo(artist.Metadata.Value.ForeignArtistId, artist.MetadataProfileId);
|
2017-05-07 16:58:24 +00:00
|
|
|
}
|
|
|
|
catch (ArtistNotFoundException)
|
|
|
|
{
|
2019-03-15 12:10:45 +00:00
|
|
|
_logger.Error($"Artist {artist} was not found, it may have been removed from Metadata sources.");
|
2019-06-08 19:13:58 +00:00
|
|
|
return updated;
|
2017-05-07 16:58:24 +00:00
|
|
|
}
|
|
|
|
|
2019-03-15 12:10:45 +00:00
|
|
|
var forceUpdateFileTags = artist.Name != artistInfo.Name;
|
2019-06-08 19:13:58 +00:00
|
|
|
updated |= forceUpdateFileTags;
|
2019-03-15 12:10:45 +00:00
|
|
|
|
2018-12-15 00:02:43 +00:00
|
|
|
if (artist.Metadata.Value.ForeignArtistId != artistInfo.Metadata.Value.ForeignArtistId)
|
2017-05-07 16:58:24 +00:00
|
|
|
{
|
2019-03-15 12:10:45 +00:00
|
|
|
_logger.Warn($"Artist {artist} was replaced with {artistInfo} because the original was a duplicate.");
|
2019-03-01 22:26:36 +00:00
|
|
|
|
|
|
|
// Update list exclusion if one exists
|
|
|
|
var importExclusion = _importListExclusionService.FindByForeignId(artist.Metadata.Value.ForeignArtistId);
|
|
|
|
|
|
|
|
if (importExclusion != null)
|
|
|
|
{
|
|
|
|
importExclusion.ForeignId = artistInfo.Metadata.Value.ForeignArtistId;
|
|
|
|
_importListExclusionService.Update(importExclusion);
|
|
|
|
}
|
|
|
|
|
2018-12-15 00:02:43 +00:00
|
|
|
artist.Metadata.Value.ForeignArtistId = artistInfo.Metadata.Value.ForeignArtistId;
|
2019-03-15 12:10:45 +00:00
|
|
|
forceUpdateFileTags = true;
|
2019-06-08 19:13:58 +00:00
|
|
|
updated = true;
|
2017-05-07 16:58:24 +00:00
|
|
|
}
|
|
|
|
|
2018-12-15 00:02:43 +00:00
|
|
|
artist.Metadata.Value.ApplyChanges(artistInfo.Metadata.Value);
|
2017-06-13 02:02:17 +00:00
|
|
|
artist.CleanName = artistInfo.CleanName;
|
2017-08-14 02:58:42 +00:00
|
|
|
artist.SortName = artistInfo.SortName;
|
2017-05-07 16:58:24 +00:00
|
|
|
artist.LastInfoSync = DateTime.UtcNow;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
artist.Path = new DirectoryInfo(artist.Path).FullName;
|
|
|
|
artist.Path = artist.Path.GetActualCasing();
|
|
|
|
}
|
|
|
|
catch (Exception e)
|
|
|
|
{
|
|
|
|
_logger.Warn(e, "Couldn't update artist path for " + artist.Path);
|
|
|
|
}
|
2018-01-28 06:27:33 +00:00
|
|
|
|
2019-03-15 12:10:45 +00:00
|
|
|
var remoteAlbums = artistInfo.Albums.Value.DistinctBy(m => m.ForeignAlbumId).ToList();
|
2018-01-28 06:27:33 +00:00
|
|
|
|
|
|
|
// Get list of DB current db albums for artist
|
2019-03-15 12:10:45 +00:00
|
|
|
var existingAlbums = _albumService.GetAlbumsForRefresh(artist.ArtistMetadataId, remoteAlbums.Select(x => x.ForeignAlbumId));
|
2018-01-28 06:27:33 +00:00
|
|
|
var newAlbumsList = new List<Album>();
|
|
|
|
var updateAlbumsList = new List<Album>();
|
|
|
|
|
|
|
|
// Cycle thru albums
|
|
|
|
foreach (var album in remoteAlbums)
|
|
|
|
{
|
|
|
|
// Check for album in existing albums, if not set properties and add to new list
|
2019-03-15 12:10:45 +00:00
|
|
|
var albumToRefresh = existingAlbums.SingleOrDefault(s => s.ForeignAlbumId == album.ForeignAlbumId);
|
2018-01-28 06:27:33 +00:00
|
|
|
|
|
|
|
if (albumToRefresh != null)
|
|
|
|
{
|
2019-03-15 12:10:45 +00:00
|
|
|
albumToRefresh.Artist = artist;
|
2018-01-28 06:27:33 +00:00
|
|
|
existingAlbums.Remove(albumToRefresh);
|
|
|
|
updateAlbumsList.Add(albumToRefresh);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-03-15 12:10:45 +00:00
|
|
|
album.Artist = artist;
|
2018-01-28 06:27:33 +00:00
|
|
|
newAlbumsList.Add(album);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-12 16:56:13 +00:00
|
|
|
_logger.Debug("{0} Deleting {1}, Updating {2}, Adding {3} albums",
|
|
|
|
artist, existingAlbums.Count, updateAlbumsList.Count, newAlbumsList.Count);
|
|
|
|
|
2019-03-15 12:10:45 +00:00
|
|
|
// before deleting anything, remove musicbrainz ids for things we are deleting
|
|
|
|
_audioTagService.RemoveMusicBrainzTags(existingAlbums);
|
|
|
|
|
2018-12-15 00:02:43 +00:00
|
|
|
// Delete old albums first - this avoids errors if albums have been merged and we'll
|
|
|
|
// end up trying to duplicate an existing release under a new album
|
|
|
|
_albumService.DeleteMany(existingAlbums);
|
|
|
|
|
2018-01-28 06:27:33 +00:00
|
|
|
// Update new albums with artist info and correct monitored status
|
|
|
|
newAlbumsList = UpdateAlbums(artist, newAlbumsList);
|
|
|
|
_addAlbumService.AddAlbums(newAlbumsList);
|
|
|
|
|
2019-06-08 19:13:58 +00:00
|
|
|
updated |= existingAlbums.Any() || newAlbumsList.Any();
|
|
|
|
|
|
|
|
updated |= _refreshAlbumService.RefreshAlbumInfo(updateAlbumsList, forceAlbumRefresh, forceUpdateFileTags);
|
2018-01-28 06:27:33 +00:00
|
|
|
|
2019-04-16 00:52:43 +00:00
|
|
|
// Do this last so artist only marked as refreshed if refresh of tracks / albums completed successfully
|
|
|
|
_artistService.UpdateArtist(artist);
|
2018-03-20 01:56:05 +00:00
|
|
|
|
2019-04-16 00:52:43 +00:00
|
|
|
_eventAggregator.PublishEvent(new AlbumInfoRefreshedEvent(artist, newAlbumsList, updateAlbumsList));
|
2019-06-08 19:13:58 +00:00
|
|
|
|
|
|
|
if (updated)
|
|
|
|
{
|
|
|
|
_eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist));
|
|
|
|
}
|
|
|
|
|
2019-04-16 00:52:43 +00:00
|
|
|
_logger.Debug("Finished artist refresh for {0}", artist.Name);
|
2019-06-08 19:13:58 +00:00
|
|
|
|
|
|
|
return updated;
|
2017-05-07 16:58:24 +00:00
|
|
|
}
|
|
|
|
|
2018-01-28 06:27:33 +00:00
|
|
|
private List<Album> UpdateAlbums(Artist artist, List<Album> albumsToUpdate)
|
|
|
|
{
|
|
|
|
foreach (var album in albumsToUpdate)
|
|
|
|
{
|
2019-02-23 22:39:11 +00:00
|
|
|
album.ProfileId = artist.QualityProfileId;
|
2018-01-28 06:27:33 +00:00
|
|
|
album.Monitored = artist.Monitored;
|
|
|
|
}
|
|
|
|
|
|
|
|
return albumsToUpdate;
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
_eventAggregator.PublishEvent(new ArtistRefreshStartingEvent(trigger == CommandTrigger.Manual));
|
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-06-08 19:13:58 +00:00
|
|
|
updated = RefreshArtistInfo(artist, true);
|
|
|
|
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-06-08 19:13:58 +00:00
|
|
|
updated = RefreshArtistInfo(artist, manualTrigger);
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|