using NLog; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Music.Events; using System; using System.Collections.Generic; using System.Linq; using NzbDrone.Core.Datastore; using NzbDrone.Core.Parser; using NzbDrone.Common.Extensions; namespace NzbDrone.Core.Music { public interface IAlbumService { Album GetAlbum(int albumId); List GetAlbums(IEnumerable albumIds); List GetAlbumsByArtist(int artistId); Album AddAlbum(Album newAlbum); List AddAlbums(List newAlbums); Album FindById(string spotifyId); Album FindByTitle(int artistId, string title); Album FindByTitleInexact(int artistId, string title); void DeleteAlbum(int albumId, bool deleteFiles); List GetAllAlbums(); Album UpdateAlbum(Album album); List UpdateAlbums(List album); void SetAlbumMonitored(int albumId, bool monitored); void SetMonitored(IEnumerable ids, bool monitored); PagingSpec AlbumsWithoutFiles(PagingSpec pagingSpec); List AlbumsBetweenDates(DateTime start, DateTime end, bool includeUnmonitored); List ArtistAlbumsBetweenDates(Artist artist, DateTime start, DateTime end, bool includeUnmonitored); void InsertMany(List albums); void UpdateMany(List albums); void DeleteMany(List albums); void RemoveAddOptions(Album album); Album FindAlbumByRelease(string releaseId); List GetArtistAlbumsWithFiles(Artist artist); } public class AlbumService : IAlbumService, IHandleAsync { private readonly IAlbumRepository _albumRepository; private readonly IEventAggregator _eventAggregator; private readonly ITrackService _trackService; private readonly Logger _logger; public AlbumService(IAlbumRepository albumRepository, IEventAggregator eventAggregator, ITrackService trackService, Logger logger) { _albumRepository = albumRepository; _eventAggregator = eventAggregator; _trackService = trackService; _logger = logger; } public Album AddAlbum(Album newAlbum) { _albumRepository.Insert(newAlbum); //_eventAggregator.PublishEvent(new AlbumAddedEvent(GetAlbum(newAlbum.Id))); return newAlbum; } public List AddAlbums(List newAlbums) { _albumRepository.InsertMany(newAlbums); //_eventAggregator.PublishEvent(new AlbumsAddedEvent(newAlbums.Select(s => s.Id).ToList())); return newAlbums; } public void DeleteAlbum(int albumId, bool deleteFiles) { var album = _albumRepository.Get(albumId); _albumRepository.Delete(albumId); _eventAggregator.PublishEvent(new AlbumDeletedEvent(album, deleteFiles)); } public Album FindById(string lidarrId) { return _albumRepository.FindById(lidarrId); } public Album FindByTitle(int artistId, string title) { return _albumRepository.FindByTitle(artistId, title); } public Album FindByTitleInexact(int artistId, string title) { var cleanTitle = title.CleanArtistName(); var albums = GetAlbumsByArtist(artistId); Func< Func, string, Tuple, string>> tc = Tuple.Create; var scoringFunctions = new List, string>> { tc((a, t) => a.CleanTitle.FuzzyMatch(t), cleanTitle), tc((a, t) => a.Title.FuzzyMatch(t), title), tc((a, t) => a.CleanTitle.FuzzyMatch(t), title.RemoveBracketsAndContents().CleanArtistName()), tc((a, t) => a.CleanTitle.FuzzyMatch(t), title.RemoveAfterDash().CleanArtistName()), tc((a, t) => a.CleanTitle.FuzzyMatch(t), title.RemoveBracketsAndContents().RemoveAfterDash().CleanArtistName()), tc((a, t) => t.FuzzyContains(a.CleanTitle), cleanTitle), tc((a, t) => t.FuzzyContains(a.Title), title) }; foreach (var func in scoringFunctions) { var album = FindByStringInexact(albums, func.Item1, func.Item2); if (album != null) { return album; } } return null; } private Album FindByStringInexact(List albums, Func scoreFunction, string title) { const double fuzzThreshold = 0.7; const double fuzzGap = 0.4; var sortedAlbums = albums.Select(s => new { MatchProb = scoreFunction(s, title), Album = s }) .ToList() .OrderByDescending(s => s.MatchProb) .ToList(); if (!sortedAlbums.Any()) { return null; } _logger.Trace("\nFuzzy album match on '{0}':\n{1}", title, string.Join("\n", sortedAlbums.Select(x => $"[{x.Album.Title}] {x.Album.CleanTitle}: {x.MatchProb}"))); if (sortedAlbums[0].MatchProb > fuzzThreshold && (sortedAlbums.Count == 1 || sortedAlbums[0].MatchProb - sortedAlbums[1].MatchProb > fuzzGap)) { return sortedAlbums[0].Album; } return null; } public List GetAllAlbums() { return _albumRepository.All().ToList(); } public Album GetAlbum(int albumId) { return _albumRepository.Get(albumId); } public List GetAlbums(IEnumerable albumIds) { return _albumRepository.Get(albumIds).ToList(); } public List GetAlbumsByArtist(int artistId) { return _albumRepository.GetAlbums(artistId).ToList(); } public Album FindAlbumByRelease(string releaseId) { return _albumRepository.FindAlbumByRelease(releaseId); } public void RemoveAddOptions(Album album) { _albumRepository.SetFields(album, s => s.AddOptions); } public PagingSpec AlbumsWithoutFiles(PagingSpec pagingSpec) { var albumResult = _albumRepository.AlbumsWithoutFiles(pagingSpec); return albumResult; } public List AlbumsBetweenDates(DateTime start, DateTime end, bool includeUnmonitored) { var albums = _albumRepository.AlbumsBetweenDates(start.ToUniversalTime(), end.ToUniversalTime(), includeUnmonitored); return albums; } public List ArtistAlbumsBetweenDates(Artist artist, DateTime start, DateTime end, bool includeUnmonitored) { var albums = _albumRepository.ArtistAlbumsBetweenDates(artist, start.ToUniversalTime(), end.ToUniversalTime(), includeUnmonitored); return albums; } public List GetArtistAlbumsWithFiles(Artist artist) { return _albumRepository.GetArtistAlbumsWithFiles(artist); } public void InsertMany(List albums) { _albumRepository.InsertMany(albums); } public void UpdateMany(List albums) { _albumRepository.UpdateMany(albums); } public void DeleteMany(List albums) { _albumRepository.DeleteMany(albums); foreach (var album in albums) { _eventAggregator.PublishEvent(new AlbumDeletedEvent(album, false)); } } public Album UpdateAlbum(Album album) { var storedAlbum = GetAlbum(album.Id); var updatedAlbum = _albumRepository.Update(album); _eventAggregator.PublishEvent(new AlbumEditedEvent(updatedAlbum, storedAlbum)); return updatedAlbum; } public void SetAlbumMonitored(int albumId, bool monitored) { var album = _albumRepository.Get(albumId); _albumRepository.SetMonitoredFlat(album, monitored); var tracks = _trackService.GetTracksByAlbum(albumId); foreach (var track in tracks) { track.Monitored = monitored; } _trackService.UpdateTracks(tracks); _logger.Debug("Monitored flag for Album:{0} was set to {1}", albumId, monitored); } public void SetMonitored(IEnumerable ids, bool monitored) { _albumRepository.SetMonitored(ids, monitored); foreach (var id in ids) { var tracks = _trackService.GetTracksByAlbum(id); foreach (var track in tracks) { track.Monitored = monitored; } _trackService.UpdateTracks(tracks); } } public List UpdateAlbums(List albums) { _logger.Debug("Updating {0} albums", albums.Count); _albumRepository.UpdateMany(albums); _logger.Debug("{0} albums updated", albums.Count); return albums; } public void HandleAsync(ArtistDeletedEvent message) { var albums = GetAlbumsByArtist(message.Artist.Id); _albumRepository.DeleteMany(albums); } } }