mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-14 01:02:57 -07:00
We now have infinite scroll!
Also fixed some more unit tests
This commit is contained in:
parent
4e0459eef0
commit
961ba4297a
11 changed files with 162 additions and 68 deletions
|
@ -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)
|
||||
var pages = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, _theMovieDbMaxPageItems);
|
||||
|
||||
var results = new List<MovieSearchResult>();
|
||||
foreach (var pagesToLoad in pages)
|
||||
{
|
||||
throw new ApplicationException("Please load less than or equal to 20 items at a time due to a API limit");
|
||||
var apiResult = await MovieApi.PopularMovies(langCode, pagesToLoad.Page);
|
||||
results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take));
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// First page
|
||||
}
|
||||
|
||||
|
||||
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…
Add table
Add a link
Reference in a new issue