From 961ba4297ae4d1afb395715c0709457bd17840e0 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Wed, 17 Apr 2019 17:19:06 +0100 Subject: [PATCH] We now have infinite scroll! Also fixed some more unit tests --- .../Engine/Interfaces/ITvSearchEngine.cs | 1 + src/Ombi.Core/Engine/TvSearchEngine.cs | 14 +++ .../Engine/V2/MovieSearchEngineV2.cs | 28 ++--- .../PagnationHelperTests.cs | 21 +++- src/Ombi.Helpers/PaginationHelper.cs | 18 ++- .../Ombi.Schedule.Tests.csproj | 1 + .../PlexAvailabilityCheckerTests.cs | 12 +- .../src/app/discover/discover.component.html | 7 +- .../src/app/discover/discover.component.ts | 106 +++++++++++++----- .../src/app/services/searchV2.service.ts | 5 + src/Ombi/Controllers/V2/SearchController.cs | 15 ++- 11 files changed, 161 insertions(+), 67 deletions(-) diff --git a/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs b/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs index 1ca119862..8e9148b4e 100644 --- a/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs @@ -9,6 +9,7 @@ namespace Ombi.Core.Engine.Interfaces Task> Search(string searchTerm); Task GetShowInformation(int tvdbid); Task> Popular(); + Task> Popular(int currentlyLoaded, int amountToLoad); Task> Anticipated(); Task> MostWatches(); Task> Trending(); diff --git a/src/Ombi.Core/Engine/TvSearchEngine.cs b/src/Ombi.Core/Engine/TvSearchEngine.cs index b04e1d711..16f951f10 100644 --- a/src/Ombi.Core/Engine/TvSearchEngine.cs +++ b/src/Ombi.Core/Engine/TvSearchEngine.cs @@ -21,6 +21,7 @@ using Ombi.Core.Authentication; using Ombi.Helpers; using Ombi.Settings.Settings.Models; using Ombi.Store.Entities; +using TraktApiSharp.Objects.Get.Shows; namespace Ombi.Core.Engine { @@ -127,6 +128,19 @@ namespace Ombi.Core.Engine return processed; } + public async Task> Popular(int currentlyLoaded, int amountToLoad) + { + var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit); + var results = new List(); + foreach (var pagesToLoad in pages) + { + var apiResult = await TraktApi.GetPopularShows(pagesToLoad.Page, ResultLimit); + results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take)); + } + var processed = ProcessResults(results); + return processed; + } + public async Task> Anticipated() { diff --git a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs index 3029c4fc1..893679e9b 100644 --- a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs +++ b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs @@ -98,6 +98,9 @@ namespace Ombi.Core.Engine.V2 return null; } + + private const int _theMovieDbMaxPageItems = 20; + /// /// Gets popular movies by paging /// @@ -106,28 +109,15 @@ namespace Ombi.Core.Engine.V2 { var langCode = await DefaultLanguageCode(null); - // Pages of 20 - if(toLoad > 20) - { - throw new ApplicationException("Please load less than or equal to 20 items at a time due to a API limit"); - } + var pages = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, _theMovieDbMaxPageItems); - // TheMovieDb only shows pages of 20, let's work out how many we need to load - - var page = Math.Round((decimal)(currentlyLoaded / 10) / 2, 0); - if(page == 0) + var results = new List(); + foreach (var pagesToLoad in pages) { - // First page + var apiResult = await MovieApi.PopularMovies(langCode, pagesToLoad.Page); + results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take)); } - - - var result = await MovieApi.PopularMovies(langCode); - - if (result != null) - { - return await TransformMovieResultsToResponse(result.Shuffle().Take(ResultLimit)); // Take x to stop us overloading the API - } - return null; + return await TransformMovieResultsToResponse(results); } /// diff --git a/src/Ombi.Helpers.Tests/PagnationHelperTests.cs b/src/Ombi.Helpers.Tests/PagnationHelperTests.cs index 9c637af6e..4b058c403 100644 --- a/src/Ombi.Helpers.Tests/PagnationHelperTests.cs +++ b/src/Ombi.Helpers.Tests/PagnationHelperTests.cs @@ -14,12 +14,12 @@ namespace Ombi.Helpers.Tests var pages = result.Select(x => x.Page).ToArray(); Assert.That(pages.Length, Is.EqualTo(expectedPages.Length), "Did not contain the correct amount of pages"); - for (int i = 0; i < pages.Length; i++) + for (var i = 0; i < pages.Length; i++) { Assert.That(pages[i], Is.EqualTo(expectedPages[i])); } } - + public static IEnumerable TestPageData { get @@ -37,6 +37,7 @@ namespace Ombi.Helpers.Tests yield return new TestCaseData(20, 9, 20, new[] { 2 }).SetName("Pagination_Load_LessThan_Half_Second_Page"); yield return new TestCaseData(30, 10, 20, new[] { 2 }).SetName("Pagination_Load_All_Second_Page_With_Half_Take"); yield return new TestCaseData(49, 1, 50, new[] { 1 }).SetName("Pagination_Load_49_OutOf_50"); + yield return new TestCaseData(49, 1, 100,new[] { 1 }).SetName("Pagination_Load_50_OutOf_100"); } } @@ -56,7 +57,7 @@ namespace Ombi.Helpers.Tests yield return new TestCaseData(0, 10, 20, 10, 0).SetName("PaginationPosition_Load_First_Half_Of_Page"); yield return new TestCaseData(10, 10, 20, 10, 10).SetName("PaginationPosition_Load_EndHalf_First_Page"); yield return new TestCaseData(19, 1, 20, 1, 19).SetName("PaginationPosition_Load_LastItem_Of_First_Page"); - yield return new TestCaseData(20, 20, 20, 20, 20).SetName("PaginationPosition_Load_Full_Second_Page"); + yield return new TestCaseData(20, 20, 300, 20, 20).SetName("PaginationPosition_Load_Full_Second_Page"); } } @@ -87,10 +88,20 @@ namespace Ombi.Helpers.Tests yield return new TestCaseData(18, 22, 20, new List { new MultiplePagesTestData(1, 2, 18), new MultiplePagesTestData(2, 20, 0) }) .SetName("PaginationPosition_Load_EndFirstPage_Full_SecondPage"); yield return new TestCaseData(38, 4, 20, new List { new MultiplePagesTestData(2, 2, 18), new MultiplePagesTestData(3, 2, 0) }) - .SetName("PaginationPosition_Load_EndSecondPage_Some_ThirdPage"); + .SetName("PaginationPosition_Load_EndSecondPage_Some_ThirdPage"); + yield return new TestCaseData(15, 20, 10, new List { new MultiplePagesTestData(2, 5, 5), new MultiplePagesTestData(3, 10, 0), new MultiplePagesTestData(4, 5, 0) }) + .SetName("PaginationPosition_Load_EndSecondPage_All_ThirdPage_Some_ForthPage"); + yield return new TestCaseData(24, 12, 12, new List { new MultiplePagesTestData(3, 12, 0) }) + .SetName("PaginationPosition_Load_ThirdPage_Of_12"); + yield return new TestCaseData(12, 12, 12, new List { new MultiplePagesTestData(2, 12, 0) }) + .SetName("PaginationPosition_Load_SecondPage_Of_12"); + yield return new TestCaseData(40, 20, 20, new List { new MultiplePagesTestData(3, 20, 0) }) + .SetName("PaginationPosition_Load_FullThird_Page"); + yield return new TestCaseData(240, 12, 20, new List { new MultiplePagesTestData(13, 12, 0) }) + .SetName("PaginationPosition_Load_Page_13"); } } - + public class MultiplePagesTestData { public MultiplePagesTestData(int page, int take, int skip) diff --git a/src/Ombi.Helpers/PaginationHelper.cs b/src/Ombi.Helpers/PaginationHelper.cs index cf4303e43..520f56b19 100644 --- a/src/Ombi.Helpers/PaginationHelper.cs +++ b/src/Ombi.Helpers/PaginationHelper.cs @@ -17,12 +17,16 @@ namespace Ombi.Helpers var lastPage = lastItemIndex / maxItemsPerPage + 1; var stopPos = lastItemIndex % maxItemsPerPage + 1; - if (currentlyLoaded > maxItemsPerPage) + while (currentlyLoaded > maxItemsPerPage) { - currentlyLoaded = currentlyLoaded - maxItemsPerPage; + currentlyLoaded -= maxItemsPerPage; + } + if ((currentlyLoaded % maxItemsPerPage) == 0 && (currentlyLoaded % toTake) == 0) + { + currentlyLoaded = 0; } - var page1 = new PagesToLoad {Page = firstPage, Skip = currentlyLoaded, Take = toTake}; + var page1 = new PagesToLoad { Page = firstPage, Skip = currentlyLoaded, Take = toTake }; if (toTake + startPos - 1 > maxItemsPerPage) { @@ -31,15 +35,19 @@ namespace Ombi.Helpers for (var i = firstPage + 1; i < lastPage; i++) { - var nextPage = new PagesToLoad {Page = i, Skip = 0, Take = maxItemsPerPage}; + var nextPage = new PagesToLoad { Page = i, Skip = 0, Take = maxItemsPerPage }; result.Add(nextPage); } - var pageN = new PagesToLoad {Page = lastPage, Skip = 0, Take = stopPos}; + var pageN = new PagesToLoad { Page = lastPage, Skip = 0, Take = stopPos }; result.Add(pageN); } else { + if (page1.Skip + page1.Take > maxItemsPerPage) + { + page1.Skip = 0; + } result.Add(page1); } diff --git a/src/Ombi.Schedule.Tests/Ombi.Schedule.Tests.csproj b/src/Ombi.Schedule.Tests/Ombi.Schedule.Tests.csproj index 8254957fa..367221735 100644 --- a/src/Ombi.Schedule.Tests/Ombi.Schedule.Tests.csproj +++ b/src/Ombi.Schedule.Tests/Ombi.Schedule.Tests.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs b/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs index 55c9dc288..cd212b1e5 100644 --- a/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs +++ b/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Castle.Components.DictionaryAdapter; using Hangfire; using Moq; +using MockQueryable.Moq; using NUnit.Framework; using Ombi.Core.Notifications; using Ombi.Schedule.Jobs.Plex; @@ -68,7 +69,6 @@ namespace Ombi.Schedule.Tests } [Test] - [Ignore("EF IAsyncQueryProvider")] public async Task ProcessTv_ShouldMark_Episode_Available_WhenInPlex() { var request = new ChildRequests @@ -90,21 +90,25 @@ namespace Ombi.Schedule.Tests } } } + }, + RequestedUser = new OmbiUser + { + Email = "abc" } }; - _tv.Setup(x => x.GetChild()).Returns(new List { request }.AsQueryable()); + _tv.Setup(x => x.GetChild()).Returns(new List { request }.AsQueryable().BuildMock().Object); _repo.Setup(x => x.GetAllEpisodes()).Returns(new List { new PlexEpisode { Series = new PlexServerContent { - ImdbId = 1.ToString(), + TvDbId = 1.ToString(), }, EpisodeNumber = 1, SeasonNumber = 2 } - }.AsQueryable); + }.AsQueryable().BuildMock().Object); _repo.Setup(x => x.Include(It.IsAny>(),It.IsAny>>())); await Checker.Start(); diff --git a/src/Ombi/ClientApp/src/app/discover/discover.component.html b/src/Ombi/ClientApp/src/app/discover/discover.component.html index cffab5d79..8919cf202 100644 --- a/src/Ombi/ClientApp/src/app/discover/discover.component.html +++ b/src/Ombi/ClientApp/src/app/discover/discover.component.html @@ -13,9 +13,7 @@ color="primary">{{'Discovery.UpcomingTab' | translate}} -
- -
+
+
+ +
diff --git a/src/Ombi/ClientApp/src/app/discover/discover.component.ts b/src/Ombi/ClientApp/src/app/discover/discover.component.ts index 7c30eb20e..5be78f404 100644 --- a/src/Ombi/ClientApp/src/app/discover/discover.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/discover.component.ts @@ -10,10 +10,10 @@ import { trigger, transition, style, animate } from "@angular/animations"; animations: [ trigger('slideIn', [ transition(':enter', [ - style({transform: 'translateX(100%)'}), - animate('200ms ease-in', style({transform: 'translateY(0%)'})) + style({ transform: 'translateX(100%)' }), + animate('200ms ease-in', style({ transform: 'translateY(0%)' })) ]) - ]) + ]) ], }) export class DiscoverComponent implements OnInit { @@ -31,48 +31,60 @@ export class DiscoverComponent implements OnInit { public loadingFlag: boolean; private contentLoaded: number; + private isScrolling: boolean = false; constructor(private searchService: SearchV2Service) { } public async ngOnInit() { this.loading() - this.movies = await this.searchService.popularMovies().toPromise(); - this.tvShows = await this.searchService.popularTv().toPromise(); + this.movies = await this.searchService.popularMoviesByPage(0,12).toPromise(); + this.tvShows = await this.searchService.popularTvByPage(0,12); this.contentLoaded = 12; - - this.createModel(true); + + this.createInitialModel(); } public async onScroll() { - console.log("SCROLLED!") - this.movies = await this.searchService.popularMoviesByPage(this.contentLoaded, 12).toPromise(); - this.tvShows = []; - this.contentLoaded+=12; - - this.createModel(false); + if (!this.contentLoaded) { + return; + } + if (!this.isScrolling) { + debugger; + this.isScrolling = true; + console.log("SCROLLED!") + this.loading(); + if (this.popularActive) { + this.movies = await this.searchService.popularMoviesByPage(this.contentLoaded, 12).toPromise(); + this.tvShows = await this.searchService.popularTvByPage(this.contentLoaded, 12); + this.contentLoaded += 12; + } + + this.createModel(); + this.isScrolling = false; + } } public async popular() { this.clear(); - + this.contentLoaded = 12; this.loading() this.popularActive = true; this.trendingActive = false; this.upcomingActive = false; - this.movies = await this.searchService.popularMovies().toPromise(); - this.tvShows = await this.searchService.popularTv().toPromise(); + this.movies = await this.searchService.popularMoviesByPage(0, 12).toPromise(); + this.tvShows = await this.searchService.popularTvByPage(0, 12); + - - this.createModel(true); + this.createModel(); } - - public async trending() { + + public async trending() { this.clear(); - + this.contentLoaded = 12; this.loading() this.popularActive = false; @@ -81,10 +93,10 @@ export class DiscoverComponent implements OnInit { this.movies = await this.searchService.nowPlayingMovies().toPromise(); this.tvShows = await this.searchService.trendingTv().toPromise(); - this.createModel(true); - } + this.createModel(); + } - public async upcoming() { + public async upcoming() { this.clear(); this.contentLoaded = 12; this.loading() @@ -94,11 +106,46 @@ export class DiscoverComponent implements OnInit { this.movies = await this.searchService.upcomingMovies().toPromise(); this.tvShows = await this.searchService.anticipatedTv().toPromise(); - this.createModel(true); + this.createModel(); } - private createModel(shuffle: boolean) { + private createModel() { + const tempResults = []; + this.movies.forEach(m => { + tempResults.push({ + available: m.available, + posterPath: `https://image.tmdb.org/t/p/w300/${m.posterPath}`, + requested: m.requested, + title: m.title, + type: RequestType.movie, + id: m.id, + url: `http://www.imdb.com/title/${m.imdbId}/`, + rating: m.voteAverage, + overview: m.overview, + approved: m.approved + }); + }); + this.tvShows.forEach(m => { + tempResults.push({ + available: m.available, + posterPath: "../../../images/default_tv_poster.png", + requested: m.requested, + title: m.title, + type: RequestType.tvShow, + id: m.id, + url: undefined, + rating: +m.rating, + overview: m.overview, + approved: m.approved + }); + }); + this.shuffle(tempResults); + this.discoverResults.push(...tempResults); + this.finishLoading(); + } + + private createInitialModel() { this.movies.forEach(m => { this.discoverResults.push({ available: m.available, @@ -127,12 +174,11 @@ export class DiscoverComponent implements OnInit { approved: m.approved }); }); - if(shuffle) { - this.shuffle(this.discoverResults); - } + this.shuffle(this.discoverResults); + this.finishLoading(); } - private shuffle(discover: IDiscoverCardResult[]) : IDiscoverCardResult[] { + private shuffle(discover: IDiscoverCardResult[]): IDiscoverCardResult[] { for (let i = discover.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [discover[i], discover[j]] = [discover[j], discover[i]]; diff --git a/src/Ombi/ClientApp/src/app/services/searchV2.service.ts b/src/Ombi/ClientApp/src/app/services/searchV2.service.ts index 29d880e76..7e720a31f 100644 --- a/src/Ombi/ClientApp/src/app/services/searchV2.service.ts +++ b/src/Ombi/ClientApp/src/app/services/searchV2.service.ts @@ -53,6 +53,11 @@ export class SearchV2Service extends ServiceHelpers { public popularTv(): Observable { return this.http.get(`${this.url}/Tv/popular`, { headers: this.headers }); } + + public popularTvByPage(currentlyLoaded: number, toLoad: number): Promise { + return this.http.get(`${this.url}/Tv/popular/${currentlyLoaded}/${toLoad}`, { headers: this.headers }).toPromise(); + } + public mostWatchedTv(): Observable { return this.http.get(`${this.url}/Tv/mostwatched`, { headers: this.headers }); } diff --git a/src/Ombi/Controllers/V2/SearchController.cs b/src/Ombi/Controllers/V2/SearchController.cs index a8effce96..c29e2365d 100644 --- a/src/Ombi/Controllers/V2/SearchController.cs +++ b/src/Ombi/Controllers/V2/SearchController.cs @@ -127,7 +127,7 @@ namespace Ombi.Controllers.V2 ///
/// We use TheMovieDb as the Movie Provider /// - [HttpGet("movie/popular/{currentPostion}/{amountToLoad}")] + [HttpGet("movie/popular/{currentPosition}/{amountToLoad}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesDefaultResponseType] public async Task> Popular(int currentPosition, int amountToLoad) @@ -187,6 +187,19 @@ namespace Ombi.Controllers.V2 return await _tvSearchEngine.Popular(); } + /// + /// Returns Popular Tv Shows + /// + /// We use Trakt.tv as the Provider + /// + [HttpGet("tv/popular/{currentPosition}/{amountToLoad}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesDefaultResponseType] + public async Task> PopularTv(int currentPosition, int amountToLoad) + { + return await _tvSearchEngine.Popular(currentPosition, amountToLoad); + } + /// /// Returns most Anticiplateds tv shows. ///