diff --git a/gulp/less.js b/gulp/less.js index 0baf2eb31..e250a79eb 100644 --- a/gulp/less.js +++ b/gulp/less.js @@ -16,10 +16,10 @@ gulp.task('less', function() { paths.src.content + 'bootstrap.less', paths.src.content + 'theme.less', paths.src.content + 'overrides.less', - paths.src.root + 'Series/series.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 + 'AddSeries/addSeries.less', paths.src.root + 'AddArtist/addArtist.less', paths.src.root + 'Calendar/calendar.less', paths.src.root + 'Cells/cells.less', diff --git a/src/NzbDrone.Api/Albums/AlbumModule.cs b/src/NzbDrone.Api/Albums/AlbumModule.cs new file mode 100644 index 000000000..f7e65d1ad --- /dev/null +++ b/src/NzbDrone.Api/Albums/AlbumModule.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using NzbDrone.Api.REST; +using NzbDrone.Core.Music; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.SignalR; + +namespace NzbDrone.Api.Albums +{ + public class AlbumModule : AlbumModuleWithSignalR + { + public AlbumModule(IArtistService artistService, + IAlbumService albumService, + IQualityUpgradableSpecification qualityUpgradableSpecification, + IBroadcastSignalRMessage signalRBroadcaster) + : base(albumService, artistService, qualityUpgradableSpecification, signalRBroadcaster) + { + GetResourceAll = GetAlbums; + UpdateResource = SetMonitored; + } + + private List GetAlbums() + { + if (!Request.Query.ArtistId.HasValue) + { + throw new BadRequestException("artistId is missing"); + } + + var artistId = (int)Request.Query.ArtistId; + + var resources = MapToResource(_albumService.GetAlbumsByArtist(artistId), false); + + return resources; + } + + private void SetMonitored(AlbumResource albumResource) + { + _albumService.SetAlbumMonitored(albumResource.Id, albumResource.Monitored); + } + } +} diff --git a/src/NzbDrone.Api/Albums/AlbumModuleWithSignalR.cs b/src/NzbDrone.Api/Albums/AlbumModuleWithSignalR.cs new file mode 100644 index 000000000..df6aba322 --- /dev/null +++ b/src/NzbDrone.Api/Albums/AlbumModuleWithSignalR.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using NzbDrone.Common.Extensions; +using NzbDrone.Api.TrackFiles; +using NzbDrone.Api.Music; +using NzbDrone.Core.Datastore.Events; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Download; +using NzbDrone.Core.MediaFiles.Events; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Music; +using NzbDrone.SignalR; + +namespace NzbDrone.Api.Albums +{ + public abstract class AlbumModuleWithSignalR : NzbDroneRestModuleWithSignalR + { + protected readonly IAlbumService _albumService; + protected readonly IArtistService _artistService; + protected readonly IQualityUpgradableSpecification _qualityUpgradableSpecification; + + protected AlbumModuleWithSignalR(IAlbumService albumService, + IArtistService artistService, + IQualityUpgradableSpecification qualityUpgradableSpecification, + IBroadcastSignalRMessage signalRBroadcaster) + : base(signalRBroadcaster) + { + _albumService = albumService; + _artistService = artistService; + _qualityUpgradableSpecification = qualityUpgradableSpecification; + + GetResourceById = GetAlbum; + } + + protected AlbumModuleWithSignalR(IAlbumService albumService, + IArtistService artistService, + IQualityUpgradableSpecification qualityUpgradableSpecification, + IBroadcastSignalRMessage signalRBroadcaster, + string resource) + : base(signalRBroadcaster, resource) + { + _albumService = albumService; + _artistService = artistService; + _qualityUpgradableSpecification = qualityUpgradableSpecification; + + GetResourceById = GetAlbum; + } + + protected AlbumResource GetAlbum(int id) + { + var album = _albumService.GetAlbum(id); + var resource = MapToResource(album, true); + return resource; + } + + protected AlbumResource MapToResource(Album album, bool includeArtist) + { + var resource = album.ToResource(); + + if (includeArtist) + { + var artist = album.Artist ?? _artistService.GetArtist(album.ArtistId); + + if (includeArtist) + { + resource.Artist = artist.ToResource(); + } + } + + return resource; + } + + protected List MapToResource(List albums, bool includeArtist) + { + var result = albums.ToResource(); + + if (includeArtist) + { + var artistDict = new Dictionary(); + for (var i = 0; i < albums.Count; i++) + { + var album = albums[i]; + var resource = result[i]; + + var artist = album.Artist ?? artistDict.GetValueOrDefault(albums[i].ArtistId) ?? _artistService.GetArtist(albums[i].ArtistId); + artistDict[artist.Id] = artist; + + if (includeArtist) + { + resource.Artist = artist.ToResource(); + } + } + } + + return result; + } + + //TODO: Implement Track or Album Grabbed/Dowloaded Events + + //public void Handle(TrackGrabbedEvent message) + //{ + // foreach (var track in message.Track.Tracks) + // { + // var resource = track.ToResource(); + // resource.Grabbed = true; + + // BroadcastResourceChange(ModelAction.Updated, resource); + // } + //} + + //public void Handle(TrackDownloadedEvent message) + //{ + // foreach (var album in message.Album.Albums) + // { + // BroadcastResourceChange(ModelAction.Updated, album.Id); + // } + //} + } +} diff --git a/src/NzbDrone.Api/Music/AlbumResource.cs b/src/NzbDrone.Api/Albums/AlbumResource.cs similarity index 52% rename from src/NzbDrone.Api/Music/AlbumResource.cs rename to src/NzbDrone.Api/Albums/AlbumResource.cs index 5ae4b2efa..6c92ceaa6 100644 --- a/src/NzbDrone.Api/Music/AlbumResource.cs +++ b/src/NzbDrone.Api/Albums/AlbumResource.cs @@ -3,33 +3,47 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using NzbDrone.Api.REST; +using NzbDrone.Api.Music; +using NzbDrone.Core.MediaCover; -namespace NzbDrone.Api.Music +namespace NzbDrone.Api.Albums { - public class AlbumResource + public class AlbumResource : RestResource { - public string AlbumId { get; set; } - public string AlbumName { get; set; } + + public string Title { get; set; } + public string Label { get; set; } public bool Monitored { get; set; } + public string Path { get; set; } + public int ProfileId { get; set; } + public Ratings Ratings { get; set; } public DateTime ReleaseDate { get; set; } public List Genres { get; set; } - public string ArtworkUrl { get; set; } + public ArtistResource Artist { get; set; } + public List Images { get; set; } } public static class AlbumResourceMapper { - public static AlbumResource ToResource(this Album model) + public static AlbumResource ToResource(this Core.Music.Album model) { if (model == null) return null; return new AlbumResource { - AlbumId = model.ForeignAlbumId, + Id = model.Id, + Label = model.Label, + Path = model.Path, + ProfileId = model.ProfileId, Monitored = model.Monitored, ReleaseDate = model.ReleaseDate, - AlbumName = model.Title, Genres = model.Genres, + Title = model.Title, + Images = model.Images, + Ratings = model.Ratings, + //Genres = model.Genres, //ArtworkUrl = model.ArtworkUrl }; } @@ -38,13 +52,19 @@ namespace NzbDrone.Api.Music { if (resource == null) return null; - return new Album + return new Core.Music.Album { - ForeignAlbumId = resource.AlbumId, + Id = resource.Id, + Label = resource.Label, + Path = resource.Path, Monitored = resource.Monitored, + ProfileId = resource.ProfileId, ReleaseDate = resource.ReleaseDate, - Title = resource.AlbumName, Genres = resource.Genres, + Title = resource.Title, + Images = resource.Images, + Ratings = resource.Ratings, + //Genres = resource.Genres, //ArtworkUrl = resource.ArtworkUrl }; } diff --git a/src/NzbDrone.Api/Music/ArtistModule.cs b/src/NzbDrone.Api/Music/ArtistModule.cs index 4ee6898b9..de2337c9c 100644 --- a/src/NzbDrone.Api/Music/ArtistModule.cs +++ b/src/NzbDrone.Api/Music/ArtistModule.cs @@ -1,4 +1,7 @@ -using FluentValidation; +using System; +using System.Collections.Generic; +using System.Linq; +using FluentValidation; using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.MediaCover; @@ -7,12 +10,10 @@ using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Music; using NzbDrone.Core.Music.Events; -using NzbDrone.Core.SeriesStats; +using NzbDrone.Core.ArtistStats; using NzbDrone.Core.Validation; using NzbDrone.Core.Validation.Paths; using NzbDrone.SignalR; -using System; -using System.Collections.Generic; namespace NzbDrone.Api.Music { @@ -26,14 +27,14 @@ namespace NzbDrone.Api.Music //IHandle { private readonly IArtistService _artistService; - private readonly IAddArtistService _addSeriesService; - private readonly ISeriesStatisticsService _seriesStatisticsService; + private readonly IAddArtistService _addArtistService; + private readonly IArtistStatisticsService _artistStatisticsService; private readonly IMapCoversToLocal _coverMapper; public ArtistModule(IBroadcastSignalRMessage signalRBroadcaster, IArtistService artistService, - IAddArtistService addSeriesService, - ISeriesStatisticsService seriesStatisticsService, + IAddArtistService addArtistService, + IArtistStatisticsService artistStatisticsService, IMapCoversToLocal coverMapper, RootFolderValidator rootFolderValidator, ArtistPathValidator seriesPathValidator, @@ -45,8 +46,8 @@ namespace NzbDrone.Api.Music : base(signalRBroadcaster) { _artistService = artistService; - _addSeriesService = addSeriesService; - _seriesStatisticsService = seriesStatisticsService; + _addArtistService = addArtistService; + _artistStatisticsService = artistStatisticsService; _coverMapper = coverMapper; @@ -88,7 +89,7 @@ namespace NzbDrone.Api.Music var resource = artist.ToResource(); MapCoversToLocal(resource); - //FetchAndLinkSeriesStatistics(resource); + FetchAndLinkArtistStatistics(resource); //PopulateAlternateTitles(resource); return resource; @@ -96,10 +97,11 @@ namespace NzbDrone.Api.Music private List AllArtist() { - //var seriesStats = _seriesStatisticsService.SeriesStatistics(); + var artistStats = _artistStatisticsService.ArtistStatistics(); var artistResources = _artistService.GetAllArtists().ToResource(); + MapCoversToLocal(artistResources.ToArray()); - //LinkSeriesStatistics(seriesResources, seriesStats); + LinkArtistStatistics(artistResources, artistStats); //PopulateAlternateTitles(seriesResources); return artistResources; @@ -109,7 +111,7 @@ namespace NzbDrone.Api.Music { var model = artistResource.ToModel(); - return _addSeriesService.AddArtist(model).Id; + return _addArtistService.AddArtist(model).Id; } private void UpdatArtist(ArtistResource artistResource) @@ -142,6 +144,44 @@ namespace NzbDrone.Api.Music } } + private void FetchAndLinkArtistStatistics(ArtistResource resource) + { + LinkArtistStatistics(resource, _artistStatisticsService.ArtistStatistics(resource.Id)); + } + + + private void LinkArtistStatistics(List resources, List artistStatistics) + { + var dictArtistStats = artistStatistics.ToDictionary(v => v.ArtistId); + + foreach (var artist in resources) + { + var stats = dictArtistStats.GetValueOrDefault(artist.Id); + if (stats == null) continue; + + LinkArtistStatistics(artist, stats); + } + } + + private void LinkArtistStatistics(ArtistResource resource, ArtistStatistics artistStatistics) + { + resource.TotalTrackCount = artistStatistics.TotalTrackCount; + resource.TrackCount = artistStatistics.TrackCount; + resource.TrackFileCount = artistStatistics.TrackFileCount; + resource.SizeOnDisk = artistStatistics.SizeOnDisk; + resource.AlbumCount = artistStatistics.AlbumCount; + + //if (artistStatistics.AlbumStatistics != null) + //{ + // var dictSeasonStats = artistStatistics.SeasonStatistics.ToDictionary(v => v.SeasonNumber); + + // foreach (var album in resource.Albums) + // { + // album.Statistics = dictSeasonStats.GetValueOrDefault(album.Id).ToResource(); + // } + //} + } + public void Handle(TrackImportedEvent message) { BroadcastResourceChange(ModelAction.Updated, message.ImportedTrack.Id); // TODO: Ensure we can pass DB ID instead of Metadata ID (SpotifyID) diff --git a/src/NzbDrone.Api/Music/ArtistResource.cs b/src/NzbDrone.Api/Music/ArtistResource.cs index 4405c0d38..784755d64 100644 --- a/src/NzbDrone.Api/Music/ArtistResource.cs +++ b/src/NzbDrone.Api/Music/ArtistResource.cs @@ -1,7 +1,8 @@ using NzbDrone.Api.REST; using NzbDrone.Api.Series; +using NzbDrone.Api.Albums; using NzbDrone.Core.MediaCover; -using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; using System; using System.Collections.Generic; using System.Linq; @@ -23,19 +24,10 @@ namespace NzbDrone.Api.Music public string MBId { get; set; } public int TADBId { get; set; } public int DiscogsId { get; set; } - public string AMId { get; set; } + public string AllMusicId { get; set; } public string Overview { get; set; } - public int AlbumCount - { - get - { - if (Albums == null) return 0; - - return Albums.Where(s => s.AlbumId != "").Count(); // TODO: CHeck this condition - } - } - + public int? AlbumCount{ get; set; } public int? TotalTrackCount { get; set; } public int? TrackCount { get; set; } public int? TrackFileCount { get; set; } @@ -43,6 +35,7 @@ namespace NzbDrone.Api.Music //public SeriesStatusType Status { get; set; } public List Images { get; set; } + public List Members { get; set; } public string RemotePoster { get; set; } public List Albums { get; set; } @@ -61,7 +54,7 @@ namespace NzbDrone.Api.Music public List Genres { get; set; } public HashSet Tags { get; set; } public DateTime Added { get; set; } - public AddSeriesOptions AddOptions { get; set; } + public AddArtistOptions AddOptions { get; set; } public Ratings Ratings { get; set; } public string NameSlug { get; set; } } @@ -78,14 +71,14 @@ namespace NzbDrone.Api.Music MBId = model.MBId, TADBId = model.TADBId, DiscogsId = model.DiscogsId, - AMId = model.AMId, + AllMusicId = model.AMId, Name = model.Name, //AlternateTitles //SortTitle = resource.SortTitle, - //TotalEpisodeCount - //EpisodeCount - //EpisodeFileCount + //TotalTrackCount + //TrackCount + //TrackFileCount //SizeOnDisk //Status = resource.Status, Overview = model.Overview, @@ -94,6 +87,7 @@ namespace NzbDrone.Api.Music //Network = resource.Network, //AirTime = resource.AirTime, Images = model.Images, + Members = model.Members, Albums = model.Albums.ToResource(), //Year = resource.Year, @@ -119,7 +113,7 @@ namespace NzbDrone.Api.Music Tags = model.Tags, Added = model.Added, AddOptions = model.AddOptions, - //Ratings = resource.Ratings + Ratings = model.Ratings, }; } @@ -137,10 +131,10 @@ namespace NzbDrone.Api.Music MBId = resource.MBId, TADBId = resource.TADBId, DiscogsId = resource.DiscogsId, - AMId = resource.AMId, + AMId = resource.AllMusicId, //TODO change model and DB to AllMusic instead of AM //TotalEpisodeCount - //EpisodeCount - //EpisodeFileCount + //TrackCount + //TrackFileCount //SizeOnDisk //Status = resource.Status, Overview = resource.Overview, @@ -149,6 +143,7 @@ namespace NzbDrone.Api.Music //Network = resource.Network, //AirTime = resource.AirTime, Images = resource.Images, + Members = resource.Members, Albums = resource.Albums.ToModel(), //Year = resource.Year, @@ -166,7 +161,7 @@ namespace NzbDrone.Api.Music Tags = resource.Tags, Added = resource.Added, AddOptions = resource.AddOptions, - //Ratings = resource.Ratings + Ratings = resource.Ratings }; } diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 877563c2a..3d504af28 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -88,6 +88,7 @@ Properties\SharedAssemblyInfo.cs + @@ -106,6 +107,7 @@ + @@ -118,7 +120,7 @@ - + diff --git a/src/NzbDrone.Api/Tracks/TrackResource.cs b/src/NzbDrone.Api/Tracks/TrackResource.cs index f12a9948c..34915d2d7 100644 --- a/src/NzbDrone.Api/Tracks/TrackResource.cs +++ b/src/NzbDrone.Api/Tracks/TrackResource.cs @@ -14,7 +14,8 @@ namespace NzbDrone.Api.Tracks public int ArtistId { get; set; } public int TrackFileId { get; set; } public int AlbumId { get; set; } - //public int EpisodeNumber { get; set; } + public bool Explicit { get; set; } + public int TrackNumber { get; set; } public string Title { get; set; } //public string AirDate { get; set; } //public DateTime? AirDateUtc { get; set; } @@ -30,6 +31,7 @@ namespace NzbDrone.Api.Tracks //public bool UnverifiedSceneNumbering { get; set; } //public string SeriesTitle { get; set; } public ArtistResource Artist { get; set; } + public Ratings Ratings { get; set; } //Hiding this so people don't think its usable (only used to set the initial state) [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] @@ -49,7 +51,8 @@ namespace NzbDrone.Api.Tracks ArtistId = model.ArtistId, TrackFileId = model.TrackFileId, AlbumId = model.AlbumId, - //EpisodeNumber = model.EpisodeNumber, + Explicit = model.Explicit, + TrackNumber = model.TrackNumber, Title = model.Title, //AirDate = model.AirDate, //AirDateUtc = model.AirDateUtc, @@ -58,6 +61,7 @@ namespace NzbDrone.Api.Tracks HasFile = model.HasFile, Monitored = model.Monitored, + Ratings = model.Ratings, //AbsoluteEpisodeNumber = model.AbsoluteEpisodeNumber, //SceneAbsoluteEpisodeNumber = model.SceneAbsoluteEpisodeNumber, //SceneEpisodeNumber = model.SceneEpisodeNumber, diff --git a/src/NzbDrone.Core/ArtistStats/AlbumStatistics.cs b/src/NzbDrone.Core/ArtistStats/AlbumStatistics.cs new file mode 100644 index 000000000..ec291a43f --- /dev/null +++ b/src/NzbDrone.Core/ArtistStats/AlbumStatistics.cs @@ -0,0 +1,15 @@ +using System; +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Core.ArtistStats +{ + public class AlbumStatistics : ResultSet + { + public int ArtistId { get; set; } + public int AlbumId { get; set; } + public int TrackFileCount { get; set; } + public int TrackCount { get; set; } + public int TotalTrackCount { get; set; } + public long SizeOnDisk { get; set; } + } +} diff --git a/src/NzbDrone.Core/ArtistStats/ArtistStatistics.cs b/src/NzbDrone.Core/ArtistStats/ArtistStatistics.cs new file mode 100644 index 000000000..3989393f4 --- /dev/null +++ b/src/NzbDrone.Core/ArtistStats/ArtistStatistics.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Core.ArtistStats +{ + public class ArtistStatistics : ResultSet + { + public int ArtistId { get; set; } + public int AlbumCount { get; set; } + public int TrackFileCount { get; set; } + public int TrackCount { get; set; } + public int TotalTrackCount { get; set; } + public long SizeOnDisk { get; set; } + public List AlbumStatistics { get; set; } + } +} diff --git a/src/NzbDrone.Core/ArtistStats/ArtistStatisticsRepository.cs b/src/NzbDrone.Core/ArtistStats/ArtistStatisticsRepository.cs new file mode 100644 index 000000000..c67af6921 --- /dev/null +++ b/src/NzbDrone.Core/ArtistStats/ArtistStatisticsRepository.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Text; +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Core.ArtistStats +{ + public interface IArtistStatisticsRepository + { + List ArtistStatistics(); + List ArtistStatistics(int artistId); + } + + public class ArtistStatisticsRepository : IArtistStatisticsRepository + { + private readonly IMainDatabase _database; + + public ArtistStatisticsRepository(IMainDatabase database) + { + _database = database; + } + + public List ArtistStatistics() + { + var mapper = _database.GetDataMapper(); + + mapper.AddParameter("currentDate", DateTime.UtcNow); + + var sb = new StringBuilder(); + sb.AppendLine(GetSelectClause()); + sb.AppendLine(GetTrackFilesJoin()); + sb.AppendLine(GetGroupByClause()); + var queryText = sb.ToString(); + + return mapper.Query(queryText); + } + + public List ArtistStatistics(int artistId) + { + var mapper = _database.GetDataMapper(); + + mapper.AddParameter("currentDate", DateTime.UtcNow); + mapper.AddParameter("artistId", artistId); + + var sb = new StringBuilder(); + sb.AppendLine(GetSelectClause()); + sb.AppendLine(GetTrackFilesJoin()); + sb.AppendLine("WHERE Tracks.ArtistId = @artistId"); + sb.AppendLine(GetGroupByClause()); + var queryText = sb.ToString(); + + return mapper.Query(queryText); + } + + private string GetSelectClause() + { + return @"SELECT Tracks.*, SUM(TrackFiles.Size) as SizeOnDisk FROM + (SELECT + Tracks.ArtistId, + Tracks.AlbumId, + SUM(CASE WHEN TrackFileId > 0 THEN 1 ELSE 0 END) AS TotalTrackCount, + SUM(CASE WHEN Monitored = 1 OR TrackFileId > 0 THEN 1 ELSE 0 END) AS TrackCount, + SUM(CASE WHEN TrackFileId > 0 THEN 1 ELSE 0 END) AS TrackFileCount + FROM Tracks + GROUP BY Tracks.ArtistId, Tracks.AlbumId) as Tracks"; + } + + private string GetGroupByClause() + { + return "GROUP BY Tracks.ArtistId, Tracks.AlbumId"; + } + + private string GetTrackFilesJoin() + { + return @"LEFT OUTER JOIN TrackFiles + ON TrackFiles.ArtistId = Tracks.ArtistId + AND TrackFiles.AlbumId = Tracks.AlbumId"; + } + } +} diff --git a/src/NzbDrone.Core/ArtistStats/ArtistStatisticsService.cs b/src/NzbDrone.Core/ArtistStats/ArtistStatisticsService.cs new file mode 100644 index 000000000..f352ff3f5 --- /dev/null +++ b/src/NzbDrone.Core/ArtistStats/ArtistStatisticsService.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; + +namespace NzbDrone.Core.ArtistStats +{ + public interface IArtistStatisticsService + { + List ArtistStatistics(); + ArtistStatistics ArtistStatistics(int artistId); + } + + public class ArtistStatisticsService : IArtistStatisticsService + { + private readonly IArtistStatisticsRepository _artistStatisticsRepository; + + public ArtistStatisticsService(IArtistStatisticsRepository artistStatisticsRepository) + { + _artistStatisticsRepository = artistStatisticsRepository; + } + + public List ArtistStatistics() + { + var albumStatistics = _artistStatisticsRepository.ArtistStatistics(); + + return albumStatistics.GroupBy(s => s.ArtistId).Select(s => MapArtistStatistics(s.ToList())).ToList(); + } + + public ArtistStatistics ArtistStatistics(int artistId) + { + var stats = _artistStatisticsRepository.ArtistStatistics(artistId); + + if (stats == null || stats.Count == 0) return new ArtistStatistics(); + + return MapArtistStatistics(stats); + } + + private ArtistStatistics MapArtistStatistics(List albumStatistics) + { + var artistStatistics = new ArtistStatistics + { + AlbumStatistics = albumStatistics, + AlbumCount = albumStatistics.Count, + ArtistId = albumStatistics.First().ArtistId, + TrackFileCount = albumStatistics.Sum(s => s.TrackFileCount), + TrackCount = albumStatistics.Sum(s => s.TrackCount), + TotalTrackCount = albumStatistics.Sum(s => s.TotalTrackCount), + SizeOnDisk = albumStatistics.Sum(s => s.SizeOnDisk) + }; + + return artistStatistics; + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index c5945047b..1b13c5af5 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -23,6 +23,7 @@ using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; using NzbDrone.Core.Restrictions; using NzbDrone.Core.RootFolders; +using NzbDrone.Core.ArtistStats; using NzbDrone.Core.SeriesStats; using NzbDrone.Core.Tags; using NzbDrone.Core.ThingiProvider; @@ -121,7 +122,7 @@ namespace NzbDrone.Core.Datastore Mapper.Entity().RegisterModel("Profiles"); Mapper.Entity().RegisterModel("Logs"); Mapper.Entity().RegisterModel("NamingConfig"); - Mapper.Entity().MapResultSet(); + Mapper.Entity().MapResultSet(); Mapper.Entity().RegisterModel("Blacklist"); Mapper.Entity().RegisterModel("MetadataFiles"); Mapper.Entity().RegisterModel("SubtitleFiles"); diff --git a/src/NzbDrone.Core/MediaCover/MediaCover.cs b/src/NzbDrone.Core/MediaCover/MediaCover.cs index 4b4f54b00..3808e9e20 100644 --- a/src/NzbDrone.Core/MediaCover/MediaCover.cs +++ b/src/NzbDrone.Core/MediaCover/MediaCover.cs @@ -1,4 +1,4 @@ -using NzbDrone.Core.Datastore; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.MediaCover { @@ -10,7 +10,9 @@ namespace NzbDrone.Core.MediaCover Banner = 2, Fanart = 3, Screenshot = 4, - Headshot = 5 + Headshot = 5, + Cover = 6, + Disc = 7 } public class MediaCover : IEmbeddedDocument diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index dec8c8c9b..0c729b7d4 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -365,7 +365,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook series.TitleSlug = show.Slug; series.Status = MapSeriesStatus(show.Status); - series.Ratings = MapRatings(show.Rating); + //series.Ratings = MapRatings(show.Rating); series.Genres = show.Genres; if (show.ContentRating.IsNotNullOrWhiteSpace()) @@ -412,7 +412,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook episode.AirDate = oracleEpisode.AirDate; episode.AirDateUtc = oracleEpisode.AirDateUtc; - episode.Ratings = MapRatings(oracleEpisode.Rating); + //episode.Ratings = MapRatings(oracleEpisode.Rating); //Don't include series fanart images as episode screenshot if (oracleEpisode.Image != null) @@ -443,14 +443,14 @@ namespace NzbDrone.Core.MetadataSource.SkyHook return SeriesStatusType.Continuing; } - private static Ratings MapRatings(RatingResource rating) + private static Core.Music.Ratings MapRatings(RatingResource rating) { if (rating == null) { - return new Ratings(); + return new Core.Music.Ratings(); } - return new Ratings + return new Core.Music.Ratings { Votes = rating.Count, Value = rating.Value diff --git a/src/NzbDrone.Core/Music/Album.cs b/src/NzbDrone.Core/Music/Album.cs index ee88b2e02..b6993cb29 100644 --- a/src/NzbDrone.Core/Music/Album.cs +++ b/src/NzbDrone.Core/Music/Album.cs @@ -17,21 +17,25 @@ namespace NzbDrone.Core.Music public string ForeignAlbumId { get; set; } public int ArtistId { get; set; } - public string Title { get; set; } // NOTE: This should be CollectionName in API + public string Title { get; set; } public string CleanTitle { get; set; } public DateTime ReleaseDate { get; set; } + public string Label { get; set; } //public int TrackCount { get; set; } public string Path { get; set; } + public int ProfileId { get; set; } public List Tracks { 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; } // TODO: These are band members. TODO: Refactor public List Genres { get; set; } - public String AlbumType { get; set; } //Turn this into a type similar to Series Type in TV + public String AlbumType { get; set; } // TODO: 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 Artist Artist { get; set; } + public Ratings Ratings { get; set; } public override string ToString() { diff --git a/src/NzbDrone.Core/Music/AlbumRepository.cs b/src/NzbDrone.Core/Music/AlbumRepository.cs index 1b072e19a..aba0b1945 100644 --- a/src/NzbDrone.Core/Music/AlbumRepository.cs +++ b/src/NzbDrone.Core/Music/AlbumRepository.cs @@ -11,6 +11,7 @@ namespace NzbDrone.Core.Music List GetAlbums(int artistId); Album FindByName(string cleanTitle); Album FindById(string spotifyId); + void SetMonitoredFlat(Album album, bool monitored); } public class AlbumRepository : BasicRepository, IAlbumRepository @@ -35,6 +36,12 @@ namespace NzbDrone.Core.Music return Query.Where(s => s.ForeignAlbumId == foreignAlbumId).SingleOrDefault(); } + public void SetMonitoredFlat(Album album, bool monitored) + { + album.Monitored = monitored; + SetFields(album, p => p.Monitored); + } + public Album FindByName(string cleanTitle) { cleanTitle = cleanTitle.ToLowerInvariant(); diff --git a/src/NzbDrone.Core/Music/AlbumService.cs b/src/NzbDrone.Core/Music/AlbumService.cs index 8d470830c..f957fe765 100644 --- a/src/NzbDrone.Core/Music/AlbumService.cs +++ b/src/NzbDrone.Core/Music/AlbumService.cs @@ -24,6 +24,7 @@ namespace NzbDrone.Core.Music List GetAllAlbums(); Album UpdateAlbum(Album album); List UpdateAlbums(List album); + void SetAlbumMonitored(int albumId, bool monitored); void InsertMany(List albums); void UpdateMany(List albums); void DeleteMany(List albums); @@ -134,6 +135,14 @@ namespace NzbDrone.Core.Music return updatedAlbum; } + public void SetAlbumMonitored(int albumId, bool monitored) + { + var album = _albumRepository.Get(albumId); + _albumRepository.SetMonitoredFlat(album, monitored); + + _logger.Debug("Monitored flag for Album:{0} was set to {1}", albumId, monitored); + } + public List UpdateAlbums(List album) { _logger.Debug("Updating {0} album", album.Count); diff --git a/src/NzbDrone.Core/Music/Artist.cs b/src/NzbDrone.Core/Music/Artist.cs index 0ed7ff34e..f16f4c3b7 100644 --- a/src/NzbDrone.Core/Music/Artist.cs +++ b/src/NzbDrone.Core/Music/Artist.cs @@ -2,7 +2,7 @@ using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore; using NzbDrone.Core.Profiles; -using NzbDrone.Core.Tv; +using NzbDrone.Core.Music; using System; using System.Collections.Generic; using System.Linq; @@ -16,7 +16,7 @@ namespace NzbDrone.Core.Music { Images = new List(); Genres = new List(); - //Members = new List(); // Artist Band Member? (NOTE: This should be per album) + Members = new List(); Albums = new List(); Tags = new HashSet(); @@ -45,7 +45,9 @@ namespace NzbDrone.Core.Music public int ProfileId { get; set; } public List Albums { get; set; } public HashSet Tags { get; set; } - public AddSeriesOptions AddOptions { get; set; } + public AddArtistOptions AddOptions { get; set; } + public Ratings Ratings { get; set; } + public List Members { get; set; } public override string ToString() { @@ -76,6 +78,8 @@ namespace NzbDrone.Core.Music Albums = otherArtist.Albums; Tags = otherArtist.Tags; AddOptions = otherArtist.AddOptions; + Ratings = otherArtist.Ratings; + Members = otherArtist.Members; Albums = otherArtist.Albums; Path = otherArtist.Path; @@ -85,6 +89,7 @@ namespace NzbDrone.Core.Music RootFolderPath = otherArtist.RootFolderPath; Tags = otherArtist.Tags; AddOptions = otherArtist.AddOptions; + } } diff --git a/src/NzbDrone.Core/Music/Member.cs b/src/NzbDrone.Core/Music/Member.cs new file mode 100644 index 000000000..e948c9936 --- /dev/null +++ b/src/NzbDrone.Core/Music/Member.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Core.Music +{ + public class Member : IEmbeddedDocument + { + public Member() + { + Images = new List(); + } + + public string Name { get; set; } + public string Instrument { get; set; } + public List Images { get; set; } + } +} diff --git a/src/NzbDrone.Core/Music/Ratings.cs b/src/NzbDrone.Core/Music/Ratings.cs new file mode 100644 index 000000000..530e5a168 --- /dev/null +++ b/src/NzbDrone.Core/Music/Ratings.cs @@ -0,0 +1,10 @@ +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Core.Music +{ + public class Ratings : IEmbeddedDocument + { + public int Votes { get; set; } + public decimal Value { get; set; } + } +} diff --git a/src/NzbDrone.Core/Music/Track.cs b/src/NzbDrone.Core/Music/Track.cs index f0f25ae96..6b23fa965 100644 --- a/src/NzbDrone.Core/Music/Track.cs +++ b/src/NzbDrone.Core/Music/Track.cs @@ -30,6 +30,7 @@ namespace NzbDrone.Core.Music public bool Explicit { get; set; } public bool Monitored { get; set; } public int TrackFileId { get; set; } + public Ratings Ratings { get; set; } //public DateTime? ReleaseDate { get; set; } public LazyLoaded TrackFile { get; set; } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 963338f76..efbb3f72e 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -859,6 +859,7 @@ + @@ -882,6 +883,7 @@ + @@ -1104,6 +1106,10 @@ + + + + diff --git a/src/UI/AddSeries/AddSeriesCollection.js b/src/UI/AddSeries/AddSeriesCollection.js deleted file mode 100644 index a243649f4..000000000 --- a/src/UI/AddSeries/AddSeriesCollection.js +++ /dev/null @@ -1,23 +0,0 @@ -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/AddSeries/AddSeriesLayout.js b/src/UI/AddSeries/AddSeriesLayout.js deleted file mode 100644 index 949ae4942..000000000 --- a/src/UI/AddSeries/AddSeriesLayout.js +++ /dev/null @@ -1,53 +0,0 @@ -var vent = require('vent'); -var AppLayout = require('../AppLayout'); -var Marionette = require('marionette'); -var RootFolderLayout = require('./RootFolders/RootFolderLayout'); -var ExistingSeriesCollectionView = require('./Existing/AddExistingSeriesCollectionView'); -var AddSeriesView = require('./AddSeriesView'); -var ProfileCollection = require('../Profile/ProfileCollection'); -var RootFolderCollection = require('./RootFolders/RootFolderCollection'); -require('../Artist/ArtistCollection'); - -module.exports = Marionette.Layout.extend({ - template : 'AddSeries/AddSeriesLayoutTemplate', - - regions : { - workspace : '#add-series-workspace' - }, - - events : { - 'click .x-import' : '_importSeries', - 'click .x-add-new' : '_addSeries' - }, - - attributes : { - id : 'add-series-screen' - }, - - initialize : function() { - ProfileCollection.fetch(); - RootFolderCollection.fetch().done(function() { - RootFolderCollection.synced = true; - }); - }, - - onShow : function() { - this.workspace.show(new AddSeriesView()); - }, - - _folderSelected : function(options) { - vent.trigger(vent.Commands.CloseModalCommand); - - this.workspace.show(new ExistingSeriesCollectionView({ model : options.model })); - }, - - _importSeries : function() { - this.rootFolderLayout = new RootFolderLayout(); - this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected); - AppLayout.modalRegion.show(this.rootFolderLayout); - }, - - _addSeries : function() { - this.workspace.show(new AddSeriesView()); - } -}); \ No newline at end of file diff --git a/src/UI/AddSeries/AddSeriesLayoutTemplate.hbs b/src/UI/AddSeries/AddSeriesLayoutTemplate.hbs deleted file mode 100644 index f3daf14aa..000000000 --- a/src/UI/AddSeries/AddSeriesLayoutTemplate.hbs +++ /dev/null @@ -1,17 +0,0 @@ -
-
-
- - -
-
-
-
-
-
-
-
- diff --git a/src/UI/AddSeries/AddSeriesView.js b/src/UI/AddSeries/AddSeriesView.js deleted file mode 100644 index 0c128eb40..000000000 --- a/src/UI/AddSeries/AddSeriesView.js +++ /dev/null @@ -1,183 +0,0 @@ -var _ = require('underscore'); -var vent = require('vent'); -var Marionette = require('marionette'); -var AddSeriesCollection = require('./AddSeriesCollection'); -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 : 'AddSeries/AddSeriesViewTemplate', - - regions : { - searchResult : '#search-result' - }, - - ui : { - seriesSearch : '.x-series-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 AddSeriesCollection(); - console.log('this.collection:', this.collection); - - if (this.isExisting) { - this.collection.unmappedFolderModel = this.model; - } - - if (this.isExisting) { - this.className = 'existing-series'; - } else { - this.className = 'new-series'; - } - - this.listenTo(vent, vent.Events.SeriesAdded, this._onSeriesAdded); - 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.seriesSearch.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.seriesSearch.val() - }); - }); - - this._clearResults(); - - if (this.isExisting) { - this.ui.searchBar.hide(); - } - }, - - onShow : function() { - this.ui.seriesSearch.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; - }, - - _onSeriesAdded : function(options) { - if (this.isExisting && options.series.get('path') === this.model.get('folder').path) { - this.close(); - } - - else if (!this.isExisting) { - this.collection.term = ''; - this.collection.reset(); - this._clearResults(); - this.ui.seriesSearch.val(''); - this.ui.seriesSearch.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/AddSeries/AddSeriesViewTemplate.hbs b/src/UI/AddSeries/AddSeriesViewTemplate.hbs deleted file mode 100644 index 05b1101d6..000000000 --- a/src/UI/AddSeries/AddSeriesViewTemplate.hbs +++ /dev/null @@ -1,24 +0,0 @@ -{{#if folder.path}} -
-
- {{folder.path}} -
-
{{/if}} - -
-
-
- diff --git a/src/UI/AddSeries/EmptyView.js b/src/UI/AddSeries/EmptyView.js deleted file mode 100644 index 047a07ca5..000000000 --- a/src/UI/AddSeries/EmptyView.js +++ /dev/null @@ -1,5 +0,0 @@ -var Marionette = require('marionette'); - -module.exports = Marionette.CompositeView.extend({ - template : 'AddSeries/EmptyViewTemplate' -}); \ No newline at end of file diff --git a/src/UI/AddSeries/EmptyViewTemplate.hbs b/src/UI/AddSeries/EmptyViewTemplate.hbs deleted file mode 100644 index 8c2b29e22..000000000 --- a/src/UI/AddSeries/EmptyViewTemplate.hbs +++ /dev/null @@ -1,3 +0,0 @@ -
- You can also search by iTunes using the itunes: prefixes. -
diff --git a/src/UI/AddSeries/ErrorView.js b/src/UI/AddSeries/ErrorView.js deleted file mode 100644 index 3b619bcb2..000000000 --- a/src/UI/AddSeries/ErrorView.js +++ /dev/null @@ -1,13 +0,0 @@ -var Marionette = require('marionette'); - -module.exports = Marionette.CompositeView.extend({ - template : 'AddSeries/ErrorViewTemplate', - - initialize : function(options) { - this.options = options; - }, - - templateHelpers : function() { - return this.options; - } -}); \ No newline at end of file diff --git a/src/UI/AddSeries/ErrorViewTemplate.hbs b/src/UI/AddSeries/ErrorViewTemplate.hbs deleted file mode 100644 index c0b1e3673..000000000 --- a/src/UI/AddSeries/ErrorViewTemplate.hbs +++ /dev/null @@ -1,7 +0,0 @@ -
-

- 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/AddSeries/Existing/AddExistingSeriesCollectionView.js b/src/UI/AddSeries/Existing/AddExistingSeriesCollectionView.js deleted file mode 100644 index 5c5eddc64..000000000 --- a/src/UI/AddSeries/Existing/AddExistingSeriesCollectionView.js +++ /dev/null @@ -1,51 +0,0 @@ -var Marionette = require('marionette'); -var AddSeriesView = require('../AddSeriesView'); -var UnmappedFolderCollection = require('./UnmappedFolderCollection'); - -module.exports = Marionette.CompositeView.extend({ - itemView : AddSeriesView, - itemViewContainer : '.x-loading-folders', - template : 'AddSeries/Existing/AddExistingSeriesCollectionViewTemplate', - - 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/AddSeries/Existing/AddExistingSeriesCollectionViewTemplate.hbs b/src/UI/AddSeries/Existing/AddExistingSeriesCollectionViewTemplate.hbs deleted file mode 100644 index 6dcb1ecc2..000000000 --- a/src/UI/AddSeries/Existing/AddExistingSeriesCollectionViewTemplate.hbs +++ /dev/null @@ -1,5 +0,0 @@ -
-
- 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/Existing/UnmappedFolderCollection.js b/src/UI/AddSeries/Existing/UnmappedFolderCollection.js deleted file mode 100644 index bd2a83f49..000000000 --- a/src/UI/AddSeries/Existing/UnmappedFolderCollection.js +++ /dev/null @@ -1,20 +0,0 @@ -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/AddSeries/Existing/UnmappedFolderModel.js b/src/UI/AddSeries/Existing/UnmappedFolderModel.js deleted file mode 100644 index 3986a5948..000000000 --- a/src/UI/AddSeries/Existing/UnmappedFolderModel.js +++ /dev/null @@ -1,3 +0,0 @@ -var Backbone = require('backbone'); - -module.exports = Backbone.Model.extend({}); \ No newline at end of file diff --git a/src/UI/AddSeries/MonitoringTooltipTemplate.hbs b/src/UI/AddSeries/MonitoringTooltipTemplate.hbs deleted file mode 100644 index 0cf813e98..000000000 --- a/src/UI/AddSeries/MonitoringTooltipTemplate.hbs +++ /dev/null @@ -1,18 +0,0 @@ -
-
All
-
Monitor all episodes except specials
-
Future
-
Monitor episodes that have not aired yet
-
Missing
-
Monitor episodes that do not have files or have not aired yet
-
Existing
-
Monitor episodes that have files or have not aired yet
-
First Season
-
Monitor all episodes of the first season. All other seasons will be ignored
-
Latest Season
-
Monitor all episodes of the latest season and future seasons
-
None
-
No episodes will be monitored.
- - -
\ No newline at end of file diff --git a/src/UI/AddSeries/NotFoundView.js b/src/UI/AddSeries/NotFoundView.js deleted file mode 100644 index 9dce2bf85..000000000 --- a/src/UI/AddSeries/NotFoundView.js +++ /dev/null @@ -1,13 +0,0 @@ -var Marionette = require('marionette'); - -module.exports = Marionette.CompositeView.extend({ - template : 'AddSeries/NotFoundViewTemplate', - - initialize : function(options) { - this.options = options; - }, - - templateHelpers : function() { - return this.options; - } -}); \ No newline at end of file diff --git a/src/UI/AddSeries/NotFoundViewTemplate.hbs b/src/UI/AddSeries/NotFoundViewTemplate.hbs deleted file mode 100644 index f203260e2..000000000 --- a/src/UI/AddSeries/NotFoundViewTemplate.hbs +++ /dev/null @@ -1,7 +0,0 @@ -
-

- Sorry. We couldn't find any series matching '{{term}}' -

- Why can't I find my show? - -
diff --git a/src/UI/AddSeries/RootFolders/RootFolderCollection.js b/src/UI/AddSeries/RootFolders/RootFolderCollection.js deleted file mode 100644 index 81050c19d..000000000 --- a/src/UI/AddSeries/RootFolders/RootFolderCollection.js +++ /dev/null @@ -1,10 +0,0 @@ -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/AddSeries/RootFolders/RootFolderCollectionView.js b/src/UI/AddSeries/RootFolders/RootFolderCollectionView.js deleted file mode 100644 index f781f21d7..000000000 --- a/src/UI/AddSeries/RootFolders/RootFolderCollectionView.js +++ /dev/null @@ -1,8 +0,0 @@ -var Marionette = require('marionette'); -var RootFolderItemView = require('./RootFolderItemView'); - -module.exports = Marionette.CompositeView.extend({ - template : 'AddSeries/RootFolders/RootFolderCollectionViewTemplate', - itemViewContainer : '.x-root-folders', - itemView : RootFolderItemView -}); \ No newline at end of file diff --git a/src/UI/AddSeries/RootFolders/RootFolderCollectionViewTemplate.hbs b/src/UI/AddSeries/RootFolders/RootFolderCollectionViewTemplate.hbs deleted file mode 100644 index 70755bbca..000000000 --- a/src/UI/AddSeries/RootFolders/RootFolderCollectionViewTemplate.hbs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - -
- Path - - Free Space -
\ No newline at end of file diff --git a/src/UI/AddSeries/RootFolders/RootFolderItemView.js b/src/UI/AddSeries/RootFolders/RootFolderItemView.js deleted file mode 100644 index a0e98100b..000000000 --- a/src/UI/AddSeries/RootFolders/RootFolderItemView.js +++ /dev/null @@ -1,28 +0,0 @@ -var Marionette = require('marionette'); - -module.exports = Marionette.ItemView.extend({ - template : 'AddSeries/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/AddSeries/RootFolders/RootFolderItemViewTemplate.hbs b/src/UI/AddSeries/RootFolders/RootFolderItemViewTemplate.hbs deleted file mode 100644 index c1378207a..000000000 --- a/src/UI/AddSeries/RootFolders/RootFolderItemViewTemplate.hbs +++ /dev/null @@ -1,9 +0,0 @@ - - {{path}} - - - {{Bytes freeSpace}} - - - - diff --git a/src/UI/AddSeries/RootFolders/RootFolderLayout.js b/src/UI/AddSeries/RootFolders/RootFolderLayout.js deleted file mode 100644 index d96b01a7b..000000000 --- a/src/UI/AddSeries/RootFolders/RootFolderLayout.js +++ /dev/null @@ -1,80 +0,0 @@ -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 : 'AddSeries/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/AddSeries/RootFolders/RootFolderLayoutTemplate.hbs b/src/UI/AddSeries/RootFolders/RootFolderLayoutTemplate.hbs deleted file mode 100644 index d733c4ba7..000000000 --- a/src/UI/AddSeries/RootFolders/RootFolderLayoutTemplate.hbs +++ /dev/null @@ -1,36 +0,0 @@ - diff --git a/src/UI/AddSeries/RootFolders/RootFolderModel.js b/src/UI/AddSeries/RootFolders/RootFolderModel.js deleted file mode 100644 index 28681768b..000000000 --- a/src/UI/AddSeries/RootFolders/RootFolderModel.js +++ /dev/null @@ -1,8 +0,0 @@ -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/AddSeries/RootFolders/RootFolderSelectionPartial.hbs b/src/UI/AddSeries/RootFolders/RootFolderSelectionPartial.hbs deleted file mode 100644 index 56729b0dd..000000000 --- a/src/UI/AddSeries/RootFolders/RootFolderSelectionPartial.hbs +++ /dev/null @@ -1,11 +0,0 @@ - - diff --git a/src/UI/AddSeries/SearchResultCollectionView.js b/src/UI/AddSeries/SearchResultCollectionView.js deleted file mode 100644 index e533085ac..000000000 --- a/src/UI/AddSeries/SearchResultCollectionView.js +++ /dev/null @@ -1,29 +0,0 @@ -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/AddSeries/SearchResultView.js b/src/UI/AddSeries/SearchResultView.js deleted file mode 100644 index 8c4c70e7e..000000000 --- a/src/UI/AddSeries/SearchResultView.js +++ /dev/null @@ -1,297 +0,0 @@ -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 : 'AddSeries/SearchResultViewTemplate', - - ui : { - profile : '.x-profile', - rootFolder : '.x-root-folder', - seasonFolder : '.x-season-folder', - seriesType : '.x-series-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-season-folder' : '_seasonFolderChanged', - 'change .x-series-type' : '_seriesTypeChanged', - '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 defaultSeriesType = 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.seasonFolder.prop('checked', useSeasonFolder); - this.ui.seriesType.val(defaultSeriesType); - 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('AddSeries/MonitoringTooltipTemplate'); - var content = this.templateFunction(); - - this.ui.monitorTooltip.popover({ - content : content, - html : true, - trigger : 'hover', - title : 'Episode Monitoring Options', - placement : 'right', - container : this.$el - }); - }, - - _configureTemplateHelpers : function() { - var existingSeries = ArtistCollection.where({ iTunesId : this.model.get('itunesId') }); - - if (existingSeries.length > 0) { - this.templateHelpers.existing = existingSeries[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.UseSeasonFolder) { - this.ui.seasonFolder.prop('checked', options.value); - } - - else if (options.key === Config.Keys.DefaultSeriesType) { - this.ui.seriesType.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()); - }, - - _seasonFolderChanged : function() { - Config.setValue(Config.Keys.UseSeasonFolder, this.ui.seasonFolder.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); - } - }, - - _seriesTypeChanged : function() { - Config.setValue(Config.Keys.DefaultSeriesType, this.ui.seriesType.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._addSeries(false); - }, - - _addAndSearch : function() { - this._addSeries(true); - }, - - _addSeries : function(searchForMissing) { - // TODO: Refactor to handle multiple add buttons/albums - var addButton = this.ui.addButton; - var addSearchButton = this.ui.addSearchButton; - console.log('_addSeries, searchForMissing=', searchForMissing); - - addButton.addClass('disabled'); - addSearchButton.addClass('disabled'); - - var profile = this.ui.profile.val(); - var rootFolderPath = this.ui.rootFolder.children(':selected').text(); - var seriesType = this.ui.seriesType.val(); // Perhaps make this a differnitator between artist or Album? - var seasonFolder = this.ui.seasonFolder.prop('checked'); - - var options = this._getAddSeriesOptions(); - options.searchForMissing = searchForMissing; - - this.model.set({ - profileId : profile, - rootFolderPath : rootFolderPath, - seasonFolder : seasonFolder, - seriesType : seriesType, - 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] _addSeries promise resolve:', self.model); - ArtistCollection.add(self.model); - - self.close(); - - Messenger.show({ - message : 'Added: ' + self.model.get('artistName'), - actions : { - goToSeries : { - label : 'Go to Artist', - action : function() { - Backbone.history.navigate('/artist/' + self.model.get('artistSlug'), { trigger : true }); - } - } - }, - hideAfter : 8, - hideOnNavigate : true - }); - - vent.trigger(vent.Events.SeriesAdded, { series : self.model }); - }); - }, - - _rootFoldersUpdated : function() { - this._configureTemplateHelpers(); - this.render(); - }, - - _getAddSeriesOptions : 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 = { - ignoreEpisodesWithFiles : false, - ignoreEpisodesWithoutFiles : false - }; - - if (monitor === 'all') { - return options; - } - - else if (monitor === 'future') { - options.ignoreEpisodesWithFiles = true; - options.ignoreEpisodesWithoutFiles = 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.ignoreEpisodesWithFiles = true; - } - - else if (monitor === 'existing') { - options.ignoreEpisodesWithoutFiles = true; - } - - /*else if (monitor === 'none') { - this.model.setSeasonPass(lastSeason.seasonNumber + 1); - }*/ - - return options; - } -}); - -AsValidatedView.apply(view); - -module.exports = view; diff --git a/src/UI/AddSeries/SearchResultViewTemplate.hbs b/src/UI/AddSeries/SearchResultViewTemplate.hbs deleted file mode 100644 index a91fe80af..000000000 --- a/src/UI/AddSeries/SearchResultViewTemplate.hbs +++ /dev/null @@ -1,146 +0,0 @@ -
-
-
-
-
-

- - {{artistName}} - - -

-
-
- -
- {{#unless existing}} - {{#unless path}} -
- - {{> RootFolderSelectionPartial rootFolders}} -
- {{/unless}} - -
- - -
- -
- - {{> ProfileSelectionPartial profiles}} -
- - - -
- - -
-
- {{/unless}} -
-
- {{#unless existing}} - {{#if artistName}} -
- - -
- - - -
-
- {{else}} -
- -
- {{/if}} - {{else}} - - {{/unless}} -
-
-
-
- {{#each albums}} -
- -
-

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

- {{#unless existing}} - {{#if albumName}} -
- - -
- - - -
-
- {{else}} -
- -
- {{/if}} - {{else}} - - {{/unless}} -
-
- {{/each}} -
-
diff --git a/src/UI/AddSeries/SeriesTypeSelectionPartial.hbs b/src/UI/AddSeries/SeriesTypeSelectionPartial.hbs deleted file mode 100644 index ec2990640..000000000 --- a/src/UI/AddSeries/SeriesTypeSelectionPartial.hbs +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/src/UI/AddSeries/StartingSeasonSelectionPartial.hbs b/src/UI/AddSeries/StartingSeasonSelectionPartial.hbs deleted file mode 100644 index e5623e33a..000000000 --- a/src/UI/AddSeries/StartingSeasonSelectionPartial.hbs +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/src/UI/AddSeries/addSeries.less b/src/UI/AddSeries/addSeries.less deleted file mode 100644 index f958e01a3..000000000 --- a/src/UI/AddSeries/addSeries.less +++ /dev/null @@ -1,181 +0,0 @@ -@import "../Shared/Styles/card.less"; -@import "../Shared/Styles/clickable.less"; - -#add-series-screen { - .existing-series { - - .card(); - margin : 30px 0px; - - .unmapped-folder-path { - padding: 20px; - margin-left : 0px; - font-weight : 100; - font-size : 25px; - text-align : center; - } - - .new-series-loadmore { - font-size : 30px; - font-weight : 300; - padding-top : 10px; - padding-bottom : 10px; - } - } - - .new-series { - .search-item { - .card(); - margin : 40px 0px; - } - } - - .add-series-search { - margin-top : 20px; - margin-bottom : 20px; - } - - .search-item { - - padding-bottom : 20px; - - .series-title { - margin-top : 5px; - - .labels { - margin-left : 10px; - - .label { - font-size : 12px; - vertical-align : middle; - } - } - - .year { - font-style : italic; - color : #aaaaaa; - } - } - - .new-series-overview { - overflow : hidden; - height : 103px; - - .overview-internal { - overflow : hidden; - height : 80px; - } - } - - .series-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 index 61faa2046..c129665c0 100644 --- a/src/UI/Artist/AlbumCollection.js +++ b/src/UI/Artist/AlbumCollection.js @@ -2,9 +2,27 @@ var Backbone = require('backbone'); var AlbumModel = require('./AlbumModel'); module.exports = Backbone.Collection.extend({ + url : window.NzbDrone.ApiRoot + '/album', model : AlbumModel, - comparator : function(season) { - return -season.get('seasonNumber'); + originalFetch : Backbone.Collection.prototype.fetch, + + initialize : function(options) { + this.artistId = options.artistId; + this.models = []; + }, + + fetch : function(options) { + if (!this.artistId) { + throw 'artistId is required'; + } + + if (!options) { + options = {}; + } + + options.data = { artistId : this.artistId }; + + return this.originalFetch.call(this, options); } }); \ No newline at end of file diff --git a/src/UI/Artist/AlbumModel.js b/src/UI/Artist/AlbumModel.js index 1ba049eb6..ecdda2ce4 100644 --- a/src/UI/Artist/AlbumModel.js +++ b/src/UI/Artist/AlbumModel.js @@ -1,11 +1,8 @@ var Backbone = require('backbone'); module.exports = Backbone.Model.extend({ + defaults : { - seasonNumber : 0 + artistId : 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 8e700d7bf..ed9df3168 100644 --- a/src/UI/Artist/ArtistCollection.js +++ b/src/UI/Artist/ArtistCollection.js @@ -67,7 +67,7 @@ var Collection = PageableCollection.extend({ 'missing' : [ null, null, - function(model) { return model.get('episodeCount') !== model.get('episodeFileCount'); } + function(model) { return model.get('trackCount') !== model.get('trackFileCount'); } ] }, @@ -96,12 +96,12 @@ var Collection = PageableCollection.extend({ } }, - percentOfEpisodes : { + percentOfTracks : { sortValue : function(model, attr) { - var percentOfEpisodes = model.get(attr); - var episodeCount = model.get('episodeCount'); + var percentOfTracks = model.get(attr); + var trackCount = model.get('trackCount'); - return percentOfEpisodes + episodeCount / 1000000; + return percentOfTracks + trackCount / 1000000; } }, diff --git a/src/UI/Artist/ArtistModel.js b/src/UI/Artist/ArtistModel.js index a3c3dde50..5ac5ba8cf 100644 --- a/src/UI/Artist/ArtistModel.js +++ b/src/UI/Artist/ArtistModel.js @@ -5,14 +5,15 @@ module.exports = Backbone.Model.extend({ urlRoot : window.NzbDrone.ApiRoot + '/artist', defaults : { - episodeFileCount : 0, - episodeCount : 0, + trackFileCount : 0, + trackCount : 0, isExisting : false, status : 0 }, setAlbumsMonitored : function(albumName) { _.each(this.get('albums'), function(album) { + console.log(album); if (album.albumName === albumName) { album.monitored = !album.monitored; } diff --git a/src/UI/Artist/Details/AlbumCollectionView.js b/src/UI/Artist/Details/AlbumCollectionView.js index 133dff7c5..32b24c3ac 100644 --- a/src/UI/Artist/Details/AlbumCollectionView.js +++ b/src/UI/Artist/Details/AlbumCollectionView.js @@ -1,23 +1,25 @@ var _ = require('underscore'); var Marionette = require('marionette'); -var SeasonLayout = require('./AlbumLayout'); +var AlbumLayout = require('./AlbumLayout'); var AsSortedCollectionView = require('../../Mixins/AsSortedCollectionView'); var view = Marionette.CollectionView.extend({ - itemView : SeasonLayout, + itemView : AlbumLayout, initialize : function(options) { if (!options.trackCollection) { throw 'trackCollection is needed'; } - + console.log(options); + this.albumCollection = options.collection; this.trackCollection = options.trackCollection; this.artist = options.artist; }, itemViewOptions : function() { return { + albumCollection : this.albumCollection, trackCollection : this.trackCollection, artist : this.artist }; diff --git a/src/UI/Artist/Details/AlbumInfoView.js b/src/UI/Artist/Details/AlbumInfoView.js new file mode 100644 index 000000000..ea32386d4 --- /dev/null +++ b/src/UI/Artist/Details/AlbumInfoView.js @@ -0,0 +1,11 @@ +var Marionette = require('marionette'); + +module.exports = Marionette.ItemView.extend({ + template : 'Artist/Details/AlbumInfoViewTemplate', + + initialize : function(options) { + + this.listenTo(this.model, 'change', this.render); + }, + +}); \ No newline at end of file diff --git a/src/UI/Artist/Details/AlbumInfoViewTemplate.hbs b/src/UI/Artist/Details/AlbumInfoViewTemplate.hbs new file mode 100644 index 000000000..4e7409474 --- /dev/null +++ b/src/UI/Artist/Details/AlbumInfoViewTemplate.hbs @@ -0,0 +1,41 @@ +
+
+ {{profile profileId}} + + {{#if label}} + {{label}} + {{/if}} + + {{path}} + + {{#if ratings}} + {{ratings.value}} + {{/if}} + +
+
+ + MusicBrainz + + {{#if tadbId}} + The AudioDB + {{/if}} + + {{#if discogsId}} + Discogs + {{/if}} + + {{#if amId}} + AllMusic + {{/if}} + +
+
+ +{{#if tags}} +
+
+ {{tagDisplay tags}} +
+
+{{/if}} diff --git a/src/UI/Artist/Details/AlbumLayout.js b/src/UI/Artist/Details/AlbumLayout.js index 733f54b6f..ce94535c4 100644 --- a/src/UI/Artist/Details/AlbumLayout.js +++ b/src/UI/Artist/Details/AlbumLayout.js @@ -1,39 +1,44 @@ var vent = require('vent'); var Marionette = require('marionette'); var Backgrid = require('backgrid'); -var ToggleCell = require('../../Cells/EpisodeMonitoredCell'); -var EpisodeTitleCell = require('../../Cells/EpisodeTitleCell'); +var ToggleCell = require('../../Cells/TrackMonitoredCell'); +var TrackTitleCell = require('../../Cells/TrackTitleCell'); +var TrackExplicitCell = require('../../Cells/TrackExplicitCell'); var RelativeDateCell = require('../../Cells/RelativeDateCell'); -var EpisodeStatusCell = require('../../Cells/EpisodeStatusCell'); -var EpisodeActionsCell = require('../../Cells/EpisodeActionsCell'); +var TrackStatusCell = require('../../Cells/TrackStatusCell'); +var TrackActionsCell = require('../../Cells/TrackActionsCell'); var TrackNumberCell = require('./TrackNumberCell'); var TrackWarningCell = require('./TrackWarningCell'); +var TrackRatingCell = require('./TrackRatingCell'); +var AlbumInfoView = require('./AlbumInfoView'); var CommandController = require('../../Commands/CommandController'); -var EpisodeFileEditorLayout = require('../../EpisodeFile/Editor/EpisodeFileEditorLayout'); +//var TrackFileEditorLayout = require('../../TrackFile/Editor/TrackFileEditorLayout'); var moment = require('moment'); var _ = require('underscore'); var Messenger = require('../../Shared/Messenger'); module.exports = Marionette.Layout.extend({ - template : 'Artist/Details/ArtistLayoutTemplate', + template : 'Artist/Details/AlbumLayoutTemplate', ui : { - seasonSearch : '.x-album-search', - seasonMonitored : '.x-album-monitored', - seasonRename : '.x-album-rename' + albumSearch : '.x-album-search', + albumMonitored : '.x-album-monitored', + albumRename : '.x-album-rename', + cover : '.x-album-cover' }, events : { - 'click .x-album-episode-file-editor' : '_openEpisodeFileEditor', + 'click .x-track-file-editor' : '_openTrackFileEditor', 'click .x-album-monitored' : '_albumMonitored', 'click .x-album-search' : '_albumSearch', 'click .x-album-rename' : '_albumRename', - 'click .x-show-hide-episodes' : '_showHideEpisodes', - 'dblclick .artist-album h2' : '_showHideEpisodes' + 'click .x-show-hide-tracks' : '_showHideTracks', + 'dblclick .artist-album h2' : '_showHideTracks' }, regions : { - episodeGrid : '.x-episode-grid' + trackGrid : '.x-track-grid', + albumInfo : '#album-info' }, columns : [ @@ -61,110 +66,135 @@ module.exports = Marionette.Layout.extend({ { name : 'this', label : 'Title', - hideSeriesLink : true, - cell : EpisodeTitleCell, + hideArtistLink : true, + cell : TrackTitleCell, sortable : false }, { - name : 'airDateUtc', - label : 'Air Date', - cell : RelativeDateCell + name : 'this', + label : 'Rating', + cell : TrackRatingCell }, { - name : 'status', - label : 'Status', - cell : EpisodeStatusCell, - sortable : false + name : 'this', + label : 'Content', + cell : TrackExplicitCell }, - { - name : 'this', - label : '', - cell : EpisodeActionsCell, - sortable : false - } + //{ + // name : 'airDateUtc', + // label : 'Air Date', + // cell : RelativeDateCell + //}, + { + name : 'status', + label : 'Status', + cell : TrackStatusCell, + sortable : false + }, + //{ + // name : 'this', + // label : '', + // cell : TrackActionsCell, + // sortable : false + //} ], templateHelpers : function() { - var episodeCount = this.episodeCollection.filter(function(episode) { - return episode.get('hasFile') || episode.get('monitored') && moment(episode.get('airDateUtc')).isBefore(moment()); + var trackCount = this.trackCollection.filter(function(track) { + return track.get('hasFile') || track.get('monitored'); }).length; - var episodeFileCount = this.episodeCollection.where({ hasFile : true }).length; - var percentOfEpisodes = 100; + var trackFileCount = this.trackCollection.where({ hasFile : true }).length; + var percentOfTracks = 100; - if (episodeCount > 0) { - percentOfEpisodes = episodeFileCount / episodeCount * 100; + if (trackCount > 0) { + percentOfTracks = trackFileCount / trackCount * 100; } return { - showingEpisodes : this.showingEpisodes, - episodeCount : episodeCount, - episodeFileCount : episodeFileCount, - percentOfEpisodes : percentOfEpisodes + showingTracks : this.showingTracks, + trackCount : trackCount, + trackFileCount : trackFileCount, + percentOfTracks : percentOfTracks }; }, initialize : function(options) { - if (!options.episodeCollection) { - throw 'episodeCollection is required'; + if (!options.trackCollection) { + throw 'trackCollection is required'; } + + this.artist = options.artist; + this.fullTrackCollection = options.trackCollection; + + this.trackCollection = this.fullTrackCollection.byAlbum(this.model.get('id')); + this._updateTrackCollection(); - this.series = options.series; - this.fullEpisodeCollection = options.episodeCollection; - this.episodeCollection = this.fullEpisodeCollection.bySeason(this.model.get('seasonNumber')); - this._updateEpisodeCollection(); + console.log(options); - this.showingEpisodes = this._shouldShowEpisodes(); + this.showingTracks = this._shouldShowTracks(); this.listenTo(this.model, 'sync', this._afterSeasonMonitored); - this.listenTo(this.episodeCollection, 'sync', this.render); - - this.listenTo(this.fullEpisodeCollection, 'sync', this._refreshEpisodes); + this.listenTo(this.trackCollection, 'sync', this.render); + this.listenTo(this.fullTrackCollection, 'sync', this._refreshTracks); + this.listenTo(this.model, 'change:images', this._updateImages); }, onRender : function() { - if (this.showingEpisodes) { - this._showEpisodes(); + if (this.showingTracks) { + this._showTracks(); } - this._setSeasonMonitoredState(); + this._showAlbumInfo(); + + this._setAlbumMonitoredState(); CommandController.bindToCommand({ - element : this.ui.seasonSearch, + element : this.ui.albumSearch, command : { - name : 'seasonSearch', - seriesId : this.series.id, - seasonNumber : this.model.get('seasonNumber') + name : 'albumSearch', + artistId : this.artist.id, + albumId : this.model.get('albumId') } }); CommandController.bindToCommand({ - element : this.ui.seasonRename, + element : this.ui.albumRename, command : { name : 'renameFiles', - seriesId : this.series.id, - seasonNumber : this.model.get('seasonNumber') + artistId : this.artist.id, + albumId : this.model.get('albumId') } }); }, - _seasonSearch : function() { - CommandController.Execute('seasonSearch', { - name : 'seasonSearch', - seriesId : this.series.id, - seasonNumber : this.model.get('seasonNumber') + _getImage : function(type) { + var image = _.where(this.model.get('images'), { coverType : type }); + + if (image && image[0]) { + return image[0].url; + } + + return undefined; + }, + + _albumSearch : function() { + CommandController.Execute('albumSearch', { + name : 'albumSearch', + artistId : this.artist.id, + albumId : this.model.get('albumId') }); }, - _seasonRename : function() { + _albumRename : function() { vent.trigger(vent.Commands.ShowRenamePreview, { - series : this.series, - seasonNumber : this.model.get('seasonNumber') + artist : this.artist, + albumId : this.model.get('albumId') }); }, - _seasonMonitored : function() { - if (!this.series.get('monitored')) { + _albumMonitored : function() { + if (!this.artist.get('monitored')) { Messenger.show({ message : 'Unable to change monitored state when series is not monitored', @@ -176,80 +206,86 @@ module.exports = Marionette.Layout.extend({ var name = 'monitored'; this.model.set(name, !this.model.get(name)); - this.series.setSeasonMonitored(this.model.get('seasonNumber')); + this.artist.setSeasonMonitored(this.model.get('albumId')); - var savePromise = this.series.save().always(this._afterSeasonMonitored.bind(this)); + var savePromise = this.artist.save().always(this._afterSeasonMonitored.bind(this)); - this.ui.seasonMonitored.spinForPromise(savePromise); + this.ui.albumMonitored.spinForPromise(savePromise); }, _afterSeasonMonitored : function() { var self = this; - _.each(this.episodeCollection.models, function(episode) { - episode.set({ monitored : self.model.get('monitored') }); + _.each(this.trackCollection.models, function(track) { + track.set({ monitored : self.model.get('monitored') }); }); this.render(); }, - _setSeasonMonitoredState : function() { - this.ui.seasonMonitored.removeClass('icon-lidarr-spinner fa-spin'); + _setAlbumMonitoredState : function() { + this.ui.albumMonitored.removeClass('icon-lidarr-spinner fa-spin'); if (this.model.get('monitored')) { - this.ui.seasonMonitored.addClass('icon-lidarr-monitored'); - this.ui.seasonMonitored.removeClass('icon-lidarr-unmonitored'); + this.ui.albumMonitored.addClass('icon-lidarr-monitored'); + this.ui.albumMonitored.removeClass('icon-lidarr-unmonitored'); } else { - this.ui.seasonMonitored.addClass('icon-lidarr-unmonitored'); - this.ui.seasonMonitored.removeClass('icon-lidarr-monitored'); + this.ui.albumMonitored.addClass('icon-lidarr-unmonitored'); + this.ui.albumMonitored.removeClass('icon-lidarr-monitored'); } }, - _showEpisodes : function() { - this.episodeGrid.show(new Backgrid.Grid({ + _showTracks : function() { + this.trackGrid.show(new Backgrid.Grid({ columns : this.columns, - collection : this.episodeCollection, - className : 'table table-hover season-grid' + collection : this.trackCollection, + className : 'table table-hover track-grid' + })); + }, + + _showAlbumInfo : function() { + this.albumInfo.show(new AlbumInfoView({ + model : this.model })); }, - _shouldShowEpisodes : function() { + _shouldShowTracks : function() { var startDate = moment().add('month', -1); var endDate = moment().add('year', 1); + return true; + //return this.trackCollection.some(function(track) { + // var airDate = track.get('releasedDate'); - return this.episodeCollection.some(function(episode) { - var airDate = episode.get('airDateUtc'); + // if (airDate) { + // var airDateMoment = moment(airDate); - if (airDate) { - var airDateMoment = moment(airDate); + // if (airDateMoment.isAfter(startDate) && airDateMoment.isBefore(endDate)) { + // return true; + // } + // } - if (airDateMoment.isAfter(startDate) && airDateMoment.isBefore(endDate)) { - return true; - } - } - - return false; - }); + // return false; + //}); }, - _showHideEpisodes : function() { - if (this.showingEpisodes) { - this.showingEpisodes = false; - this.episodeGrid.close(); + _showHideTracks : function() { + if (this.showingTracks) { + this.showingTracks = false; + this.trackGrid.close(); } else { - this.showingEpisodes = true; - this._showEpisodes(); + this.showingTracks = true; + this._showTracks(); } - this.templateHelpers.showingEpisodes = this.showingEpisodes; + this.templateHelpers.showingTracks = this.showingTracks; this.render(); }, - _episodeMonitoredToggled : function(options) { + _trackMonitoredToggled : function(options) { var model = options.model; var shiftKey = options.shiftKey; - if (!this.episodeCollection.get(model.get('id'))) { + if (!this.trackCollection.get(model.get('id'))) { return; } @@ -257,45 +293,53 @@ module.exports = Marionette.Layout.extend({ return; } - var lastToggled = this.episodeCollection.lastToggled; + var lastToggled = this.trackCollection.lastToggled; if (!lastToggled) { return; } - var currentIndex = this.episodeCollection.indexOf(model); - var lastIndex = this.episodeCollection.indexOf(lastToggled); + var currentIndex = this.trackCollection.indexOf(model); + var lastIndex = this.trackCollection.indexOf(lastToggled); var low = Math.min(currentIndex, lastIndex); var high = Math.max(currentIndex, lastIndex); var range = _.range(low + 1, high); - this.episodeCollection.lastToggled = model; + this.trackCollection.lastToggled = model; }, - _updateEpisodeCollection : function() { + _updateTrackCollection : function() { var self = this; - this.episodeCollection.add(this.fullEpisodeCollection.bySeason(this.model.get('seasonNumber')).models, { merge : true }); + this.trackCollection.add(this.fullTrackCollection.byAlbum(this.model.get('albumId')).models, { merge : true }); - this.episodeCollection.each(function(model) { - model.episodeCollection = self.episodeCollection; + this.trackCollection.each(function(model) { + model.trackCollection = self.trackCollection; }); }, - _refreshEpisodes : function() { - this._updateEpisodeCollection(); - this.episodeCollection.fullCollection.sort(); + _updateImages : function () { + var cover = this._getImage('cover'); + + if (cover) { + this.ui.poster.attr('src', cover); + } + }, + + _refreshTracks : function() { + this._updateTrackCollection(); + this.trackCollection.fullCollection.sort(); this.render(); }, - _openEpisodeFileEditor : function() { - var view = new EpisodeFileEditorLayout({ - model : this.model, - series : this.series, - episodeCollection : this.episodeCollection - }); + _openTrackFileEditor : function() { + //var view = new TrackFileEditorLayout({ + // model : this.model, + // artist : this.artist, + // trackCollection : this.trackCollection + //}); - vent.trigger(vent.Commands.OpenModalCommand, view); + //vent.trigger(vent.Commands.OpenModalCommand, view); } }); \ No newline at end of file diff --git a/src/UI/Artist/Details/AlbumLayoutTemplate.hbs b/src/UI/Artist/Details/AlbumLayoutTemplate.hbs index 685c8b97c..fb3fa0ed7 100644 --- a/src/UI/Artist/Details/AlbumLayoutTemplate.hbs +++ b/src/UI/Artist/Details/AlbumLayoutTemplate.hbs @@ -1,50 +1,68 @@ -
-

- - - {{#if seasonNumber}} - Season {{seasonNumber}} - {{else}} - Specials - {{/if}} +
+
+ {{cover}} +
+
+

+ + {{#if title}} + {{title}} ({{albumYear}}) - {{#if_eq episodeCount compare=0}} - {{#if monitored}} -   {{else}} -   + Specials {{/if}} - {{else}} - {{#if_eq percentOfEpisodes compare=100}} - {{episodeFileCount}} / {{episodeCount}} + + + {{#if_eq trackCount compare=0}} + {{#if monitored}} +   + {{else}} +   + {{/if}} {{else}} - {{episodeFileCount}} / {{episodeCount}} + {{#if_eq percentOfTracks compare=100}} + {{trackFileCount}} / {{trackCount}} Tracks + {{else}} + {{trackFileCount}} / {{trackCount}} Tracks + {{/if_eq}} {{/if_eq}} - {{/if_eq}} - - -
- -
-
- -
- -
-

-
-

- {{#if showingEpisodes}} - - Hide Episodes - {{else}} - - Show Episodes - {{/if}} -

+ + +
+ +
+
+ +
+ +
+ +

+ +
+ Release Date: {{albumReleaseDate}} +
+
+ Type: Album +
+ +
-
+ +
+

+ {{#if showingTracks}} + + Hide Tracks + {{else}} + + Show Tracks + {{/if}} +

+
+ +
diff --git a/src/UI/Artist/Details/ArtistDetailsLayout.js b/src/UI/Artist/Details/ArtistDetailsLayout.js index fc068d291..0247a2e94 100644 --- a/src/UI/Artist/Details/ArtistDetailsLayout.js +++ b/src/UI/Artist/Details/ArtistDetailsLayout.js @@ -36,7 +36,7 @@ module.exports = Marionette.Layout.extend({ }, events : { - 'click .x-episode-file-editor' : '_openEpisodeFileEditor', + 'click .x-track-file-editor' : '_openTrackFileEditor', 'click .x-monitored' : '_toggleMonitored', 'click .x-edit' : '_editArtist', 'click .x-refresh' : '_refreshArtist', @@ -142,7 +142,7 @@ module.exports = Marionette.Layout.extend({ _refreshArtist : function() { CommandController.Execute('refreshArtist', { name : 'refreshArtist', - seriesId : this.model.id + artistId : this.model.id }); }, @@ -167,30 +167,19 @@ module.exports = Marionette.Layout.extend({ this.albums.show(new LoadingView()); - this.albumCollection = new AlbumCollection(this.model.get('albums')); + this.albumCollection = new AlbumCollection({ artistId : this.model.id }).bindSignalR(); + this.trackCollection = new TrackCollection({ artistId : this.model.id }).bindSignalR(); this.trackFileCollection = new TrackFileCollection({ artistId : this.model.id }).bindSignalR(); + console.log (this.trackCollection); + reqres.setHandler(reqres.Requests.GetEpisodeFileById, function(trackFileId) { return self.trackFileCollection.get(trackFileId); }); - reqres.setHandler(reqres.Requests.GetAlternateNameBySeasonNumber, function(artistId, seasonNumber, sceneSeasonNumber) { - if (self.model.get('id') !== artistId) { - return []; - } - - if (sceneSeasonNumber === undefined) { - sceneSeasonNumber = seasonNumber; - } - - return _.where(self.model.get('alternateTitles'), - function(alt) { - return alt.sceneSeasonNumber === sceneSeasonNumber || alt.seasonNumber === seasonNumber; - }); - }); - $.when(this.trackCollection.fetch(), this.trackFileCollection.fetch()).done(function() { + $.when(this.albumCollection.fetch(), this.trackCollection.fetch(), this.trackFileCollection.fetch()).done(function() { var albumCollectionView = new AlbumCollectionView({ collection : self.albumCollection, trackCollection : self.trackCollection, @@ -219,7 +208,7 @@ module.exports = Marionette.Layout.extend({ }, _refresh : function() { - this.albumCollection.add(this.model.get('albums'), { merge : true }); + this.albumCollection.fetch(); this.trackCollection.fetch(); this.trackFileCollection.fetch(); diff --git a/src/UI/Artist/Details/ArtistDetailsTemplate.hbs b/src/UI/Artist/Details/ArtistDetailsTemplate.hbs index 8d5b1f5b1..ffa9a0716 100644 --- a/src/UI/Artist/Details/ArtistDetailsTemplate.hbs +++ b/src/UI/Artist/Details/ArtistDetailsTemplate.hbs @@ -9,7 +9,7 @@ {{name}}
- +
@@ -32,4 +32,4 @@
-
+
diff --git a/src/UI/Artist/Details/InfoViewTemplate.hbs b/src/UI/Artist/Details/InfoViewTemplate.hbs index f305c976b..04bd11d8e 100644 --- a/src/UI/Artist/Details/InfoViewTemplate.hbs +++ b/src/UI/Artist/Details/InfoViewTemplate.hbs @@ -39,7 +39,7 @@ Discogs {{/if}} - {{#if amId}} + {{#if allMusicId}} AllMusic {{/if}} diff --git a/src/UI/Artist/Details/TrackNumberCell.js b/src/UI/Artist/Details/TrackNumberCell.js index a597c5c8a..c085e1d15 100644 --- a/src/UI/Artist/Details/TrackNumberCell.js +++ b/src/UI/Artist/Details/TrackNumberCell.js @@ -4,18 +4,14 @@ var reqres = require('../../reqres'); var ArtistCollection = require('../ArtistCollection'); module.exports = NzbDroneCell.extend({ - className : 'episode-number-cell', + className : 'track-number-cell', template : 'Artist/Details/TrackNumberCellTemplate', render : function() { this.$el.empty(); this.$el.html(this.model.get('trackNumber')); - - var series = ArtistCollection.get(this.model.get('seriesId')); - - if (series.get('seriesType') === 'anime' && this.model.has('absoluteEpisodeNumber')) { - this.$el.html('{0} ({1})'.format(this.model.get('episodeNumber'), this.model.get('absoluteEpisodeNumber'))); - } + + var artist = ArtistCollection.get(this.model.get('artistId')); var alternateTitles = []; diff --git a/src/UI/Artist/Details/TrackRatingCell.js b/src/UI/Artist/Details/TrackRatingCell.js new file mode 100644 index 000000000..7c88c3963 --- /dev/null +++ b/src/UI/Artist/Details/TrackRatingCell.js @@ -0,0 +1,19 @@ +var NzbDroneCell = require('../../Cells/NzbDroneCell'); +var ArtistCollection = require('../ArtistCollection'); + +module.exports = NzbDroneCell.extend({ + className : 'track-rating-cell', + + + render : function() { + this.$el.empty(); + var ratings = this.model.get('ratings') + + if (ratings) { + this.$el.html(ratings.value + ' (' + ratings.votes + ' votes)'); + } + + this.delegateEvents(); + return this; + } +}); \ No newline at end of file diff --git a/src/UI/Artist/Index/ArtistIndexLayout.js b/src/UI/Artist/Index/ArtistIndexLayout.js index 81884d48c..936afb0f5 100644 --- a/src/UI/Artist/Index/ArtistIndexLayout.js +++ b/src/UI/Artist/Index/ArtistIndexLayout.js @@ -9,7 +9,7 @@ var RelativeDateCell = require('../../Cells/RelativeDateCell'); var ArtistTitleCell = require('../../Cells/ArtistTitleCell'); var TemplatedCell = require('../../Cells/TemplatedCell'); var ProfileCell = require('../../Cells/ProfileCell'); -var EpisodeProgressCell = require('../../Cells/EpisodeProgressCell'); +var TrackProgressCell = require('../../Cells/TrackProgressCell'); var ArtistActionsCell = require('../../Cells/ArtistActionsCell'); var ArtistStatusCell = require('../../Cells/ArtistStatusCell'); var FooterView = require('./FooterView'); @@ -62,10 +62,10 @@ module.exports = Marionette.Layout.extend({ cell : RelativeDateCell }, { - name : 'percentOfEpisodes', + name : 'percentOfTracks', label : 'Tracks', - cell : EpisodeProgressCell, - className : 'episode-progress-cell' + cell : TrackProgressCell, + className : 'track-progress-cell' }, { name : 'this', @@ -157,7 +157,7 @@ module.exports = Marionette.Layout.extend({ }, { title : 'Tracks', - name : 'percentOfEpisodes' + name : 'percentOfTracks' } ] }; @@ -330,8 +330,8 @@ module.exports = Marionette.Layout.extend({ _.each(this.artistCollection.models, function(model) { albums += model.get('albumCount'); - tracks += model.get('episodeCount'); // TODO: Refactor to Seasons and Tracks - trackFiles += model.get('episodeFileCount'); + tracks += model.get('trackCount'); // TODO: Refactor to Seasons and Tracks + trackFiles += model.get('trackFileCount'); /*if (model.get('status').toLowerCase() === 'ended') { ended++; diff --git a/src/UI/Artist/Index/Overview/ArtistOverviewItemViewTemplate.hbs b/src/UI/Artist/Index/Overview/ArtistOverviewItemViewTemplate.hbs index de34cc5cd..0f2babb96 100644 --- a/src/UI/Artist/Index/Overview/ArtistOverviewItemViewTemplate.hbs +++ b/src/UI/Artist/Index/Overview/ArtistOverviewItemViewTemplate.hbs @@ -48,7 +48,7 @@ {{profile profileId}}
- {{> EpisodeProgressPartial }} + {{> TrackProgressPartial }}
Path {{path}} diff --git a/src/UI/Artist/TrackCollection.js b/src/UI/Artist/TrackCollection.js index 302c416ea..6da0f0c53 100644 --- a/src/UI/Artist/TrackCollection.js +++ b/src/UI/Artist/TrackCollection.js @@ -21,7 +21,7 @@ module.exports = PageableCollection.extend({ this.artistId = options.artistId; }, - bySeason : function(album) { + byAlbum : function(album) { var filtered = this.filter(function(track) { return track.get('albumId') === album; }); @@ -36,11 +36,11 @@ module.exports = PageableCollection.extend({ var track2 = model2.get('trackNumber'); if (track1 < track2) { - return 1; + return -1; } if (track1 > track2) { - return -1; + return 1; } return 0; diff --git a/src/UI/Artist/artist.less b/src/UI/Artist/artist.less index e0f132e30..bfbf71de9 100644 --- a/src/UI/Artist/artist.less +++ b/src/UI/Artist/artist.less @@ -8,6 +8,11 @@ max-width: 100%; } +.album-cover { + min-width: 56px; + max-width: 100%; +} + .truncate { white-space: nowrap; overflow: hidden; @@ -22,6 +27,11 @@ width : 168px; } + .album-cover { + padding-left : 20px; + width : 168px; + } + .form-horizontal { margin-top : 10px; } @@ -81,9 +91,9 @@ .card; .opacity(0.9); margin : 30px 10px; - padding : 10px 25px; + padding : 30px 25px; - .show-hide-episodes { + .show-hide-tracks { .clickable(); text-align : center; @@ -91,6 +101,9 @@ .clickable(); } } + .header-text { + margin-top : 0px; + } } .artist-posters { @@ -261,39 +274,45 @@ .artist-album { - .episode-number-cell { + .track-number-cell { width : 40px; white-space: nowrap; } - .episode-air-date-cell { + .track-air-date-cell { width : 150px; } - .episode-status-cell { + .track-status-cell { + width : 100px; + } + .track-rating-cell { + width : 150px; + } + .track-explicit-cell { width : 100px; } - .episode-title-cell { + .track-title-cell { cursor : pointer; } } -.episode-detail-modal { +.track-detail-modal { - .episode-info { + .track-info { margin-bottom : 10px; } - .episode-overview { + .track-overview { font-style : italic; } - .episode-file-info { + .track-file-info { margin-top : 30px; font-size : 12px; } - .episode-history-details-cell .popover { + .track-history-details-cell .popover { max-width: 800px; } @@ -302,7 +321,7 @@ } } -.album-grid { +.track-grid { .toggle-cell { width : 28px; text-align : center; @@ -422,7 +441,7 @@ //artist Details .artist-not-monitored { - .album-monitored, .episode-monitored { + .album-monitored, .track-monitored { color: #888888; cursor: not-allowed; @@ -454,6 +473,28 @@ } } +.album-info { + .row { + margin-bottom : 3px; + + .label { + display : inline-block; + margin-bottom : 2px; + padding : 4px 6px 3px 6px; + max-width : 100%; + white-space : normal; + word-wrap : break-word; + } + } + + .album-info-links { + @media (max-width: @screen-sm-max) { + display : inline-block; + margin-top : 5px; + } + } +} + .scene-info { .key, .value { display : inline-block; diff --git a/src/UI/Cells/EpisodeProgressCellTemplate.hbs b/src/UI/Cells/EpisodeProgressCellTemplate.hbs deleted file mode 100644 index 98c06f4c0..000000000 --- a/src/UI/Cells/EpisodeProgressCellTemplate.hbs +++ /dev/null @@ -1 +0,0 @@ -{{> EpisodeProgressPartial }} \ No newline at end of file diff --git a/src/UI/Cells/TrackActionsCell.js b/src/UI/Cells/TrackActionsCell.js new file mode 100644 index 000000000..1bf8baaf4 --- /dev/null +++ b/src/UI/Cells/TrackActionsCell.js @@ -0,0 +1,44 @@ +var vent = require('vent'); +var NzbDroneCell = require('./NzbDroneCell'); +var CommandController = require('../Commands/CommandController'); + +module.exports = NzbDroneCell.extend({ + className : 'track-actions-cell', + + events : { + 'click .x-automatic-search' : '_automaticSearch', + 'click .x-manual-search' : '_manualSearch' + }, + + render : function() { + this.$el.empty(); + + this.$el.html('' + ''); + + CommandController.bindToCommand({ + element : this.$el.find('.x-automatic-search'), + command : { + name : 'trackSearch', + trackIds : [this.model.get('id')] + } + }); + + this.delegateEvents(); + return this; + }, + + _automaticSearch : function() { + CommandController.Execute('trackSearch', { + name : 'trackSearch', + trackIds : [this.model.get('id')] + }); + }, + + _manualSearch : function() { + vent.trigger(vent.Commands.ShowTrackDetails, { + track : this.cellValue, + hideSeriesLink : true, + openingTab : 'search' + }); + } +}); \ No newline at end of file diff --git a/src/UI/Cells/TrackExplicitCell.js b/src/UI/Cells/TrackExplicitCell.js new file mode 100644 index 000000000..19eb89ea5 --- /dev/null +++ b/src/UI/Cells/TrackExplicitCell.js @@ -0,0 +1,20 @@ +var vent = require('vent'); +var NzbDroneCell = require('./NzbDroneCell'); + +module.exports = NzbDroneCell.extend({ + className : 'track-explicit-cell', + template : 'Cells/TrackExplicitCellTemplate', + + render : function() { + var explicit = this.cellValue.get('explicit'); + var print = ''; + + if (explicit === true) { + print = 'Explicit'; + } + + this.$el.html(print); + return this; + } + +}); \ No newline at end of file diff --git a/src/UI/Cells/TrackMonitoredCell.js b/src/UI/Cells/TrackMonitoredCell.js new file mode 100644 index 000000000..dfd21b149 --- /dev/null +++ b/src/UI/Cells/TrackMonitoredCell.js @@ -0,0 +1,57 @@ +var _ = require('underscore'); +var ToggleCell = require('./ToggleCell'); +var ArtistCollection = require('../Artist/ArtistCollection'); +var Messenger = require('../Shared/Messenger'); + +module.exports = ToggleCell.extend({ + className : 'toggle-cell track-monitored', + + _originalOnClick : ToggleCell.prototype._onClick, + + _onClick : function(e) { + + var artist = ArtistCollection.get(this.model.get('artistId')); + + if (!artist.get('monitored')) { + + Messenger.show({ + message : 'Unable to change monitored state when series is not monitored', + type : 'error' + }); + + return; + } + + if (e.shiftKey && this.model.trackCollection.lastToggled) { + this._selectRange(); + + return; + } + + this._originalOnClick.apply(this, arguments); + this.model.trackCollection.lastToggled = this.model; + }, + + _selectRange : function() { + var trackCollection = this.model.trackCollection; + var lastToggled = trackCollection.lastToggled; + + var currentIndex = trackCollection.indexOf(this.model); + var lastIndex = trackCollection.indexOf(lastToggled); + + var low = Math.min(currentIndex, lastIndex); + var high = Math.max(currentIndex, lastIndex); + var range = _.range(low + 1, high); + + _.each(range, function(index) { + var model = trackCollection.at(index); + + model.set('monitored', lastToggled.get('monitored')); + model.save(); + }); + + this.model.set('monitored', lastToggled.get('monitored')); + this.model.save(); + this.model.trackCollection.lastToggled = undefined; + } +}); \ No newline at end of file diff --git a/src/UI/Cells/EpisodeProgressCell.js b/src/UI/Cells/TrackProgressCell.js similarity index 52% rename from src/UI/Cells/EpisodeProgressCell.js rename to src/UI/Cells/TrackProgressCell.js index 6208040c4..1b9f93599 100644 --- a/src/UI/Cells/EpisodeProgressCell.js +++ b/src/UI/Cells/TrackProgressCell.js @@ -2,21 +2,21 @@ var Marionette = require('marionette'); var NzbDroneCell = require('./NzbDroneCell'); module.exports = NzbDroneCell.extend({ - className : 'episode-progress-cell', - template : 'Cells/EpisodeProgressCellTemplate', + className : 'track-progress-cell', + template : 'Cells/TrackProgressCellTemplate', render : function() { - var episodeCount = this.model.get('episodeCount'); - var episodeFileCount = this.model.get('episodeFileCount'); + var trackCount = this.model.get('trackCount'); + var trackFileCount = this.model.get('trackFileCount'); var percent = 100; - if (episodeCount > 0) { - percent = episodeFileCount / episodeCount * 100; + if (trackCount > 0) { + percent = trackFileCount / trackCount * 100; } - this.model.set('percentOfEpisodes', percent); + this.model.set('percentOfTracks', percent); this.templateFunction = Marionette.TemplateCache.get(this.template); var data = this.model.toJSON(); diff --git a/src/UI/Cells/TrackProgressCellTemplate.hbs b/src/UI/Cells/TrackProgressCellTemplate.hbs new file mode 100644 index 000000000..b4899728f --- /dev/null +++ b/src/UI/Cells/TrackProgressCellTemplate.hbs @@ -0,0 +1 @@ +{{> TrackProgressPartial }} \ No newline at end of file diff --git a/src/UI/Cells/TrackStatusCell.js b/src/UI/Cells/TrackStatusCell.js new file mode 100644 index 000000000..c7e9e6362 --- /dev/null +++ b/src/UI/Cells/TrackStatusCell.js @@ -0,0 +1,127 @@ +var reqres = require('../reqres'); +var Backbone = require('backbone'); +var NzbDroneCell = require('./NzbDroneCell'); +var QueueCollection = require('../Activity/Queue/QueueCollection'); +var moment = require('moment'); +var FormatHelpers = require('../Shared/FormatHelpers'); + +module.exports = NzbDroneCell.extend({ + className : 'track-status-cell', + + render : function() { + this.listenTo(QueueCollection, 'sync', this._renderCell); + + this._renderCell(); + + return this; + }, + + _renderCell : function() { + + if (this.trackFile) { + this.stopListening(this.trackFile, 'change', this._refresh); + } + + this.$el.empty(); + + if (this.model) { + + var icon; + var tooltip; + + var hasAired = moment(this.model.get('airDateUtc')).isBefore(moment()); + this.trackFile = this._getFile(); + + if (this.trackFile) { + this.listenTo(this.trackFile, 'change', this._refresh); + + var quality = this.trackFile.get('quality'); + var revision = quality.revision; + var size = FormatHelpers.bytes(this.trackFile.get('size')); + var title = 'Track downloaded'; + + if (revision.real && revision.real > 0) { + title += '[REAL]'; + } + + if (revision.version && revision.version > 1) { + title += ' [PROPER]'; + } + + if (size !== '') { + title += ' - {0}'.format(size); + } + + if (this.trackFile.get('qualityCutoffNotMet')) { + this.$el.html('{1}'.format(title, quality.quality.name)); + } else { + this.$el.html('{1}'.format(title, quality.quality.name)); + } + + return; + } + + else { + var model = this.model; + var downloading = false; //TODO Fix this by adding to QueueCollection + + if (downloading) { + var progress = 100 - (downloading.get('sizeleft') / downloading.get('size') * 100); + + if (progress === 0) { + icon = 'icon-lidarr-downloading'; + tooltip = 'Track is downloading'; + } + + else { + this.$el.html('
'.format(progress.toFixed(1), downloading.get('title')) + + '
'.format(progress)); + return; + } + } + + else if (this.model.get('grabbed')) { + icon = 'icon-lidarr-downloading'; + tooltip = 'Track is downloading'; + } + + else if (!this.model.get('airDateUtc')) { + icon = 'icon-lidarr-tba'; + tooltip = 'TBA'; + } + + else if (hasAired) { + icon = 'icon-lidarr-missing'; + tooltip = 'Track missing from disk'; + } else { + icon = 'icon-lidarr-not-aired'; + tooltip = 'Track has not aired'; + } + } + + this.$el.html(''.format(icon, tooltip)); + } + }, + + _getFile : function() { + var hasFile = this.model.get('hasFile'); + + if (hasFile) { + var trackFile; + + if (reqres.hasHandler(reqres.Requests.GetTrackFileById)) { + trackFile = reqres.request(reqres.Requests.GetTrackFileById, this.model.get('trackFileId')); + } + + else if (this.model.has('trackFile')) { + trackFile = new Backbone.Model(this.model.get('trackFile')); + } + + if (trackFile) { + return trackFile; + } + } + + return undefined; + } +}); \ No newline at end of file diff --git a/src/UI/Cells/TrackTitleCell.js b/src/UI/Cells/TrackTitleCell.js new file mode 100644 index 000000000..66706c473 --- /dev/null +++ b/src/UI/Cells/TrackTitleCell.js @@ -0,0 +1,29 @@ +var vent = require('vent'); +var NzbDroneCell = require('./NzbDroneCell'); + +module.exports = NzbDroneCell.extend({ + className : 'track-title-cell', + + events : { + 'click' : '_showDetails' + }, + + render : function() { + var title = this.cellValue.get('title'); + + if (!title || title === '') { + title = 'TBA'; + } + + this.$el.html(title); + return this; + }, + + _showDetails : function() { + var hideArtistLink = this.column.get('hideArtistLink'); + vent.trigger(vent.Commands.ShowTrackDetails, { + track : this.cellValue, + hideArtistLink : hideArtistLink + }); + } +}); \ No newline at end of file diff --git a/src/UI/Cells/cells.less b/src/UI/Cells/cells.less index b2d97881d..0b398dd15 100644 --- a/src/UI/Cells/cells.less +++ b/src/UI/Cells/cells.less @@ -15,7 +15,7 @@ } } -.episode-title-cell { +.track-title-cell { .text-overflow(); color: #428bca; @@ -89,7 +89,7 @@ } } -td.episode-status-cell, td.quality-cell, td.history-quality-cell, td.progress-cell { +td.track-status-cell, td.quality-cell, td.history-quality-cell, td.progress-cell { text-align: center; width: 80px; @@ -118,7 +118,7 @@ td.episode-status-cell, td.quality-cell, td.history-quality-cell, td.progress-ce word-wrap: break-word; } -.episode-actions-cell { +.track-actions-cell { width: 55px; i { @@ -131,12 +131,12 @@ td.episode-status-cell, td.quality-cell, td.history-quality-cell, td.progress-ce } } -.episode-history-details-cell { +.track-history-details-cell { width : 18px; } -.episode-detail-modal { - .episode-actions-cell { +.track-detail-modal { + .track-actions-cell { width : 18px; } } @@ -173,7 +173,7 @@ td.episode-status-cell, td.quality-cell, td.history-quality-cell, td.progress-ce width : 80px; } -td.delete-episode-file-cell { +td.delete-track-file-cell { .clickable(); text-align : center; @@ -188,7 +188,7 @@ td.delete-episode-file-cell { width: 16px; } -.episode-number-cell { +.track-number-cell { cursor : default; } @@ -198,7 +198,7 @@ td.delete-episode-file-cell { .table>tbody>tr>td, .table>thead>tr>th { - &.episode-warning-cell { + &.track-warning-cell { width : 1px; padding-left : 0px; padding-right : 0px; diff --git a/src/UI/Content/icons.less b/src/UI/Content/icons.less index 5898f8f68..98b687bea 100644 --- a/src/UI/Content/icons.less +++ b/src/UI/Content/icons.less @@ -496,7 +496,7 @@ .fa-icon-content(@fa-var-retweet); } -.icon-lidarr-episode-file { +.icon-lidarr-track-file { .fa-icon-content(@fa-var-file-video-o); } diff --git a/src/UI/Content/progress-bars.less b/src/UI/Content/progress-bars.less index 9211b1c87..bc9d058dc 100644 --- a/src/UI/Content/progress-bars.less +++ b/src/UI/Content/progress-bars.less @@ -2,7 +2,7 @@ @import "Bootstrap/variables"; @import "variables"; -.progress.episode-progress { +.progress.track-progress { position : relative; margin-bottom : 2px; diff --git a/src/UI/Handlebars/Helpers/Album.js b/src/UI/Handlebars/Helpers/Album.js new file mode 100644 index 000000000..309d1e267 --- /dev/null +++ b/src/UI/Handlebars/Helpers/Album.js @@ -0,0 +1,46 @@ +var Handlebars = require('handlebars'); +var StatusModel = require('../../System/StatusModel'); +var moment = require('moment'); +var _ = require('underscore'); + +Handlebars.registerHelper('cover', function() { + + var placeholder = StatusModel.get('urlBase') + '/Content/Images/poster-dark.png'; + var cover = _.where(this.images, { coverType : 'cover' }); + + if (cover[0]) { + if (!cover[0].url.match(/^https?:\/\//)) { + return new Handlebars.SafeString(''.format(Handlebars.helpers.defaultImg.call(null, cover[0].url, 250))); + } else { + var url = cover[0].url.replace(/^https?\:/, ''); + return new Handlebars.SafeString(''.format(Handlebars.helpers.defaultImg.call(null, url))); + } + } + + return new Handlebars.SafeString(''.format(placeholder)); +}); + + + +Handlebars.registerHelper('MBAlbumUrl', function() { + return 'https://musicbrainz.org/release-group/' + this.mbId; +}); + +Handlebars.registerHelper('TADBAlbumUrl', function() { + return 'http://www.theaudiodb.com/album/' + this.tadbId; +}); + +Handlebars.registerHelper('discogsAlbumUrl', function() { + return 'https://www.discogs.com/master/' + this.discogsId; +}); + +Handlebars.registerHelper('allMusicAlbumUrl', function() { + return 'http://www.allmusic.com/album/' + this.allMusicId; +}); + +Handlebars.registerHelper('albumYear', function() { + return new Handlebars.SafeString('{0}'.format(moment(this.releaseDate).format('YYYY'))); +}); +Handlebars.registerHelper('albumReleaseDate', function() { + return new Handlebars.SafeString('{0}'.format(moment(this.releaseDate).format('L'))); +}); diff --git a/src/UI/Handlebars/Helpers/Artist.js b/src/UI/Handlebars/Helpers/Artist.js index 36c8983ba..745b17c76 100644 --- a/src/UI/Handlebars/Helpers/Artist.js +++ b/src/UI/Handlebars/Helpers/Artist.js @@ -9,16 +9,18 @@ Handlebars.registerHelper('poster', function() { if (poster[0]) { if (!poster[0].url.match(/^https?:\/\//)) { - return new Handlebars.SafeString(''.format(Handlebars.helpers.defaultImg.call(null, poster[0].url, 250))); + return new Handlebars.SafeString(''.format(Handlebars.helpers.defaultImg.call(null, poster[0].url, 250))); } else { var url = poster[0].url.replace(/^https?\:/, ''); - return new Handlebars.SafeString(''.format(Handlebars.helpers.defaultImg.call(null, url))); + return new Handlebars.SafeString(''.format(Handlebars.helpers.defaultImg.call(null, url))); } } - return new Handlebars.SafeString(''.format(placeholder)); + return new Handlebars.SafeString(''.format(placeholder)); }); + + Handlebars.registerHelper('MBUrl', function() { return 'https://musicbrainz.org/artist/' + this.mbId; }); @@ -32,25 +34,25 @@ Handlebars.registerHelper('discogsUrl', function() { }); Handlebars.registerHelper('allMusicUrl', function() { - return 'http://www.allmusic.com/artist/' + this.amId; + return 'http://www.allmusic.com/artist/' + this.allMusicId; }); Handlebars.registerHelper('route', function() { return StatusModel.get('urlBase') + '/artist/' + this.nameSlug; }); -Handlebars.registerHelper('percentOfEpisodes', function() { - var episodeCount = this.episodeCount; - var episodeFileCount = this.episodeFileCount; +// Handlebars.registerHelper('percentOfEpisodes', function() { +// var episodeCount = this.episodeCount; +// var episodeFileCount = this.episodeFileCount; - var percent = 100; +// var percent = 100; - if (episodeCount > 0) { - percent = episodeFileCount / episodeCount * 100; - } +// if (episodeCount > 0) { +// percent = episodeFileCount / episodeCount * 100; +// } - return percent; -}); +// return percent; +// }); Handlebars.registerHelper('percentOfTracks', function() { var trackCount = this.trackCount; diff --git a/src/UI/Handlebars/backbone.marionette.templates.js b/src/UI/Handlebars/backbone.marionette.templates.js index 0b1364284..6b47d0253 100644 --- a/src/UI/Handlebars/backbone.marionette.templates.js +++ b/src/UI/Handlebars/backbone.marionette.templates.js @@ -6,6 +6,7 @@ require('./Helpers/Numbers'); require('./Helpers/Episode'); //require('./Helpers/Series'); require('./Helpers/Artist'); +require('./Helpers/Album'); require('./Helpers/Quality'); require('./Helpers/System'); require('./Helpers/EachReverse');