We now have infinite scroll!

Also fixed some more unit tests
pull/3895/head
tidusjar 5 years ago
parent 4e0459eef0
commit 961ba4297a

@ -9,6 +9,7 @@ namespace Ombi.Core.Engine.Interfaces
Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm);
Task<SearchTvShowViewModel> GetShowInformation(int tvdbid);
Task<IEnumerable<SearchTvShowViewModel>> Popular();
Task<IEnumerable<SearchTvShowViewModel>> Popular(int currentlyLoaded, int amountToLoad);
Task<IEnumerable<SearchTvShowViewModel>> Anticipated();
Task<IEnumerable<SearchTvShowViewModel>> MostWatches();
Task<IEnumerable<SearchTvShowViewModel>> Trending();

@ -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<IEnumerable<SearchTvShowViewModel>> Popular(int currentlyLoaded, int amountToLoad)
{
var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit);
var results = new List<TraktShow>();
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<IEnumerable<SearchTvShowViewModel>> Anticipated()
{

@ -98,6 +98,9 @@ namespace Ombi.Core.Engine.V2
return null;
}
private const int _theMovieDbMaxPageItems = 20;
/// <summary>
/// Gets popular movies by paging
/// </summary>
@ -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<MovieSearchResult>();
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);
}
/// <summary>

@ -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<TestCaseData> 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<MultiplePagesTestData> { 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<MultiplePagesTestData> { 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<MultiplePagesTestData> { 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<MultiplePagesTestData> { new MultiplePagesTestData(3, 12, 0) })
.SetName("PaginationPosition_Load_ThirdPage_Of_12");
yield return new TestCaseData(12, 12, 12, new List<MultiplePagesTestData> { new MultiplePagesTestData(2, 12, 0) })
.SetName("PaginationPosition_Load_SecondPage_Of_12");
yield return new TestCaseData(40, 20, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(3, 20, 0) })
.SetName("PaginationPosition_Load_FullThird_Page");
yield return new TestCaseData(240, 12, 20, new List<MultiplePagesTestData> { new MultiplePagesTestData(13, 12, 0) })
.SetName("PaginationPosition_Load_Page_13");
}
}
public class MultiplePagesTestData
{
public MultiplePagesTestData(int page, int take, int skip)

@ -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);
}

@ -6,6 +6,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="MockQueryable.Moq" Version="1.1.0" />
<PackageReference Include="Moq" Version="4.10.0" />
<PackageReference Include="Nunit" Version="3.11.0" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />

@ -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<ChildRequests> { request }.AsQueryable());
_tv.Setup(x => x.GetChild()).Returns(new List<ChildRequests> { request }.AsQueryable().BuildMock().Object);
_repo.Setup(x => x.GetAllEpisodes()).Returns(new List<PlexEpisode>
{
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<IQueryable<PlexEpisode>>(),It.IsAny<Expression<Func<PlexEpisode, PlexServerContent>>>()));
await Checker.Start();

@ -13,9 +13,7 @@
color="primary">{{'Discovery.UpcomingTab' | translate}}</button>
</div>
</div>
<div *ngIf="loadingFlag" class="row justify-content-md-center top-spacing loading-spinner">
<mat-spinner [color]="'accent'"></mat-spinner>
</div>
<div *ngIf="discoverResults" class="row full-height discoverResults"
infiniteScroll
[fromRoot]="true"
@ -25,4 +23,7 @@
<discover-card [result]="result"></discover-card>
</div>
</div>
<div *ngIf="loadingFlag" class="row justify-content-md-center top-spacing loading-spinner">
<mat-spinner [color]="'accent'"></mat-spinner>
</div>
</div>

@ -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 = <IDiscoverCardResult[]>[];
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]];

@ -53,6 +53,11 @@ export class SearchV2Service extends ServiceHelpers {
public popularTv(): Observable<ISearchTvResult[]> {
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/popular`, { headers: this.headers });
}
public popularTvByPage(currentlyLoaded: number, toLoad: number): Promise<ISearchTvResult[]> {
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/popular/${currentlyLoaded}/${toLoad}`, { headers: this.headers }).toPromise();
}
public mostWatchedTv(): Observable<ISearchTvResult[]> {
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/mostwatched`, { headers: this.headers });
}

@ -127,7 +127,7 @@ namespace Ombi.Controllers.V2
/// </summary>
/// <remarks>We use TheMovieDb as the Movie Provider</remarks>
/// <returns></returns>
[HttpGet("movie/popular/{currentPostion}/{amountToLoad}")]
[HttpGet("movie/popular/{currentPosition}/{amountToLoad}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<IEnumerable<SearchMovieViewModel>> Popular(int currentPosition, int amountToLoad)
@ -187,6 +187,19 @@ namespace Ombi.Controllers.V2
return await _tvSearchEngine.Popular();
}
/// <summary>
/// Returns Popular Tv Shows
/// </summary>
/// <remarks>We use Trakt.tv as the Provider</remarks>
/// <returns></returns>
[HttpGet("tv/popular/{currentPosition}/{amountToLoad}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<IEnumerable<SearchTvShowViewModel>> PopularTv(int currentPosition, int amountToLoad)
{
return await _tvSearchEngine.Popular(currentPosition, amountToLoad);
}
/// <summary>
/// Returns most Anticiplateds tv shows.
/// </summary>

Loading…
Cancel
Save