From b4242f9fb226b58a5a36fefba7585696bc06b4a7 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 1 May 2013 22:50:34 -0700 Subject: [PATCH] Pagination for missing is alive! --- NzbDrone.Api/AutomapperBootstraper.cs | 4 + NzbDrone.Api/Episodes/EpisodeResource.cs | 1 + NzbDrone.Api/Missing/MissingModule.cs | 34 +++++- NzbDrone.Api/NzbDrone.Api.csproj | 1 + NzbDrone.Api/PagingResource.cs | 15 +++ .../EpisodesWithoutFilesFixture.cs | 25 ++++- NzbDrone.Core/Datastore/BasicRepository.cs | 2 - NzbDrone.Core/Datastore/PagingSpec.cs | 18 ++++ NzbDrone.Core/NzbDrone.Core.csproj | 1 + NzbDrone.Core/Tv/Episode.cs | 2 + NzbDrone.Core/Tv/EpisodeRepository.cs | Bin 7709 -> 9328 bytes NzbDrone.Core/Tv/EpisodeService.cs | 12 ++- UI/JsLibraries/backbone.backgrid.paginator.js | 2 +- UI/Missing/Collection.js | 20 ++-- UI/Missing/MissingLayout.js | 16 ++- UI/Missing/MissingLayoutTemplate.html | 5 + UI/Mixins/backbone.Backgrid.mixin.js | 99 ++++++++++++++++++ 17 files changed, 235 insertions(+), 22 deletions(-) create mode 100644 NzbDrone.Api/PagingResource.cs create mode 100644 NzbDrone.Core/Datastore/PagingSpec.cs diff --git a/NzbDrone.Api/AutomapperBootstraper.cs b/NzbDrone.Api/AutomapperBootstraper.cs index 393ecb18c..ba6d4c4e6 100644 --- a/NzbDrone.Api/AutomapperBootstraper.cs +++ b/NzbDrone.Api/AutomapperBootstraper.cs @@ -7,6 +7,7 @@ using NzbDrone.Api.QualityProfiles; using NzbDrone.Api.QualityType; using NzbDrone.Api.Resolvers; using NzbDrone.Api.Series; +using NzbDrone.Core.Datastore; using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; @@ -38,6 +39,9 @@ namespace NzbDrone.Api //Episode Mapper.CreateMap(); + + //Episode Paging + Mapper.CreateMap, PagingResource>(); } } } \ No newline at end of file diff --git a/NzbDrone.Api/Episodes/EpisodeResource.cs b/NzbDrone.Api/Episodes/EpisodeResource.cs index 409c0fe50..2290eb729 100644 --- a/NzbDrone.Api/Episodes/EpisodeResource.cs +++ b/NzbDrone.Api/Episodes/EpisodeResource.cs @@ -29,5 +29,6 @@ namespace NzbDrone.Api.Episodes public DateTime? GrabDate { get; set; } public PostDownloadStatusType PostDownloadStatus { get; set; } public Core.Tv.Series Series { get; set; } + public String SeriesTitle { get; set; } } } diff --git a/NzbDrone.Api/Missing/MissingModule.cs b/NzbDrone.Api/Missing/MissingModule.cs index 2acc848ac..99d0a5443 100644 --- a/NzbDrone.Api/Missing/MissingModule.cs +++ b/NzbDrone.Api/Missing/MissingModule.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using AutoMapper; using Nancy; using NzbDrone.Api.Episodes; using NzbDrone.Api.Extensions; +using NzbDrone.Core.Datastore; using NzbDrone.Core.Tv; namespace NzbDrone.Api.Missing @@ -25,11 +27,35 @@ namespace NzbDrone.Api.Missing bool includeSpecials; Boolean.TryParse(PrimitiveExtensions.ToNullSafeString(Request.Query.IncludeSpecials), out includeSpecials); - var episodes = _episodeService.EpisodesWithoutFiles(includeSpecials); + int pageSize; + Int32.TryParse(PrimitiveExtensions.ToNullSafeString(Request.Query.PageSize), out pageSize); + if (pageSize == 0) pageSize = 20; - //TODO: Include the Series Title - //TODO: Remove Take(20) - return Mapper.Map, List>(episodes).Take(20).AsResponse(); + int page; + Int32.TryParse(PrimitiveExtensions.ToNullSafeString(Request.Query.Page), out page); + if (page == 0) page = 1; + + var sortKey = PrimitiveExtensions.ToNullSafeString(Request.Query.SortKey) + .Equals("SeriesTitle", StringComparison.InvariantCultureIgnoreCase) + ? "SeriesTitle" + : "AirDate"; + + var sortDirection = PrimitiveExtensions.ToNullSafeString(Request.Query.SortDir) + .Equals("Asc", StringComparison.InvariantCultureIgnoreCase) + ? ListSortDirection.Ascending + : ListSortDirection.Descending; + + var pagingSpec = new PagingSpec + { + Page = page, + PageSize = pageSize, + SortKey = sortKey, + SortDirection = sortDirection + }; + + var result = _episodeService.EpisodesWithoutFiles(pagingSpec, includeSpecials); + + return Mapper.Map, PagingResource>(result).AsResponse(); } } } \ No newline at end of file diff --git a/NzbDrone.Api/NzbDrone.Api.csproj b/NzbDrone.Api/NzbDrone.Api.csproj index d718ddc63..5af7f8900 100644 --- a/NzbDrone.Api/NzbDrone.Api.csproj +++ b/NzbDrone.Api/NzbDrone.Api.csproj @@ -102,6 +102,7 @@ + diff --git a/NzbDrone.Api/PagingResource.cs b/NzbDrone.Api/PagingResource.cs new file mode 100644 index 000000000..389552c5b --- /dev/null +++ b/NzbDrone.Api/PagingResource.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Api +{ + public class PagingResource + { + public int Page { get; set; } + public string SortKey { get; set; } + public int TotalRecords { get; set; } + public List Records { get; set; } + } +} diff --git a/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs b/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs index a487bace7..405c51f27 100644 --- a/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs +++ b/NzbDrone.Core.Test/TvTests/EpisodeRepositoryTests/EpisodesWithoutFilesFixture.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; +using NzbDrone.Core.Datastore; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; @@ -38,15 +40,30 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests [Test] public void should_get_episodes() { - var episodes = Subject.EpisodesWithoutFiles(false); - episodes.Should().HaveCount(1); + var episodes = + Subject.EpisodesWithoutFiles(new PagingSpec + { + Page = 1, + PageSize = 10, + SortKey = "AirDate", + SortDirection = ListSortDirection.Ascending + }, false); + episodes.Records.Should().HaveCount(1); } [Test] + [Ignore("Specials not implemented")] public void should_get_episode_including_specials() { - var episodes = Subject.EpisodesWithoutFiles(true); - episodes.Should().HaveCount(2); + var episodes = + Subject.EpisodesWithoutFiles(new PagingSpec + { + Page = 1, + PageSize = 10, + SortKey = "AirDate", + SortDirection = ListSortDirection.Ascending + }, true); + episodes.Records.Should().HaveCount(2); } } } diff --git a/NzbDrone.Core/Datastore/BasicRepository.cs b/NzbDrone.Core/Datastore/BasicRepository.cs index 9d8369fd5..9b0f303f6 100644 --- a/NzbDrone.Core/Datastore/BasicRepository.cs +++ b/NzbDrone.Core/Datastore/BasicRepository.cs @@ -114,7 +114,6 @@ namespace NzbDrone.Core.Datastore return model; } - public void Delete(TModel model) { _dataMapper.Delete(c => c.Id == model.Id); @@ -185,6 +184,5 @@ namespace NzbDrone.Core.Datastore .Entity(model) .Execute(); } - } } diff --git a/NzbDrone.Core/Datastore/PagingSpec.cs b/NzbDrone.Core/Datastore/PagingSpec.cs new file mode 100644 index 000000000..08c498e4d --- /dev/null +++ b/NzbDrone.Core/Datastore/PagingSpec.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Datastore +{ + public class PagingSpec + { + public int Page { get; set; } + public int PageSize { get; set; } + public int TotalRecords { get; set; } + public string SortKey { get; set; } + public ListSortDirection SortDirection { get; set; } + public List Records { get; set; } + } +} diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index d313c6f54..f13a67421 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -208,6 +208,7 @@ + diff --git a/NzbDrone.Core/Tv/Episode.cs b/NzbDrone.Core/Tv/Episode.cs index 5e1da65f8..cce3045b7 100644 --- a/NzbDrone.Core/Tv/Episode.cs +++ b/NzbDrone.Core/Tv/Episode.cs @@ -71,6 +71,8 @@ namespace NzbDrone.Core.Tv } } + public String SeriesTitle { get; private set; } + public Series Series { get; set; } public EpisodeFile EpisodeFile { get; set; } diff --git a/NzbDrone.Core/Tv/EpisodeRepository.cs b/NzbDrone.Core/Tv/EpisodeRepository.cs index 63d2a8d52f330a51b454963064b7668242b519cb..a539ba78abc7a30b9c7df6f4ae89a98db3ce0c34 100644 GIT binary patch delta 1471 zcmbVM-A)rh6b=Z)R`4$bgE4y8M6$)Yz%5X)wu>x63vCnPl^MEY8MED4W@kYpq&|XV zuQk4c;lelYaSV^(nHF}oC|Est2?g!>x%NkK`)zXlUy5B|Bgtx-3JGVDoVHelRy^3U)vw1wo0+eeVdI zGLdAH1OXNB)=k!n@j&)r{B3CaDd3kfKminLmxR>PuJP%}le6LBpEv$HyZF~lt1~;> zsFte>u+Ete_>+4SLMj*yK{Dy#hQlF+Ch@`LxDgVS0FGEWmQ{&fe(W8XQ@VJoj?mZi z$g=k%;)SMB?*?tCm_{DzQed1C@3aFVP&wan`~zGOUzQ?IMuIvmp9@;TqH{1k-Or^s z|HV~g0)^1`Uw8+NGy7gsj4X7Z1rH=b5^K7oW6`|egt zbsy($wN~9sT(xNeO-`j^*I>QsR4&}LS>5SsCGoR*bdIaK*1Z^(<7)IHMDMKGHT%** zJvUwOoCZxIs8}hZKf(J`iQ2pPo-9KOaZi!f%9zv3H-OWieea@W!`XBiy#djN2WYsQ zsC?2lZSVZ4bE-o%+C=9Ni%LCT)^QlG;CMDO{u>1G)s_GN delta 169 zcmez1G1q2-{lr+UjWh2tPR?N#;|K_-X7T|h(a9UQxhB8k!lIMgxkX{h*`dmT;+m5WuyRj+&E&OtkH8jT E04WMKXaE2J diff --git a/NzbDrone.Core/Tv/EpisodeService.cs b/NzbDrone.Core/Tv/EpisodeService.cs index b891e5d28..ea726474f 100644 --- a/NzbDrone.Core/Tv/EpisodeService.cs +++ b/NzbDrone.Core/Tv/EpisodeService.cs @@ -4,7 +4,9 @@ using System.Linq; using NLog; using NzbDrone.Common.Messaging; using NzbDrone.Core.Configuration; +using NzbDrone.Core.Datastore; using NzbDrone.Core.Download; +using NzbDrone.Core.Helpers; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.MetadataSource; using NzbDrone.Core.Model; @@ -19,7 +21,7 @@ namespace NzbDrone.Core.Tv Episode GetEpisode(int seriesId, DateTime date); List GetEpisodeBySeries(int seriesId); List GetEpisodesBySeason(int seriesId, int seasonNumber); - List EpisodesWithoutFiles(bool includeSpecials); + PagingSpec EpisodesWithoutFiles(PagingSpec pagingSpec, bool includeSpecials); List GetEpisodesByFileId(int episodeFileId); List EpisodesWithFiles(); void RefreshEpisodeInfo(Series series); @@ -93,11 +95,13 @@ namespace NzbDrone.Core.Tv return _episodeRepository.GetEpisodes(seriesId, seasonNumber); } - public List EpisodesWithoutFiles(bool includeSpecials) + public PagingSpec EpisodesWithoutFiles(PagingSpec pagingSpec, bool includeSpecials) { - var episodes = _episodeRepository.EpisodesWithoutFiles(includeSpecials); + var episodeResult = _episodeRepository.EpisodesWithoutFiles(pagingSpec, includeSpecials); - return LinkSeriesToEpisodes(episodes); + episodeResult.Records = LinkSeriesToEpisodes(episodeResult.Records); + + return episodeResult; } public List GetEpisodesByFileId(int episodeFileId) diff --git a/UI/JsLibraries/backbone.backgrid.paginator.js b/UI/JsLibraries/backbone.backgrid.paginator.js index 4c89b1ade..d10125e65 100644 --- a/UI/JsLibraries/backbone.backgrid.paginator.js +++ b/UI/JsLibraries/backbone.backgrid.paginator.js @@ -58,7 +58,7 @@ @param {boolean} [options.fastForwardHandleLabels] Whether to render fast forward buttons. */ initialize: function (options) { - Backgrid.requireOptions(options, ["columns", "collection"]); + //Backgrid.requireOptions(options, ["columns", "collection"]); this.columns = options.columns; if (!(this.columns instanceof Backbone.Collection)) { diff --git a/UI/Missing/Collection.js b/UI/Missing/Collection.js index 6dfc35fda..d2ccbbb86 100644 --- a/UI/Missing/Collection.js +++ b/UI/Missing/Collection.js @@ -4,10 +4,6 @@ define(['app', 'Series/EpisodeModel'], function () { url : NzbDrone.Constants.ApiRoot + '/missing', model : NzbDrone.Series.EpisodeModel, - comparator: function (model) { - return model.get('airDate'); - }, - state: { pageSize: 10, sortKey: "airDate", @@ -18,12 +14,24 @@ define(['app', 'Series/EpisodeModel'], function () { totalPages: null, totalRecords: null, pageSize: 'pageSize', - sortKey: "sortBy", - order: "direction", + sortKey: "sortKey", + order: "sortDir", directions: { "-1": "asc", "1": "desc" } + }, + + parseState: function (resp, queryParams, state) { + return {totalRecords: resp.totalRecords}; + }, + + parseRecords: function (resp) { + if (resp) { + return resp.records; + } + + return resp; } }); }); \ No newline at end of file diff --git a/UI/Missing/MissingLayout.js b/UI/Missing/MissingLayout.js index b52ff7f8b..08fc99b12 100644 --- a/UI/Missing/MissingLayout.js +++ b/UI/Missing/MissingLayout.js @@ -13,7 +13,8 @@ define([ regions: { missing: '#x-missing', - toolbar: '#x-toolbar' + toolbar: '#x-toolbar', + pager : '#x-pager' }, showTable: function () { @@ -48,6 +49,12 @@ define([ editable : false, cell : 'airDate', headerCell: 'nzbDrone' +// headerCell: Backgrid.NzbDroneHeaderCell.extend({ +// initialize: function(options) { +// this.constructor.__super__.initialize.apply(this, [options]); +// this.direction('descending'); +// } +// }) }, { name : 'edit', @@ -66,6 +73,13 @@ define([ collection: this.missingCollection, className : 'table table-hover' })); + + this.pager.show(new Backgrid.NzbDronePaginator({ + columns: columns, + collection: this.missingCollection + })); + + this.missingCollection.getFirstPage(); }, initialize: function () { diff --git a/UI/Missing/MissingLayoutTemplate.html b/UI/Missing/MissingLayoutTemplate.html index a5013b74e..88040b869 100644 --- a/UI/Missing/MissingLayoutTemplate.html +++ b/UI/Missing/MissingLayoutTemplate.html @@ -4,3 +4,8 @@
+
+
+
+
+
\ No newline at end of file diff --git a/UI/Mixins/backbone.Backgrid.mixin.js b/UI/Mixins/backbone.Backgrid.mixin.js index aaa69bc67..e89645285 100644 --- a/UI/Mixins/backbone.Backgrid.mixin.js +++ b/UI/Mixins/backbone.Backgrid.mixin.js @@ -40,6 +40,40 @@ Backgrid.NzbDroneHeaderCell = Backgrid.HeaderCell.extend({ return this._direction; }, + onClick: function (e) { + e.preventDefault(); + + var columnName = this.column.get("name"); + + if (this.column.get("sortable")) { + if (this.direction() === "ascending") { + this.sort(columnName, "descending", function (left, right) { + var leftVal = left.get(columnName); + var rightVal = right.get(columnName); + if (leftVal === rightVal) { + return 0; + } + else if (leftVal > rightVal) { return -1; } + return 1; + }); + } + else if (this.direction() === "descending") { + this.sort(columnName, "ascending"); + } + else { + this.sort(columnName, "ascending", function (left, right) { + var leftVal = left.get(columnName); + var rightVal = right.get(columnName); + if (leftVal === rightVal) { + return 0; + } + else if (leftVal < rightVal) { return -1; } + return 1; + }); + } + } + }, + _convertDirectionToIcon: function (dir) { if (dir === 'ascending') { return 'icon-sort-up'; @@ -47,4 +81,69 @@ Backgrid.NzbDroneHeaderCell = Backgrid.HeaderCell.extend({ return 'icon-sort-down'; } +}); + +Backgrid.NzbDronePaginator = Backgrid.Extension.Paginator.extend({ + + events: { + "click a": "changePage", + "click i": "preventLinkClick" + }, + + windowSize: 1, + + fastForwardHandleLabels: { + first: '', + prev: '', + next: '', + last: '' + }, + + changePage: function (e) { + e.preventDefault(); + + var target = $(e.target); + + if (target.closest('li').hasClass('disabled')) { + return; + } + + if (!$(target).is('a')){ + target = target.parent('a'); + } + + var label = target.html(); + var ffLabels = this.fastForwardHandleLabels; + + var collection = this.collection; + + if (ffLabels) { + switch (label) { + case ffLabels.first: + collection.getFirstPage(); + return; + case ffLabels.prev: + if (collection.hasPrevious()) { + collection.getPreviousPage(); + } + return; + case ffLabels.next: + if (collection.hasNext()) { + collection.getNextPage(); + } + return; + case ffLabels.last: + collection.getLastPage(); + return; + } + } + + var state = collection.state; + var pageIndex = $(e.target).text() * 1; + collection.getPage(state.firstPage === 0 ? pageIndex - 1 : pageIndex); + }, + + preventLinkClick: function (e) { + e.preventDefault(); + } }); \ No newline at end of file