From f477c4640685d359d230b82dbaa5c4768c3a66d0 Mon Sep 17 00:00:00 2001 From: Devin Buhl Date: Fri, 10 Feb 2017 00:15:41 -0500 Subject: [PATCH] Wanted & Missing (#687) * Remove Season Pass, Update Header name, remove useless function * Cutoff Tab now works --- src/NzbDrone.Api/NzbDrone.Api.csproj | 1 + src/NzbDrone.Api/Wanted/MovieCutoffModule.cs | 48 ++++++++++++++++ src/NzbDrone.Api/Wanted/MovieMissingModule.cs | 9 +-- src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + src/NzbDrone.Core/Tv/MovieCutoffService.cs | 48 ++++++++++++++++ src/NzbDrone.Core/Tv/MovieRepository.cs | 37 +++++++++++++ src/UI/Wanted/Cutoff/CutoffUnmetCollection.js | 14 ++--- src/UI/Wanted/Cutoff/CutoffUnmetLayout.js | 55 +++++++------------ src/UI/Wanted/Missing/MissingLayout.js | 7 +-- 9 files changed, 165 insertions(+), 55 deletions(-) create mode 100644 src/NzbDrone.Api/Wanted/MovieCutoffModule.cs create mode 100644 src/NzbDrone.Core/Tv/MovieCutoffService.cs diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 0df46ff4a..ec5e30df4 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -267,6 +267,7 @@ + diff --git a/src/NzbDrone.Api/Wanted/MovieCutoffModule.cs b/src/NzbDrone.Api/Wanted/MovieCutoffModule.cs new file mode 100644 index 000000000..b454d5d78 --- /dev/null +++ b/src/NzbDrone.Api/Wanted/MovieCutoffModule.cs @@ -0,0 +1,48 @@ +using NzbDrone.Api.Movie; +using NzbDrone.Api.Movies; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Datastore; +using NzbDrone.SignalR; + +namespace NzbDrone.Api.Wanted +{ + public class MovieCutoffModule : NzbDroneRestModuleWithSignalR + { + private readonly IMovieCutoffService _movieCutoffService; + + public MovieCutoffModule(IMovieCutoffService movieCutoffService, + IMovieService movieService, + IQualityUpgradableSpecification qualityUpgradableSpecification, + IBroadcastSignalRMessage signalRBroadcaster) + : base(signalRBroadcaster, "wanted/cutoff") + { + _movieCutoffService = movieCutoffService; + GetResourcePaged = GetCutoffUnmetMovies; + } + + private PagingResource GetCutoffUnmetMovies(PagingResource pagingResource) + { + var pagingSpec = pagingResource.MapToPagingSpec("title", SortDirection.Ascending); + + if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "false") + { + pagingSpec.FilterExpression = v => v.Monitored == false; + } + else + { + pagingSpec.FilterExpression = v => v.Monitored == true; + } + + var resource = ApplyToPage(_movieCutoffService.MoviesWhereCutoffUnmet, pagingSpec, v => MapToResource(v)); + + return resource; + } + + private MovieResource MapToResource(Core.Tv.Movie movie) + { + var resource = movie.ToResource(); + return resource; + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Wanted/MovieMissingModule.cs b/src/NzbDrone.Api/Wanted/MovieMissingModule.cs index ea6c63fe1..4b1b89503 100644 --- a/src/NzbDrone.Api/Wanted/MovieMissingModule.cs +++ b/src/NzbDrone.Api/Wanted/MovieMissingModule.cs @@ -30,7 +30,7 @@ namespace NzbDrone.Api.Wanted private PagingResource GetMissingMovies(PagingResource pagingResource) { - var pagingSpec = pagingResource.MapToPagingSpec("physicalRelease", SortDirection.Descending); + var pagingSpec = pagingResource.MapToPagingSpec("title", SortDirection.Descending); if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "false") { @@ -46,13 +46,6 @@ namespace NzbDrone.Api.Wanted return resource; } - private MovieResource GetMovie(int id) - { - var movie = _movieService.GetMovie(id); - var resource = MapToResource(movie, true); - return resource; - } - private MovieResource MapToResource(Core.Tv.Movie movie, bool includeMovieFile) { var resource = movie.ToResource(); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 644461930..37bfae84f 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -1164,6 +1164,7 @@ + diff --git a/src/NzbDrone.Core/Tv/MovieCutoffService.cs b/src/NzbDrone.Core/Tv/MovieCutoffService.cs new file mode 100644 index 000000000..7d55257cd --- /dev/null +++ b/src/NzbDrone.Core/Tv/MovieCutoffService.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Profiles; +using NzbDrone.Core.Qualities; + +namespace NzbDrone.Core.Tv +{ + public interface IMovieCutoffService + { + PagingSpec MoviesWhereCutoffUnmet(PagingSpec pagingSpec); + } + + public class MovieCutoffService : IMovieCutoffService + { + private readonly IMovieRepository _movieRepository; + private readonly IProfileService _profileService; + private readonly Logger _logger; + + public MovieCutoffService(IMovieRepository movieRepository, IProfileService profileService, Logger logger) + { + _movieRepository = movieRepository; + _profileService = profileService; + _logger = logger; + } + + public PagingSpec MoviesWhereCutoffUnmet(PagingSpec pagingSpec) + { + var qualitiesBelowCutoff = new List(); + var profiles = _profileService.All(); + + //Get all items less than the cutoff + foreach (var profile in profiles) + { + var cutoffIndex = profile.Items.FindIndex(v => v.Quality == profile.Cutoff); + var belowCutoff = profile.Items.Take(cutoffIndex).ToList(); + + if (belowCutoff.Any()) + { + qualitiesBelowCutoff.Add(new QualitiesBelowCutoff(profile.Id, belowCutoff.Select(i => i.Quality.Id))); + } + } + + return _movieRepository.MoviesWhereCutoffUnmet(pagingSpec, qualitiesBelowCutoff); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Tv/MovieRepository.cs b/src/NzbDrone.Core/Tv/MovieRepository.cs index e9cd70af7..974fdc3d3 100644 --- a/src/NzbDrone.Core/Tv/MovieRepository.cs +++ b/src/NzbDrone.Core/Tv/MovieRepository.cs @@ -6,6 +6,7 @@ using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Datastore.Extensions; using Marr.Data.QGen; using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Qualities; namespace NzbDrone.Core.Tv { @@ -22,6 +23,7 @@ namespace NzbDrone.Core.Tv PagingSpec MoviesWithoutFiles(PagingSpec pagingSpec); List GetMoviesByFileId(int fileId); void SetFileId(int fileId, int movieId); + PagingSpec MoviesWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff); } public class MovieRepository : BasicRepository, IMovieRepository @@ -200,6 +202,41 @@ namespace NzbDrone.Core.Tv .Take(pagingSpec.PageSize); } + public PagingSpec MoviesWhereCutoffUnmet(PagingSpec pagingSpec, List qualitiesBelowCutoff) + { + + pagingSpec.TotalRecords = MoviesWhereCutoffUnmetQuery(pagingSpec, qualitiesBelowCutoff).GetRowCount(); + pagingSpec.Records = MoviesWhereCutoffUnmetQuery(pagingSpec, qualitiesBelowCutoff).ToList(); + + return pagingSpec; + } + + private SortBuilder MoviesWhereCutoffUnmetQuery(PagingSpec pagingSpec, List qualitiesBelowCutoff) + { + return Query.Join(JoinType.Left, e => e.MovieFile, (e, s) => e.MovieFileId == s.Id) + .Where(pagingSpec.FilterExpression) + .AndWhere(m => m.MovieFileId != 0) + .AndWhere(BuildQualityCutoffWhereClause(qualitiesBelowCutoff)) + .OrderBy(pagingSpec.OrderByClause(), pagingSpec.ToSortDirection()) + .Skip(pagingSpec.PagingOffset()) + .Take(pagingSpec.PageSize); + } + + private string BuildQualityCutoffWhereClause(List qualitiesBelowCutoff) + { + var clauses = new List(); + + foreach (var profile in qualitiesBelowCutoff) + { + foreach (var belowCutoff in profile.QualityIds) + { + clauses.Add(string.Format("([t0].[ProfileId] = {0} AND [t1].[Quality] LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff)); + } + } + + return string.Format("({0})", string.Join(" OR ", clauses)); + } + public Movie FindByTmdbId(int tmdbid) { return Query.Where(m => m.TmdbId == tmdbid).FirstOrDefault(); diff --git a/src/UI/Wanted/Cutoff/CutoffUnmetCollection.js b/src/UI/Wanted/Cutoff/CutoffUnmetCollection.js index 5f2a6546f..e12a4084c 100644 --- a/src/UI/Wanted/Cutoff/CutoffUnmetCollection.js +++ b/src/UI/Wanted/Cutoff/CutoffUnmetCollection.js @@ -1,5 +1,5 @@ var _ = require('underscore'); -var EpisodeModel = require('../../Series/EpisodeModel'); +var MovieModel = require('../../Movies/MovieModel'); var PagableCollection = require('backbone.pageable'); var AsFilteredCollection = require('../../Mixins/AsFilteredCollection'); var AsSortedCollection = require('../../Mixins/AsSortedCollection'); @@ -7,13 +7,13 @@ var AsPersistedStateCollection = require('../../Mixins/AsPersistedStateCollectio var Collection = PagableCollection.extend({ url : window.NzbDrone.ApiRoot + '/wanted/cutoff', - model : EpisodeModel, + model : MovieModel, tableName : 'wanted.cutoff', state : { pageSize : 15, - sortKey : 'airDateUtc', - order : 1 + sortKey : 'title', + order : -1 }, queryParams : { @@ -40,9 +40,9 @@ var Collection = PagableCollection.extend({ ], }, - sortMappings : { - 'series' : { sortKey : 'series.sortTitle' } - }, + // sortMappings : { + // 'this' : { sortKey : 'this.sortTitle' } + // }, parseState : function(resp) { return { totalRecords : resp.totalRecords }; diff --git a/src/UI/Wanted/Cutoff/CutoffUnmetLayout.js b/src/UI/Wanted/Cutoff/CutoffUnmetLayout.js index 2221f04fe..ef6b9eacc 100644 --- a/src/UI/Wanted/Cutoff/CutoffUnmetLayout.js +++ b/src/UI/Wanted/Cutoff/CutoffUnmetLayout.js @@ -3,16 +3,15 @@ var Marionette = require('marionette'); var Backgrid = require('backgrid'); var CutoffUnmetCollection = require('./CutoffUnmetCollection'); var SelectAllCell = require('../../Cells/SelectAllCell'); -var SeriesTitleCell = require('../../Cells/SeriesTitleCell'); -var EpisodeNumberCell = require('../../Cells/EpisodeNumberCell'); -var EpisodeTitleCell = require('../../Cells/EpisodeTitleCell'); +var MovieTitleCell = require('../../Cells/MovieTitleCell'); +var MovieStatusWithTextCell = require('../../Cells/MovieStatusWithTextCell'); var RelativeDateCell = require('../../Cells/RelativeDateCell'); -var EpisodeStatusCell = require('../../Cells/EpisodeStatusCell'); var GridPager = require('../../Shared/Grid/Pager'); var ToolbarLayout = require('../../Shared/Toolbar/ToolbarLayout'); var LoadingView = require('../../Shared/LoadingView'); var Messenger = require('../../Shared/Messenger'); var CommandController = require('../../Commands/CommandController'); + require('backgrid.selectall'); require('../../Mixins/backbone.signalr.mixin'); @@ -37,32 +36,25 @@ module.exports = Marionette.Layout.extend({ sortable : false }, { - name : 'series', - label : 'Series Title', - cell : SeriesTitleCell, - sortValue : 'series.sortTitle' + name : 'this', + label : 'Movie Title', + cell : MovieTitleCell, + sortValue : this.sortTitle }, { - name : 'this', - label : 'Episode', - cell : EpisodeNumberCell, - sortable : false + name : 'inCinemas', + label : 'In Cinemas', + cell : RelativeDateCell }, { - name : 'this', - label : 'Episode Title', - cell : EpisodeTitleCell, - sortable : false - }, - { - name : 'airDateUtc', - label : 'Air Date', - cell : RelativeDateCell + name : 'physicalRelease', + label : 'Physical Release', + cell : RelativeDateCell }, { name : 'status', label : 'Status', - cell : EpisodeStatusCell, + cell : MovieStatusWithTextCell, sortable : false } ], @@ -105,11 +97,6 @@ module.exports = Marionette.Layout.extend({ callback : this._searchSelected, ownerContext : this, className : 'x-search-selected' - }, - { - title : 'Season Pass', - icon : 'icon-sonarr-monitored', - route : 'seasonpass' } ] }; @@ -148,9 +135,9 @@ module.exports = Marionette.Layout.extend({ })); CommandController.bindToCommand({ - element : this.$('.x-search-selected'), - command : { - name : 'episodeSearch' + element : this.$('.x-search-selected'), + command : { + name : 'moviesSearch' } }); }, @@ -172,7 +159,7 @@ module.exports = Marionette.Layout.extend({ if (selected.length === 0) { Messenger.show({ type : 'error', - message : 'No episodes selected' + message : 'No movies selected' }); return; @@ -180,9 +167,9 @@ module.exports = Marionette.Layout.extend({ var ids = _.pluck(selected, 'id'); - CommandController.Execute('episodeSearch', { - name : 'episodeSearch', - episodeIds : ids + CommandController.Execute('moviesSearch', { + name : 'moviesSearch', + movieIds : ids }); } }); \ No newline at end of file diff --git a/src/UI/Wanted/Missing/MissingLayout.js b/src/UI/Wanted/Missing/MissingLayout.js index 1572708b0..fbf1206f8 100644 --- a/src/UI/Wanted/Missing/MissingLayout.js +++ b/src/UI/Wanted/Missing/MissingLayout.js @@ -49,7 +49,7 @@ module.exports = Marionette.Layout.extend({ }, { name : 'physicalRelease', - label : 'PhysicalRelease', + label : 'Physical Release', cell : RelativeDateCell }, { @@ -115,11 +115,6 @@ module.exports = Marionette.Layout.extend({ ownerContext : this, className : 'x-unmonitor-selected' }, - { - title : 'Season Pass', - icon : 'icon-sonarr-monitored', - route : 'seasonpass' - }, { title : 'Rescan Drone Factory Folder', icon : 'icon-sonarr-refresh',