From 2813fccc78b027cecc4aa01804fa94b4d612cd84 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Fri, 28 Apr 2017 17:05:35 -0500 Subject: [PATCH] Updated the code to allow a search to be made from UI to iTunes and return a mock result. --- src/NzbDrone.Api/Music/AlbumResource.cs | 55 +++++ src/NzbDrone.Api/Music/ArtistLookupModule.cs | 46 ++++ src/NzbDrone.Api/Music/ArtistResource.cs | 205 ++++++++++++++++++ src/NzbDrone.Api/NzbDrone.Api.csproj | 3 + .../Cloud/SonarrCloudRequestBuilder.cs | 11 +- .../MetadataSource/ISearchForNewSeries.cs | 2 + .../SkyHook/Resource/ArtistResource.cs | 32 +++ .../MetadataSource/SkyHook/SkyHookProxy.cs | 145 ++++++++++--- src/NzbDrone.Core/Music/Artist.cs | 2 + src/NzbDrone.Core/Music/ArtistRepository.cs | 129 +++++++++++ src/NzbDrone.Core/Music/ArtistService.cs | 95 ++++++++ src/NzbDrone.Core/Music/Compilation.cs | 19 -- src/NzbDrone.Core/Music/TrackService.cs | 117 ++++++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 6 + src/NzbDrone.Core/Parser/Model/LocalTrack.cs | 42 ++++ .../Parser/Model/ParsedTrackInfo.cs | 97 +++++++++ src/NzbDrone.Core/Parser/Parser.cs | 135 ++++++++++++ src/NzbDrone.Core/Parser/ParsingService.cs | 95 +++++++- src/UI/AddSeries/AddSeriesCollection.js | 8 +- src/UI/AddSeries/AddSeriesLayoutTemplate.hbs | 4 +- src/UI/AddSeries/AddSeriesView.js | 1 + src/UI/AddSeries/AddSeriesViewTemplate.hbs | 2 +- src/UI/AddSeries/EmptyViewTemplate.hbs | 2 +- src/UI/AddSeries/ErrorViewTemplate.hbs | 2 +- ...ddExistingSeriesCollectionViewTemplate.hbs | 2 +- src/UI/AddSeries/SearchResultView.js | 4 +- src/UI/AddSeries/SearchResultViewTemplate.hbs | 10 +- src/UI/Artist/ArtistCollection.js | 124 +++++++++++ src/UI/Artist/ArtistController.js | 37 ++++ src/UI/Artist/ArtistModel.js | 31 +++ src/UI/Handlebars/Helpers/Series.js | 4 +- src/UI/Series/Details/SeriesDetailsLayout.js | 5 +- 32 files changed, 1404 insertions(+), 68 deletions(-) create mode 100644 src/NzbDrone.Api/Music/AlbumResource.cs create mode 100644 src/NzbDrone.Api/Music/ArtistLookupModule.cs create mode 100644 src/NzbDrone.Api/Music/ArtistResource.cs create mode 100644 src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs create mode 100644 src/NzbDrone.Core/Music/ArtistRepository.cs create mode 100644 src/NzbDrone.Core/Music/ArtistService.cs delete mode 100644 src/NzbDrone.Core/Music/Compilation.cs create mode 100644 src/NzbDrone.Core/Music/TrackService.cs create mode 100644 src/NzbDrone.Core/Parser/Model/LocalTrack.cs create mode 100644 src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs create mode 100644 src/UI/Artist/ArtistCollection.js create mode 100644 src/UI/Artist/ArtistController.js create mode 100644 src/UI/Artist/ArtistModel.js diff --git a/src/NzbDrone.Api/Music/AlbumResource.cs b/src/NzbDrone.Api/Music/AlbumResource.cs new file mode 100644 index 000000000..c0235e48a --- /dev/null +++ b/src/NzbDrone.Api/Music/AlbumResource.cs @@ -0,0 +1,55 @@ +using NzbDrone.Core.Music; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Api.Music +{ + public class AlbumResource + { + public int AlbumId { get; set; } + public bool Monitored { get; set; } + //public string Overview { get; set; } + public int Year { get; set; } + //public SeasonStatisticsResource Statistics { get; set; } + } + + public static class AlbumResourceMapper + { + public static AlbumResource ToResource(this Album model) + { + if (model == null) return null; + + return new AlbumResource + { + AlbumId = model.AlbumId, + Monitored = model.Monitored, + //Overview = model.Overview; //TODO: Inspect if Album needs an overview + Year = model.Year + }; + } + + public static Album ToModel(this AlbumResource resource) + { + if (resource == null) return null; + + return new Album + { + AlbumId = resource.AlbumId, + Monitored = resource.Monitored, + Year = resource.Year + }; + } + + public static List ToResource(this IEnumerable models) + { + return models.Select(ToResource).ToList(); + } + + public static List ToModel(this IEnumerable resources) + { + return resources?.Select(ToModel).ToList() ?? new List(); + } + } +} diff --git a/src/NzbDrone.Api/Music/ArtistLookupModule.cs b/src/NzbDrone.Api/Music/ArtistLookupModule.cs new file mode 100644 index 000000000..4f6d5e030 --- /dev/null +++ b/src/NzbDrone.Api/Music/ArtistLookupModule.cs @@ -0,0 +1,46 @@ +using Nancy; +using NzbDrone.Api.Extensions; +using NzbDrone.Core.MediaCover; +using NzbDrone.Core.MetadataSource; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Api.Music +{ + public class ArtistLookupModule : NzbDroneRestModule + { + private readonly ISearchForNewSeries _searchProxy; //TODO: Switch out for Music varriant + + public ArtistLookupModule(ISearchForNewSeries searchProxy) + : base("/artist/lookup") + { + _searchProxy = searchProxy; + Get["/"] = x => Search(); + } + + + private Response Search() + { + var iTunesResults = _searchProxy.SearchForNewArtist((string)Request.Query.term); + return MapToResource(iTunesResults).AsResponse(); + } + + + private static IEnumerable MapToResource(IEnumerable artists) + { + foreach (var currentArtist in artists) + { + var resource = currentArtist.ToResource(); + var poster = currentArtist.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster); + if (poster != null) + { + resource.RemotePoster = poster.Url; + } + + yield return resource; + } + } + } +} diff --git a/src/NzbDrone.Api/Music/ArtistResource.cs b/src/NzbDrone.Api/Music/ArtistResource.cs new file mode 100644 index 000000000..0921ea1b5 --- /dev/null +++ b/src/NzbDrone.Api/Music/ArtistResource.cs @@ -0,0 +1,205 @@ +using NzbDrone.Api.REST; +using NzbDrone.Api.Series; +using NzbDrone.Core.MediaCover; +using NzbDrone.Core.Tv; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Api.Music +{ + public class ArtistResource : RestResource + { + public ArtistResource() + { + Monitored = true; + } + + //Todo: Sorters should be done completely on the client + //Todo: Is there an easy way to keep IgnoreArticlesWhenSorting in sync between, Series, History, Missing? + //Todo: We should get the entire Profile instead of ID and Name separately + + //View Only + public string ArtistName { get; set; } + //public List AlternateTitles { get; set; } + //public string SortTitle { get; set; } + + public int AlbumCount + { + get + { + if (Albums == null) return 0; + + return Albums.Where(s => s.AlbumId > 0).Count(); // TODO: CHeck this condition + } + } + + public int? TotalTrackCount { get; set; } + public int? TrackCount { get; set; } + public int? TrackFileCount { get; set; } + public long? SizeOnDisk { get; set; } + //public SeriesStatusType Status { get; set; } + + public List Images { get; set; } + + public string RemotePoster { get; set; } + public List Albums { get; set; } + + + //View & Edit + public string Path { get; set; } + public int ProfileId { get; set; } + + //Editing Only + public bool ArtistFolder { get; set; } + public bool Monitored { get; set; } + + //public bool UseSceneNumbering { get; set; } + //public int Runtime { get; set; } + //public int TvdbId { get; set; } + //public int TvRageId { get; set; } + //public int TvMazeId { get; set; } + //public DateTime? FirstAired { get; set; } + //public DateTime? LastInfoSync { get; set; } + //public SeriesTypes SeriesType { get; set; } + public string CleanTitle { get; set; } + public int ItunesId { get; set; } + //public string TitleSlug { get; set; } + public string RootFolderPath { get; set; } + public string Certification { get; set; } + public List Genres { get; set; } + public HashSet Tags { get; set; } + public DateTime Added { get; set; } + public AddSeriesOptions AddOptions { get; set; } + public Ratings Ratings { get; set; } + public string ArtistSlug { get; internal set; } + } + + public static class ArtistResourceMapper + { + public static ArtistResource ToResource(this Core.Music.Artist model) + { + if (model == null) return null; + + return new ArtistResource + { + Id = model.Id, + + ArtistName = model.ArtistName, + //AlternateTitles + //SortTitle = resource.SortTitle, + + //TotalEpisodeCount + //EpisodeCount + //EpisodeFileCount + //SizeOnDisk + //Status = resource.Status, + //Overview = resource.Overview, + //NextAiring + //PreviousAiring + //Network = resource.Network, + //AirTime = resource.AirTime, + Images = model.Images, + + Albums = model.Albums.ToResource(), + //Year = resource.Year, + + Path = model.Path, + ProfileId = model.ProfileId, + + ArtistFolder = model.ArtistFolder, + Monitored = model.Monitored, + + //UseSceneNumbering = resource.UseSceneNumbering, + //Runtime = resource.Runtime, + //TvdbId = resource.TvdbId, + //TvRageId = resource.TvRageId, + //TvMazeId = resource.TvMazeId, + //FirstAired = resource.FirstAired, + //LastInfoSync = resource.LastInfoSync, + //SeriesType = resource.SeriesType, + CleanTitle = model.CleanTitle, + ItunesId = model.ItunesId, + ArtistSlug = model.ArtistSlug, + + RootFolderPath = model.RootFolderPath, + Certification = model.Certification, + Genres = model.Genres, + Tags = model.Tags, + Added = model.Added, + //AddOptions = resource.AddOptions, + //Ratings = resource.Ratings + }; + } + + public static Core.Music.Artist ToModel(this ArtistResource resource) + { + if (resource == null) return null; + + return new Core.Music.Artist + { + Id = resource.Id, + + ArtistName = resource.ArtistName, + //AlternateTitles + //SortTitle = resource.SortTitle, + + //TotalEpisodeCount + //EpisodeCount + //EpisodeFileCount + //SizeOnDisk + //Status = resource.Status, + //Overview = resource.Overview, + //NextAiring + //PreviousAiring + //Network = resource.Network, + //AirTime = resource.AirTime, + Images = resource.Images, + + Albums = resource.Albums.ToModel(), + //Year = resource.Year, + + Path = resource.Path, + ProfileId = resource.ProfileId, + + ArtistFolder = resource.ArtistFolder, + Monitored = resource.Monitored, + + //UseSceneNumbering = resource.UseSceneNumbering, + //Runtime = resource.Runtime, + //TvdbId = resource.TvdbId, + //TvRageId = resource.TvRageId, + //TvMazeId = resource.TvMazeId, + //FirstAired = resource.FirstAired, + //LastInfoSync = resource.LastInfoSync, + //SeriesType = resource.SeriesType, + CleanTitle = resource.CleanTitle, + ItunesId = resource.ItunesId, + ArtistSlug = resource.ArtistSlug, + + RootFolderPath = resource.RootFolderPath, + Certification = resource.Certification, + Genres = resource.Genres, + Tags = resource.Tags, + Added = resource.Added, + //AddOptions = resource.AddOptions, + //Ratings = resource.Ratings + }; + } + + public static Core.Music.Artist ToModel(this ArtistResource resource, Core.Music.Artist artist) + { + var updatedArtist = resource.ToModel(); + + artist.ApplyChanges(updatedArtist); + + return artist; + } + + public static List ToResource(this IEnumerable artist) + { + return artist.Select(ToResource).ToList(); + } + } +} diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index cce77e637..89f43a55a 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -111,6 +111,9 @@ + + + diff --git a/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs b/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs index 668535986..23674eaec 100644 --- a/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs +++ b/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs @@ -5,6 +5,7 @@ namespace NzbDrone.Common.Cloud public interface ILidarrCloudRequestBuilder { IHttpRequestBuilderFactory Services { get; } + IHttpRequestBuilderFactory Search { get; } IHttpRequestBuilderFactory SkyHookTvdb { get; } } @@ -12,16 +13,22 @@ namespace NzbDrone.Common.Cloud { public LidarrCloudRequestBuilder() { - Services = new HttpRequestBuilder("http://services.Lidarr.tv/v1/") + + Services = new HttpRequestBuilder("http://services.lidarr.tv/v1/") .CreateFactory(); - SkyHookTvdb = new HttpRequestBuilder("http://skyhook.Lidarr.tv/v1/tvdb/{route}/{language}/") + Search = new HttpRequestBuilder("https://itunes.apple.com/search/") + .CreateFactory(); + + SkyHookTvdb = new HttpRequestBuilder("http://skyhook.lidarr.tv/v1/tvdb/{route}/{language}/") .SetSegment("language", "en") .CreateFactory(); } public IHttpRequestBuilderFactory Services { get; } + public IHttpRequestBuilderFactory Search { get; } + public IHttpRequestBuilderFactory SkyHookTvdb { get; } } } diff --git a/src/NzbDrone.Core/MetadataSource/ISearchForNewSeries.cs b/src/NzbDrone.Core/MetadataSource/ISearchForNewSeries.cs index 5abd02bcc..01b096254 100644 --- a/src/NzbDrone.Core/MetadataSource/ISearchForNewSeries.cs +++ b/src/NzbDrone.Core/MetadataSource/ISearchForNewSeries.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; namespace NzbDrone.Core.MetadataSource { public interface ISearchForNewSeries { List SearchForNewSeries(string title); + List SearchForNewArtist(string title); } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs new file mode 100644 index 000000000..7e6040021 --- /dev/null +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MetadataSource.SkyHook.Resource +{ + public class AlbumResource + { + public AlbumResource() + { + + } + + public string ArtistName { get; set; } + public int ArtistId { get; set; } + public string CollectionName { get; set; } + } + + public class ArtistResource + { + public ArtistResource() + { + + } + + public int ResultCount { get; set; } + public List Results { get; set; } + //public string ArtistName { get; set; } + //public List Albums { get; set; } + } +} diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index 2dd0612d6..0b68cb78f 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -10,6 +10,9 @@ using NzbDrone.Core.Exceptions; using NzbDrone.Core.MediaCover; using NzbDrone.Core.MetadataSource.SkyHook.Resource; using NzbDrone.Core.Tv; +using Newtonsoft.Json.Linq; +using NzbDrone.Core.Music; +using Newtonsoft.Json; namespace NzbDrone.Core.MetadataSource.SkyHook { @@ -23,12 +26,13 @@ namespace NzbDrone.Core.MetadataSource.SkyHook public SkyHookProxy(IHttpClient httpClient, ILidarrCloudRequestBuilder requestBuilder, Logger logger) { _httpClient = httpClient; - _requestBuilder = requestBuilder.SkyHookTvdb; + _requestBuilder = requestBuilder.Search; _logger = logger; } public Tuple> GetSeriesInfo(int tvdbSeriesId) { + Console.WriteLine("[GetSeriesInfo] id:" + tvdbSeriesId); var httpRequest = _requestBuilder.Create() .SetSegment("route", "shows") .Resource(tvdbSeriesId.ToString()) @@ -62,36 +66,114 @@ namespace NzbDrone.Core.MetadataSource.SkyHook try { var lowerTitle = title.ToLowerInvariant(); + Console.WriteLine("Searching for " + lowerTitle); + + //if (lowerTitle.StartsWith("tvdb:") || lowerTitle.StartsWith("tvdbid:")) + //{ + // var slug = lowerTitle.Split(':')[1].Trim(); + + // int tvdbId; + + // if (slug.IsNullOrWhiteSpace() || slug.Any(char.IsWhiteSpace) || !int.TryParse(slug, out tvdbId) || tvdbId <= 0) + // { + // return new List(); + // } + + // try + // { + // return new List { GetSeriesInfo(tvdbId).Item1 }; + // } + // catch (SeriesNotFoundException) + // { + // return new List(); + // } + //} + + // Majora: Temporarily, use iTunes to test. + var httpRequest = _requestBuilder.Create() + .AddQueryParam("entity", "album") + .AddQueryParam("term", title.ToLower().Trim()) + .Build(); - if (lowerTitle.StartsWith("tvdb:") || lowerTitle.StartsWith("tvdbid:")) - { - var slug = lowerTitle.Split(':')[1].Trim(); - - int tvdbId; - - if (slug.IsNullOrWhiteSpace() || slug.Any(char.IsWhiteSpace) || !int.TryParse(slug, out tvdbId) || tvdbId <= 0) - { - return new List(); - } - - try - { - return new List { GetSeriesInfo(tvdbId).Item1 }; - } - catch (SeriesNotFoundException) - { - return new List(); - } - } + + + Console.WriteLine("httpRequest: ", httpRequest); + + var httpResponse = _httpClient.Get(httpRequest); + + //Console.WriteLine("Response: ", httpResponse.GetType()); + //_logger.Info("Response: ", httpResponse.Resource.ResultCount); + + //_logger.Info("HTTP Response: ", httpResponse.Resource.ResultCount); + var tempList = new List(); + var tempSeries = new Series(); + tempSeries.Title = "AFI"; + tempList.Add(tempSeries); + return tempList; + + //return httpResponse.Resource.Results.SelectList(MapArtist); + } + catch (HttpException) + { + throw new SkyHookException("Search for '{0}' failed. Unable to communicate with SkyHook.", title); + } + catch (Exception ex) + { + _logger.Warn(ex, ex.Message); + throw new SkyHookException("Search for '{0}' failed. Invalid response received from SkyHook.", title); + } + } + + public List SearchForNewArtist(string title) + { + try + { + var lowerTitle = title.ToLowerInvariant(); + Console.WriteLine("Searching for " + lowerTitle); + + //if (lowerTitle.StartsWith("tvdb:") || lowerTitle.StartsWith("tvdbid:")) + //{ + // var slug = lowerTitle.Split(':')[1].Trim(); + + // int tvdbId; + + // if (slug.IsNullOrWhiteSpace() || slug.Any(char.IsWhiteSpace) || !int.TryParse(slug, out tvdbId) || tvdbId <= 0) + // { + // return new List(); + // } + + // try + // { + // return new List { GetSeriesInfo(tvdbId).Item1 }; + // } + // catch (SeriesNotFoundException) + // { + // return new List(); + // } + //} var httpRequest = _requestBuilder.Create() - .SetSegment("route", "search") - .AddQueryParam("term", title.ToLower().Trim()) - .Build(); + .AddQueryParam("entity", "album") + .AddQueryParam("term", title.ToLower().Trim()) + .Build(); + + - var httpResponse = _httpClient.Get>(httpRequest); + Console.WriteLine("httpRequest: ", httpRequest); - return httpResponse.Resource.SelectList(MapSeries); + var httpResponse = _httpClient.Get(httpRequest); + + //Console.WriteLine("Response: ", httpResponse.GetType()); + //_logger.Info("Response: ", httpResponse.Resource.ResultCount); + + //_logger.Info("HTTP Response: ", httpResponse.Resource.ResultCount); + var tempList = new List(); + var tempSeries = new Artist(); + tempSeries.ArtistName = "AFI"; + tempList.Add(tempSeries); + return tempList; + + //return httpResponse.Resource.Results.SelectList(MapArtist); } catch (HttpException) { @@ -104,6 +186,17 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } } + private static Artist MapArtist(ArtistResource artistQuery) + { + var artist = new Artist(); + //artist.ItunesId = artistQuery.artistId; + + // artist.ArtistName = artistQuery.ArtistName; + + + return artist; + } + private static Series MapSeries(ShowResource show) { var series = new Series(); diff --git a/src/NzbDrone.Core/Music/Artist.cs b/src/NzbDrone.Core/Music/Artist.cs index f0faed6b1..50c297a56 100644 --- a/src/NzbDrone.Core/Music/Artist.cs +++ b/src/NzbDrone.Core/Music/Artist.cs @@ -55,6 +55,8 @@ namespace NzbDrone.Core.Music public List Albums { get; set; } public HashSet Tags { get; set; } + public bool ArtistFolder { get; set; } + //public AddSeriesOptions AddOptions { get; set; } // TODO: Learn what this does public override string ToString() diff --git a/src/NzbDrone.Core/Music/ArtistRepository.cs b/src/NzbDrone.Core/Music/ArtistRepository.cs new file mode 100644 index 000000000..39fcbb1db --- /dev/null +++ b/src/NzbDrone.Core/Music/ArtistRepository.cs @@ -0,0 +1,129 @@ +using NzbDrone.Core.Datastore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Linq.Expressions; + +namespace NzbDrone.Core.Music +{ + public interface IArtistRepository : IBasicRepository + { + bool ArtistPathExists(string path); + Artist FindByTitle(string cleanTitle); + Artist FindByItunesId(int iTunesId); + } + + public class ArtistRepository : IArtistRepository + { + public IEnumerable All() + { + throw new NotImplementedException(); + } + + public bool ArtistPathExists(string path) + { + throw new NotImplementedException(); + } + + public int Count() + { + throw new NotImplementedException(); + } + + public void Delete(Artist model) + { + throw new NotImplementedException(); + } + + public void Delete(int id) + { + throw new NotImplementedException(); + } + + public void DeleteMany(IEnumerable ids) + { + throw new NotImplementedException(); + } + + public void DeleteMany(List model) + { + throw new NotImplementedException(); + } + + public Artist FindByItunesId(int iTunesId) + { + throw new NotImplementedException(); + } + + public Artist FindByTitle(string cleanTitle) + { + throw new NotImplementedException(); + } + + public IEnumerable Get(IEnumerable ids) + { + throw new NotImplementedException(); + } + + public Artist Get(int id) + { + throw new NotImplementedException(); + } + + public PagingSpec GetPaged(PagingSpec pagingSpec) + { + throw new NotImplementedException(); + } + + public bool HasItems() + { + throw new NotImplementedException(); + } + + public Artist Insert(Artist model) + { + throw new NotImplementedException(); + } + + public void InsertMany(IList model) + { + throw new NotImplementedException(); + } + + public void Purge(bool vacuum = false) + { + throw new NotImplementedException(); + } + + public void SetFields(Artist model, params Expression>[] properties) + { + throw new NotImplementedException(); + } + + public Artist Single() + { + throw new NotImplementedException(); + } + + public Artist SingleOrDefault() + { + throw new NotImplementedException(); + } + + public Artist Update(Artist model) + { + throw new NotImplementedException(); + } + + public void UpdateMany(IList model) + { + throw new NotImplementedException(); + } + + public Artist Upsert(Artist model) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/NzbDrone.Core/Music/ArtistService.cs b/src/NzbDrone.Core/Music/ArtistService.cs new file mode 100644 index 000000000..4aecdb36d --- /dev/null +++ b/src/NzbDrone.Core/Music/ArtistService.cs @@ -0,0 +1,95 @@ +using NLog; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Organizer; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music +{ + public interface IArtistService + { + Artist GetArtist(int artistId); + List GetArtists(IEnumerable artistIds); + Artist AddArtist(Artist newArtist); + Artist FindByItunesId(int itunesId); + Artist FindByTitle(string title); + Artist FindByTitleInexact(string title); + void DeleteArtist(int artistId, bool deleteFiles); + List GetAllArtists(); + Artist UpdateArtist(Artist artist); + List UpdateArtists(List artist); + bool ArtistPathExists(string folder); + void RemoveAddOptions(Artist artist); + } + + public class ArtistService : IArtistService + { + private readonly IArtistRepository _artistRepository; + private readonly IEventAggregator _eventAggregator; + private readonly ITrackService _trackService; + private readonly IBuildFileNames _fileNameBuilder; + private readonly Logger _logger; + + public Artist AddArtist(Artist newArtist) + { + throw new NotImplementedException(); + } + + public bool ArtistPathExists(string folder) + { + throw new NotImplementedException(); + } + + public void DeleteArtist(int artistId, bool deleteFiles) + { + throw new NotImplementedException(); + } + + public Artist FindByItunesId(int itunesId) + { + throw new NotImplementedException(); + } + + public Artist FindByTitle(string title) + { + throw new NotImplementedException(); + } + + public Artist FindByTitleInexact(string title) + { + throw new NotImplementedException(); + } + + public List GetAllArtists() + { + throw new NotImplementedException(); + } + + public Artist GetArtist(int artistId) + { + throw new NotImplementedException(); + } + + public List GetArtists(IEnumerable artistIds) + { + throw new NotImplementedException(); + } + + public void RemoveAddOptions(Artist artist) + { + throw new NotImplementedException(); + } + + public Artist UpdateArtist(Artist artist) + { + throw new NotImplementedException(); + } + + public List UpdateArtists(List artist) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/NzbDrone.Core/Music/Compilation.cs b/src/NzbDrone.Core/Music/Compilation.cs deleted file mode 100644 index 923e6d7f3..000000000 --- a/src/NzbDrone.Core/Music/Compilation.cs +++ /dev/null @@ -1,19 +0,0 @@ -using NzbDrone.Core.Datastore; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace NzbDrone.Core.Music -{ - public class Compilation : ModelBase - { - public Compilation() - { - - } - - public int CompilationId { get; set; } - public LazyList Artists { get; set; } - } -} diff --git a/src/NzbDrone.Core/Music/TrackService.cs b/src/NzbDrone.Core/Music/TrackService.cs new file mode 100644 index 000000000..91bdeb5f7 --- /dev/null +++ b/src/NzbDrone.Core/Music/TrackService.cs @@ -0,0 +1,117 @@ +using NzbDrone.Core.Datastore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music +{ + public interface ITrackService + { + Track GetTrack(int id); + List GetTracks(IEnumerable ids); + Track FindTrack(int artistId, int albumId, int trackNumber); + Track FindTrackByTitle(int artistId, int albumId, string releaseTitle); + List GetTrackByArtist(int artistId); + List GetTracksByAblum(int artistId, int albumId); + List GetTracksByAblumTitle(int artistId, string albumTitle); + List TracksWithFiles(int artistId); + PagingSpec TracksWithoutFiles(PagingSpec pagingSpec); + List GeTracksByFileId(int trackFileId); + void UpdateTrack(Track track); + void SetTrackMonitored(int trackId, bool monitored); + void UpdateTracks(List tracks); + void InsertMany(List tracks); + void UpdateMany(List tracks); + void DeleteMany(List tracks); + void SetTrackMonitoredByAlbum(int artistId, int albumId, bool monitored); + } + + public class TrackService : ITrackService + { + public void DeleteMany(List tracks) + { + throw new NotImplementedException(); + } + + public Track FindTrack(int artistId, int albumId, int trackNumber) + { + throw new NotImplementedException(); + } + + public Track FindTrackByTitle(int artistId, int albumId, string releaseTitle) + { + throw new NotImplementedException(); + } + + public List GeTracksByFileId(int trackFileId) + { + throw new NotImplementedException(); + } + + public Track GetTrack(int id) + { + throw new NotImplementedException(); + } + + public List GetTrackByArtist(int artistId) + { + throw new NotImplementedException(); + } + + public List GetTracks(IEnumerable ids) + { + throw new NotImplementedException(); + } + + public List GetTracksByAblum(int artistId, int albumId) + { + throw new NotImplementedException(); + } + + public List GetTracksByAblumTitle(int artistId, string albumTitle) + { + throw new NotImplementedException(); + } + + public void InsertMany(List tracks) + { + throw new NotImplementedException(); + } + + public void SetTrackMonitored(int trackId, bool monitored) + { + throw new NotImplementedException(); + } + + public void SetTrackMonitoredByAlbum(int artistId, int albumId, bool monitored) + { + throw new NotImplementedException(); + } + + public List TracksWithFiles(int artistId) + { + throw new NotImplementedException(); + } + + public PagingSpec TracksWithoutFiles(PagingSpec pagingSpec) + { + throw new NotImplementedException(); + } + + public void UpdateMany(List tracks) + { + throw new NotImplementedException(); + } + + public void UpdateTrack(Track track) + { + throw new NotImplementedException(); + } + + public void UpdateTracks(List tracks) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index d82211ee6..3b00c7cf0 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -809,6 +809,7 @@ + @@ -841,7 +842,10 @@ + + + @@ -897,6 +901,8 @@ + + diff --git a/src/NzbDrone.Core/Parser/Model/LocalTrack.cs b/src/NzbDrone.Core/Parser/Model/LocalTrack.cs new file mode 100644 index 000000000..e3577527d --- /dev/null +++ b/src/NzbDrone.Core/Parser/Model/LocalTrack.cs @@ -0,0 +1,42 @@ +using NzbDrone.Core.MediaFiles.MediaInfo; +using NzbDrone.Core.Music; +using NzbDrone.Core.Qualities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Parser.Model +{ + public class LocalTrack + { + public LocalTrack() + { + Tracks = new List(); + } + + public string Path { get; set; } + public long Size { get; set; } + public ParsedTrackInfo ParsedTrackInfo { get; set; } + public Artist Artist { get; set; } + public List Tracks { get; set; } + public QualityModel Quality { get; set; } + public MediaInfoModel MediaInfo { get; set; } + public bool ExistingFile { get; set; } + + public int Album + { + get + { + return Tracks.Select(c => c.AlbumId).Distinct().Single(); + } + } + + public bool IsSpecial => Album == 0; + + public override string ToString() + { + return Path; + } + } +} diff --git a/src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs b/src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs new file mode 100644 index 000000000..53cb470c9 --- /dev/null +++ b/src/NzbDrone.Core/Parser/Model/ParsedTrackInfo.cs @@ -0,0 +1,97 @@ +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Qualities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Parser.Model +{ + public class ParsedTrackInfo + { + // [TODO]: Properly fill this out + public string ArtistTitle { get; set; } + public string AlbumTitle { get; set; } + public SeriesTitleInfo SeriesTitleInfo { get; set; } + public QualityModel Quality { get; set; } + public int SeasonNumber { get; set; } + public int[] EpisodeNumbers { get; set; } + public int[] AbsoluteEpisodeNumbers { get; set; } + public string AirDate { get; set; } + public Language Language { get; set; } + public bool FullSeason { get; set; } + public bool Special { get; set; } + public string ReleaseGroup { get; set; } + public string ReleaseHash { get; set; } + + public ParsedTrackInfo() + { + EpisodeNumbers = new int[0]; + AbsoluteEpisodeNumbers = new int[0]; + } + + public bool IsDaily + { + get + { + return !string.IsNullOrWhiteSpace(AirDate); + } + + //This prevents manually downloading a release from blowing up in mono + //TODO: Is there a better way? + private set { } + } + + public bool IsAbsoluteNumbering + { + get + { + return AbsoluteEpisodeNumbers.Any(); + } + + //This prevents manually downloading a release from blowing up in mono + //TODO: Is there a better way? + private set { } + } + + public bool IsPossibleSpecialEpisode + { + get + { + // if we don't have eny episode numbers we are likely a special episode and need to do a search by episode title + return (AirDate.IsNullOrWhiteSpace() && + ArtistTitle.IsNullOrWhiteSpace() && + (EpisodeNumbers.Length == 0 || SeasonNumber == 0) || + !ArtistTitle.IsNullOrWhiteSpace() && Special); + } + + //This prevents manually downloading a release from blowing up in mono + //TODO: Is there a better way? + private set { } + } + + public override string ToString() + { + string episodeString = "[Unknown Episode]"; + + if (IsDaily && EpisodeNumbers.Empty()) + { + episodeString = string.Format("{0}", AirDate); + } + else if (FullSeason) + { + episodeString = string.Format("Season {0:00}", SeasonNumber); + } + else if (EpisodeNumbers != null && EpisodeNumbers.Any()) + { + episodeString = string.Format("S{0:00}E{1}", SeasonNumber, string.Join("-", EpisodeNumbers.Select(c => c.ToString("00")))); + } + else if (AbsoluteEpisodeNumbers != null && AbsoluteEpisodeNumbers.Any()) + { + episodeString = string.Format("{0}", string.Join("-", AbsoluteEpisodeNumbers.Select(c => c.ToString("000")))); + } + + return string.Format("{0} - {1} {2}", ArtistTitle, episodeString, Quality); + } + } +} diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 4855926a9..1a541cd1c 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -277,6 +277,27 @@ namespace NzbDrone.Core.Parser private static readonly string[] Numbers = new[] { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }; + public static ParsedTrackInfo ParseMusicPath(string path) + { + var fileInfo = new FileInfo(path); + + var result = ParseMusicTitle(fileInfo.Name); + + if (result == null) + { + Logger.Debug("Attempting to parse track info using directory and file names. {0}", fileInfo.Directory.Name); + result = ParseMusicTitle(fileInfo.Directory.Name + " " + fileInfo.Name); + } + + if (result == null) + { + Logger.Debug("Attempting to parse track info using directory name. {0}", fileInfo.Directory.Name); + result = ParseMusicTitle(fileInfo.Directory.Name + fileInfo.Extension); + } + + return result; + } + public static ParsedEpisodeInfo ParsePath(string path) { var fileInfo = new FileInfo(path); @@ -298,6 +319,116 @@ namespace NzbDrone.Core.Parser return result; } + public static ParsedTrackInfo ParseMusicTitle(string title) + { + try + { + if (!ValidateBeforeParsing(title)) return null; + + Logger.Debug("Parsing string '{0}'", title); + + if (ReversedTitleRegex.IsMatch(title)) + { + var titleWithoutExtension = RemoveFileExtension(title).ToCharArray(); + Array.Reverse(titleWithoutExtension); + + title = new string(titleWithoutExtension) + title.Substring(titleWithoutExtension.Length); + + Logger.Debug("Reversed name detected. Converted to '{0}'", title); + } + + var simpleTitle = SimpleTitleRegex.Replace(title, string.Empty); + + simpleTitle = RemoveFileExtension(simpleTitle); + + // TODO: Quick fix stripping [url] - prefixes. + simpleTitle = WebsitePrefixRegex.Replace(simpleTitle, string.Empty); + + simpleTitle = CleanTorrentSuffixRegex.Replace(simpleTitle, string.Empty); + + var airDateMatch = AirDateRegex.Match(simpleTitle); + if (airDateMatch.Success) + { + simpleTitle = airDateMatch.Groups[1].Value + airDateMatch.Groups["airyear"].Value + "." + airDateMatch.Groups["airmonth"].Value + "." + airDateMatch.Groups["airday"].Value; + } + + var sixDigitAirDateMatch = SixDigitAirDateRegex.Match(simpleTitle); + if (sixDigitAirDateMatch.Success) + { + var airYear = sixDigitAirDateMatch.Groups["airyear"].Value; + var airMonth = sixDigitAirDateMatch.Groups["airmonth"].Value; + var airDay = sixDigitAirDateMatch.Groups["airday"].Value; + + if (airMonth != "00" || airDay != "00") + { + var fixedDate = string.Format("20{0}.{1}.{2}", airYear, airMonth, airDay); + + simpleTitle = simpleTitle.Replace(sixDigitAirDateMatch.Groups["airdate"].Value, fixedDate); + } + } + + foreach (var regex in ReportTitleRegex) + { + var match = regex.Matches(simpleTitle); + + if (match.Count != 0) + { + Logger.Trace(regex); + try + { + var result = ParseMatchMusicCollection(match); + + if (result != null) + { + if (result.FullSeason && title.ContainsIgnoreCase("Special")) + { + result.FullSeason = false; + result.Special = true; + } + + result.Language = LanguageParser.ParseLanguage(title); + Logger.Debug("Language parsed: {0}", result.Language); + + result.Quality = QualityParser.ParseQuality(title); + Logger.Debug("Quality parsed: {0}", result.Quality); + + result.ReleaseGroup = ParseReleaseGroup(title); + + var subGroup = GetSubGroup(match); + if (!subGroup.IsNullOrWhiteSpace()) + { + result.ReleaseGroup = subGroup; + } + + Logger.Debug("Release Group parsed: {0}", result.ReleaseGroup); + + result.ReleaseHash = GetReleaseHash(match); + if (!result.ReleaseHash.IsNullOrWhiteSpace()) + { + Logger.Debug("Release Hash parsed: {0}", result.ReleaseHash); + } + + return result; + } + } + catch (InvalidDateException ex) + { + Logger.Debug(ex, ex.Message); + break; + } + } + } + } + catch (Exception e) + { + if (!title.ToLower().Contains("password") && !title.ToLower().Contains("yenc")) + Logger.Error(e, "An error has occurred while trying to parse {0}", title); + } + + Logger.Debug("Unable to parse {0}", title); + return null; + } + public static ParsedEpisodeInfo ParseTitle(string title) { try @@ -522,6 +653,10 @@ namespace NzbDrone.Core.Parser return seriesTitleInfo; } + private static ParsedTrackInfo ParseMatchMusicCollection(MatchCollection matchCollection) + { + throw new NotImplementedException(); + } private static ParsedEpisodeInfo ParseMatchCollection(MatchCollection matchCollection) { var seriesName = matchCollection[0].Groups["title"].Value.Replace('.', ' ').Replace('_', ' '); diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index c47c5f37a..627bd6df1 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -8,6 +8,8 @@ using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; +using System; namespace NzbDrone.Core.Parser { @@ -20,13 +22,20 @@ namespace NzbDrone.Core.Parser RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable episodeIds); List GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null); ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null); + + // Music stuff here + LocalTrack GetLocalTrack(string filename, Artist artist); + LocalTrack GetLocalTrack(string filename, Artist artist, ParsedTrackInfo folderInfo, bool sceneSource); + } public class ParsingService : IParsingService { private readonly IEpisodeService _episodeService; private readonly ISeriesService _seriesService; - // private readonly ISceneMappingService _sceneMappingService; + + private readonly IArtistService _artistService; + private readonly ITrackService _trackService; private readonly Logger _logger; public ParsingService(IEpisodeService episodeService, @@ -474,5 +483,89 @@ namespace NzbDrone.Core.Parser return result; } + + public LocalTrack GetLocalTrack(string filename, Artist artist) + { + return GetLocalTrack(filename, artist, null, false); + } + + public LocalTrack GetLocalTrack(string filename, Artist artist, ParsedTrackInfo folderInfo, bool sceneSource) + { + ParsedTrackInfo parsedTrackInfo; + + if (folderInfo != null) + { + parsedTrackInfo = folderInfo.JsonClone(); + parsedTrackInfo.Quality = QualityParser.ParseQuality(Path.GetFileName(filename)); + } + + else + { + parsedTrackInfo = Parser.ParseMusicPath(filename); + } + + if (parsedTrackInfo == null || parsedTrackInfo.IsPossibleSpecialEpisode) + { + var title = Path.GetFileNameWithoutExtension(filename); + //var specialEpisodeInfo = ParseSpecialEpisodeTitle(title, series); + + //if (specialEpisodeInfo != null) + //{ + // parsedTrackInfo = specialEpisodeInfo; + //} + } + + if (parsedTrackInfo == null) + { + if (MediaFileExtensions.Extensions.Contains(Path.GetExtension(filename))) + { + _logger.Warn("Unable to parse track info from path {0}", filename); + } + + return null; + } + + var tracks = GetTracks(parsedTrackInfo, artist, sceneSource); + + return new LocalTrack + { + Artist = artist, + Quality = parsedTrackInfo.Quality, + Tracks = tracks, + Path = filename, + ParsedTrackInfo = parsedTrackInfo, + ExistingFile = artist.Path.IsParentPath(filename) + }; + } + + private List GetTracks(ParsedTrackInfo parsedTrackInfo, Artist artist, bool sceneSource) + { + throw new NotImplementedException(); + + /*if (parsedTrackInfo.FullSeason) // IF Album + { + return _trackService.GetTracksByAlbumTitle(artist.Id, parsedTrackInfo.AlbumTitle); + } + + if (parsedTrackInfo.IsDaily) + { + if (artist.SeriesType == SeriesTypes.Standard) + { + _logger.Warn("Found daily-style episode for non-daily series: {0}.", series); + return new List(); + } + + var episodeInfo = GetDailyEpisode(artist, parsedTrackInfo.AirDate, searchCriteria); + + if (episodeInfo != null) + { + return new List { episodeInfo }; + } + + return new List(); + } + + return GetStandardEpisodes(artist, parsedTrackInfo, sceneSource, searchCriteria);*/ + } } } \ No newline at end of file diff --git a/src/UI/AddSeries/AddSeriesCollection.js b/src/UI/AddSeries/AddSeriesCollection.js index 5be24d3a7..d81094eca 100644 --- a/src/UI/AddSeries/AddSeriesCollection.js +++ b/src/UI/AddSeries/AddSeriesCollection.js @@ -1,10 +1,10 @@ var Backbone = require('backbone'); -var SeriesModel = require('../Series/SeriesModel'); +var ArtistModel = require('../Artist/ArtistModel'); var _ = require('underscore'); module.exports = Backbone.Collection.extend({ - url : window.NzbDrone.ApiRoot + '/series/lookup', - model : SeriesModel, + url : window.NzbDrone.ApiRoot + '/artist/lookup', + model : ArtistModel, parse : function(response) { var self = this; @@ -15,7 +15,9 @@ module.exports = Backbone.Collection.extend({ if (self.unmappedFolderModel) { model.path = self.unmappedFolderModel.get('folder').path; } + console.log('model: ', model); }); + console.log('response: ', response); // Note: this gets called after api responds with artist model return response; } diff --git a/src/UI/AddSeries/AddSeriesLayoutTemplate.hbs b/src/UI/AddSeries/AddSeriesLayoutTemplate.hbs index ab6e5e6c0..69b78aedb 100644 --- a/src/UI/AddSeries/AddSeriesLayoutTemplate.hbs +++ b/src/UI/AddSeries/AddSeriesLayoutTemplate.hbs @@ -3,9 +3,9 @@
- +
diff --git a/src/UI/AddSeries/AddSeriesView.js b/src/UI/AddSeries/AddSeriesView.js index 3cda1db63..0c128eb40 100644 --- a/src/UI/AddSeries/AddSeriesView.js +++ b/src/UI/AddSeries/AddSeriesView.js @@ -28,6 +28,7 @@ module.exports = Marionette.Layout.extend({ initialize : function(options) { this.isExisting = options.isExisting; this.collection = new AddSeriesCollection(); + console.log('this.collection:', this.collection); if (this.isExisting) { this.collection.unmappedFolderModel = this.model; diff --git a/src/UI/AddSeries/AddSeriesViewTemplate.hbs b/src/UI/AddSeries/AddSeriesViewTemplate.hbs index 18ed2ffb3..56d69b616 100644 --- a/src/UI/AddSeries/AddSeriesViewTemplate.hbs +++ b/src/UI/AddSeries/AddSeriesViewTemplate.hbs @@ -11,7 +11,7 @@ {{#if folder}} {{else}} - + {{/if}} diff --git a/src/UI/AddSeries/EmptyViewTemplate.hbs b/src/UI/AddSeries/EmptyViewTemplate.hbs index 60346f0c0..8c2b29e22 100644 --- a/src/UI/AddSeries/EmptyViewTemplate.hbs +++ b/src/UI/AddSeries/EmptyViewTemplate.hbs @@ -1,3 +1,3 @@
- You can also search by tvdbid using the tvdb: prefixes. + You can also search by iTunes using the itunes: prefixes.
diff --git a/src/UI/AddSeries/ErrorViewTemplate.hbs b/src/UI/AddSeries/ErrorViewTemplate.hbs index 163779c26..c0b1e3673 100644 --- a/src/UI/AddSeries/ErrorViewTemplate.hbs +++ b/src/UI/AddSeries/ErrorViewTemplate.hbs @@ -3,5 +3,5 @@ There was an error searching for '{{term}}'. - If the series title contains non-alphanumeric characters try removing them, otherwise try your search again later. + If the artist name contains non-alphanumeric characters try removing them, otherwise try your search again later. diff --git a/src/UI/AddSeries/Existing/AddExistingSeriesCollectionViewTemplate.hbs b/src/UI/AddSeries/Existing/AddExistingSeriesCollectionViewTemplate.hbs index d613a52d4..6dcb1ecc2 100644 --- a/src/UI/AddSeries/Existing/AddExistingSeriesCollectionViewTemplate.hbs +++ b/src/UI/AddSeries/Existing/AddExistingSeriesCollectionViewTemplate.hbs @@ -1,5 +1,5 @@
- Loading search results from TheTVDB for your series, this may take a few minutes. + Loading search results from iTunes for your artists, this may take a few minutes.
\ No newline at end of file diff --git a/src/UI/AddSeries/SearchResultView.js b/src/UI/AddSeries/SearchResultView.js index 817ab78ea..348413a35 100644 --- a/src/UI/AddSeries/SearchResultView.js +++ b/src/UI/AddSeries/SearchResultView.js @@ -6,7 +6,8 @@ var Marionette = require('marionette'); var Profiles = require('../Profile/ProfileCollection'); var RootFolders = require('./RootFolders/RootFolderCollection'); var RootFolderLayout = require('./RootFolders/RootFolderLayout'); -var SeriesCollection = require('../Series/SeriesCollection'); +//var SeriesCollection = require('../Series/SeriesCollection'); +var SeriesCollection = require('../Artist/ArtistCollection'); var Config = require('../Config'); var Messenger = require('../Shared/Messenger'); var AsValidatedView = require('../Mixins/AsValidatedView'); @@ -210,6 +211,7 @@ var view = Marionette.ItemView.extend({ }); promise.done(function() { + console.log('[SearchResultView] _addSeries promise resolve:', self.model); SeriesCollection.add(self.model); self.close(); diff --git a/src/UI/AddSeries/SearchResultViewTemplate.hbs b/src/UI/AddSeries/SearchResultViewTemplate.hbs index 2eafdf2b0..bccef713d 100644 --- a/src/UI/AddSeries/SearchResultViewTemplate.hbs +++ b/src/UI/AddSeries/SearchResultViewTemplate.hbs @@ -10,6 +10,7 @@

{{titleWithYear}} + {{artistName}} {{network}} @@ -41,8 +42,6 @@ - -

@@ -52,10 +51,9 @@ {{> ProfileSelectionPartial profiles}} -
- - {{> SeriesTypeSelectionPartial}} -
+
diff --git a/src/UI/Artist/ArtistCollection.js b/src/UI/Artist/ArtistCollection.js new file mode 100644 index 000000000..f9908032d --- /dev/null +++ b/src/UI/Artist/ArtistCollection.js @@ -0,0 +1,124 @@ +var _ = require('underscore'); +var Backbone = require('backbone'); +var PageableCollection = require('backbone.pageable'); +var ArtistModel = require('./ArtistModel'); +var ApiData = require('../Shared/ApiData'); +var AsFilteredCollection = require('../Mixins/AsFilteredCollection'); +var AsSortedCollection = require('../Mixins/AsSortedCollection'); +var AsPersistedStateCollection = require('../Mixins/AsPersistedStateCollection'); +var moment = require('moment'); +require('../Mixins/backbone.signalr.mixin'); + +var Collection = PageableCollection.extend({ + url : window.NzbDrone.ApiRoot + '/artist', + model : ArtistModel, + tableName : 'artist', + + state : { + sortKey : 'sortTitle', + order : -1, + pageSize : 100000, + secondarySortKey : 'sortTitle', + secondarySortOrder : -1 + }, + + mode : 'client', + + save : function() { + var self = this; + + var proxy = _.extend(new Backbone.Model(), { + id : '', + + url : self.url + '/editor', + + toJSON : function() { + return self.filter(function(model) { + return model.edited; + }); + } + }); + + this.listenTo(proxy, 'sync', function(proxyModel, models) { + this.add(models, { merge : true }); + this.trigger('save', this); + }); + + return proxy.save(); + }, + + filterModes : { + 'all' : [ + null, + null + ], + 'continuing' : [ + 'status', + 'continuing' + ], + 'ended' : [ + 'status', + 'ended' + ], + 'monitored' : [ + 'monitored', + true + ], + 'missing' : [ + null, + null, + function(model) { return model.get('episodeCount') !== model.get('episodeFileCount'); } + ] + }, + + sortMappings : { + title : { + sortKey : 'sortTitle' + }, + + artistName: { + sortKey : 'artistName' + }, + + nextAiring : { + sortValue : function(model, attr, order) { + var nextAiring = model.get(attr); + + if (nextAiring) { + return moment(nextAiring).unix(); + } + + if (order === 1) { + return 0; + } + + return Number.MAX_VALUE; + } + }, + + percentOfEpisodes : { + sortValue : function(model, attr) { + var percentOfEpisodes = model.get(attr); + var episodeCount = model.get('episodeCount'); + + return percentOfEpisodes + episodeCount / 1000000; + } + }, + + path : { + sortValue : function(model) { + var path = model.get('path'); + + return path.toLowerCase(); + } + } + } +}); + +Collection = AsFilteredCollection.call(Collection); +Collection = AsSortedCollection.call(Collection); +Collection = AsPersistedStateCollection.call(Collection); + +var data = ApiData.get('series'); // 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 new file mode 100644 index 000000000..838018670 --- /dev/null +++ b/src/UI/Artist/ArtistController.js @@ -0,0 +1,37 @@ +var NzbDroneController = require('../Shared/NzbDroneController'); +var AppLayout = require('../AppLayout'); +var ArtistCollection = require('./ArtistCollection'); +var SeriesIndexLayout = require('./Index/SeriesIndexLayout'); +var SeriesDetailsLayout = require('../Series/Details/SeriesDetailsLayout'); + +module.exports = NzbDroneController.extend({ + _originalInit : NzbDroneController.prototype.initialize, + + initialize : function() { + this.route('', this.series); + this.route('artist', this.series); + this.route('artist/:query', this.seriesDetails); + + this._originalInit.apply(this, arguments); + }, + + artist : function() { + this.setTitle('Lidarr'); + this.setArtistName('Lidarr'); + this.showMainRegion(new SeriesIndexLayout()); + }, + + seriesDetails : function(query) { + var artists = ArtistCollection.where({ artistNameSlug : query }); + console.log('seriesDetails, artists: ', artists); + if (artists.length !== 0) { + var targetSeries = artists[0]; + console.log("[ArtistController] targetSeries: ", targetSeries); + this.setTitle(targetSeries.get('title')); + this.setArtistName(targetSeries.get('artistName')); + this.showMainRegion(new SeriesDetailsLayout({ model : targetSeries })); + } else { + this.showNotFound(); + } + } +}); \ No newline at end of file diff --git a/src/UI/Artist/ArtistModel.js b/src/UI/Artist/ArtistModel.js new file mode 100644 index 000000000..70763dac2 --- /dev/null +++ b/src/UI/Artist/ArtistModel.js @@ -0,0 +1,31 @@ +var Backbone = require('backbone'); +var _ = require('underscore'); + +module.exports = Backbone.Model.extend({ + urlRoot : window.NzbDrone.ApiRoot + '/artist', + + defaults : { + episodeFileCount : 0, + episodeCount : 0, + isExisting : false, + status : 0 + }, + + setAlbumsMonitored : function(seasonNumber) { + _.each(this.get('albums'), function(album) { + if (season.seasonNumber === seasonNumber) { + album.monitored = !album.monitored; + } + }); + }, + + setAlbumPass : function(seasonNumber) { + _.each(this.get('albums'), function(album) { + if (album.seasonNumber >= seasonNumber) { + album.monitored = true; + } else { + album.monitored = false; + } + }); + } +}); \ No newline at end of file diff --git a/src/UI/Handlebars/Helpers/Series.js b/src/UI/Handlebars/Helpers/Series.js index 2c8a96bed..ff3ffd7f1 100644 --- a/src/UI/Handlebars/Helpers/Series.js +++ b/src/UI/Handlebars/Helpers/Series.js @@ -71,7 +71,7 @@ Handlebars.registerHelper('seasonCountHelper', function() { return new Handlebars.SafeString('{0} Seasons'.format(seasonCount)); }); -Handlebars.registerHelper('titleWithYear', function() { +/*Handlebars.registerHelper('titleWithYear', function() { if (this.title.endsWith(' ({0})'.format(this.year))) { return this.title; } @@ -81,4 +81,4 @@ Handlebars.registerHelper('titleWithYear', function() { } return new Handlebars.SafeString('{0} ({1})'.format(this.title, this.year)); -}); +});*/ diff --git a/src/UI/Series/Details/SeriesDetailsLayout.js b/src/UI/Series/Details/SeriesDetailsLayout.js index f33cb0414..e3a0294e9 100644 --- a/src/UI/Series/Details/SeriesDetailsLayout.js +++ b/src/UI/Series/Details/SeriesDetailsLayout.js @@ -4,7 +4,7 @@ var vent = require('vent'); var reqres = require('../../reqres'); var Marionette = require('marionette'); var Backbone = require('backbone'); -var SeriesCollection = require('../SeriesCollection'); +var ArtistCollection = require('../../Artist/ArtistCollection'); var EpisodeCollection = require('../EpisodeCollection'); var EpisodeFileCollection = require('../EpisodeFileCollection'); var SeasonCollection = require('../SeasonCollection'); @@ -45,7 +45,7 @@ module.exports = Marionette.Layout.extend({ }, initialize : function() { - this.seriesCollection = SeriesCollection.clone(); + this.seriesCollection = ArtistCollection.clone(); this.seriesCollection.shadowCollection.bindSignalR(); this.listenTo(this.model, 'change:monitored', this._setMonitoredState); @@ -155,6 +155,7 @@ module.exports = Marionette.Layout.extend({ }, _seriesSearch : function() { + console.log('_seriesSearch:', this.model); CommandController.Execute('seriesSearch', { name : 'seriesSearch', seriesId : this.model.id