diff --git a/gulp/less.js b/gulp/less.js index 76e04b8dc..0baf2eb31 100644 --- a/gulp/less.js +++ b/gulp/less.js @@ -17,8 +17,10 @@ gulp.task('less', function() { paths.src.content + 'theme.less', paths.src.content + 'overrides.less', paths.src.root + 'Series/series.less', + paths.src.root + 'Artist/artist.less', paths.src.root + 'Activity/activity.less', paths.src.root + 'AddSeries/addSeries.less', + paths.src.root + 'AddArtist/addArtist.less', paths.src.root + 'Calendar/calendar.less', paths.src.root + 'Cells/cells.less', paths.src.root + 'ManualImport/manualimport.less', diff --git a/src/NzbDrone.Api/Config/NamingConfigModule.cs b/src/NzbDrone.Api/Config/NamingConfigModule.cs index 0b72e0b0c..d2f05539a 100644 --- a/src/NzbDrone.Api/Config/NamingConfigModule.cs +++ b/src/NzbDrone.Api/Config/NamingConfigModule.cs @@ -35,10 +35,13 @@ namespace NzbDrone.Api.Config SharedValidator.RuleFor(c => c.MultiEpisodeStyle).InclusiveBetween(0, 5); SharedValidator.RuleFor(c => c.StandardEpisodeFormat).ValidEpisodeFormat(); + SharedValidator.RuleFor(c => c.StandardTrackFormat).ValidTrackFormat(); SharedValidator.RuleFor(c => c.DailyEpisodeFormat).ValidDailyEpisodeFormat(); SharedValidator.RuleFor(c => c.AnimeEpisodeFormat).ValidAnimeEpisodeFormat(); SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat(); SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat(); + SharedValidator.RuleFor(c => c.ArtistFolderFormat).ValidArtistFolderFormat(); + SharedValidator.RuleFor(c => c.AlbumFolderFormat).ValidAlbumFolderFormat(); } private void UpdateNamingConfig(NamingConfigResource resource) @@ -74,6 +77,7 @@ namespace NzbDrone.Api.Config var sampleResource = new NamingSampleResource(); var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec); + var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec); var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec); var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec); var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec); @@ -83,6 +87,10 @@ namespace NzbDrone.Api.Config ? "Invalid format" : singleEpisodeSampleResult.FileName; + sampleResource.SingleTrackExample = _filenameValidationService.ValidateTrackFilename(singleTrackSampleResult) != null + ? "Invalid format" + : singleTrackSampleResult.FileName; + sampleResource.MultiEpisodeExample = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult) != null ? "Invalid format" : multiEpisodeSampleResult.FileName; @@ -107,18 +115,28 @@ namespace NzbDrone.Api.Config ? "Invalid format" : _filenameSampleService.GetSeasonFolderSample(nameSpec); + sampleResource.ArtistFolderExample = nameSpec.ArtistFolderFormat.IsNullOrWhiteSpace() + ? "Invalid format" + : _filenameSampleService.GetArtistFolderSample(nameSpec); + + sampleResource.AlbumFolderExample = nameSpec.AlbumFolderFormat.IsNullOrWhiteSpace() + ? "Invalid format" + : _filenameSampleService.GetAlbumFolderSample(nameSpec); + return sampleResource.AsResponse(); } private void ValidateFormatResult(NamingConfig nameSpec) { var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec); + var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec); var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec); var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec); var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec); var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec); var singleEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult); + var singleTrackValidationResult = _filenameValidationService.ValidateTrackFilename(singleTrackSampleResult); var multiEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult); var dailyEpisodeValidationResult = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult); var animeEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult); @@ -127,6 +145,7 @@ namespace NzbDrone.Api.Config var validationFailures = new List(); validationFailures.AddIfNotNull(singleEpisodeValidationResult); + validationFailures.AddIfNotNull(singleTrackValidationResult); validationFailures.AddIfNotNull(multiEpisodeValidationResult); validationFailures.AddIfNotNull(dailyEpisodeValidationResult); validationFailures.AddIfNotNull(animeEpisodeValidationResult); diff --git a/src/NzbDrone.Api/Config/NamingConfigResource.cs b/src/NzbDrone.Api/Config/NamingConfigResource.cs index 39147b993..d49c39936 100644 --- a/src/NzbDrone.Api/Config/NamingConfigResource.cs +++ b/src/NzbDrone.Api/Config/NamingConfigResource.cs @@ -6,13 +6,17 @@ namespace NzbDrone.Api.Config public class NamingConfigResource : RestResource { public bool RenameEpisodes { get; set; } + public bool RenameTracks { get; set; } public bool ReplaceIllegalCharacters { get; set; } public int MultiEpisodeStyle { get; set; } public string StandardEpisodeFormat { get; set; } + public string StandardTrackFormat { get; set; } public string DailyEpisodeFormat { get; set; } public string AnimeEpisodeFormat { get; set; } public string SeriesFolderFormat { get; set; } public string SeasonFolderFormat { get; set; } + public string ArtistFolderFormat { get; set; } + public string AlbumFolderFormat { get; set; } public bool IncludeSeriesTitle { get; set; } public bool IncludeEpisodeTitle { get; set; } public bool IncludeQuality { get; set; } @@ -30,13 +34,17 @@ namespace NzbDrone.Api.Config Id = model.Id, RenameEpisodes = model.RenameEpisodes, + RenameTracks = model.RenameTracks, ReplaceIllegalCharacters = model.ReplaceIllegalCharacters, MultiEpisodeStyle = model.MultiEpisodeStyle, StandardEpisodeFormat = model.StandardEpisodeFormat, + StandardTrackFormat = model.StandardTrackFormat, DailyEpisodeFormat = model.DailyEpisodeFormat, AnimeEpisodeFormat = model.AnimeEpisodeFormat, SeriesFolderFormat = model.SeriesFolderFormat, - SeasonFolderFormat = model.SeasonFolderFormat + SeasonFolderFormat = model.SeasonFolderFormat, + ArtistFolderFormat = model.ArtistFolderFormat, + AlbumFolderFormat = model.AlbumFolderFormat //IncludeSeriesTitle //IncludeEpisodeTitle //IncludeQuality @@ -63,13 +71,17 @@ namespace NzbDrone.Api.Config Id = resource.Id, RenameEpisodes = resource.RenameEpisodes, + RenameTracks = resource.RenameTracks, ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters, MultiEpisodeStyle = resource.MultiEpisodeStyle, StandardEpisodeFormat = resource.StandardEpisodeFormat, + StandardTrackFormat = resource.StandardTrackFormat, DailyEpisodeFormat = resource.DailyEpisodeFormat, AnimeEpisodeFormat = resource.AnimeEpisodeFormat, SeriesFolderFormat = resource.SeriesFolderFormat, - SeasonFolderFormat = resource.SeasonFolderFormat + SeasonFolderFormat = resource.SeasonFolderFormat, + ArtistFolderFormat = resource.ArtistFolderFormat, + AlbumFolderFormat = resource.AlbumFolderFormat }; } } diff --git a/src/NzbDrone.Api/Config/NamingSampleResource.cs b/src/NzbDrone.Api/Config/NamingSampleResource.cs index 1f9c7f066..f6d6d15b3 100644 --- a/src/NzbDrone.Api/Config/NamingSampleResource.cs +++ b/src/NzbDrone.Api/Config/NamingSampleResource.cs @@ -3,11 +3,14 @@ public class NamingSampleResource { public string SingleEpisodeExample { get; set; } + public string SingleTrackExample { get; set; } public string MultiEpisodeExample { get; set; } public string DailyEpisodeExample { get; set; } public string AnimeEpisodeExample { get; set; } public string AnimeMultiEpisodeExample { get; set; } public string SeriesFolderExample { get; set; } public string SeasonFolderExample { get; set; } + public string ArtistFolderExample { get; set; } + public string AlbumFolderExample { get; set; } } } \ No newline at end of file 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.Api/Music/ArtistModule.cs b/src/NzbDrone.Api/Music/ArtistModule.cs index 61acceb0e..4ee6898b9 100644 --- a/src/NzbDrone.Api/Music/ArtistModule.cs +++ b/src/NzbDrone.Api/Music/ArtistModule.cs @@ -71,7 +71,7 @@ namespace NzbDrone.Api.Music PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace()); PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace()); - PostValidator.RuleFor(s => s.SpotifyId).NotEqual("").SetValidator(artistExistsValidator); + PostValidator.RuleFor(s => s.ForeignArtistId).NotEqual("").SetValidator(artistExistsValidator); PutValidator.RuleFor(s => s.Path).IsValidPath(); } diff --git a/src/NzbDrone.Api/Music/ArtistResource.cs b/src/NzbDrone.Api/Music/ArtistResource.cs index 71cc14b85..d75fbebdb 100644 --- a/src/NzbDrone.Api/Music/ArtistResource.cs +++ b/src/NzbDrone.Api/Music/ArtistResource.cs @@ -18,8 +18,8 @@ namespace NzbDrone.Api.Music //View Only - public string ArtistName { get; set; } - public string SpotifyId { get; set; } + public string Name { get; set; } + public string ForeignArtistId { get; set; } public string Overview { get; set; } public int AlbumCount @@ -59,7 +59,7 @@ namespace NzbDrone.Api.Music public DateTime Added { get; set; } public AddSeriesOptions AddOptions { get; set; } public Ratings Ratings { get; set; } - public string ArtistSlug { get; internal set; } + public string NameSlug { get; set; } } public static class ArtistResourceMapper @@ -72,7 +72,7 @@ namespace NzbDrone.Api.Music { Id = model.Id, - ArtistName = model.ArtistName, + Name = model.Name, //AlternateTitles //SortTitle = resource.SortTitle, @@ -94,7 +94,6 @@ namespace NzbDrone.Api.Music Path = model.Path, ProfileId = model.ProfileId, - ArtistFolder = model.ArtistFolder, Monitored = model.Monitored, //UseSceneNumbering = resource.UseSceneNumbering, @@ -105,8 +104,8 @@ namespace NzbDrone.Api.Music //FirstAired = resource.FirstAired, //LastInfoSync = resource.LastInfoSync, //SeriesType = resource.SeriesType, - SpotifyId = model.SpotifyId, - ArtistSlug = model.ArtistSlug, + ForeignArtistId = model.ForeignArtistId, + NameSlug = model.NameSlug, RootFolderPath = model.RootFolderPath, Genres = model.Genres, @@ -125,7 +124,7 @@ namespace NzbDrone.Api.Music { Id = resource.Id, - ArtistName = resource.ArtistName, + Name = resource.Name, //AlternateTitles //SortTitle = resource.SortTitle, @@ -147,11 +146,10 @@ namespace NzbDrone.Api.Music Path = resource.Path, ProfileId = resource.ProfileId, - ArtistFolder = resource.ArtistFolder, Monitored = resource.Monitored, //LastInfoSync = resource.LastInfoSync, - SpotifyId = resource.SpotifyId, - ArtistSlug = resource.ArtistSlug, + ForeignArtistId = resource.ForeignArtistId, + NameSlug = resource.NameSlug, RootFolderPath = resource.RootFolderPath, Genres = resource.Genres, diff --git a/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs b/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs index 9efdda47c..ca8385242 100644 --- a/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs +++ b/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs @@ -17,8 +17,10 @@ namespace NzbDrone.Common.Cloud Services = new HttpRequestBuilder("http://services.lidarr.tv/v1/") .CreateFactory(); - Search = new HttpRequestBuilder("https://api.spotify.com/{version}/{route}/") // TODO: maybe use {version} - .SetSegment("version", "v1") + //Search = new HttpRequestBuilder("https://api.spotify.com/{version}/{route}/") // TODO: maybe use {version} + // .SetSegment("version", "v1") + // .CreateFactory(); + Search = new HttpRequestBuilder("http://localhost:5000/{route}/") // TODO: maybe use {version} .CreateFactory(); InternalSearch = new HttpRequestBuilder("https://itunes.apple.com/WebObjects/MZStore.woa/wa/{route}") //viewArtist or search diff --git a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs index 12b0c2d0b..117412062 100644 --- a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs +++ b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs @@ -12,64 +12,94 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - Create.TableForModel("Artist") - .WithColumn("SpotifyId").AsString().Nullable().Unique() - .WithColumn("ArtistName").AsString().Unique() - .WithColumn("ArtistSlug").AsString().Nullable() //.Unique() - .WithColumn("CleanTitle").AsString().Nullable() // Do we need this? - .WithColumn("Monitored").AsBoolean() + Create.TableForModel("Artists") + .WithColumn("ForeignArtistId").AsString().Unique() + .WithColumn("MBId").AsString().Nullable() + .WithColumn("AMId").AsString().Nullable() + .WithColumn("TADBId").AsInt32().Nullable() + .WithColumn("DiscogsId").AsInt32().Nullable() + .WithColumn("Name").AsString() + .WithColumn("NameSlug").AsString().Nullable().Unique() + .WithColumn("CleanName").AsString().Indexed() + .WithColumn("Status").AsInt32() .WithColumn("Overview").AsString().Nullable() - .WithColumn("AlbumFolder").AsBoolean().Nullable() - .WithColumn("ArtistFolder").AsBoolean().Nullable() + .WithColumn("Images").AsString() + .WithColumn("Path").AsString().Indexed() + .WithColumn("Monitored").AsBoolean() + .WithColumn("AlbumFolder").AsBoolean() .WithColumn("LastInfoSync").AsDateTime().Nullable() .WithColumn("LastDiskSync").AsDateTime().Nullable() - .WithColumn("Status").AsInt32().Nullable() - .WithColumn("Path").AsString() - .WithColumn("Images").AsString().Nullable() - .WithColumn("QualityProfileId").AsInt32().Nullable() - .WithColumn("RootFolderPath").AsString().Nullable() - .WithColumn("Added").AsDateTime().Nullable() - .WithColumn("ProfileId").AsInt32().Nullable() // This is either ProfileId or Profile + .WithColumn("DateFormed").AsDateTime().Nullable() + .WithColumn("Members").AsString().Nullable() + .WithColumn("Ratings").AsString().Nullable() .WithColumn("Genres").AsString().Nullable() - .WithColumn("Albums").AsString().Nullable() + .WithColumn("SortName").AsString().Nullable() + .WithColumn("ProfileId").AsInt32().Nullable() .WithColumn("Tags").AsString().Nullable() - .WithColumn("AddOptions").AsString().Nullable() - ; + .WithColumn("Added").AsDateTime().Nullable() + .WithColumn("AddOptions").AsString().Nullable(); Create.TableForModel("Albums") - .WithColumn("AlbumId").AsString().Unique() - .WithColumn("ArtistId").AsInt32() // Should this be artistId (string) + .WithColumn("ForeignAlbumId").AsString().Unique() + .WithColumn("ArtistId").AsInt32() + .WithColumn("MBId").AsString().Nullable().Indexed() + .WithColumn("AMId").AsString().Nullable() + .WithColumn("TADBId").AsInt32().Nullable().Indexed() + .WithColumn("DiscogsId").AsInt32().Nullable() .WithColumn("Title").AsString() - .WithColumn("Year").AsInt32() - .WithColumn("Image").AsInt32() - .WithColumn("TrackCount").AsInt32() - .WithColumn("DiscCount").AsInt32() + .WithColumn("TitleSlug").AsString().Nullable().Unique() + .WithColumn("CleanTitle").AsString().Indexed() + .WithColumn("Overview").AsString().Nullable() + .WithColumn("Images").AsString() + .WithColumn("Path").AsString().Indexed() .WithColumn("Monitored").AsBoolean() - .WithColumn("Overview").AsString(); + .WithColumn("LastInfoSync").AsDateTime().Nullable() + .WithColumn("LastDiskSync").AsDateTime().Nullable() + .WithColumn("ReleaseDate").AsDateTime().Nullable() + .WithColumn("Ratings").AsString().Nullable() + .WithColumn("Genres").AsString().Nullable() + .WithColumn("Label").AsString().Nullable() + .WithColumn("SortTitle").AsString().Nullable() + .WithColumn("ProfileId").AsInt32().Nullable() + .WithColumn("Tags").AsString().Nullable() + .WithColumn("Added").AsDateTime().Nullable() + .WithColumn("AlbumType").AsString() + .WithColumn("AddOptions").AsString().Nullable(); Create.TableForModel("Tracks") - .WithColumn("SpotifyTrackId").AsString().Nullable() // This shouldn't be nullable, but TrackRepository won't behave. Someone please fix this. - .WithColumn("AlbumId").AsString() - .WithColumn("ArtistId").AsString() // This may be a list of Ids in future for compilations - .WithColumn("ArtistSpotifyId").AsString() - .WithColumn("Compilation").AsBoolean() + .WithColumn("ForeignTrackId").AsString().Unique() + .WithColumn("ArtistId").AsInt32().Indexed() + .WithColumn("AlbumId").AsInt32() + .WithColumn("MBId").AsString().Nullable().Indexed() .WithColumn("TrackNumber").AsInt32() .WithColumn("Title").AsString().Nullable() - .WithColumn("Ignored").AsBoolean().Nullable() - .WithColumn("Explict").AsBoolean() + .WithColumn("Explicit").AsBoolean() + .WithColumn("Compilation").AsBoolean() + .WithColumn("DiscNumber").AsInt32().Nullable() + .WithColumn("TrackFileId").AsInt32().Nullable().Indexed() .WithColumn("Monitored").AsBoolean() - .WithColumn("TrackFileId").AsInt32().Nullable() - .WithColumn("ReleaseDate").AsDateTime().Nullable(); + .WithColumn("Ratings").AsString().Nullable(); + Create.Index().OnTable("Tracks").OnColumn("ArtistId").Ascending() + .OnColumn("AlbumId").Ascending() + .OnColumn("TrackNumber").Ascending(); Create.TableForModel("TrackFiles") - .WithColumn("ArtistId").AsInt32() - .WithColumn("Path").AsString().Unique() + .WithColumn("ArtistId").AsInt32().Indexed() + .WithColumn("AlbumId").AsInt32().Indexed() .WithColumn("Quality").AsString() .WithColumn("Size").AsInt64() .WithColumn("DateAdded").AsDateTime() - .WithColumn("AlbumId").AsInt32(); // How does this impact stand alone tracks? + .WithColumn("SceneName").AsString().Nullable() + .WithColumn("ReleaseGroup").AsString().Nullable() + .WithColumn("MediaInfo").AsString().Nullable() + .WithColumn("RelativePath").AsString().Nullable(); + Alter.Table("NamingConfig") + .AddColumn("ArtistFolderFormat").AsString().Nullable() + .AddColumn("RenameTracks").AsBoolean().Nullable() + .AddColumn("StandardTrackFormat").AsString().Nullable() + .AddColumn("AlbumFolderFormat").AsString().Nullable(); } } diff --git a/src/NzbDrone.Core/Datastore/Migration/112_Add_music_fields_to_NamingConfig.cs b/src/NzbDrone.Core/Datastore/Migration/112_Add_music_fields_to_NamingConfig.cs deleted file mode 100644 index b855e1e28..000000000 --- a/src/NzbDrone.Core/Datastore/Migration/112_Add_music_fields_to_NamingConfig.cs +++ /dev/null @@ -1,15 +0,0 @@ -using FluentMigrator; -using NzbDrone.Core.Datastore.Migration.Framework; - -namespace NzbDrone.Core.Datastore.Migration -{ - [Migration(112)] - public class add_music_fields_to_namingconfig : NzbDroneMigrationBase - { - protected override void MainDbUpgrade() - { - Alter.Table("NamingConfig").AddColumn("ArtistFolderFormat").AsAnsiString().Nullable(); - Alter.Table("NamingConfig").AddColumn("AlbumFolderFormat").AsAnsiString().Nullable(); - } - } -} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index 9b9905adc..c5945047b 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -92,11 +92,13 @@ namespace NzbDrone.Core.Datastore .Relationship() .HasOne(episode => episode.EpisodeFile, episode => episode.EpisodeFileId); - Mapper.Entity().RegisterModel("Artist") + Mapper.Entity().RegisterModel("Artists") .Ignore(s => s.RootFolderPath) .Relationship() .HasOne(a => a.Profile, a => a.ProfileId); + Mapper.Entity().RegisterModel("Albums"); + Mapper.Entity().RegisterModel("TrackFiles") .Ignore(f => f.Path) .Relationships.AutoMapICollectionOrComplexProperties() diff --git a/src/NzbDrone.Core/Download/AlbumGrabbedEvent.cs b/src/NzbDrone.Core/Download/AlbumGrabbedEvent.cs new file mode 100644 index 000000000..5b7972802 --- /dev/null +++ b/src/NzbDrone.Core/Download/AlbumGrabbedEvent.cs @@ -0,0 +1,17 @@ +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Download +{ + public class AlbumGrabbedEvent : IEvent + { + public RemoteAlbum Album { get; private set; } + public string DownloadClient { get; set; } + public string DownloadId { get; set; } + + public AlbumGrabbedEvent(RemoteAlbum album) + { + Album = album; + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs b/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs index b03e16cd4..6fafae599 100644 --- a/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Deluge/DelugeSettings.cs @@ -25,7 +25,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge Host = "localhost"; Port = 8112; Password = "deluge"; - TvCategory = "tv-Lidarr"; + TvCategory = "lidarr"; } [FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)] @@ -43,10 +43,10 @@ namespace NzbDrone.Core.Download.Clients.Deluge [FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")] public string TvCategory { get; set; } - [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")] public int RecentTvPriority { get; set; } - [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")] public int OlderTvPriority { get; set; } [FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)] diff --git a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs index 9a4561fa0..b4e22fe91 100644 --- a/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/NzbVortex/NzbVortexSettings.cs @@ -29,7 +29,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex { Host = "localhost"; Port = 4321; - TvCategory = "TV Shows"; + TvCategory = "Music"; RecentTvPriority = (int)NzbVortexPriority.Normal; OlderTvPriority = (int)NzbVortexPriority.Normal; } @@ -46,10 +46,10 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex [FieldDefinition(3, Label = "Group", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")] public string TvCategory { get; set; } - [FieldDefinition(4, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(4, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")] public int RecentTvPriority { get; set; } - [FieldDefinition(5, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(5, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")] public int OlderTvPriority { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs index 3b20b010c..8a75be249 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/NzbgetSettings.cs @@ -26,7 +26,9 @@ namespace NzbDrone.Core.Download.Clients.Nzbget { Host = "localhost"; Port = 6789; - TvCategory = "tv"; + TvCategory = "Music"; + Username = "nzbget"; + Password = "tegbzn6789"; RecentTvPriority = (int)NzbgetPriority.Normal; OlderTvPriority = (int)NzbgetPriority.Normal; } @@ -46,10 +48,10 @@ namespace NzbDrone.Core.Download.Clients.Nzbget [FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")] public string TvCategory { get; set; } - [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")] public int RecentTvPriority { get; set; } - [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")] public int OlderTvPriority { get; set; } [FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)] diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs index aeddcbf3a..6bdffa80c 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentSettings.cs @@ -22,7 +22,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent { Host = "localhost"; Port = 9091; - TvCategory = "tv-Lidarr"; + TvCategory = "lidarr"; } [FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)] @@ -40,10 +40,10 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent [FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")] public string TvCategory { get; set; } - [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")] public int RecentTvPriority { get; set; } - [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")] public int OlderTvPriority { get; set; } [FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use a secure connection. See Options -> Web UI -> 'Use HTTPS instead of HTTP' in qBittorrent.")] diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs index 374079134..ce86d3773 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/SabnzbdSettings.cs @@ -38,7 +38,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd { Host = "localhost"; Port = 8080; - TvCategory = "tv"; + TvCategory = "music"; RecentTvPriority = (int)SabnzbdPriority.Default; OlderTvPriority = (int)SabnzbdPriority.Default; } @@ -61,10 +61,10 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd [FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")] public string TvCategory { get; set; } - [FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")] public int RecentTvPriority { get; set; } - [FieldDefinition(7, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(7, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")] public int OlderTvPriority { get; set; } [FieldDefinition(8, Label = "Use SSL", Type = FieldType.Checkbox)] diff --git a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs index 38f2a60a1..9626eb7f7 100644 --- a/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/Transmission/TransmissionSettings.cs @@ -56,10 +56,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission [FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Transmission location")] public string TvDirectory { get; set; } - [FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")] public int RecentTvPriority { get; set; } - [FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")] public int OlderTvPriority { get; set; } [FieldDefinition(9, Label = "Use SSL", Type = FieldType.Checkbox)] diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs index 7e964db7a..07a5c9b6f 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs @@ -26,7 +26,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent Host = "localhost"; Port = 8080; UrlBase = "RPC2"; - TvCategory = "tv-Lidarr"; + TvCategory = "lidarr"; OlderTvPriority = (int)RTorrentPriority.Normal; RecentTvPriority = (int)RTorrentPriority.Normal; } @@ -55,10 +55,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent [FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default rTorrent location")] public string TvDirectory { get; set; } - [FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")] public int RecentTvPriority { get; set; } - [FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")] public int OlderTvPriority { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs index e76406084..103bec26e 100644 --- a/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/uTorrent/UTorrentSettings.cs @@ -23,7 +23,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent { Host = "localhost"; Port = 9091; - TvCategory = "tv-Lidarr"; + TvCategory = "lidarr"; } [FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)] @@ -41,10 +41,10 @@ namespace NzbDrone.Core.Download.Clients.UTorrent [FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")] public string TvCategory { get; set; } - [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")] public int RecentTvPriority { get; set; } - [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")] public int OlderTvPriority { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/Download/TorrentClientBase.cs b/src/NzbDrone.Core/Download/TorrentClientBase.cs index 609cd3bd6..61986ce8f 100644 --- a/src/NzbDrone.Core/Download/TorrentClientBase.cs +++ b/src/NzbDrone.Core/Download/TorrentClientBase.cs @@ -154,7 +154,7 @@ namespace NzbDrone.Core.Download torrentFile = response.ResponseData; - _logger.Debug("Downloading torrent for episode '{0}' finished ({1} bytes from {2})", remoteEpisode.Release.Title, torrentFile.Length, torrentUrl); + _logger.Debug("Downloading torrent for release '{0}' finished ({1} bytes from {2})", remoteEpisode.Release.Title, torrentFile.Length, torrentUrl); } catch (HttpException ex) { @@ -164,14 +164,14 @@ namespace NzbDrone.Core.Download } else { - _logger.Error(ex, "Downloading torrent file for episode '{0}' failed ({1})", remoteEpisode.Release.Title, torrentUrl); + _logger.Error(ex, "Downloading torrent file for release '{0}' failed ({1})", remoteEpisode.Release.Title, torrentUrl); } throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading torrent failed", ex); } catch (WebException ex) { - _logger.Error(ex, "Downloading torrent file for episode '{0}' failed ({1})", remoteEpisode.Release.Title, torrentUrl); + _logger.Error(ex, "Downloading torrent file for release '{0}' failed ({1})", remoteEpisode.Release.Title, torrentUrl); throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading torrent failed", ex); } @@ -201,7 +201,7 @@ namespace NzbDrone.Core.Download } catch (FormatException ex) { - _logger.Error(ex, "Failed to parse magnetlink for episode '{0}': '{1}'", remoteEpisode.Release.Title, magnetUrl); + _logger.Error(ex, "Failed to parse magnetlink for release '{0}': '{1}'", remoteEpisode.Release.Title, magnetUrl); return null; } diff --git a/src/NzbDrone.Core/Download/UsenetClientBase.cs b/src/NzbDrone.Core/Download/UsenetClientBase.cs index a6c0ed7d5..1d50dd178 100644 --- a/src/NzbDrone.Core/Download/UsenetClientBase.cs +++ b/src/NzbDrone.Core/Download/UsenetClientBase.cs @@ -42,7 +42,7 @@ namespace NzbDrone.Core.Download { nzbData = _httpClient.Get(new HttpRequest(url)).ResponseData; - _logger.Debug("Downloaded nzb for episode '{0}' finished ({1} bytes from {2})", remoteEpisode.Release.Title, nzbData.Length, url); + _logger.Debug("Downloaded nzb for release '{0}' finished ({1} bytes from {2})", remoteEpisode.Release.Title, nzbData.Length, url); } catch (HttpException ex) { @@ -52,14 +52,14 @@ namespace NzbDrone.Core.Download } else { - _logger.Error(ex, "Downloading nzb for episode '{0}' failed ({1})", remoteEpisode.Release.Title, url); + _logger.Error(ex, "Downloading nzb for release '{0}' failed ({1})", remoteEpisode.Release.Title, url); } throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading nzb failed", ex); } catch (WebException ex) { - _logger.Error(ex, "Downloading nzb for episode '{0}' failed ({1})", remoteEpisode.Release.Title, url); + _logger.Error(ex, "Downloading nzb for release '{0}' failed ({1})", remoteEpisode.Release.Title, url); throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading nzb failed", ex); } 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/AlbumInfoResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumInfoResource.cs deleted file mode 100644 index d442be7ec..000000000 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumInfoResource.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace NzbDrone.Core.MetadataSource.SkyHook.Resource -{ - public class AlbumInfoResource - { - public AlbumInfoResource() - { - - } - 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 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 List Images { get; set; } - public string Name { get; set; } // In case of a takedown, this may be empty - } - - -} diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs new file mode 100644 index 000000000..5e5a1f13d --- /dev/null +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumResource.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MetadataSource.SkyHook.Resource +{ + public class AlbumResource + { + 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 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 DateTime ReleaseDate { get; set; } + public List Images { get; set; } + 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/ArtistInfoResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistInfoResource.cs index 35f969001..8a81d873f 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistInfoResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistInfoResource.cs @@ -11,10 +11,9 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource public List Genres { get; set; } public string AristUrl { get; set; } + public string Overview { get; set; } public string Id { get; set; } public List Images { get; set; } - public string Name { get; set; } - - // We may need external_urls.spotify to external linking... + public string ArtistName { get; set; } } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs index 8d98d6890..f2ab72f94 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs @@ -5,44 +5,19 @@ using System.Text; namespace NzbDrone.Core.MetadataSource.SkyHook.Resource { - - public class AristResultResource - { - public AristResultResource() - { - - } - - public List Items { get; set; } - } - - public class AlbumResultResource - { - public AlbumResultResource() - { - - } - - public List Items { get; set; } - } - - public class TrackResultResource - { - public TrackResultResource() - { - - } - - public List Items { get; set; } - } public class ArtistResource { - public ArtistResource() - { - + public ArtistResource() { + Albums = new List(); } - public AristResultResource Artists { get; set; } - public AristResultResource Albums { get; set; } + public List Genres { get; set; } + public string AristUrl { get; set; } + public string Overview { get; set; } + public string Id { get; set; } + public List Images { get; set; } + public string ArtistName { get; set; } + public List Albums { get; set; } + } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackInfoResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackResource.cs similarity index 71% rename from src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackInfoResource.cs rename to src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackResource.cs index 2f905637d..eeb86ba48 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackInfoResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackResource.cs @@ -5,9 +5,9 @@ using System.Text; namespace NzbDrone.Core.MetadataSource.SkyHook.Resource { - public class TrackInfoResource + public class TrackResource { - public TrackInfoResource() + public TrackResource() { } @@ -16,10 +16,10 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource public int DurationMs { get; set; } public string Href { get; set; } public string Id { get; set; } - public string Name { get; set; } + public string TrackName { get; set; } public int TrackNumber { get; set; } public bool Explicit { get; set; } - public List Artists { get; set; } + public List Artists { get; set; } } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index d2db40331..bafc84ecc 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -74,17 +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); - - ///v1/albums/{id} - // + _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()) @@ -95,14 +92,14 @@ namespace NzbDrone.Core.MetadataSource.SkyHook httpRequest.AllowAutoRedirect = true; httpRequest.SuppressHttpError = true; - var httpResponse = _httpClient.Get(httpRequest); + var httpResponse = _httpClient.Get(httpRequest); if (httpResponse.HasHttpError) { if (httpResponse.StatusCode == HttpStatusCode.NotFound) { - throw new ArtistNotFoundException(spotifyId); + throw new ArtistNotFoundException(foreignArtistId); } else { @@ -110,92 +107,96 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } } - Artist artist = new Artist(); - artist.ArtistName = httpResponse.Resource.Name; - artist.SpotifyId = httpResponse.Resource.Id; - artist.Genres = httpResponse.Resource.Genres; + // It is safe to assume an id will only return one Artist back + var albums = httpResponse.Resource.Albums.Select(MapAlbum); + var artist = MapArtist(httpResponse.Resource); - var albumRet = MapAlbums(artist); - artist = albumRet.Item1; + //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>(albumRet.Item1, albumRet.Item2); + return new Tuple>(artist, albums.ToList()); } - private Tuple> MapAlbums(Artist artist) - { - - // Find all albums for the artist and all tracks for said album - ///v1/artists/{id}/albums - var httpRequest = _requestBuilder.Create() - .SetSegment("route", "artists/" + artist.SpotifyId + "/albums") - .Build(); - httpRequest.AllowAutoRedirect = true; - httpRequest.SuppressHttpError = true; - - var httpResponse = _httpClient.Get(httpRequest); - - if (httpResponse.HasHttpError) - { - throw new HttpException(httpRequest, httpResponse); - } - - List masterTracks = new List(); - List albums = new List(); - foreach(var albumResource in httpResponse.Resource.Items) - { - Album album = new Album(); - album.AlbumId = albumResource.Id; - album.Title = albumResource.Name; - album.ArtworkUrl = albumResource.Images.Count > 0 ? albumResource.Images[0].Url : ""; - album.Tracks = MapTracksToAlbum(album); - masterTracks.InsertRange(masterTracks.Count, album.Tracks); - albums.Add(album); - } - - // TODO: We now need to get all tracks for each album - - artist.Albums = albums; - return new Tuple>(artist, masterTracks); - } - - private List MapTracksToAlbum(Album album) - { - var httpRequest = _requestBuilder.Create() - .SetSegment("route", "albums/" + album.AlbumId + "/tracks") - .Build(); - - httpRequest.AllowAutoRedirect = true; - httpRequest.SuppressHttpError = true; - var httpResponse = _httpClient.Get(httpRequest); - if (httpResponse.HasHttpError) - { - throw new HttpException(httpRequest, httpResponse); - } + //private Tuple> MapAlbums(Artist artist) + //{ - List tracks = new List(); - foreach(var trackResource in httpResponse.Resource.Items) - { - Track track = new Track(); - track.AlbumId = album.AlbumId; - //track.Album = album; // This will cause infinite loop when trying to serialize. - // TODO: Implement more track mapping - //track.Artist = trackResource.Artists - //track.ArtistId = album. - track.SpotifyTrackId = trackResource.Id; - track.ArtistSpotifyId = trackResource.Artists.Count > 0 ? trackResource.Artists[0].Id : null; - track.Explict = trackResource.Explicit; - track.Compilation = trackResource.Artists.Count > 1; - track.TrackNumber = trackResource.TrackNumber; - track.Title = trackResource.Name; - tracks.Add(track); - } + // // Find all albums for the artist and all tracks for said album + // ///v1/artists/{id}/albums + // var httpRequest = _requestBuilder.Create() + // .SetSegment("route", "artists/" + artist.ForeignArtistId + "/albums") + // .Build(); + // httpRequest.AllowAutoRedirect = true; + // httpRequest.SuppressHttpError = true; + + // var httpResponse = _httpClient.Get(httpRequest); + + // if (httpResponse.HasHttpError) + // { + // throw new HttpException(httpRequest, httpResponse); + // } + + // List masterTracks = new List(); + // List albums = new List(); + // foreach(var albumResource in httpResponse.Resource.Items) + // { + // Album album = new Album(); + // album.AlbumId = albumResource.Id; + // album.Title = albumResource.AlbumName; + // album.ArtworkUrl = albumResource.Images.Count > 0 ? albumResource.Images[0].Url : ""; + // album.Tracks = MapTracksToAlbum(album); + // masterTracks.InsertRange(masterTracks.Count, album.Tracks); + // albums.Add(album); + // } + + // // TODO: We now need to get all tracks for each album + + // artist.Albums = albums; + // return new Tuple>(artist, masterTracks); + //} - return tracks; - } + //private List MapTracksToAlbum(Album album) + //{ + // var httpRequest = _requestBuilder.Create() + // .SetSegment("route", "albums/" + album.AlbumId + "/tracks") + // .Build(); + + // httpRequest.AllowAutoRedirect = true; + // httpRequest.SuppressHttpError = true; + + // var httpResponse = _httpClient.Get(httpRequest); + + // if (httpResponse.HasHttpError) + // { + // throw new HttpException(httpRequest, httpResponse); + // } + + // List tracks = new List(); + // foreach (var trackResource in httpResponse.Resource.Items) + // { + // Track track = new Track(); + // track.AlbumId = album.AlbumId; + // //track.Album = album; // This will cause infinite loop when trying to serialize. + // // TODO: Implement more track mapping + // //track.Artist = trackResource.Artists + // //track.ArtistId = album. + // track.SpotifyTrackId = trackResource.Id; + // track.ArtistSpotifyId = trackResource.Artists.Count > 0 ? trackResource.Artists[0].Id : null; + // track.Explict = trackResource.Explicit; + // track.Compilation = trackResource.Artists.Count > 1; + // track.TrackNumber = trackResource.TrackNumber; + // track.Title = trackResource.TrackName; + // tracks.Add(track); + // } + + // return tracks; + //} public List SearchForNewArtist(string title) @@ -226,18 +227,32 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var httpRequest = _requestBuilder.Create() .SetSegment("route", "search") - .AddQueryParam("type", "artist,album") - .AddQueryParam("q", title.ToLower().Trim()) + .AddQueryParam("type", "artist") // TODO: LidarrAPI.Metadata is getting , encoded. Needs to be raw , + .AddQueryParam("query", title.ToLower().Trim()) .Build(); - var httpResponse = _httpClient.Get(httpRequest); + var httpResponse = _httpClient.Get>(httpRequest); + + return httpResponse.Resource.SelectList(MapArtist); + //List artists = MapArtists(httpResponse.Resource); + //List artists = new List(); + //foreach (var artistResource in httpResponse.Resource.Artists.Items) + //{ + // Artist artist = new Artist(); + // artist.Name = artistResource.ArtistName; + // artist.ForeignArtistId = artistResource.Id; // TODO: Rename spotifyId to LidarrId + // artist.Genres = artistResource.Genres; + // artist.NameSlug = Parser.Parser.CleanArtistTitle(artist.Name); + // artist.CleanName = Parser.Parser.CleanArtistTitle(artist.Name); + //artist.Images = artistResource.Images; + // artists.Add(artist); + //} - List artists = MapArtists(httpResponse.Resource); - return artists; + //return artists; } catch (HttpException) { @@ -250,39 +265,50 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } } - private Artist MapArtistInfo(ArtistInfoResource resource) + private static Album MapAlbum(AlbumResource resource) { - // This expects ArtistInfoResource, thus just need to populate one artist - Artist artist = new Artist(); - //artist.Overview = resource.artistBio; - //artist.ArtistName = resource.name; - //foreach(var genre in resource.genreNames) - //{ - // artist.Genres.Add(genre); - //} + 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 artist; + 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; } - private List MapArtists(ArtistResource resource) + private static Artist MapArtist(ArtistResource resource) { + Artist artist = new Artist(); + + artist.Name = resource.ArtistName; + artist.ForeignArtistId = resource.Id; // TODO: Rename spotifyId to LidarrId + artist.Genres = resource.Genres; + artist.Overview = resource.Overview; + artist.NameSlug = Parser.Parser.CleanArtistTitle(artist.Name); + artist.CleanName = Parser.Parser.CleanArtistTitle(artist.Name); + //artist.Images = resource.Artists.Items[0].Images; + - List artists = new List(); - foreach(var artistResource in resource.Artists.Items) - { - Artist artist = new Artist(); - artist.ArtistName = artistResource.Name; - artist.SpotifyId = artistResource.Id; - artist.Genres = artistResource.Genres; - artist.ArtistSlug = Parser.Parser.CleanArtistTitle(artist.ArtistName); - artists.Add(artist); - } // Maybe? Get all the albums for said artist - return artists; + return artist; } //private Album MapAlbum(AlbumResource albumQuery) diff --git a/src/NzbDrone.Core/Music/AddArtistService.cs b/src/NzbDrone.Core/Music/AddArtistService.cs index 07f995f69..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.ArtistName;// TODO: _fileNameBuilder.GetArtistFolder(newArtist); + var folderName = _fileNameBuilder.GetArtistFolder(newArtist); newArtist.Path = Path.Combine(newArtist.RootFolderPath, folderName); } - newArtist.CleanTitle = newArtist.ArtistName.CleanSeriesTitle(); + newArtist.CleanName = newArtist.Name.CleanArtistTitle(); //newArtist.SortTitle = ArtistNameNormalizer.Normalize(newArtist.ArtistName, newArtist.ItunesId); // There is no Sort Title newArtist.Added = DateTime.UtcNow; @@ -71,19 +71,19 @@ namespace NzbDrone.Core.Music private Artist AddSkyhookData(Artist newArtist) { - Tuple> tuple; + Tuple> tuple; try { - tuple = _artistInfo.GetArtistInfo(newArtist.SpotifyId); + tuple = _artistInfo.GetArtistInfo(newArtist.ForeignArtistId); } - catch (SeriesNotFoundException) + catch (ArtistNotFoundException) { - _logger.Error("SpotifyId {1} was not found, it may have been removed from Spotify.", newArtist.SpotifyId); + _logger.Error("LidarrId {1} was not found, it may have been removed from Lidarr.", newArtist.ForeignArtistId); throw new ValidationException(new List { - new ValidationFailure("SpotifyId", "An artist with this ID was not found", newArtist.SpotifyId) + new ValidationFailure("SpotifyId", "An artist with this ID was not found", newArtist.ForeignArtistId) }); } diff --git a/src/NzbDrone.Core/Music/AddArtistValidator.cs b/src/NzbDrone.Core/Music/AddArtistValidator.cs index ab789c2fc..bc860f09e 100644 --- a/src/NzbDrone.Core/Music/AddArtistValidator.cs +++ b/src/NzbDrone.Core/Music/AddArtistValidator.cs @@ -28,7 +28,7 @@ namespace NzbDrone.Core.Music .SetValidator(droneFactoryValidator) .SetValidator(seriesAncestorValidator); - RuleFor(c => c.ArtistSlug).SetValidator(artistTitleSlugValidator);// TODO: Check if we are going to use a slug or artistName + RuleFor(c => c.NameSlug).SetValidator(artistTitleSlugValidator);// TODO: Check if we are going to use a slug or artistName } } } diff --git a/src/NzbDrone.Core/Music/Album.cs b/src/NzbDrone.Core/Music/Album.cs index 6d18a766b..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; @@ -7,24 +8,34 @@ using System.Text; namespace NzbDrone.Core.Music { - public class Album : IEmbeddedDocument + public class Album : ModelBase { public Album() { 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/Artist.cs b/src/NzbDrone.Core/Music/Artist.cs index 000aaf928..cdce64860 100644 --- a/src/NzbDrone.Core/Music/Artist.cs +++ b/src/NzbDrone.Core/Music/Artist.cs @@ -22,21 +22,19 @@ namespace NzbDrone.Core.Music } - public string SpotifyId { get; set; } - public string ArtistName { get; set; } - public string ArtistSlug { get; set; } - public string CleanTitle { get; set; } + public string ForeignArtistId { get; set; } + public string Name { get; set; } + public string NameSlug { get; set; } + public string CleanName { get; set; } public string Overview { get; set; } public bool Monitored { get; set; } public bool AlbumFolder { get; set; } - public bool ArtistFolder { get; set; } public DateTime? LastInfoSync { get; set; } public DateTime? LastDiskSync { get; set; } public int Status { get; set; } // TODO: Figure out what this is, do we need it? public string Path { get; set; } public List Images { get; set; } public List Genres { get; set; } - public int QualityProfileId { get; set; } public string RootFolderPath { get; set; } public DateTime Added { get; set; } public LazyLoaded Profile { get; set; } @@ -47,16 +45,16 @@ namespace NzbDrone.Core.Music public override string ToString() { - return string.Format("[{0}][{1}]", SpotifyId, ArtistName.NullSafe()); + return string.Format("[{0}][{1}]", ForeignArtistId, Name.NullSafe()); } public void ApplyChanges(Artist otherArtist) { - SpotifyId = otherArtist.SpotifyId; - ArtistName = otherArtist.ArtistName; - ArtistSlug = otherArtist.ArtistSlug; - CleanTitle = otherArtist.CleanTitle; + ForeignArtistId = otherArtist.ForeignArtistId; + Name = otherArtist.Name; + NameSlug = otherArtist.NameSlug; + CleanName = otherArtist.CleanName; Monitored = otherArtist.Monitored; AlbumFolder = otherArtist.AlbumFolder; LastInfoSync = otherArtist.LastInfoSync; @@ -69,7 +67,6 @@ namespace NzbDrone.Core.Music ProfileId = otherArtist.ProfileId; Albums = otherArtist.Albums; Tags = otherArtist.Tags; - ArtistFolder = otherArtist.ArtistFolder; AddOptions = otherArtist.AddOptions; Albums = otherArtist.Albums; diff --git a/src/NzbDrone.Core/Music/ArtistRepository.cs b/src/NzbDrone.Core/Music/ArtistRepository.cs index 0da04ad0d..907af1662 100644 --- a/src/NzbDrone.Core/Music/ArtistRepository.cs +++ b/src/NzbDrone.Core/Music/ArtistRepository.cs @@ -24,16 +24,16 @@ namespace NzbDrone.Core.Music return Query.Where(c => c.Path == path).Any(); } - public Artist FindById(string spotifyId) + public Artist FindById(string foreignArtistId) { - return Query.Where(s => s.SpotifyId == spotifyId).SingleOrDefault(); + return Query.Where(s => s.ForeignArtistId == foreignArtistId).SingleOrDefault(); } public Artist FindByName(string cleanName) { cleanName = cleanName.ToLowerInvariant(); - return Query.Where(s => s.CleanTitle == cleanName) + return Query.Where(s => s.CleanName == cleanName) .SingleOrDefault(); } } diff --git a/src/NzbDrone.Core/Music/ArtistService.cs b/src/NzbDrone.Core/Music/ArtistService.cs index f6f45cc8f..211f0c978 100644 --- a/src/NzbDrone.Core/Music/ArtistService.cs +++ b/src/NzbDrone.Core/Music/ArtistService.cs @@ -111,11 +111,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.SpotifyId, album.AlbumId, album.Monitored); + _trackService.SetTrackMonitoredByAlbum(artist.ForeignArtistId, album.ForeignAlbumId, album.Monitored); } } @@ -130,17 +130,17 @@ namespace NzbDrone.Core.Music _logger.Debug("Updating {0} artist", artist.Count); foreach (var s in artist) { - _logger.Trace("Updating: {0}", s.ArtistName); + _logger.Trace("Updating: {0}", s.Name); if (!s.RootFolderPath.IsNullOrWhiteSpace()) { var folderName = new DirectoryInfo(s.Path).Name; s.Path = Path.Combine(s.RootFolderPath, folderName); - _logger.Trace("Changing path for {0} to {1}", s.ArtistName, s.Path); + _logger.Trace("Changing path for {0} to {1}", s.Name, s.Path); } else { - _logger.Trace("Not changing path for: {0}", s.ArtistName); + _logger.Trace("Not changing path for: {0}", s.Name); } } diff --git a/src/NzbDrone.Core/Music/ArtistSlugValidator.cs b/src/NzbDrone.Core/Music/ArtistSlugValidator.cs index 4d5626c89..1d894fee2 100644 --- a/src/NzbDrone.Core/Music/ArtistSlugValidator.cs +++ b/src/NzbDrone.Core/Music/ArtistSlugValidator.cs @@ -23,7 +23,7 @@ namespace NzbDrone.Core.Music dynamic instance = context.ParentContext.InstanceToValidate; var instanceId = (int)instance.Id; - return !_artistService.GetAllArtists().Exists(s => s.ArtistSlug.Equals(context.PropertyValue.ToString()) && s.Id != instanceId); + return !_artistService.GetAllArtists().Exists(s => s.NameSlug.Equals(context.PropertyValue.ToString()) && s.Id != instanceId); } } } 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 0eae8692f..5b159bc9e 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; @@ -45,33 +45,33 @@ namespace NzbDrone.Core.Music private void RefreshArtistInfo(Artist artist) { - _logger.ProgressInfo("Updating Info for {0}", artist.ArtistName); + _logger.ProgressInfo("Updating Info for {0}", artist.Name); - Tuple> tuple; + Tuple> tuple; try { - tuple = _artistInfo.GetArtistInfo(artist.SpotifyId); + tuple = _artistInfo.GetArtistInfo(artist.ForeignArtistId); } catch (ArtistNotFoundException) { - _logger.Error("Artist '{0}' (SpotifyId {1}) was not found, it may have been removed from Spotify.", artist.ArtistName, artist.SpotifyId); + _logger.Error("Artist '{0}' (SpotifyId {1}) was not found, it may have been removed from Spotify.", artist.Name, artist.ForeignArtistId); return; } var artistInfo = tuple.Item1; - if (artist.SpotifyId != artistInfo.SpotifyId) + if (artist.ForeignArtistId != artistInfo.ForeignArtistId) { - _logger.Warn("Artist '{0}' (SpotifyId {1}) was replaced with '{2}' (SpotifyId {3}), because the original was a duplicate.", artist.ArtistName, artist.SpotifyId, artistInfo.ArtistName, artistInfo.SpotifyId); - artist.SpotifyId = artistInfo.SpotifyId; + _logger.Warn("Artist '{0}' (SpotifyId {1}) was replaced with '{2}' (SpotifyId {3}), because the original was a duplicate.", artist.Name, artist.ForeignArtistId, artistInfo.Name, artistInfo.ForeignArtistId); + artist.ForeignArtistId = artistInfo.ForeignArtistId; } - artist.ArtistName = artistInfo.ArtistName; - artist.ArtistSlug = artistInfo.ArtistSlug; + artist.Name = artistInfo.Name; + artist.NameSlug = artistInfo.NameSlug; artist.Overview = artistInfo.Overview; artist.Status = artistInfo.Status; - artist.CleanTitle = artistInfo.CleanTitle; + artist.CleanName = artistInfo.CleanName; artist.LastInfoSync = DateTime.UtcNow; artist.Images = artistInfo.Images; artist.Genres = artistInfo.Genres; @@ -89,19 +89,20 @@ 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.ArtistName); + _logger.Debug("Finished artist refresh for {0}", artist.Name); _eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist)); } 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) @@ -112,7 +113,7 @@ namespace NzbDrone.Core.Music // continue; //} - _logger.Debug("New album ({0}) for artist: [{1}] {2}, setting monitored to true", album.Title, artist.SpotifyId, artist.ArtistName); + _logger.Debug("New album ({0}) for artist: [{1}] {2}, setting monitored to true", album.Title, artist.ForeignArtistId, artist.Name); album.Monitored = true; } @@ -136,7 +137,7 @@ namespace NzbDrone.Core.Music } else { - var allArtists = _artistService.GetAllArtists().OrderBy(c => c.ArtistName).ToList(); + var allArtists = _artistService.GetAllArtists().OrderBy(c => c.Name).ToList(); foreach (var artist in allArtists) { @@ -156,8 +157,8 @@ namespace NzbDrone.Core.Music { try { - _logger.Info("Skipping refresh of artist: {0}", artist.ArtistName); - _diskScanService.Scan(artist); + _logger.Info("Skipping refresh of artist: {0}", artist.Name); + _diskScanService.Scan(artist); } catch (Exception e) { diff --git a/src/NzbDrone.Core/Music/RefreshTrackService.cs b/src/NzbDrone.Core/Music/RefreshTrackService.cs index 169582e3d..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.SpotifyId); - 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,24 +54,17 @@ namespace NzbDrone.Core.Music else { trackToUpdate = new Track(); - trackToUpdate.Monitored = GetMonitoredStatus(track, albums); + trackToUpdate.Monitored = album.Monitored; newList.Add(trackToUpdate); } - trackToUpdate.SpotifyTrackId = track.SpotifyTrackId; + 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.Explict = track.Explict; - if (track.ArtistSpotifyId.IsNullOrWhiteSpace()) - { - trackToUpdate.ArtistSpotifyId = artist.SpotifyId; - } else - { - trackToUpdate.ArtistSpotifyId = track.ArtistSpotifyId; - } - trackToUpdate.ArtistId = track.ArtistId; + trackToUpdate.Explicit = track.Explicit; + trackToUpdate.ArtistId = album.ArtistId; trackToUpdate.Compilation = track.Compilation; // TODO: Implement rest of [RefreshTrackService] fields @@ -82,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++; } } @@ -99,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.ArtistName, 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); } } @@ -119,17 +112,17 @@ namespace NzbDrone.Core.Music return false; } - var album = albums.SingleOrDefault(c => c.AlbumId == track.AlbumId); + var album = albums.SingleOrDefault(c => c.Id == track.AlbumId); return album == null || album.Monitored; } - 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/ShouldRefreshArtist.cs b/src/NzbDrone.Core/Music/ShouldRefreshArtist.cs index 669a1db3e..f38ccaaf5 100644 --- a/src/NzbDrone.Core/Music/ShouldRefreshArtist.cs +++ b/src/NzbDrone.Core/Music/ShouldRefreshArtist.cs @@ -26,13 +26,13 @@ namespace NzbDrone.Core.Music { if (artist.LastInfoSync < DateTime.UtcNow.AddDays(-30)) { - _logger.Trace("Artist {0} last updated more than 30 days ago, should refresh.", artist.ArtistName); + _logger.Trace("Artist {0} last updated more than 30 days ago, should refresh.", artist.Name); return true; } if (artist.LastInfoSync >= DateTime.UtcNow.AddHours(-6)) { - _logger.Trace("Artist {0} last updated less than 6 hours ago, should not be refreshed.", artist.ArtistName); + _logger.Trace("Artist {0} last updated less than 6 hours ago, should not be refreshed.", artist.Name); return false; } diff --git a/src/NzbDrone.Core/Music/Track.cs b/src/NzbDrone.Core/Music/Track.cs index 599328c58..8e281260a 100644 --- a/src/NzbDrone.Core/Music/Track.cs +++ b/src/NzbDrone.Core/Music/Track.cs @@ -17,20 +17,20 @@ namespace NzbDrone.Core.Music public const string RELEASE_DATE_FORMAT = "yyyy-MM-dd"; - public string SpotifyTrackId { get; set; } - public string AlbumId { get; set; } + public string ForeignTrackId { get; set; } + public int AlbumId { get; set; } public LazyLoaded Artist { get; set; } - public string ArtistSpotifyId { get; set; } - public long ArtistId { get; set; } // This is the DB Id of the Artist, not the SpotifyId + + public int ArtistId { get; set; } // This is the DB Id of the Artist, not the SpotifyId //public int CompilationId { get; set; } public bool Compilation { get; set; } public int TrackNumber { get; set; } public string Title { get; set; } - public bool Ignored { get; set; } - public bool Explict { get; set; } + //public bool Ignored { get; set; } + public bool Explicit { get; set; } public bool Monitored { get; set; } public int TrackFileId { get; set; } - public DateTime? ReleaseDate { get; set; } + //public DateTime? ReleaseDate { get; set; } public LazyLoaded TrackFile { get; set; } @@ -40,7 +40,7 @@ namespace NzbDrone.Core.Music public override string ToString() { - return string.Format("[{0}]{1}", SpotifyTrackId, Title.NullSafe()); + return string.Format("[{0}]{1}", ForeignTrackId, Title.NullSafe()); } } } diff --git a/src/NzbDrone.Core/Music/TrackRepository.cs b/src/NzbDrone.Core/Music/TrackRepository.cs index 354d09afc..948ce5936 100644 --- a/src/NzbDrone.Core/Music/TrackRepository.cs +++ b/src/NzbDrone.Core/Music/TrackRepository.cs @@ -13,11 +13,11 @@ namespace NzbDrone.Core.Music { public interface ITrackRepository : IBasicRepository { - Track Find(string artistId, string albumId, int trackNumber); - List GetTracks(string artistId); - List GetTracks(string artistId, string albumId); + Track Find(int artistId, int albumId, int trackNumber); + List GetTracks(int artistId); + List GetTracks(int artistId, int albumId); List GetTracksByFileId(int fileId); - List TracksWithFiles(string artistId); + List TracksWithFiles(int artistId); PagingSpec TracksWithoutFiles(PagingSpec pagingSpec); PagingSpec TracksWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff); void SetMonitoredFlat(Track episode, bool monitored); @@ -37,23 +37,23 @@ namespace NzbDrone.Core.Music _logger = logger; } - public Track Find(string artistId, string albumId, int trackNumber) + public Track Find(int artistId, int albumId, int trackNumber) { - return Query.Where(s => s.ArtistSpotifyId == artistId) + return Query.Where(s => s.ArtistId == artistId) .AndWhere(s => s.AlbumId == albumId) .AndWhere(s => s.TrackNumber == trackNumber) .SingleOrDefault(); } - public List GetTracks(string artistId) + public List GetTracks(int artistId) { - return Query.Where(s => s.ArtistSpotifyId == artistId).ToList(); + return Query.Where(s => s.ArtistId == artistId).ToList(); } - public List GetTracks(string artistId, string albumId) + public List GetTracks(int artistId, int albumId) { - return Query.Where(s => s.ArtistSpotifyId == artistId) + return Query.Where(s => s.ArtistId == artistId) .AndWhere(s => s.AlbumId == albumId) .ToList(); } @@ -63,10 +63,10 @@ namespace NzbDrone.Core.Music return Query.Where(e => e.TrackFileId == fileId).ToList(); } - public List TracksWithFiles(string artistId) + public List TracksWithFiles(int artistId) { return Query.Join(JoinType.Inner, e => e.TrackFile, (e, ef) => e.TrackFileId == ef.Id) - .Where(e => e.ArtistSpotifyId == artistId); + .Where(e => e.ArtistId == artistId); } public PagingSpec TracksWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff) @@ -118,7 +118,7 @@ namespace NzbDrone.Core.Music private SortBuilder GetMissingEpisodesQuery(PagingSpec pagingSpec, DateTime currentTime) { - return Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistSpotifyId == s.SpotifyId) + return Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistId == s.Id) .Where(pagingSpec.FilterExpression) .AndWhere(e => e.TrackFileId == 0) .AndWhere(BuildAirDateUtcCutoffWhereClause(currentTime)) @@ -130,7 +130,7 @@ namespace NzbDrone.Core.Music private SortBuilder EpisodesWhereCutoffUnmetQuery(PagingSpec pagingSpec, List qualitiesBelowCutoff) { - return Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistSpotifyId == s.SpotifyId) + return Query.Join(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistId == s.Id) .Join(JoinType.Left, e => e.TrackFile, (e, s) => e.TrackFileId == s.Id) .Where(pagingSpec.FilterExpression) .AndWhere(e => e.TrackFileId != 0) diff --git a/src/NzbDrone.Core/Music/TrackService.cs b/src/NzbDrone.Core/Music/TrackService.cs index 7b57b7749..bf19f76b3 100644 --- a/src/NzbDrone.Core/Music/TrackService.cs +++ b/src/NzbDrone.Core/Music/TrackService.cs @@ -15,12 +15,12 @@ namespace NzbDrone.Core.Music { Track GetTrack(int id); List GetTracks(IEnumerable ids); - Track FindTrack(string artistId, string albumId, int trackNumber); - Track FindTrackByTitle(string artistId, string albumId, string releaseTitle); - List GetTracksByArtist(string artistId); - //List GetTracksByAlbum(string artistId, string albumId); + Track FindTrack(int artistId, int albumId, int trackNumber); + Track FindTrackByTitle(int artistId, int albumId, string releaseTitle); + List GetTracksByArtist(int artistId); + List GetTracksByAlbum(int artistId, int albumId); //List GetTracksByAlbumTitle(string artistId, string albumTitle); - List TracksWithFiles(string artistId); + List TracksWithFiles(int artistId); //PagingSpec TracksWithoutFiles(PagingSpec pagingSpec); List GetTracksByFileId(int trackFileId); void UpdateTrack(Track track); @@ -60,22 +60,17 @@ namespace NzbDrone.Core.Music return _trackRepository.Find(artistId, albumId, trackNumber); } - //public Track FindTrack(string artistId, int trackNumber) - //{ - // return _trackRepository.Find(artistId, trackNumber); - //} - public List GetTracksByArtist(string artistId) { return _trackRepository.GetTracks(artistId).ToList(); } - public List GetTracksByAlbum(string artistId, string albumId) + public List GetTracksByAlbum(int artistId, int albumId) { return _trackRepository.GetTracks(artistId, albumId); } - public Track FindTrackByTitle(string artistId, string albumId, string releaseTitle) + public Track FindTrackByTitle(int artistId, int albumId, string releaseTitle) { // TODO: can replace this search mechanism with something smarter/faster/better var normalizedReleaseTitle = Parser.Parser.NormalizeEpisodeTitle(releaseTitle).Replace(".", " "); @@ -101,7 +96,7 @@ namespace NzbDrone.Core.Music return null; } - public List TracksWithFiles(string artistId) + public List TracksWithFiles(int artistId) { return _trackRepository.TracksWithFiles(artistId); } @@ -159,7 +154,7 @@ namespace NzbDrone.Core.Music public void HandleAsync(ArtistDeletedEvent message) { - var tracks = GetTracksByArtist(message.Artist.SpotifyId); + var tracks = GetTracksByArtist(message.Artist.Id); _trackRepository.DeleteMany(tracks); } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index e0793e352..0818a8ce7 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -288,7 +288,6 @@ - @@ -823,7 +822,7 @@ - + @@ -832,7 +831,7 @@ - + @@ -864,17 +863,24 @@ + + + + + + + diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index c85e72927..f5d3c3c24 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -18,10 +18,13 @@ namespace NzbDrone.Core.Organizer public interface IBuildFileNames { string BuildFileName(List episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null); + string BuildTrackFileName(List tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig namingConfig = null); string BuildFilePath(Series series, int seasonNumber, string fileName, string extension); 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(Artist artist, Album album, NamingConfig namingConfig = null); string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null); // TODO: Implement Music functions @@ -42,6 +45,9 @@ namespace NzbDrone.Core.Organizer private static readonly Regex EpisodeRegex = new Regex(@"(?\{episode(?:\:0+)?})", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex TrackRegex = new Regex(@"(?\{track(?:\:0+)?})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex SeasonRegex = new Regex(@"(?\{season(?:\:0+)?})", RegexOptions.Compiled | RegexOptions.IgnoreCase); @@ -59,6 +65,12 @@ namespace NzbDrone.Core.Organizer public static readonly Regex SeriesTitleRegex = new Regex(@"(?\{(?:Series)(?[- ._])(Clean)?Title\})", RegexOptions.Compiled | RegexOptions.IgnoreCase); + public static readonly Regex ArtistNameRegex = new Regex(@"(?\{(?:Artist)(?[- ._])(Clean)?Name\})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex AlbumTitleRegex = new Regex(@"(?\{(?:Album)(?[- ._])(Clean)?Title\})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled); private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled); @@ -140,6 +152,47 @@ namespace NzbDrone.Core.Organizer return fileName; } + public string BuildTrackFileName(List tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + if (!namingConfig.RenameTracks) + { + return GetOriginalTitle(trackFile); + } + + if (namingConfig.StandardTrackFormat.IsNullOrWhiteSpace()) + { + throw new NamingFormatException("Standard track format cannot be empty"); + } + + var pattern = namingConfig.StandardTrackFormat; + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + tracks = tracks.OrderBy(e => e.AlbumId).ThenBy(e => e.TrackNumber).ToList(); + + //pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig); + + pattern = FormatTrackNumberTokens(pattern, "", tracks); + //pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig); + + AddArtistTokens(tokenHandlers, artist); + AddAlbumTokens(tokenHandlers, album); + AddTrackTokens(tokenHandlers, tracks); + AddTrackFileTokens(tokenHandlers, trackFile); + AddQualityTokens(tokenHandlers, artist, trackFile); + //AddMediaInfoTokens(tokenHandlers, trackFile); TODO ReWork MediaInfo for Tracks + + var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); + fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString()); + fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty); + + return fileName; + } + public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension) { Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); @@ -232,6 +285,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 +314,21 @@ namespace NzbDrone.Core.Organizer return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig)); } + public string GetAlbumFolder(Artist artist, Album album, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddAlbumTokens(tokenHandlers, album); + AddArtistTokens(tokenHandlers, artist); + + return CleanFolderName(ReplaceTokens(namingConfig.AlbumFolderFormat, tokenHandlers, namingConfig)); + } + public static string CleanTitle(string title) { title = title.Replace("&", "and"); @@ -284,8 +366,15 @@ namespace NzbDrone.Core.Organizer private void AddArtistTokens(Dictionary> tokenHandlers, Artist artist) { - tokenHandlers["{Artist Name}"] = m => artist.ArtistName; - tokenHandlers["{Artist CleanTitle}"] = m => CleanTitle(artist.ArtistName); + tokenHandlers["{Artist Name}"] = m => 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["{Release Year}"] = m => album.ReleaseDate.Year.ToString(); } private string AddSeasonEpisodeNumberingTokens(string pattern, Dictionary> tokenHandlers, List episodes, NamingConfig namingConfig) @@ -432,6 +521,12 @@ namespace NzbDrone.Core.Organizer tokenHandlers["{Episode CleanTitle}"] = m => CleanTitle(GetEpisodeTitle(episodes, "and")); } + private void AddTrackTokens(Dictionary> tokenHandlers, List tracks) + { + tokenHandlers["{Track Title}"] = m => GetTrackTitle(tracks, "+"); + tokenHandlers["{Track CleanTitle}"] = m => CleanTitle(GetTrackTitle(tracks, "and")); + } + private void AddEpisodeFileTokens(Dictionary> tokenHandlers, EpisodeFile episodeFile) { tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile); @@ -439,6 +534,13 @@ namespace NzbDrone.Core.Organizer tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? m.DefaultValue("Lidarr"); } + private void AddTrackFileTokens(Dictionary> tokenHandlers, TrackFile trackFile) + { + tokenHandlers["{Original Title}"] = m => GetOriginalTitle(trackFile); + tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(trackFile); + tokenHandlers["{Release Group}"] = m => trackFile.ReleaseGroup ?? m.DefaultValue("Lidarr"); + } + private void AddQualityTokens(Dictionary> tokenHandlers, Series series, EpisodeFile episodeFile) { var qualityTitle = _qualityDefinitionService.Get(episodeFile.Quality.Quality).Title; @@ -451,6 +553,18 @@ namespace NzbDrone.Core.Organizer tokenHandlers["{Quality Real}"] = m => qualityReal; } + private void AddQualityTokens(Dictionary> tokenHandlers, Artist artist, TrackFile trackFile) + { + var qualityTitle = _qualityDefinitionService.Get(trackFile.Quality.Quality).Title; + //var qualityProper = GetQualityProper(artist, trackFile.Quality); + //var qualityReal = GetQualityReal(artist, trackFile.Quality); + + tokenHandlers["{Quality Full}"] = m => String.Format("{0}", qualityTitle); + tokenHandlers["{Quality Title}"] = m => qualityTitle; + //tokenHandlers["{Quality Proper}"] = m => qualityProper; + //tokenHandlers["{Quality Real}"] = m => qualityReal; + } + private void AddMediaInfoTokens(Dictionary> tokenHandlers, EpisodeFile episodeFile) { if (episodeFile.MediaInfo == null) return; @@ -646,6 +760,20 @@ namespace NzbDrone.Core.Organizer return ReplaceSeasonTokens(pattern, episodes.First().SeasonNumber); } + private string FormatTrackNumberTokens(string basePattern, string formatPattern, List tracks) + { + var pattern = string.Empty; + + for (int i = 0; i < tracks.Count; i++) + { + var patternToReplace = i == 0 ? basePattern : formatPattern; + + pattern += TrackRegex.Replace(patternToReplace, match => ReplaceNumberToken(match.Groups["track"].Value, tracks[i].TrackNumber)); + } + + return pattern; + } + private string FormatAbsoluteNumberTokens(string basePattern, string formatPattern, List episodes) { var pattern = string.Empty; @@ -728,6 +856,30 @@ namespace NzbDrone.Core.Organizer return string.Join(separator, titles); } + private string GetTrackTitle(List tracks, string separator) + { + separator = string.Format(" {0} ", separator.Trim()); + + if (tracks.Count == 1) + { + return tracks.First().Title.TrimEnd(EpisodeTitleTrimCharacters); + } + + var titles = tracks.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters)) + .Select(CleanupEpisodeTitle) + .Distinct() + .ToList(); + + if (titles.All(t => t.IsNullOrWhiteSpace())) + { + titles = tracks.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters)) + .Distinct() + .ToList(); + } + + return string.Join(separator, titles); + } + private string CleanupEpisodeTitle(string title) { //this will remove (1),(2) from the end of multi part episodes. @@ -769,6 +921,16 @@ namespace NzbDrone.Core.Organizer return episodeFile.SceneName; } + private string GetOriginalTitle(TrackFile trackFile) + { + if (trackFile.SceneName.IsNullOrWhiteSpace()) + { + return GetOriginalFileName(trackFile); + } + + return trackFile.SceneName; + } + private string GetOriginalFileName(EpisodeFile episodeFile) { if (episodeFile.RelativePath.IsNullOrWhiteSpace()) @@ -779,35 +941,16 @@ namespace NzbDrone.Core.Organizer return Path.GetFileNameWithoutExtension(episodeFile.RelativePath); } - //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("{Artist Name}", tokenHandlers, namingConfig)); //namingConfig.ArtistFolderFormat, - //} - - //public string GetAlbumFolder(Artist artist, string albumName, NamingConfig namingConfig = null) - //{ - // throw new NotImplementedException(); - // //if (namingConfig == null) - // //{ - // // namingConfig = _namingConfigService.GetConfig(); - // //} - - // //var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + private string GetOriginalFileName(TrackFile trackFile) + { + if (trackFile.RelativePath.IsNullOrWhiteSpace()) + { + return Path.GetFileNameWithoutExtension(trackFile.Path); + } - // //AddSeriesTokens(tokenHandlers, artist); - // //AddSeasonTokens(tokenHandlers, seasonNumber); + return Path.GetFileNameWithoutExtension(trackFile.RelativePath); + } - // //return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig)); - //} } internal sealed class TokenMatch diff --git a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs index 7f92fe180..324e6e524 100644 --- a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs +++ b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs @@ -2,6 +2,7 @@ using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; using NzbDrone.Core.MediaFiles.MediaInfo; namespace NzbDrone.Core.Organizer @@ -9,26 +10,34 @@ namespace NzbDrone.Core.Organizer public interface IFilenameSampleService { SampleResult GetStandardSample(NamingConfig nameSpec); + SampleResult GetStandardTrackSample(NamingConfig nameSpec); SampleResult GetMultiEpisodeSample(NamingConfig nameSpec); SampleResult GetDailySample(NamingConfig nameSpec); SampleResult GetAnimeSample(NamingConfig nameSpec); SampleResult GetAnimeMultiEpisodeSample(NamingConfig nameSpec); string GetSeriesFolderSample(NamingConfig nameSpec); string GetSeasonFolderSample(NamingConfig nameSpec); + string GetArtistFolderSample(NamingConfig nameSpec); + string GetAlbumFolderSample(NamingConfig nameSpec); } public class FileNameSampleService : IFilenameSampleService { private readonly IBuildFileNames _buildFileNames; private static Series _standardSeries; + private static Artist _standardArtist; + private static Album _standardAlbum; + private static Track _track1; private static Series _dailySeries; private static Series _animeSeries; private static Episode _episode1; private static Episode _episode2; private static Episode _episode3; private static List _singleEpisode; + private static List _singleTrack; private static List _multiEpisodes; private static EpisodeFile _singleEpisodeFile; + private static TrackFile _singleTrackFile; private static EpisodeFile _multiEpisodeFile; private static EpisodeFile _dailyEpisodeFile; private static EpisodeFile _animeEpisodeFile; @@ -44,6 +53,17 @@ namespace NzbDrone.Core.Organizer Title = "Series Title (2010)" }; + _standardArtist = new Artist + { + Name = "Artist Name" + }; + + _standardAlbum = new Album + { + Title = "Album Title", + ReleaseDate = System.DateTime.Today + }; + _dailySeries = new Series { SeriesType = SeriesTypes.Daily, @@ -56,6 +76,14 @@ namespace NzbDrone.Core.Organizer Title = "Series Title (2010)" }; + _track1 = new Track + { + TrackNumber = 3, + + Title = "Track Title (1)", + + }; + _episode1 = new Episode { SeasonNumber = 1, @@ -82,6 +110,7 @@ namespace NzbDrone.Core.Organizer }; _singleEpisode = new List { _episode1 }; + _singleTrack = new List { _track1 }; _multiEpisodes = new List { _episode1, _episode2, _episode3 }; var mediaInfo = new MediaInfoModel() @@ -115,6 +144,15 @@ namespace NzbDrone.Core.Organizer MediaInfo = mediaInfo }; + _singleTrackFile = new TrackFile + { + Quality = new QualityModel(Quality.MP3256, new Revision(2)), + RelativePath = "Artist.Name.Album.Name.TrackNum.Track.Title.MP3256.mp3", + SceneName = "Artist.Name.Album.Name.TrackNum.Track.Title.MP3256", + ReleaseGroup = "RlsGrp", + MediaInfo = mediaInfo + }; + _multiEpisodeFile = new EpisodeFile { Quality = new QualityModel(Quality.MP3256, new Revision(2)), @@ -165,6 +203,20 @@ namespace NzbDrone.Core.Organizer return result; } + public SampleResult GetStandardTrackSample(NamingConfig nameSpec) + { + var result = new SampleResult + { + FileName = BuildTrackSample(_singleTrack, _standardArtist, _standardAlbum, _singleTrackFile, nameSpec), + Artist = _standardArtist, + Album = _standardAlbum, + Tracks = _singleTrack, + TrackFile = _singleTrackFile + }; + + return result; + } + public SampleResult GetMultiEpisodeSample(NamingConfig nameSpec) { var result = new SampleResult @@ -227,6 +279,16 @@ namespace NzbDrone.Core.Organizer return _buildFileNames.GetSeasonFolder(_standardSeries, _episode1.SeasonNumber, nameSpec); } + public string GetArtistFolderSample(NamingConfig nameSpec) + { + return _buildFileNames.GetArtistFolder(_standardArtist, nameSpec); + } + + public string GetAlbumFolderSample(NamingConfig nameSpec) + { + return _buildFileNames.GetAlbumFolder(_standardArtist, _standardAlbum, nameSpec); + } + private string BuildSample(List episodes, Series series, EpisodeFile episodeFile, NamingConfig nameSpec) { try @@ -238,5 +300,17 @@ namespace NzbDrone.Core.Organizer return string.Empty; } } + + private string BuildTrackSample(List tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig nameSpec) + { + try + { + return _buildFileNames.BuildTrackFileName(tracks, artist, album, trackFile, nameSpec); + } + catch (NamingFormatException) + { + return string.Empty; + } + } } } diff --git a/src/NzbDrone.Core/Organizer/FileNameValidation.cs b/src/NzbDrone.Core/Organizer/FileNameValidation.cs index 930b8a044..5231f1fe6 100644 --- a/src/NzbDrone.Core/Organizer/FileNameValidation.cs +++ b/src/NzbDrone.Core/Organizer/FileNameValidation.cs @@ -18,6 +18,12 @@ namespace NzbDrone.Core.Organizer return ruleBuilder.SetValidator(new ValidStandardEpisodeFormatValidator()); } + public static IRuleBuilderOptions ValidTrackFormat(this IRuleBuilder ruleBuilder) + { + ruleBuilder.SetValidator(new NotEmptyValidator(null)); + return ruleBuilder.SetValidator(new ValidStandardTrackFormatValidator()); + } + public static IRuleBuilderOptions ValidDailyEpisodeFormat(this IRuleBuilder ruleBuilder) { ruleBuilder.SetValidator(new NotEmptyValidator(null)); @@ -41,6 +47,17 @@ namespace NzbDrone.Core.Organizer ruleBuilder.SetValidator(new NotEmptyValidator(null)); return ruleBuilder.SetValidator(new RegularExpressionValidator(SeasonFolderRegex)).WithMessage("Must contain season number"); } + public static IRuleBuilderOptions ValidArtistFolderFormat(this IRuleBuilder ruleBuilder) + { + ruleBuilder.SetValidator(new NotEmptyValidator(null)); + return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.ArtistNameRegex)).WithMessage("Must contain Artist name"); + } + + public static IRuleBuilderOptions ValidAlbumFolderFormat(this IRuleBuilder ruleBuilder) + { + ruleBuilder.SetValidator(new NotEmptyValidator(null)); + return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.AlbumTitleRegex)).WithMessage("Must contain Album name"); + } } public class ValidStandardEpisodeFormatValidator : PropertyValidator @@ -65,6 +82,21 @@ namespace NzbDrone.Core.Organizer } } + public class ValidStandardTrackFormatValidator : PropertyValidator + { + public ValidStandardTrackFormatValidator() + : base("Must contain Album Title and Track numbers OR Original Title") + { + + } + + protected override bool IsValid(PropertyValidatorContext context) + { + + return true; //TODO Add Logic here + } + } + public class ValidDailyEpisodeFormatValidator : PropertyValidator { public ValidDailyEpisodeFormatValidator() diff --git a/src/NzbDrone.Core/Organizer/FileNameValidationService.cs b/src/NzbDrone.Core/Organizer/FileNameValidationService.cs index 9367c11d8..a26b619c8 100644 --- a/src/NzbDrone.Core/Organizer/FileNameValidationService.cs +++ b/src/NzbDrone.Core/Organizer/FileNameValidationService.cs @@ -9,6 +9,7 @@ namespace NzbDrone.Core.Organizer public interface IFilenameValidationService { ValidationFailure ValidateStandardFilename(SampleResult sampleResult); + ValidationFailure ValidateTrackFilename(SampleResult sampleResult); ValidationFailure ValidateDailyFilename(SampleResult sampleResult); ValidationFailure ValidateAnimeFilename(SampleResult sampleResult); } @@ -35,6 +36,27 @@ namespace NzbDrone.Core.Organizer return null; } + public ValidationFailure ValidateTrackFilename(SampleResult sampleResult) + { + var validationFailure = new ValidationFailure("StandardTrackFormat", ERROR_MESSAGE); + + //TODO Add Validation for TrackFilename + //var parsedEpisodeInfo = Parser.Parser.ParseTitle(sampleResult.FileName); + + + //if (parsedEpisodeInfo == null) + //{ + // return validationFailure; + //} + + //if (!ValidateSeasonAndEpisodeNumbers(sampleResult.Episodes, parsedEpisodeInfo)) + //{ + // return validationFailure; + //} + + return null; + } + public ValidationFailure ValidateDailyFilename(SampleResult sampleResult) { var validationFailure = new ValidationFailure("DailyEpisodeFormat", ERROR_MESSAGE); diff --git a/src/NzbDrone.Core/Organizer/NamingConfig.cs b/src/NzbDrone.Core/Organizer/NamingConfig.cs index 637cd15cd..b0a4d811a 100644 --- a/src/NzbDrone.Core/Organizer/NamingConfig.cs +++ b/src/NzbDrone.Core/Organizer/NamingConfig.cs @@ -1,4 +1,4 @@ -using NzbDrone.Core.Datastore; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.Organizer { @@ -7,21 +7,25 @@ namespace NzbDrone.Core.Organizer public static NamingConfig Default => new NamingConfig { RenameEpisodes = false, + RenameTracks = false, ReplaceIllegalCharacters = true, MultiEpisodeStyle = 0, StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}", + StandardTrackFormat = "{Artist Name} - {track:00} - {Album Title} - {Track Title}", DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title} {Quality Full}", AnimeEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}", SeriesFolderFormat = "{Series Title}", SeasonFolderFormat = "Season {season}", ArtistFolderFormat = "{Artist Name}", - AlbumFolderFormat = "{Album Name} ({Year})" + AlbumFolderFormat = "{Album Title} ({Release Year})" }; public bool RenameEpisodes { get; set; } + public bool RenameTracks { get; set; } public bool ReplaceIllegalCharacters { get; set; } public int MultiEpisodeStyle { get; set; } public string StandardEpisodeFormat { get; set; } + public string StandardTrackFormat { get; set; } public string DailyEpisodeFormat { get; set; } public string AnimeEpisodeFormat { get; set; } public string SeriesFolderFormat { get; set; } diff --git a/src/NzbDrone.Core/Organizer/SampleResult.cs b/src/NzbDrone.Core/Organizer/SampleResult.cs index 0f3885a1b..3075032ce 100644 --- a/src/NzbDrone.Core/Organizer/SampleResult.cs +++ b/src/NzbDrone.Core/Organizer/SampleResult.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; namespace NzbDrone.Core.Organizer { @@ -8,7 +9,11 @@ namespace NzbDrone.Core.Organizer { public string FileName { get; set; } public Series Series { get; set; } + public Artist Artist { get; set; } + public Album Album { get; set; } public List Episodes { get; set; } public EpisodeFile EpisodeFile { get; set; } + public List Tracks { get; set; } + public TrackFile TrackFile { get; set; } } } diff --git a/src/NzbDrone.Core/Parser/Model/LocalTrack.cs b/src/NzbDrone.Core/Parser/Model/LocalTrack.cs index 2f8b35588..359ce6285 100644 --- a/src/NzbDrone.Core/Parser/Model/LocalTrack.cs +++ b/src/NzbDrone.Core/Parser/Model/LocalTrack.cs @@ -19,20 +19,12 @@ namespace NzbDrone.Core.Parser.Model public long Size { get; set; } public ParsedTrackInfo ParsedTrackInfo { get; set; } public Artist Artist { get; set; } + public Album Album { get; set; } public List Tracks { get; set; } public QualityModel Quality { get; set; } public MediaInfoModel MediaInfo { get; set; } public bool ExistingFile { get; set; } - public string Album - { - get - { - return Tracks.Select(c => c.AlbumId).Distinct().Single(); - } - } - - public bool IsSpecial => Album != ""; public override string ToString() { diff --git a/src/NzbDrone.Core/Parser/Model/RemoteAlbum.cs b/src/NzbDrone.Core/Parser/Model/RemoteAlbum.cs new file mode 100644 index 000000000..2b1f0ceb0 --- /dev/null +++ b/src/NzbDrone.Core/Parser/Model/RemoteAlbum.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.Music; + +namespace NzbDrone.Core.Parser.Model +{ + public class RemoteAlbum + { + public ReleaseInfo Release { get; set; } + public ParsedTrackInfo ParsedTrackInfo { get; set; } + public Artist Artist { get; set; } + public List Albums { get; set; } + public bool DownloadAllowed { get; set; } + + public bool IsRecentAlbum() + { + return Albums.Any(e => e.ReleaseDate >= DateTime.UtcNow.Date.AddDays(-14)); + } + + public override string ToString() + { + return Release.Title; + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs b/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs index 3260895c5..2e59d3a8d 100644 --- a/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs +++ b/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs @@ -21,7 +21,7 @@ namespace NzbDrone.Core.Validation.Paths { if (context.PropertyValue == null) return true; - return (!_artistService.GetAllArtists().Exists(s => s.SpotifyId == context.PropertyValue.ToString())); + return (!_artistService.GetAllArtists().Exists(s => s.ForeignArtistId == context.PropertyValue.ToString())); } } } diff --git a/src/UI/.idea/runConfigurations/Debug___Chrome.xml b/src/UI/.idea/runConfigurations/Debug___Chrome.xml index 659f9572b..2ff8dbf6b 100644 --- a/src/UI/.idea/runConfigurations/Debug___Chrome.xml +++ b/src/UI/.idea/runConfigurations/Debug___Chrome.xml @@ -10,7 +10,7 @@ - + diff --git a/src/UI/.idea/runConfigurations/Debug___Firefox.xml b/src/UI/.idea/runConfigurations/Debug___Firefox.xml index 8646368d1..dbbdebbe4 100644 --- a/src/UI/.idea/runConfigurations/Debug___Firefox.xml +++ b/src/UI/.idea/runConfigurations/Debug___Firefox.xml @@ -9,7 +9,7 @@ - + diff --git a/src/UI/AddArtist/AddArtistCollection.js b/src/UI/AddArtist/AddArtistCollection.js new file mode 100644 index 000000000..a243649f4 --- /dev/null +++ b/src/UI/AddArtist/AddArtistCollection.js @@ -0,0 +1,23 @@ +var Backbone = require('backbone'); +var ArtistModel = require('../Artist/ArtistModel'); +var _ = require('underscore'); + +module.exports = Backbone.Collection.extend({ + url : window.NzbDrone.ApiRoot + '/artist/lookup', + model : ArtistModel, + + parse : function(response) { + var self = this; + + _.each(response, function(model) { + model.id = undefined; + + if (self.unmappedFolderModel) { + model.path = self.unmappedFolderModel.get('folder').path; + } + }); + console.log('response: ', response); + + return response; + } +}); \ No newline at end of file diff --git a/src/UI/AddArtist/AddArtistLayout.js b/src/UI/AddArtist/AddArtistLayout.js new file mode 100644 index 000000000..2b398335e --- /dev/null +++ b/src/UI/AddArtist/AddArtistLayout.js @@ -0,0 +1,53 @@ +var vent = require('vent'); +var AppLayout = require('../AppLayout'); +var Marionette = require('marionette'); +var RootFolderLayout = require('./RootFolders/RootFolderLayout'); +var ExistingArtistCollectionView = require('./Existing/AddExistingArtistCollectionView'); +var AddArtistView = require('./AddArtistView'); +var ProfileCollection = require('../Profile/ProfileCollection'); +var RootFolderCollection = require('./RootFolders/RootFolderCollection'); +require('../Artist/ArtistCollection'); + +module.exports = Marionette.Layout.extend({ + template : 'AddArtist/AddArtistLayoutTemplate', + + regions : { + workspace : '#add-artist-workspace' + }, + + events : { + 'click .x-import' : '_importArtist', + 'click .x-add-new' : '_addArtist' + }, + + attributes : { + id : 'add-artist-screen' + }, + + initialize : function() { + ProfileCollection.fetch(); + RootFolderCollection.fetch().done(function() { + RootFolderCollection.synced = true; + }); + }, + + onShow : function() { + this.workspace.show(new AddArtistView()); + }, + + _folderSelected : function(options) { + vent.trigger(vent.Commands.CloseModalCommand); + + this.workspace.show(new ExistingArtistCollectionView({ model : options.model })); + }, + + _importArtist : function() { + this.rootFolderLayout = new RootFolderLayout(); + this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected); + AppLayout.modalRegion.show(this.rootFolderLayout); + }, + + _addArtist : function() { + this.workspace.show(new AddArtistView()); + } +}); \ No newline at end of file diff --git a/src/UI/AddArtist/AddArtistLayoutTemplate.hbs b/src/UI/AddArtist/AddArtistLayoutTemplate.hbs new file mode 100644 index 000000000..53f225a1e --- /dev/null +++ b/src/UI/AddArtist/AddArtistLayoutTemplate.hbs @@ -0,0 +1,17 @@ +
+
+
+ + +
+
+
+
+
+
+
+
+ diff --git a/src/UI/AddArtist/AddArtistView.js b/src/UI/AddArtist/AddArtistView.js new file mode 100644 index 000000000..85e47a02d --- /dev/null +++ b/src/UI/AddArtist/AddArtistView.js @@ -0,0 +1,183 @@ +var _ = require('underscore'); +var vent = require('vent'); +var Marionette = require('marionette'); +var AddArtistCollection = require('./AddArtistCollection'); +var SearchResultCollectionView = require('./SearchResultCollectionView'); +var EmptyView = require('./EmptyView'); +var NotFoundView = require('./NotFoundView'); +var ErrorView = require('./ErrorView'); +var LoadingView = require('../Shared/LoadingView'); + +module.exports = Marionette.Layout.extend({ + template : 'AddArtist/AddArtistViewTemplate', + + regions : { + searchResult : '#search-result' + }, + + ui : { + artistSearch : '.x-artist-search', + searchBar : '.x-search-bar', + loadMore : '.x-load-more' + }, + + events : { + 'click .x-load-more' : '_onLoadMore' + }, + + initialize : function(options) { + this.isExisting = options.isExisting; + this.collection = new AddArtistCollection(); + console.log('this.collection:', this.collection); + + if (this.isExisting) { + this.collection.unmappedFolderModel = this.model; + } + + if (this.isExisting) { + this.className = 'existing-artist'; + } else { + this.className = 'new-artist'; + } + + this.listenTo(vent, vent.Events.ArtistAdded, this._onArtistAdded); + this.listenTo(this.collection, 'sync', this._showResults); + + this.resultCollectionView = new SearchResultCollectionView({ + collection : this.collection, + isExisting : this.isExisting + }); + + this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this); + }, + + onRender : function() { + var self = this; + + this.$el.addClass(this.className); + + this.ui.artistSearch.keyup(function(e) { + + if (_.contains([ + 9, + 16, + 17, + 18, + 19, + 20, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 91, + 92, + 93 + ], e.keyCode)) { + return; + } + + self._abortExistingSearch(); + self.throttledSearch({ + term : self.ui.artistSearch.val() + }); + }); + + this._clearResults(); + + if (this.isExisting) { + this.ui.searchBar.hide(); + } + }, + + onShow : function() { + this.ui.artistSearch.focus(); + }, + + search : function(options) { + var self = this; + + this.collection.reset(); + + if (!options.term || options.term === this.collection.term) { + return Marionette.$.Deferred().resolve(); + } + + this.searchResult.show(new LoadingView()); + this.collection.term = options.term; + this.currentSearchPromise = this.collection.fetch({ + data : { term : options.term } + }); + + this.currentSearchPromise.fail(function() { + self._showError(); + }); + + return this.currentSearchPromise; + }, + + _onArtistAdded : function(options) { + if (this.isExisting && options.artist.get('path') === this.model.get('folder').path) { + this.close(); + } + + else if (!this.isExisting) { + this.collection.term = ''; + this.collection.reset(); + this._clearResults(); + this.ui.artistSearch.val(''); + this.ui.artistSearch.focus(); + } + }, + + _onLoadMore : function() { + var showingAll = this.resultCollectionView.showMore(); + this.ui.searchBar.show(); + + if (showingAll) { + this.ui.loadMore.hide(); + } + }, + + _clearResults : function() { + if (!this.isExisting) { + this.searchResult.show(new EmptyView()); + } else { + this.searchResult.close(); + } + }, + + _showResults : function() { + if (!this.isClosed) { + if (this.collection.length === 0) { + this.ui.searchBar.show(); + this.searchResult.show(new NotFoundView({ term : this.collection.term })); + } else { + this.searchResult.show(this.resultCollectionView); + if (!this.showingAll && this.isExisting) { + this.ui.loadMore.show(); + } + } + } + }, + + _abortExistingSearch : function() { + if (this.currentSearchPromise && this.currentSearchPromise.readyState > 0 && this.currentSearchPromise.readyState < 4) { + console.log('aborting previous pending search request.'); + this.currentSearchPromise.abort(); + } else { + this._clearResults(); + } + }, + + _showError : function() { + if (!this.isClosed) { + this.ui.searchBar.show(); + this.searchResult.show(new ErrorView({ term : this.collection.term })); + this.collection.term = ''; + } + } +}); \ No newline at end of file diff --git a/src/UI/AddArtist/AddArtistViewTemplate.hbs b/src/UI/AddArtist/AddArtistViewTemplate.hbs new file mode 100644 index 000000000..adadf0569 --- /dev/null +++ b/src/UI/AddArtist/AddArtistViewTemplate.hbs @@ -0,0 +1,24 @@ +{{#if folder.path}} +
+
+ {{folder.path}} +
+
{{/if}} + +
+
+
+ diff --git a/src/UI/AddArtist/ArtistTypeSelectionPartial.hbs b/src/UI/AddArtist/ArtistTypeSelectionPartial.hbs new file mode 100644 index 000000000..f8cadd547 --- /dev/null +++ b/src/UI/AddArtist/ArtistTypeSelectionPartial.hbs @@ -0,0 +1,3 @@ + diff --git a/src/UI/AddArtist/EmptyView.js b/src/UI/AddArtist/EmptyView.js new file mode 100644 index 000000000..e07b1647d --- /dev/null +++ b/src/UI/AddArtist/EmptyView.js @@ -0,0 +1,5 @@ +var Marionette = require('marionette'); + +module.exports = Marionette.CompositeView.extend({ + template : 'AddArtist/EmptyViewTemplate' +}); \ No newline at end of file diff --git a/src/UI/AddArtist/EmptyViewTemplate.hbs b/src/UI/AddArtist/EmptyViewTemplate.hbs new file mode 100644 index 000000000..e2a20efdc --- /dev/null +++ b/src/UI/AddArtist/EmptyViewTemplate.hbs @@ -0,0 +1,3 @@ +
+ You can also search by MusicBrianzID using the MBID: prefixes. +
diff --git a/src/UI/AddArtist/ErrorView.js b/src/UI/AddArtist/ErrorView.js new file mode 100644 index 000000000..9d53fae8c --- /dev/null +++ b/src/UI/AddArtist/ErrorView.js @@ -0,0 +1,13 @@ +var Marionette = require('marionette'); + +module.exports = Marionette.CompositeView.extend({ + template : 'AddArtist/ErrorViewTemplate', + + initialize : function(options) { + this.options = options; + }, + + templateHelpers : function() { + return this.options; + } +}); \ No newline at end of file diff --git a/src/UI/AddArtist/ErrorViewTemplate.hbs b/src/UI/AddArtist/ErrorViewTemplate.hbs new file mode 100644 index 000000000..c0b1e3673 --- /dev/null +++ b/src/UI/AddArtist/ErrorViewTemplate.hbs @@ -0,0 +1,7 @@ +
+

+ There was an error searching for '{{term}}'. +

+ + If the artist name contains non-alphanumeric characters try removing them, otherwise try your search again later. +
diff --git a/src/UI/AddArtist/Existing/AddExistingArtistCollectionView.js b/src/UI/AddArtist/Existing/AddExistingArtistCollectionView.js new file mode 100644 index 000000000..af57bc1d2 --- /dev/null +++ b/src/UI/AddArtist/Existing/AddExistingArtistCollectionView.js @@ -0,0 +1,51 @@ +var Marionette = require('marionette'); +var AddArtistView = require('../AddArtistView'); +var UnmappedFolderCollection = require('./UnmappedFolderCollection'); + +module.exports = Marionette.CompositeView.extend({ + itemView : AddArtistView, + itemViewContainer : '.x-loading-folders', + template : 'AddArtist/Existing/AddExistingArtistCollectionViewTemplate', + + ui : { + loadingFolders : '.x-loading-folders' + }, + + initialize : function() { + this.collection = new UnmappedFolderCollection(); + this.collection.importItems(this.model); + }, + + showCollection : function() { + this._showAndSearch(0); + }, + + appendHtml : function(collectionView, itemView, index) { + collectionView.ui.loadingFolders.before(itemView.el); + }, + + _showAndSearch : function(index) { + var self = this; + var model = this.collection.at(index); + + if (model) { + var currentIndex = index; + var folderName = model.get('folder').name; + this.addItemView(model, this.getItemView(), index); + this.children.findByModel(model).search({ term : folderName }).always(function() { + if (!self.isClosed) { + self._showAndSearch(currentIndex + 1); + } + }); + } + + else { + this.ui.loadingFolders.hide(); + } + }, + + itemViewOptions : { + isExisting : true + } + +}); \ No newline at end of file diff --git a/src/UI/AddArtist/Existing/AddExistingArtistCollectionViewTemplate.hbs b/src/UI/AddArtist/Existing/AddExistingArtistCollectionViewTemplate.hbs new file mode 100644 index 000000000..5acbd1ef0 --- /dev/null +++ b/src/UI/AddArtist/Existing/AddExistingArtistCollectionViewTemplate.hbs @@ -0,0 +1,5 @@ +
+
+ Loading search results from server for your artists, this may take a few minutes. +
+
\ No newline at end of file diff --git a/src/UI/AddArtist/Existing/UnmappedFolderCollection.js b/src/UI/AddArtist/Existing/UnmappedFolderCollection.js new file mode 100644 index 000000000..bd2a83f49 --- /dev/null +++ b/src/UI/AddArtist/Existing/UnmappedFolderCollection.js @@ -0,0 +1,20 @@ +var Backbone = require('backbone'); +var UnmappedFolderModel = require('./UnmappedFolderModel'); +var _ = require('underscore'); + +module.exports = Backbone.Collection.extend({ + model : UnmappedFolderModel, + + importItems : function(rootFolderModel) { + + this.reset(); + var rootFolder = rootFolderModel; + + _.each(rootFolderModel.get('unmappedFolders'), function(folder) { + this.push(new UnmappedFolderModel({ + rootFolder : rootFolder, + folder : folder + })); + }, this); + } +}); \ No newline at end of file diff --git a/src/UI/AddArtist/Existing/UnmappedFolderModel.js b/src/UI/AddArtist/Existing/UnmappedFolderModel.js new file mode 100644 index 000000000..3986a5948 --- /dev/null +++ b/src/UI/AddArtist/Existing/UnmappedFolderModel.js @@ -0,0 +1,3 @@ +var Backbone = require('backbone'); + +module.exports = Backbone.Model.extend({}); \ No newline at end of file diff --git a/src/UI/AddArtist/MonitoringTooltipTemplate.hbs b/src/UI/AddArtist/MonitoringTooltipTemplate.hbs new file mode 100644 index 000000000..0c795cf12 --- /dev/null +++ b/src/UI/AddArtist/MonitoringTooltipTemplate.hbs @@ -0,0 +1,18 @@ +
+
All
+
Monitor all tracks except specials
+
Future
+
Monitor tracks that have not been released yet
+
Missing
+
Monitor tracks that do not have files or have not aired yet
+
Existing
+
Monitor tracks that have files or have not aired yet
+
First Season
+
Monitor all tracks of the first album. All other albums will be ignored
+
Latest Season
+
Monitor all tracks of the latest album and future albums
+
None
+
No tracks will be monitored.
+ + +
\ No newline at end of file diff --git a/src/UI/AddArtist/NotFoundView.js b/src/UI/AddArtist/NotFoundView.js new file mode 100644 index 000000000..d25f339c3 --- /dev/null +++ b/src/UI/AddArtist/NotFoundView.js @@ -0,0 +1,13 @@ +var Marionette = require('marionette'); + +module.exports = Marionette.CompositeView.extend({ + template : 'AddArtist/NotFoundViewTemplate', + + initialize : function(options) { + this.options = options; + }, + + templateHelpers : function() { + return this.options; + } +}); \ No newline at end of file diff --git a/src/UI/AddArtist/NotFoundViewTemplate.hbs b/src/UI/AddArtist/NotFoundViewTemplate.hbs new file mode 100644 index 000000000..abaca6646 --- /dev/null +++ b/src/UI/AddArtist/NotFoundViewTemplate.hbs @@ -0,0 +1,7 @@ +
+

+ Sorry. We couldn't find any artist matching '{{term}}' +

+ Why can't I find my artist? + +
diff --git a/src/UI/AddArtist/RootFolders/RootFolderCollection.js b/src/UI/AddArtist/RootFolders/RootFolderCollection.js new file mode 100644 index 000000000..81050c19d --- /dev/null +++ b/src/UI/AddArtist/RootFolders/RootFolderCollection.js @@ -0,0 +1,10 @@ +var Backbone = require('backbone'); +var RootFolderModel = require('./RootFolderModel'); +require('../../Mixins/backbone.signalr.mixin'); + +var RootFolderCollection = Backbone.Collection.extend({ + url : window.NzbDrone.ApiRoot + '/rootfolder', + model : RootFolderModel +}); + +module.exports = new RootFolderCollection(); \ No newline at end of file diff --git a/src/UI/AddArtist/RootFolders/RootFolderCollectionView.js b/src/UI/AddArtist/RootFolders/RootFolderCollectionView.js new file mode 100644 index 000000000..1029de245 --- /dev/null +++ b/src/UI/AddArtist/RootFolders/RootFolderCollectionView.js @@ -0,0 +1,8 @@ +var Marionette = require('marionette'); +var RootFolderItemView = require('./RootFolderItemView'); + +module.exports = Marionette.CompositeView.extend({ + template : 'AddArtist/RootFolders/RootFolderCollectionViewTemplate', + itemViewContainer : '.x-root-folders', + itemView : RootFolderItemView +}); \ No newline at end of file diff --git a/src/UI/AddArtist/RootFolders/RootFolderCollectionViewTemplate.hbs b/src/UI/AddArtist/RootFolders/RootFolderCollectionViewTemplate.hbs new file mode 100644 index 000000000..70755bbca --- /dev/null +++ b/src/UI/AddArtist/RootFolders/RootFolderCollectionViewTemplate.hbs @@ -0,0 +1,13 @@ + + + + + + + + +
+ Path + + Free Space +
\ No newline at end of file diff --git a/src/UI/AddArtist/RootFolders/RootFolderItemView.js b/src/UI/AddArtist/RootFolders/RootFolderItemView.js new file mode 100644 index 000000000..c22f6fcf7 --- /dev/null +++ b/src/UI/AddArtist/RootFolders/RootFolderItemView.js @@ -0,0 +1,28 @@ +var Marionette = require('marionette'); + +module.exports = Marionette.ItemView.extend({ + template : 'AddArtist/RootFolders/RootFolderItemViewTemplate', + className : 'recent-folder', + tagName : 'tr', + + initialize : function() { + this.listenTo(this.model, 'change', this.render); + }, + + events : { + 'click .x-delete' : 'removeFolder', + 'click .x-folder' : 'folderSelected' + }, + + removeFolder : function() { + var self = this; + + this.model.destroy().success(function() { + self.close(); + }); + }, + + folderSelected : function() { + this.trigger('folderSelected', this.model); + } +}); \ No newline at end of file diff --git a/src/UI/AddArtist/RootFolders/RootFolderItemViewTemplate.hbs b/src/UI/AddArtist/RootFolders/RootFolderItemViewTemplate.hbs new file mode 100644 index 000000000..c1378207a --- /dev/null +++ b/src/UI/AddArtist/RootFolders/RootFolderItemViewTemplate.hbs @@ -0,0 +1,9 @@ + + {{path}} + + + {{Bytes freeSpace}} + + + + diff --git a/src/UI/AddArtist/RootFolders/RootFolderLayout.js b/src/UI/AddArtist/RootFolders/RootFolderLayout.js new file mode 100644 index 000000000..7b5036689 --- /dev/null +++ b/src/UI/AddArtist/RootFolders/RootFolderLayout.js @@ -0,0 +1,80 @@ +var Marionette = require('marionette'); +var RootFolderCollectionView = require('./RootFolderCollectionView'); +var RootFolderCollection = require('./RootFolderCollection'); +var RootFolderModel = require('./RootFolderModel'); +var LoadingView = require('../../Shared/LoadingView'); +var AsValidatedView = require('../../Mixins/AsValidatedView'); +require('../../Mixins/FileBrowser'); + +var Layout = Marionette.Layout.extend({ + template : 'AddArtist/RootFolders/RootFolderLayoutTemplate', + + ui : { + pathInput : '.x-path' + }, + + regions : { + currentDirs : '#current-dirs' + }, + + events : { + 'click .x-add' : '_addFolder', + 'keydown .x-path input' : '_keydown' + }, + + initialize : function() { + this.collection = RootFolderCollection; + this.rootfolderListView = null; + }, + + onShow : function() { + this.listenTo(RootFolderCollection, 'sync', this._showCurrentDirs); + this.currentDirs.show(new LoadingView()); + + if (RootFolderCollection.synced) { + this._showCurrentDirs(); + } + + this.ui.pathInput.fileBrowser(); + }, + + _onFolderSelected : function(options) { + this.trigger('folderSelected', options); + }, + + _addFolder : function() { + var self = this; + + var newDir = new RootFolderModel({ + Path : this.ui.pathInput.val() + }); + + this.bindToModelValidation(newDir); + + newDir.save().done(function() { + RootFolderCollection.add(newDir); + self.trigger('folderSelected', { model : newDir }); + }); + }, + + _showCurrentDirs : function() { + if (!this.rootfolderListView) { + this.rootfolderListView = new RootFolderCollectionView({ collection : RootFolderCollection }); + this.currentDirs.show(this.rootfolderListView); + + this.listenTo(this.rootfolderListView, 'itemview:folderSelected', this._onFolderSelected); + } + }, + + _keydown : function(e) { + if (e.keyCode !== 13) { + return; + } + + this._addFolder(); + } +}); + +var Layout = AsValidatedView.apply(Layout); + +module.exports = Layout; diff --git a/src/UI/AddArtist/RootFolders/RootFolderLayoutTemplate.hbs b/src/UI/AddArtist/RootFolders/RootFolderLayoutTemplate.hbs new file mode 100644 index 000000000..efb6eb7c9 --- /dev/null +++ b/src/UI/AddArtist/RootFolders/RootFolderLayoutTemplate.hbs @@ -0,0 +1,36 @@ + diff --git a/src/UI/AddArtist/RootFolders/RootFolderModel.js b/src/UI/AddArtist/RootFolders/RootFolderModel.js new file mode 100644 index 000000000..28681768b --- /dev/null +++ b/src/UI/AddArtist/RootFolders/RootFolderModel.js @@ -0,0 +1,8 @@ +var Backbone = require('backbone'); + +module.exports = Backbone.Model.extend({ + urlRoot : window.NzbDrone.ApiRoot + '/rootfolder', + defaults : { + freeSpace : 0 + } +}); \ No newline at end of file diff --git a/src/UI/AddArtist/RootFolders/RootFolderSelectionPartial.hbs b/src/UI/AddArtist/RootFolders/RootFolderSelectionPartial.hbs new file mode 100644 index 000000000..56729b0dd --- /dev/null +++ b/src/UI/AddArtist/RootFolders/RootFolderSelectionPartial.hbs @@ -0,0 +1,11 @@ + + diff --git a/src/UI/AddArtist/SearchResultCollectionView.js b/src/UI/AddArtist/SearchResultCollectionView.js new file mode 100644 index 000000000..e533085ac --- /dev/null +++ b/src/UI/AddArtist/SearchResultCollectionView.js @@ -0,0 +1,29 @@ +var Marionette = require('marionette'); +var SearchResultView = require('./SearchResultView'); + +module.exports = Marionette.CollectionView.extend({ + itemView : SearchResultView, + + initialize : function(options) { + this.isExisting = options.isExisting; + this.showing = 1; + }, + + showAll : function() { + this.showingAll = true; + this.render(); + }, + + showMore : function() { + this.showing += 5; + this.render(); + + return this.showing >= this.collection.length; + }, + + appendHtml : function(collectionView, itemView, index) { + if (!this.isExisting || index < this.showing || index === 0) { + collectionView.$el.append(itemView.el); + } + } +}); \ No newline at end of file diff --git a/src/UI/AddArtist/SearchResultView.js b/src/UI/AddArtist/SearchResultView.js new file mode 100644 index 000000000..9c0529c60 --- /dev/null +++ b/src/UI/AddArtist/SearchResultView.js @@ -0,0 +1,297 @@ +var _ = require('underscore'); +var vent = require('vent'); +var AppLayout = require('../AppLayout'); +var Backbone = require('backbone'); +var Marionette = require('marionette'); +var Profiles = require('../Profile/ProfileCollection'); +var RootFolders = require('./RootFolders/RootFolderCollection'); +var RootFolderLayout = require('./RootFolders/RootFolderLayout'); +var ArtistCollection = require('../Artist/ArtistCollection'); +var Config = require('../Config'); +var Messenger = require('../Shared/Messenger'); +var AsValidatedView = require('../Mixins/AsValidatedView'); + +require('jquery.dotdotdot'); + +var view = Marionette.ItemView.extend({ + + template : 'AddArtist/SearchResultViewTemplate', + + ui : { + profile : '.x-profile', + rootFolder : '.x-root-folder', + albumFolder : '.x-album-folder', + artistType : '.x-artist-type', + monitor : '.x-monitor', + monitorTooltip : '.x-monitor-tooltip', + addButton : '.x-add', + addAlbumButton : '.x-add-album', + addSearchButton : '.x-add-search', + addAlbumSearchButton : '.x-add-album-search', + overview : '.x-overview' + }, + + events : { + 'click .x-add' : '_addWithoutSearch', + 'click .x-add-album' : '_addWithoutSearch', + 'click .x-add-search' : '_addAndSearch', + 'click .x-add-album-search' : '_addAndSearch', + 'change .x-profile' : '_profileChanged', + 'change .x-root-folder' : '_rootFolderChanged', + 'change .x-album-folder' : '_albumFolderChanged', + 'change .x-artist-type' : '_artistTypeChanged', + 'change .x-monitor' : '_monitorChanged' + }, + + initialize : function() { + + if (!this.model) { + throw 'model is required'; + } + + this.templateHelpers = {}; + this._configureTemplateHelpers(); + + this.listenTo(vent, Config.Events.ConfigUpdatedEvent, this._onConfigUpdated); + this.listenTo(this.model, 'change', this.render); + this.listenTo(RootFolders, 'all', this._rootFoldersUpdated); + }, + + onRender : function() { + + var defaultProfile = Config.getValue(Config.Keys.DefaultProfileId); + var defaultRoot = Config.getValue(Config.Keys.DefaultRootFolderId); + var useSeasonFolder = Config.getValueBoolean(Config.Keys.UseSeasonFolder, true); + var defaultArtistType = Config.getValue(Config.Keys.DefaultSeriesType, 'standard'); + var defaultMonitorEpisodes = Config.getValue(Config.Keys.MonitorEpisodes, 'missing'); + + if (Profiles.get(defaultProfile)) { + this.ui.profile.val(defaultProfile); + } + + if (RootFolders.get(defaultRoot)) { + this.ui.rootFolder.val(defaultRoot); + } + + this.ui.albumFolder.prop('checked', useSeasonFolder); + this.ui.artistType.val(defaultArtistType); + this.ui.monitor.val(defaultMonitorEpisodes); + + //TODO: make this work via onRender, FM? + //works with onShow, but stops working after the first render + this.ui.overview.dotdotdot({ + height : 120 + }); + + this.templateFunction = Marionette.TemplateCache.get('AddArtist/MonitoringTooltipTemplate'); + var content = this.templateFunction(); + + this.ui.monitorTooltip.popover({ + content : content, + html : true, + trigger : 'hover', + title : 'Track Monitoring Options', + placement : 'right', + container : this.$el + }); + }, + + _configureTemplateHelpers : function() { + var existingArtist = ArtistCollection.where({ foreignArtistId : this.model.get('foreignArtistId') }); + + if (existingArtist.length > 0) { + this.templateHelpers.existing = existingArtist[0].toJSON(); + } + + this.templateHelpers.profiles = Profiles.toJSON(); + + if (!this.model.get('isExisting')) { + this.templateHelpers.rootFolders = RootFolders.toJSON(); + } + }, + + _onConfigUpdated : function(options) { + if (options.key === Config.Keys.DefaultProfileId) { + this.ui.profile.val(options.value); + } + + else if (options.key === Config.Keys.DefaultRootFolderId) { + this.ui.rootFolder.val(options.value); + } + + else if (options.key === Config.Keys.UseAlbumFolder) { + this.ui.seasonFolder.prop('checked', options.value); + } + + else if (options.key === Config.Keys.DefaultArtistType) { + this.ui.artistType.val(options.value); + } + + else if (options.key === Config.Keys.MonitorEpisodes) { + this.ui.monitor.val(options.value); + } + }, + + _profileChanged : function() { + Config.setValue(Config.Keys.DefaultProfileId, this.ui.profile.val()); + }, + + _albumFolderChanged : function() { + Config.setValue(Config.Keys.UseAlbumFolder, this.ui.albumFolder.prop('checked')); + }, + + _rootFolderChanged : function() { + var rootFolderValue = this.ui.rootFolder.val(); + if (rootFolderValue === 'addNew') { + var rootFolderLayout = new RootFolderLayout(); + this.listenToOnce(rootFolderLayout, 'folderSelected', this._setRootFolder); + AppLayout.modalRegion.show(rootFolderLayout); + } else { + Config.setValue(Config.Keys.DefaultRootFolderId, rootFolderValue); + } + }, + + _artistTypeChanged : function() { + Config.setValue(Config.Keys.DefaultArtistType, this.ui.artistType.val()); + }, + + _monitorChanged : function() { + Config.setValue(Config.Keys.MonitorEpisodes, this.ui.monitor.val()); + }, + + _setRootFolder : function(options) { + vent.trigger(vent.Commands.CloseModalCommand); + this.ui.rootFolder.val(options.model.id); + this._rootFolderChanged(); + }, + + _addWithoutSearch : function(evt) { + console.log(evt); + this._addArtist(false); + }, + + _addAndSearch : function() { + this._addArtist(true); + }, + + _addArtist : function(searchForMissing) { + // TODO: Refactor to handle multiple add buttons/albums + var addButton = this.ui.addButton; + var addSearchButton = this.ui.addSearchButton; + console.log('_addArtist, searchForMissing=', searchForMissing); + + addButton.addClass('disabled'); + addSearchButton.addClass('disabled'); + + var profile = this.ui.profile.val(); + var rootFolderPath = this.ui.rootFolder.children(':selected').text(); + var artistType = this.ui.artistType.val(); // Perhaps make this a differnitator between artist or Album? + var albumFolder = this.ui.albumFolder.prop('checked'); + + var options = this._getAddArtistOptions(); + options.searchForMissing = searchForMissing; + + this.model.set({ + profileId : profile, + rootFolderPath : rootFolderPath, + albumFolder : albumFolder, + artistType : artistType, + addOptions : options, + monitored : true + }, { silent : true }); + + var self = this; + var promise = this.model.save(); + + if (searchForMissing) { + this.ui.addSearchButton.spinForPromise(promise); + } + + else { + this.ui.addButton.spinForPromise(promise); + } + + promise.always(function() { + addButton.removeClass('disabled'); + addSearchButton.removeClass('disabled'); + }); + + promise.done(function() { + console.log('[SearchResultView] _addArtist promise resolve:', self.model); + ArtistCollection.add(self.model); + + self.close(); + + Messenger.show({ + message : 'Added: ' + self.model.get('name'), + actions : { + goToArtist : { + label : 'Go to Artist', + action : function() { + Backbone.history.navigate('/artist/' + self.model.get('nameSlug'), { trigger : true }); + } + } + }, + hideAfter : 8, + hideOnNavigate : true + }); + + vent.trigger(vent.Events.ArtistAdded, { artist : self.model }); + }); + }, + + _rootFoldersUpdated : function() { + this._configureTemplateHelpers(); + this.render(); + }, + + _getAddArtistOptions : function() { + var monitor = this.ui.monitor.val(); + //[TODO]: Refactor for albums + var lastSeason = _.max(this.model.get('seasons'), 'seasonNumber'); + var firstSeason = _.min(_.reject(this.model.get('seasons'), { seasonNumber : 0 }), 'seasonNumber'); + + //this.model.setSeasonPass(firstSeason.seasonNumber); // TODO + + var options = { + ignoreTracksWithFiles : false, + ignoreTracksWithoutFiles : false + }; + + if (monitor === 'all') { + return options; + } + + else if (monitor === 'future') { + options.ignoreTracksWithFiles = true; + options.ignoreTracksWithoutFiles = true; + } + + /*else if (monitor === 'latest') { + this.model.setSeasonPass(lastSeason.seasonNumber); + } + + else if (monitor === 'first') { + this.model.setSeasonPass(lastSeason.seasonNumber + 1); + this.model.setSeasonMonitored(firstSeason.seasonNumber); + }*/ + + else if (monitor === 'missing') { + options.ignoreTracksWithFiles = true; + } + + else if (monitor === 'existing') { + options.ignoreTracksWithoutFiles = true; + } + + /*else if (monitor === 'none') { + this.model.setSeasonPass(lastSeason.seasonNumber + 1); + }*/ + + return options; + } +}); + +AsValidatedView.apply(view); + +module.exports = view; diff --git a/src/UI/AddArtist/SearchResultViewTemplate.hbs b/src/UI/AddArtist/SearchResultViewTemplate.hbs new file mode 100644 index 000000000..3c27e1b97 --- /dev/null +++ b/src/UI/AddArtist/SearchResultViewTemplate.hbs @@ -0,0 +1,146 @@ +
+
+
+
+
+

+ + {{name}} + + +

+
+
+
+
+ {{overview}} +
+
+
+ {{#unless existing}} + {{#unless path}} +
+ + {{> RootFolderSelectionPartial rootFolders}} +
+ {{/unless}} + +
+ + +
+ +
+ + {{> ProfileSelectionPartial profiles}} +
+ + + +
+ + +
+
+ {{/unless}} +
+
+ {{#unless existing}} + {{#if name}} +
+ + +
+ + + +
+
+ {{else}} +
+ +
+ {{/if}} + {{else}} + + {{/unless}} +
+
+
+
+ {{#each albums}} +
+ +
+

{{albumName}} ({{year}})

+ {{#unless existing}} + {{#if albumName}} +
+ + +
+ + + +
+
+ {{else}} +
+ +
+ {{/if}} + {{else}} + + {{/unless}} +
+
+ {{/each}} +
+
diff --git a/src/UI/AddArtist/StartingAlbumSelectionPartial.hbs b/src/UI/AddArtist/StartingAlbumSelectionPartial.hbs new file mode 100644 index 000000000..573599dab --- /dev/null +++ b/src/UI/AddArtist/StartingAlbumSelectionPartial.hbs @@ -0,0 +1,13 @@ + diff --git a/src/UI/AddArtist/addArtist.less b/src/UI/AddArtist/addArtist.less new file mode 100644 index 000000000..e53ff8eee --- /dev/null +++ b/src/UI/AddArtist/addArtist.less @@ -0,0 +1,181 @@ +@import "../Shared/Styles/card.less"; +@import "../Shared/Styles/clickable.less"; + +#add-artist-screen { + .existing-artist { + + .card(); + margin : 30px 0px; + + .unmapped-folder-path { + padding: 20px; + margin-left : 0px; + font-weight : 100; + font-size : 25px; + text-align : center; + } + + .new-artist-loadmore { + font-size : 30px; + font-weight : 300; + padding-top : 10px; + padding-bottom : 10px; + } + } + + .new-artist { + .search-item { + .card(); + margin : 40px 0px; + } + } + + .add-artist-search { + margin-top : 20px; + margin-bottom : 20px; + } + + .search-item { + + padding-bottom : 20px; + + .artist-title { + margin-top : 5px; + + .labels { + margin-left : 10px; + + .label { + font-size : 12px; + vertical-align : middle; + } + } + + .year { + font-style : italic; + color : #aaaaaa; + } + } + + .new-artist-overview { + overflow : hidden; + height : 103px; + + .overview-internal { + overflow : hidden; + height : 80px; + } + } + + .artist-poster { + min-width : 138px; + min-height : 203px; + max-width : 138px; + max-height : 203px; + margin : 10px; + } + + .album-poster { + min-width : 100px; + min-height : 100px; + max-width : 138px; + max-height : 203px; + margin : 10px; + } + + a { + color : #343434; + } + + a:hover { + text-decoration : none; + } + + select { + font-size : 14px; + } + + .checkbox { + margin-top : 0px; + } + + .add { + i { + &:before { + color : #ffffff; + } + } + } + + .monitor-tooltip { + margin-left : 5px; + } + } + + .loading-folders { + margin : 30px 0px; + text-align: center; + } + + .hint { + color : #999999; + font-style : italic; + } + + .monitor-tooltip-contents { + padding-bottom : 0px; + + dd { + padding-bottom : 8px; + } + } +} + +li.add-new { + .clickable; + + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 20px; + color: rgb(51, 51, 51); + white-space: nowrap; +} + +li.add-new:hover { + text-decoration: none; + color: rgb(255, 255, 255); + background-color: rgb(0, 129, 194); +} + +.root-folders-modal { + overflow : visible; + + .root-folders-list { + overflow-y : auto; + max-height : 300px; + + i { + .clickable(); + } + } + + .validation-errors { + display : none; + } + + .input-group { + .form-control { + background-color : white; + } + } + + .root-folders { + margin-top : 20px; + } + + .recent-folder { + .clickable(); + } +} diff --git a/src/UI/Artist/AlbumCollection.js b/src/UI/Artist/AlbumCollection.js new file mode 100644 index 000000000..61faa2046 --- /dev/null +++ b/src/UI/Artist/AlbumCollection.js @@ -0,0 +1,10 @@ +var Backbone = require('backbone'); +var AlbumModel = require('./AlbumModel'); + +module.exports = Backbone.Collection.extend({ + model : AlbumModel, + + comparator : function(season) { + return -season.get('seasonNumber'); + } +}); \ No newline at end of file diff --git a/src/UI/Artist/AlbumModel.js b/src/UI/Artist/AlbumModel.js new file mode 100644 index 000000000..1ba049eb6 --- /dev/null +++ b/src/UI/Artist/AlbumModel.js @@ -0,0 +1,11 @@ +var Backbone = require('backbone'); + +module.exports = Backbone.Model.extend({ + defaults : { + seasonNumber : 0 + }, + + initialize : function() { + this.set('id', this.get('seasonNumber')); + } +}); \ No newline at end of file diff --git a/src/UI/Artist/ArtistCollection.js b/src/UI/Artist/ArtistCollection.js index f9908032d..8e700d7bf 100644 --- a/src/UI/Artist/ArtistCollection.js +++ b/src/UI/Artist/ArtistCollection.js @@ -77,7 +77,7 @@ var Collection = PageableCollection.extend({ }, artistName: { - sortKey : 'artistName' + sortKey : 'name' }, nextAiring : { @@ -119,6 +119,6 @@ Collection = AsFilteredCollection.call(Collection); Collection = AsSortedCollection.call(Collection); Collection = AsPersistedStateCollection.call(Collection); -var data = ApiData.get('series'); // TOOD: Build backend for artist +var data = ApiData.get('artist'); // TOOD: Build backend for artist module.exports = new Collection(data, { full : true }).bindSignalR(); diff --git a/src/UI/Artist/ArtistController.js b/src/UI/Artist/ArtistController.js index 4805bd9bf..2f54bc2cf 100644 --- a/src/UI/Artist/ArtistController.js +++ b/src/UI/Artist/ArtistController.js @@ -1,8 +1,8 @@ var NzbDroneController = require('../Shared/NzbDroneController'); var AppLayout = require('../AppLayout'); var ArtistCollection = require('./ArtistCollection'); -var SeriesIndexLayout = require('../Series/Index/SeriesIndexLayout'); -var SeriesDetailsLayout = require('../Series/Details/SeriesDetailsLayout'); +var ArtistIndexLayout = require('./Index/ArtistIndexLayout'); +var ArtistDetailsLayout = require('./Details/ArtistDetailsLayout'); module.exports = NzbDroneController.extend({ _originalInit : NzbDroneController.prototype.initialize, @@ -17,18 +17,18 @@ module.exports = NzbDroneController.extend({ artist : function() { this.setTitle('Lidarr'); - this.showMainRegion(new SeriesIndexLayout()); + this.showMainRegion(new ArtistIndexLayout()); }, artistDetails : function(query) { - var artists = ArtistCollection.where({ artistNameSlug : query }); + var artists = ArtistCollection.where({ nameSlug : query }); console.log('artistDetails, artists: ', artists); if (artists.length !== 0) { - var targetSeries = artists[0]; - console.log("[ArtistController] targetSeries: ", targetSeries); - this.setTitle(targetSeries.get('artistName')); // TODO: Update NzbDroneController + var targetArtist = artists[0]; + console.log("[ArtistController] targetArtist: ", targetArtist); + this.setTitle(targetArtist.get('name')); // TODO: Update NzbDroneController //this.setArtistName(targetSeries.get('artistName')); - this.showMainRegion(new SeriesDetailsLayout({ model : targetSeries })); + this.showMainRegion(new ArtistDetailsLayout({ model : targetArtist })); } else { this.showNotFound(); } diff --git a/src/UI/Artist/ArtistModel.js b/src/UI/Artist/ArtistModel.js index 209ebc1fa..a3c3dde50 100644 --- a/src/UI/Artist/ArtistModel.js +++ b/src/UI/Artist/ArtistModel.js @@ -13,7 +13,7 @@ module.exports = Backbone.Model.extend({ setAlbumsMonitored : function(albumName) { _.each(this.get('albums'), function(album) { - if (season.albumName === albumName) { + if (album.albumName === albumName) { album.monitored = !album.monitored; } }); diff --git a/src/UI/Artist/Delete/DeleteArtistTemplate.hbs b/src/UI/Artist/Delete/DeleteArtistTemplate.hbs new file mode 100644 index 000000000..5e13328b1 --- /dev/null +++ b/src/UI/Artist/Delete/DeleteArtistTemplate.hbs @@ -0,0 +1,50 @@ +