diff --git a/src/NzbDrone.Api/Music/AlbumResource.cs b/src/NzbDrone.Api/Music/AlbumResource.cs index d3e243c66..5ae4b2efa 100644 --- a/src/NzbDrone.Api/Music/AlbumResource.cs +++ b/src/NzbDrone.Api/Music/AlbumResource.cs @@ -11,7 +11,7 @@ namespace NzbDrone.Api.Music public string AlbumId { get; set; } public string AlbumName { get; set; } public bool Monitored { get; set; } - public int Year { get; set; } + public DateTime ReleaseDate { get; set; } public List Genres { get; set; } public string ArtworkUrl { get; set; } @@ -25,12 +25,12 @@ namespace NzbDrone.Api.Music return new AlbumResource { - AlbumId = model.AlbumId, + AlbumId = model.ForeignAlbumId, Monitored = model.Monitored, - Year = model.Year, + ReleaseDate = model.ReleaseDate, AlbumName = model.Title, Genres = model.Genres, - ArtworkUrl = model.ArtworkUrl + //ArtworkUrl = model.ArtworkUrl }; } @@ -40,12 +40,12 @@ namespace NzbDrone.Api.Music return new Album { - AlbumId = resource.AlbumId, + ForeignAlbumId = resource.AlbumId, Monitored = resource.Monitored, - Year = resource.Year, + ReleaseDate = resource.ReleaseDate, Title = resource.AlbumName, Genres = resource.Genres, - ArtworkUrl = resource.ArtworkUrl + //ArtworkUrl = resource.ArtworkUrl }; } diff --git a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs index 6b4010417..d845291d8 100644 --- a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs +++ b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs @@ -42,14 +42,13 @@ namespace NzbDrone.Core.Datastore.Migration Create.TableForModel("Albums") .WithColumn("ForeignAlbumId").AsString().Unique() .WithColumn("ArtistId").AsInt32() - .WithColumn("MBId").AsString().Indexed() + .WithColumn("MBId").AsString().Nullable().Indexed() .WithColumn("AMId").AsString().Nullable() - .WithColumn("TADBId").AsInt32().Indexed() + .WithColumn("TADBId").AsInt32().Nullable().Indexed() .WithColumn("DiscogsId").AsInt32().Nullable() .WithColumn("Title").AsString() .WithColumn("TitleSlug").AsString().Nullable().Unique() .WithColumn("CleanTitle").AsString().Indexed() - .WithColumn("Status").AsInt32() .WithColumn("Overview").AsString().Nullable() .WithColumn("Images").AsString() .WithColumn("Path").AsString().Indexed() @@ -71,7 +70,7 @@ namespace NzbDrone.Core.Datastore.Migration .WithColumn("ForeignTrackId").AsString().Unique() .WithColumn("ArtistId").AsInt32().Indexed() .WithColumn("AlbumId").AsInt32() - .WithColumn("MBId").AsString().Indexed() + .WithColumn("MBId").AsString().Nullable().Indexed() .WithColumn("TrackNumber").AsInt32() .WithColumn("Title").AsString().Nullable() .WithColumn("Explicit").AsBoolean() diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index db5eaa1ac..c5945047b 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -97,7 +97,7 @@ namespace NzbDrone.Core.Datastore .Relationship() .HasOne(a => a.Profile, a => a.ProfileId); - Mapper.Entity().RegisterModel("Album"); + Mapper.Entity().RegisterModel("Albums"); Mapper.Entity().RegisterModel("TrackFiles") .Ignore(f => f.Path) diff --git a/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs b/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs index 76810655a..139d70a69 100644 --- a/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs +++ b/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs @@ -6,6 +6,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook { public interface IProvideArtistInfo { - Tuple> GetArtistInfo(string spotifyId); + Tuple> GetArtistInfo(string lidarrId); } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs index 7c1a918f0..5e5a1f13d 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs @@ -9,18 +9,20 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource { public AlbumResource() { - + Tracks = new List(); } //public string AlbumType { get; set; } // Might need to make this a separate class - public List Artists { get; set; } // Will always be length of 1 unless a compilation + public List Artists { get; set; } // Will always be length of 1 unless a compilation public string Url { get; set; } // Link to the endpoint api to give full info for this object public string Id { get; set; } // This is a unique Album ID. Needed for all future API calls - public int Year { get; set; } + public DateTime ReleaseDate { get; set; } public List Images { get; set; } - public string AlbumName { get; set; } // In case of a takedown, this may be empty + public string Title { get; set; } // In case of a takedown, this may be empty public string Overview { get; set; } public List Genres { get; set; } public string Label { get; set; } + public string Type { get; set; } + public List Tracks { get; set; } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs index c2c922d1b..f2ab72f94 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs @@ -9,7 +9,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource { public ArtistResource() { Albums = new List(); - Tracks = new List(); } public List Genres { get; set; } @@ -19,6 +18,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource public List Images { get; set; } public string ArtistName { get; set; } public List Albums { get; set; } - public List Tracks { get; set; } + } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index f4ae40a16..b411d7490 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -74,14 +74,14 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } - public Tuple> GetArtistInfo(string spotifyId) + public Tuple> GetArtistInfo(string foreignArtistId) { - _logger.Debug("Getting Artist with SpotifyId of {0}", spotifyId); + _logger.Debug("Getting Artist with SpotifyId of {0}", foreignArtistId); // We need to perform a direct lookup of the artist var httpRequest = _requestBuilder.Create() - .SetSegment("route", "artists/" + spotifyId) + .SetSegment("route", "artists/" + foreignArtistId) //.SetSegment("route", "search") //.AddQueryParam("type", "artist,album") //.AddQueryParam("q", spotifyId.ToString()) @@ -99,7 +99,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook { if (httpResponse.StatusCode == HttpStatusCode.NotFound) { - throw new ArtistNotFoundException(spotifyId); + throw new ArtistNotFoundException(foreignArtistId); } else { @@ -109,18 +109,17 @@ namespace NzbDrone.Core.MetadataSource.SkyHook // It is safe to assume an id will only return one Artist back var albums = httpResponse.Resource.Albums.Select(MapAlbum); - var tracks = httpResponse.Resource.Tracks.Select(MapTrack); var artist = MapArtist(httpResponse.Resource); //artist.Name = httpResponse.Resource.Artists.Items[0].ArtistName; - + //artist.ForeignArtistId = httpResponse.Resource.Artists.Items[0].Id; //artist.Genres = httpResponse.Resource.Artists.Items[0].Genres; //var albumRet = MapAlbums(artist); //artist = albumRet.Item1; - return new Tuple>(artist, tracks.ToList()); + return new Tuple>(artist, albums.ToList()); } @@ -179,7 +178,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook // } // List tracks = new List(); - // foreach(var trackResource in httpResponse.Resource.Items) + // foreach (var trackResource in httpResponse.Resource.Items) // { // Track track = new Track(); // track.AlbumId = album.AlbumId; @@ -269,12 +268,25 @@ namespace NzbDrone.Core.MetadataSource.SkyHook private static Album MapAlbum(AlbumResource resource) { Album album = new Album(); + album.Title = resource.Title; + album.ForeignAlbumId = resource.Id; + album.ReleaseDate = resource.ReleaseDate; + album.CleanTitle = Parser.Parser.CleanArtistTitle(album.Title); + album.AlbumType = resource.Type; + + var tracks = resource.Tracks.Select(MapTrack); + album.Tracks = tracks.ToList(); + + return album; } private static Track MapTrack(TrackResource resource) { Track track = new Track(); + track.Title = resource.TrackName; + track.ForeignTrackId = resource.Id; + track.TrackNumber = resource.TrackNumber; return track; } diff --git a/src/NzbDrone.Core/Music/AddArtistService.cs b/src/NzbDrone.Core/Music/AddArtistService.cs index aab228341..3811764f0 100644 --- a/src/NzbDrone.Core/Music/AddArtistService.cs +++ b/src/NzbDrone.Core/Music/AddArtistService.cs @@ -48,11 +48,11 @@ namespace NzbDrone.Core.Music if (string.IsNullOrWhiteSpace(newArtist.Path)) { - var folderName = newArtist.Name;// TODO: _fileNameBuilder.GetArtistFolder(newArtist); + var folderName = _fileNameBuilder.GetArtistFolder(newArtist); newArtist.Path = Path.Combine(newArtist.RootFolderPath, folderName); } - newArtist.CleanName = newArtist.Name.CleanSeriesTitle(); + newArtist.CleanName = newArtist.Name.CleanArtistTitle(); //newArtist.SortTitle = ArtistNameNormalizer.Normalize(newArtist.ArtistName, newArtist.ItunesId); // There is no Sort Title newArtist.Added = DateTime.UtcNow; @@ -71,7 +71,7 @@ namespace NzbDrone.Core.Music private Artist AddSkyhookData(Artist newArtist) { - Tuple> tuple; + Tuple> tuple; try { @@ -79,7 +79,7 @@ namespace NzbDrone.Core.Music } catch (ArtistNotFoundException) { - _logger.Error("SpotifyId {1} was not found, it may have been removed from Spotify.", newArtist.ForeignArtistId); + _logger.Error("LidarrId {1} was not found, it may have been removed from Lidarr.", newArtist.ForeignArtistId); throw new ValidationException(new List { diff --git a/src/NzbDrone.Core/Music/Album.cs b/src/NzbDrone.Core/Music/Album.cs index 76027c0c3..ee88b2e02 100644 --- a/src/NzbDrone.Core/Music/Album.cs +++ b/src/NzbDrone.Core/Music/Album.cs @@ -1,4 +1,5 @@ -using NzbDrone.Core.Datastore; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Datastore; using NzbDrone.Core.Tv; using System; using System.Collections.Generic; @@ -14,17 +15,27 @@ namespace NzbDrone.Core.Music Images = new List(); } - public string AlbumId { get; set; } + public string ForeignAlbumId { get; set; } + public int ArtistId { get; set; } public string Title { get; set; } // NOTE: This should be CollectionName in API - public int Year { get; set; } - public int TrackCount { get; set; } + public string CleanTitle { get; set; } + public DateTime ReleaseDate { get; set; } + //public int TrackCount { get; set; } + public string Path { get; set; } public List Tracks { get; set; } - public int DiscCount { get; set; } + //public int DiscCount { get; set; } public bool Monitored { get; set; } public List Images { get; set; } - public List Actors { get; set; } // These are band members. TODO: Refactor + //public List Actors { get; set; } // These are band members. TODO: Refactor public List Genres { get; set; } - public string ArtworkUrl { get; set; } - public string Explicitness { get; set; } + public String AlbumType { get; set; } //Turn this into a type similar to Series Type in TV + //public string ArtworkUrl { get; set; } + //public string Explicitness { get; set; } + public AddSeriesOptions AddOptions { get; set; } + + public override string ToString() + { + return string.Format("[{0}][{1}]", ForeignAlbumId, Title.NullSafe()); + } } } diff --git a/src/NzbDrone.Core/Music/AlbumRepository.cs b/src/NzbDrone.Core/Music/AlbumRepository.cs new file mode 100644 index 000000000..1b072e19a --- /dev/null +++ b/src/NzbDrone.Core/Music/AlbumRepository.cs @@ -0,0 +1,46 @@ +using System.Linq; +using NzbDrone.Core.Datastore; +using System.Collections.Generic; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.Music +{ + public interface IAlbumRepository : IBasicRepository + { + bool AlbumPathExists(string path); + List GetAlbums(int artistId); + Album FindByName(string cleanTitle); + Album FindById(string spotifyId); + } + + public class AlbumRepository : BasicRepository, IAlbumRepository + { + public AlbumRepository(IMainDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) + { + } + + + public bool AlbumPathExists(string path) + { + return Query.Where(c => c.Path == path).Any(); + } + public List GetAlbums(int artistId) + { + return Query.Where(s => s.ArtistId == artistId).ToList(); + } + + public Album FindById(string foreignAlbumId) + { + return Query.Where(s => s.ForeignAlbumId == foreignAlbumId).SingleOrDefault(); + } + + public Album FindByName(string cleanTitle) + { + cleanTitle = cleanTitle.ToLowerInvariant(); + + return Query.Where(s => s.CleanTitle == cleanTitle) + .SingleOrDefault(); + } + } +} diff --git a/src/NzbDrone.Core/Music/AlbumService.cs b/src/NzbDrone.Core/Music/AlbumService.cs new file mode 100644 index 000000000..8d470830c --- /dev/null +++ b/src/NzbDrone.Core/Music/AlbumService.cs @@ -0,0 +1,147 @@ +using NLog; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Music.Events; +using NzbDrone.Core.Organizer; +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.Parser; +using System.Text; +using System.IO; +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); + Album FindById(string spotifyId); + Album FindByTitleInexact(string title); + void DeleteAlbum(int albumId, bool deleteFiles); + List GetAllAlbums(); + Album UpdateAlbum(Album album); + List UpdateAlbums(List album); + void InsertMany(List albums); + void UpdateMany(List albums); + void DeleteMany(List albums); + bool AlbumPathExists(string folder); + void RemoveAddOptions(Album album); + } + + public class AlbumService : IAlbumService + { + private readonly IAlbumRepository _albumRepository; + private readonly IEventAggregator _eventAggregator; + private readonly ITrackService _trackService; + private readonly IBuildFileNames _fileNameBuilder; + private readonly Logger _logger; + + public AlbumService(IAlbumRepository albumRepository, + IEventAggregator eventAggregator, + ITrackService trackService, + IBuildFileNames fileNameBuilder, + Logger logger) + { + _albumRepository = albumRepository; + _eventAggregator = eventAggregator; + _trackService = trackService; + _fileNameBuilder = fileNameBuilder; + _logger = logger; + } + + public Album AddAlbum(Album newAlbum) + { + _albumRepository.Insert(newAlbum); + _eventAggregator.PublishEvent(new AlbumAddedEvent(GetAlbum(newAlbum.Id))); + + return newAlbum; + } + + public bool AlbumPathExists(string folder) + { + return _albumRepository.AlbumPathExists(folder); + } + + 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 spotifyId) + { + return _albumRepository.FindById(spotifyId); + } + + + + public Album FindByTitleInexact(string title) + { + throw new NotImplementedException(); + } + + 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 void RemoveAddOptions(Album album) + { + _albumRepository.SetFields(album, s => s.AddOptions); + } + + public void InsertMany(List albums) + { + _albumRepository.InsertMany(albums); + } + + public void UpdateMany(List albums) + { + _albumRepository.UpdateMany(albums); + } + + public void DeleteMany(List albums) + { + _albumRepository.DeleteMany(albums); + } + + public Album UpdateAlbum(Album album) + { + var storedAlbum = GetAlbum(album.Id); // Is it Id or iTunesId? + + var updatedAlbum = _albumRepository.Update(album); + _eventAggregator.PublishEvent(new AlbumEditedEvent(updatedAlbum, storedAlbum)); + + return updatedAlbum; + } + + public List UpdateAlbums(List album) + { + _logger.Debug("Updating {0} album", album.Count); + + _albumRepository.UpdateMany(album); + _logger.Debug("{0} albums updated", album.Count); + + return album; + } + } +} diff --git a/src/NzbDrone.Core/Music/ArtistService.cs b/src/NzbDrone.Core/Music/ArtistService.cs index 12387b3c0..b504538d7 100644 --- a/src/NzbDrone.Core/Music/ArtistService.cs +++ b/src/NzbDrone.Core/Music/ArtistService.cs @@ -110,11 +110,11 @@ namespace NzbDrone.Core.Music foreach (var album in artist.Albums) { - var storedAlbum = storedArtist.Albums.SingleOrDefault(s => s.AlbumId == album.AlbumId); + var storedAlbum = storedArtist.Albums.SingleOrDefault(s => s.ForeignAlbumId == album.ForeignAlbumId); if (storedAlbum != null && album.Monitored != storedAlbum.Monitored) { - _trackService.SetTrackMonitoredByAlbum(artist.ForeignArtistId, album.AlbumId, album.Monitored); + _trackService.SetTrackMonitoredByAlbum(artist.ForeignArtistId, album.ForeignAlbumId, album.Monitored); } } diff --git a/src/NzbDrone.Core/Music/Events/AlbumAddedEvent.cs b/src/NzbDrone.Core/Music/Events/AlbumAddedEvent.cs new file mode 100644 index 000000000..985cc419a --- /dev/null +++ b/src/NzbDrone.Core/Music/Events/AlbumAddedEvent.cs @@ -0,0 +1,18 @@ +using NzbDrone.Common.Messaging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music.Events +{ + public class AlbumAddedEvent : IEvent + { + public Album Album { get; private set; } + + public AlbumAddedEvent(Album album) + { + Album = album; + } + } +} diff --git a/src/NzbDrone.Core/Music/Events/AlbumDeletedEvent.cs b/src/NzbDrone.Core/Music/Events/AlbumDeletedEvent.cs new file mode 100644 index 000000000..28548116c --- /dev/null +++ b/src/NzbDrone.Core/Music/Events/AlbumDeletedEvent.cs @@ -0,0 +1,20 @@ +using NzbDrone.Common.Messaging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music.Events +{ + public class AlbumDeletedEvent : IEvent + { + public Album Album { get; private set; } + public bool DeleteFiles { get; private set; } + + public AlbumDeletedEvent(Album album, bool deleteFiles) + { + Album = album; + DeleteFiles = deleteFiles; + } + } +} diff --git a/src/NzbDrone.Core/Music/Events/AlbumEditedEvent.cs b/src/NzbDrone.Core/Music/Events/AlbumEditedEvent.cs new file mode 100644 index 000000000..aed8f155e --- /dev/null +++ b/src/NzbDrone.Core/Music/Events/AlbumEditedEvent.cs @@ -0,0 +1,20 @@ +using NzbDrone.Common.Messaging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music.Events +{ + public class AlbumEditedEvent : IEvent + { + public Album Album { get; private set; } + public Album OldAlbum { get; private set; } + + public AlbumEditedEvent(Album album, Album oldAlbum) + { + Album = album; + OldAlbum = oldAlbum; + } + } +} diff --git a/src/NzbDrone.Core/Music/Events/AlbumInfoRefreshedEvent.cs b/src/NzbDrone.Core/Music/Events/AlbumInfoRefreshedEvent.cs new file mode 100644 index 000000000..5fdb3f539 --- /dev/null +++ b/src/NzbDrone.Core/Music/Events/AlbumInfoRefreshedEvent.cs @@ -0,0 +1,23 @@ +using NzbDrone.Common.Messaging; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music.Events +{ + public class AlbumInfoRefreshedEvent : IEvent + { + public Artist Artist { get; set; } + public ReadOnlyCollection Added { get; private set; } + public ReadOnlyCollection Updated { get; private set; } + + public AlbumInfoRefreshedEvent(Artist artist, IList added, IList updated) + { + Artist = artist; + Added = new ReadOnlyCollection(added); + Updated = new ReadOnlyCollection(updated); + } + } +} diff --git a/src/NzbDrone.Core/Music/Events/TrackInfoRefreshedEvent.cs b/src/NzbDrone.Core/Music/Events/TrackInfoRefreshedEvent.cs index 99661c480..ef6eb1a8b 100644 --- a/src/NzbDrone.Core/Music/Events/TrackInfoRefreshedEvent.cs +++ b/src/NzbDrone.Core/Music/Events/TrackInfoRefreshedEvent.cs @@ -9,13 +9,13 @@ namespace NzbDrone.Core.Music.Events { public class TrackInfoRefreshedEvent : IEvent { - public Artist Artist { get; set; } + public Album Album { get; set; } public ReadOnlyCollection Added { get; private set; } public ReadOnlyCollection Updated { get; private set; } - public TrackInfoRefreshedEvent(Artist artist, IList added, IList updated) + public TrackInfoRefreshedEvent(Album album, IList added, IList updated) { - Artist = artist; + Album = album; Added = new ReadOnlyCollection(added); Updated = new ReadOnlyCollection(updated); } diff --git a/src/NzbDrone.Core/Music/RefreshAlbumService.cs b/src/NzbDrone.Core/Music/RefreshAlbumService.cs new file mode 100644 index 000000000..1fbb8f6a8 --- /dev/null +++ b/src/NzbDrone.Core/Music/RefreshAlbumService.cs @@ -0,0 +1,133 @@ +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Music.Events; +using System; +using System.Collections.Generic; +using NzbDrone.Core.Organizer; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music +{ + public interface IRefreshAlbumService + { + void RefreshAlbumInfo(Artist artist, IEnumerable remoteAlbums); + } + + public class RefreshAlbumService : IRefreshAlbumService + { + private readonly IAlbumService _albumService; + private readonly IRefreshTrackService _refreshTrackService; + private readonly IEventAggregator _eventAggregator; + private readonly Logger _logger; + + public RefreshAlbumService(IAlbumService albumService, IRefreshTrackService refreshTrackService, IEventAggregator eventAggregator, Logger logger) + { + _albumService = albumService; + _refreshTrackService = refreshTrackService; + _eventAggregator = eventAggregator; + _logger = logger; + } + + public void RefreshAlbumInfo(Artist artist, IEnumerable remoteAlbums) + { + _logger.Info("Starting album info refresh for: {0}", artist); + var successCount = 0; + var failCount = 0; + + var existingAlbums = _albumService.GetAlbumsByArtist(artist.Id); + var albums = artist.Albums; + + var updateList = new List(); + var newList = new List(); + var dupeFreeRemoteAlbums = remoteAlbums.DistinctBy(m => new { m.ForeignAlbumId, m.ReleaseDate }).ToList(); + + foreach (var album in OrderAlbums(artist, dupeFreeRemoteAlbums)) + { + try + { + var albumToUpdate = GetAlbumToUpdate(artist, album, existingAlbums); + + if (albumToUpdate != null) + { + existingAlbums.Remove(albumToUpdate); + updateList.Add(albumToUpdate); + } + else + { + albumToUpdate = new Album(); + albumToUpdate.Monitored = artist.Monitored; + newList.Add(albumToUpdate); + //var folderName = _fileNameBuilder.GetAlbumFolder(albumToUpdate); //This likely does not belong here, need to create AddAlbumService + //albumToUpdate.Path = Path.Combine(newArtist.RootFolderPath, folderName); + } + + albumToUpdate.ForeignAlbumId = album.ForeignAlbumId; + albumToUpdate.CleanTitle = album.CleanTitle; + //albumToUpdate.TrackNumber = album.TrackNumber; + albumToUpdate.Title = album.Title ?? "Unknown"; + //albumToUpdate.AlbumId = album.AlbumId; + //albumToUpdate.Album = album.Album; + //albumToUpdate.Explicit = album.Explicit; + albumToUpdate.ArtistId = artist.Id; + albumToUpdate.Path = artist.Path + album.Title; + albumToUpdate.AlbumType = album.AlbumType; + //albumToUpdate.Compilation = album.Compilation; + + _refreshTrackService.RefreshTrackInfo(album, album.Tracks); + + + successCount++; + } + catch (Exception e) + { + _logger.Fatal(e, "An error has occurred while updating track info for artist {0}. {1}", artist, album); + failCount++; + } + } + + var allAlbums = new List(); + allAlbums.AddRange(newList); + allAlbums.AddRange(updateList); + + // TODO: See if anything needs to be done here + //AdjustMultiEpisodeAirTime(artist, allTracks); + //AdjustDirectToDvdAirDate(artist, allTracks); + + _albumService.DeleteMany(existingAlbums); + _albumService.UpdateMany(updateList); + _albumService.InsertMany(newList); + + _eventAggregator.PublishEvent(new AlbumInfoRefreshedEvent(artist, newList, updateList)); + + if (failCount != 0) + { + _logger.Info("Finished album refresh for artist: {0}. Successful: {1} - Failed: {2} ", + artist.Name, successCount, failCount); + } + else + { + _logger.Info("Finished album refresh for artist: {0}.", artist); + } + } + + private bool GetMonitoredStatus(Album album, IEnumerable artists) + { + var artist = artists.SingleOrDefault(c => c.Id == album.ArtistId); + return album == null || album.Monitored; + } + + + private Album GetAlbumToUpdate(Artist artist, Album album, List existingAlbums) + { + return existingAlbums.FirstOrDefault(e => e.ForeignAlbumId == album.ForeignAlbumId && e.ReleaseDate == album.ReleaseDate); + } + + private IEnumerable OrderAlbums(Artist artist, List albums) + { + return albums.OrderBy(e => e.ForeignAlbumId).ThenBy(e => e.ReleaseDate); + } + } +} + diff --git a/src/NzbDrone.Core/Music/RefreshArtistService.cs b/src/NzbDrone.Core/Music/RefreshArtistService.cs index cdac3ac30..62febf26c 100644 --- a/src/NzbDrone.Core/Music/RefreshArtistService.cs +++ b/src/NzbDrone.Core/Music/RefreshArtistService.cs @@ -20,7 +20,7 @@ namespace NzbDrone.Core.Music { private readonly IProvideArtistInfo _artistInfo; private readonly IArtistService _artistService; - private readonly IRefreshTrackService _refreshTrackService; + private readonly IRefreshAlbumService _refreshAlbumService; private readonly IEventAggregator _eventAggregator; private readonly IDiskScanService _diskScanService; private readonly ICheckIfArtistShouldBeRefreshed _checkIfArtistShouldBeRefreshed; @@ -28,7 +28,7 @@ namespace NzbDrone.Core.Music public RefreshArtistService(IProvideArtistInfo artistInfo, IArtistService artistService, - IRefreshTrackService refreshTrackService, + IRefreshAlbumService refreshAlbumService, IEventAggregator eventAggregator, IDiskScanService diskScanService, ICheckIfArtistShouldBeRefreshed checkIfArtistShouldBeRefreshed, @@ -36,7 +36,7 @@ namespace NzbDrone.Core.Music { _artistInfo = artistInfo; _artistService = artistService; - _refreshTrackService = refreshTrackService; + _refreshAlbumService = refreshAlbumService; _eventAggregator = eventAggregator; _diskScanService = diskScanService; _checkIfArtistShouldBeRefreshed = checkIfArtistShouldBeRefreshed; @@ -47,7 +47,7 @@ namespace NzbDrone.Core.Music { _logger.ProgressInfo("Updating Info for {0}", artist.Name); - Tuple> tuple; + Tuple> tuple; try { @@ -89,7 +89,8 @@ namespace NzbDrone.Core.Music artist.Albums = UpdateAlbums(artist, artistInfo); _artistService.UpdateArtist(artist); - _refreshTrackService.RefreshTrackInfo(artist, tuple.Item2); + _refreshAlbumService.RefreshAlbumInfo(artist, tuple.Item2); + //_refreshTrackService.RefreshTrackInfo(artist, tuple.Item2); _logger.Debug("Finished artist refresh for {0}", artist.Name); _eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist)); @@ -97,11 +98,11 @@ namespace NzbDrone.Core.Music private List UpdateAlbums(Artist artist, Artist artistInfo) { - var albums = artistInfo.Albums.DistinctBy(s => s.AlbumId).ToList(); + var albums = artistInfo.Albums.DistinctBy(s => s.ForeignAlbumId).ToList(); foreach (var album in albums) { - var existingAlbum = artist.Albums.FirstOrDefault(s => s.AlbumId == album.AlbumId); + var existingAlbum = artist.Albums.FirstOrDefault(s => s.ForeignAlbumId == album.ForeignAlbumId); //Todo: Should this should use the previous season's monitored state? if (existingAlbum == null) diff --git a/src/NzbDrone.Core/Music/RefreshTrackService.cs b/src/NzbDrone.Core/Music/RefreshTrackService.cs index 3bd5925a5..77592e4f3 100644 --- a/src/NzbDrone.Core/Music/RefreshTrackService.cs +++ b/src/NzbDrone.Core/Music/RefreshTrackService.cs @@ -11,7 +11,7 @@ namespace NzbDrone.Core.Music { public interface IRefreshTrackService { - void RefreshTrackInfo(Artist artist, IEnumerable remoteTracks); + void RefreshTrackInfo(Album album, IEnumerable remoteTracks); } public class RefreshTrackService : IRefreshTrackService @@ -27,24 +27,24 @@ namespace NzbDrone.Core.Music _logger = logger; } - public void RefreshTrackInfo(Artist artist, IEnumerable remoteTracks) + public void RefreshTrackInfo(Album album, IEnumerable remoteTracks) { - _logger.Info("Starting track info refresh for: {0}", artist); + _logger.Info("Starting track info refresh for: {0}", album); var successCount = 0; var failCount = 0; - var existingTracks = _trackService.GetTracksByArtist(artist.Id); - var albums = artist.Albums; + var existingTracks = _trackService.GetTracksByAlbum(album.ArtistId, album.Id); + //var albums = artist.Albums; var updateList = new List(); var newList = new List(); var dupeFreeRemoteTracks = remoteTracks.DistinctBy(m => new { m.AlbumId, m.TrackNumber }).ToList(); - foreach (var track in OrderTracks(artist, dupeFreeRemoteTracks)) + foreach (var track in OrderTracks(album, dupeFreeRemoteTracks)) { try { - var trackToUpdate = GetTrackToUpdate(artist, track, existingTracks); + var trackToUpdate = GetTrackToUpdate(album, track, existingTracks); if (trackToUpdate != null) { @@ -54,17 +54,17 @@ namespace NzbDrone.Core.Music else { trackToUpdate = new Track(); - trackToUpdate.Monitored = GetMonitoredStatus(track, albums); + trackToUpdate.Monitored = album.Monitored; newList.Add(trackToUpdate); } trackToUpdate.ForeignTrackId = track.ForeignTrackId; trackToUpdate.TrackNumber = track.TrackNumber; trackToUpdate.Title = track.Title ?? "Unknown"; - trackToUpdate.AlbumId = track.AlbumId; + trackToUpdate.AlbumId = album.Id; trackToUpdate.Album = track.Album; trackToUpdate.Explicit = track.Explicit; - trackToUpdate.ArtistId = artist.Id; + trackToUpdate.ArtistId = album.ArtistId; trackToUpdate.Compilation = track.Compilation; // TODO: Implement rest of [RefreshTrackService] fields @@ -75,7 +75,7 @@ namespace NzbDrone.Core.Music } catch (Exception e) { - _logger.Fatal(e, "An error has occurred while updating track info for artist {0}. {1}", artist, track); + _logger.Fatal(e, "An error has occurred while updating track info for album {0}. {1}", album, track); failCount++; } } @@ -92,16 +92,16 @@ namespace NzbDrone.Core.Music _trackService.UpdateMany(updateList); _trackService.InsertMany(newList); - _eventAggregator.PublishEvent(new TrackInfoRefreshedEvent(artist, newList, updateList)); + _eventAggregator.PublishEvent(new TrackInfoRefreshedEvent(album, newList, updateList)); if (failCount != 0) { - _logger.Info("Finished track refresh for artist: {0}. Successful: {1} - Failed: {2} ", - artist.Name, successCount, failCount); + _logger.Info("Finished track refresh for album: {0}. Successful: {1} - Failed: {2} ", + album.Title, successCount, failCount); } else { - _logger.Info("Finished track refresh for artist: {0}.", artist); + _logger.Info("Finished track refresh for album: {0}.", album); } } @@ -117,12 +117,12 @@ namespace NzbDrone.Core.Music } - private Track GetTrackToUpdate(Artist artist, Track track, List existingTracks) + private Track GetTrackToUpdate(Album album, Track track, List existingTracks) { return existingTracks.FirstOrDefault(e => e.AlbumId == track.AlbumId && e.TrackNumber == track.TrackNumber); } - private IEnumerable OrderTracks(Artist artist, List tracks) + private IEnumerable OrderTracks(Album album, List tracks) { return tracks.OrderBy(e => e.AlbumId).ThenBy(e => e.TrackNumber); } diff --git a/src/NzbDrone.Core/Music/TrackService.cs b/src/NzbDrone.Core/Music/TrackService.cs index 834d3b054..bbb4e74c9 100644 --- a/src/NzbDrone.Core/Music/TrackService.cs +++ b/src/NzbDrone.Core/Music/TrackService.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Core.Music Track FindTrack(int artistId, int albumId, int trackNumber); Track FindTrackByTitle(int artistId, int albumId, string releaseTitle); List GetTracksByArtist(int artistId); - //List GetTracksByAlbum(string artistId, string albumId); + List GetTracksByAlbum(int artistId, int albumId); //List GetTracksByAlbumTitle(string artistId, string albumTitle); List TracksWithFiles(int artistId); //PagingSpec TracksWithoutFiles(PagingSpec pagingSpec); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index dc7c2c9b9..db0659a86 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -856,17 +856,24 @@ + + + + + + + diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index b9b53a0dc..d485da076 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -22,6 +22,8 @@ namespace NzbDrone.Core.Organizer string BuildSeasonPath(Series series, int seasonNumber); BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); string GetSeriesFolder(Series series, NamingConfig namingConfig = null); + string GetArtistFolder(Artist artist, NamingConfig namingConfig = null); + string GetAlbumFolder(Album album, NamingConfig namingConfig = null); string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null); // TODO: Implement Music functions @@ -232,6 +234,20 @@ namespace NzbDrone.Core.Organizer return CleanFolderName(ReplaceTokens(namingConfig.SeriesFolderFormat, tokenHandlers, namingConfig)); } + public string GetArtistFolder(Artist artist, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddArtistTokens(tokenHandlers, artist); + + return CleanFolderName(ReplaceTokens(namingConfig.ArtistFolderFormat, tokenHandlers, namingConfig)); + } + public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null) { if (namingConfig == null) @@ -247,6 +263,20 @@ namespace NzbDrone.Core.Organizer return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig)); } + public string GetAlbumFolder(Album album, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddAlbumTokens(tokenHandlers, album); + + return CleanFolderName(ReplaceTokens(namingConfig.AlbumFolderFormat, tokenHandlers, namingConfig)); + } + public static string CleanTitle(string title) { title = title.Replace("&", "and"); @@ -285,7 +315,14 @@ namespace NzbDrone.Core.Organizer private void AddArtistTokens(Dictionary> tokenHandlers, Artist artist) { tokenHandlers["{Artist Name}"] = m => artist.Name; - tokenHandlers["{Artist CleanTitle}"] = m => CleanTitle(artist.Name); + tokenHandlers["{Artist CleanName}"] = m => CleanTitle(artist.Name); + } + + private void AddAlbumTokens(Dictionary> tokenHandlers, Album album) + { + tokenHandlers["{Album Title}"] = m => album.Title; + tokenHandlers["{Album CleanTitle}"] = m => CleanTitle(album.Title); + tokenHandlers["{Album Year}"] = m => album.ReleaseDate.Year.ToString(); } private string AddSeasonEpisodeNumberingTokens(string pattern, Dictionary> tokenHandlers, List episodes, NamingConfig namingConfig) diff --git a/src/UI/AddArtist/SearchResultView.js b/src/UI/AddArtist/SearchResultView.js index 47dd29944..9c0529c60 100644 --- a/src/UI/AddArtist/SearchResultView.js +++ b/src/UI/AddArtist/SearchResultView.js @@ -223,12 +223,12 @@ var view = Marionette.ItemView.extend({ self.close(); Messenger.show({ - message : 'Added: ' + self.model.get('artistName'), + message : 'Added: ' + self.model.get('name'), actions : { goToArtist : { label : 'Go to Artist', action : function() { - Backbone.history.navigate('/artist/' + self.model.get('artistSlug'), { trigger : true }); + Backbone.history.navigate('/artist/' + self.model.get('nameSlug'), { trigger : true }); } } }, diff --git a/src/UI/AddArtist/SearchResultViewTemplate.hbs b/src/UI/AddArtist/SearchResultViewTemplate.hbs index 686bad6b3..3c27e1b97 100644 --- a/src/UI/AddArtist/SearchResultViewTemplate.hbs +++ b/src/UI/AddArtist/SearchResultViewTemplate.hbs @@ -16,11 +16,11 @@ - +
{{#unless existing}} {{#unless path}}