mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Added series progress/rating information back in. Left attempts at doing via a JOIN or raw SQL.
This commit is contained in:
parent
222959981f
commit
922c0153d3
@ -174,6 +174,9 @@ namespace API.Controllers
|
|||||||
var series =
|
var series =
|
||||||
await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, user.Id, userParams);
|
await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, user.Id, userParams);
|
||||||
|
|
||||||
|
// Apply progress/rating information (I can't work out how to do this in initial query)
|
||||||
|
await _unitOfWork.SeriesRepository.AddSeriesModifiers(user.Id, series);
|
||||||
|
|
||||||
Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages);
|
Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages);
|
||||||
|
|
||||||
return Ok(series);
|
return Ok(series);
|
||||||
|
@ -21,5 +21,14 @@
|
|||||||
/// Review from logged in user. Calculated at API-time.
|
/// Review from logged in user. Calculated at API-time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string UserReview { get; set; }
|
public string UserReview { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DO NOT Use. Used as a hack for <see cref="SeriesRepository.GetSeriesDtoForLibraryIdAsync()"/>
|
||||||
|
/// </summary>
|
||||||
|
public int LibraryId { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// DO NOT Use. Used as a hack for <see cref="SeriesRepository.GetSeriesDtoForLibraryIdAsync()"/>
|
||||||
|
/// </summary>
|
||||||
|
public int AppUserId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -69,7 +69,174 @@ namespace API.Data
|
|||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// // var query = QueryableExtensions.LeftJoin(_context.Series
|
||||||
|
// // .Join(_context.AppUserProgresses, series => series.Id, progress => progress.SeriesId,
|
||||||
|
// // (series, progress) =>
|
||||||
|
// // new {
|
||||||
|
// // series.LibraryId,
|
||||||
|
// // series.Id,
|
||||||
|
// // series.Created,
|
||||||
|
// // series.Name,
|
||||||
|
// // series.Pages,
|
||||||
|
// // series.Summary,
|
||||||
|
// // series.CoverImage,
|
||||||
|
// // series.OriginalName,
|
||||||
|
// // series.SortName,
|
||||||
|
// // progress.PagesRead
|
||||||
|
// // }), _context.AppUserRating, series => series.Id, rating => rating.SeriesId,
|
||||||
|
// // (series, rating) =>
|
||||||
|
// // new {
|
||||||
|
// // series.LibraryId,
|
||||||
|
// // series.Id,
|
||||||
|
// // series.Created,
|
||||||
|
// // series.Name,
|
||||||
|
// // series.Pages,
|
||||||
|
// // series.Summary,
|
||||||
|
// // series.CoverImage,
|
||||||
|
// // series.OriginalName,
|
||||||
|
// // series.SortName,
|
||||||
|
// // series.PagesRead,
|
||||||
|
// // rating.Review,
|
||||||
|
// // rating.Rating,
|
||||||
|
// // rating.AppUserId
|
||||||
|
// // })
|
||||||
|
// // .Where(s => s.LibraryId == libraryId && s.AppUserId == userId)
|
||||||
|
// // .OrderBy(s => s.SortName)
|
||||||
|
// // .Select(arg => new SeriesDto()
|
||||||
|
// // {
|
||||||
|
// // PagesRead = arg.PagesRead,
|
||||||
|
// // UserRating = arg.Rating,
|
||||||
|
// // UserReview = arg.Review,
|
||||||
|
// // Summary = arg.Summary,
|
||||||
|
// // Name = arg.Name,
|
||||||
|
// // OriginalName = arg.OriginalName,
|
||||||
|
// // SortName = arg.SortName,
|
||||||
|
// // Id = arg.Id,
|
||||||
|
// // Pages = arg.Pages,
|
||||||
|
// // CoverImage = arg.CoverImage
|
||||||
|
// // })
|
||||||
|
// // //.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||||
|
// // .AsNoTracking();
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// // var query2 = _context.Series
|
||||||
|
// // .Join(_context.AppUserProgresses, series => series.Id, progress => progress.SeriesId,
|
||||||
|
// // (series, progress) => new { series, progress}
|
||||||
|
// // /*new
|
||||||
|
// // {
|
||||||
|
// // series.LibraryId,
|
||||||
|
// // series.Id,
|
||||||
|
// // series.Created,
|
||||||
|
// // series.Name,
|
||||||
|
// // series.Pages,
|
||||||
|
// // series.Summary,
|
||||||
|
// // series.CoverImage,
|
||||||
|
// // series.OriginalName,
|
||||||
|
// // series.SortName,
|
||||||
|
// // progress.PagesRead
|
||||||
|
// // }*/)
|
||||||
|
// // .LeftJoin(_context.AppUserRating, series => series.series.Id, rating => rating.SeriesId,
|
||||||
|
// // (series, rating) => new {series.series, series.progress, rating}
|
||||||
|
// // /*
|
||||||
|
// // * new
|
||||||
|
// // {
|
||||||
|
// // series.LibraryId,
|
||||||
|
// // series.Id,
|
||||||
|
// // series.Name,
|
||||||
|
// // series.Pages,
|
||||||
|
// // series.Summary,
|
||||||
|
// // series.CoverImage,
|
||||||
|
// // series.OriginalName,
|
||||||
|
// // series.SortName,
|
||||||
|
// // series.PagesRead,
|
||||||
|
// // rating.Review,
|
||||||
|
// // rating.Rating,
|
||||||
|
// // rating.AppUserId
|
||||||
|
// // }
|
||||||
|
// // */)
|
||||||
|
// // // .Select(arg => new SeriesDto()
|
||||||
|
// // // {
|
||||||
|
// // // // Where is failing because this select changes what we select
|
||||||
|
// // // PagesRead = arg.PagesRead,
|
||||||
|
// // // UserRating = arg.Rating,
|
||||||
|
// // // UserReview = arg.Review,
|
||||||
|
// // // Summary = arg.Summary,
|
||||||
|
// // // Name = arg.Name,
|
||||||
|
// // // OriginalName = arg.OriginalName,
|
||||||
|
// // // SortName = arg.SortName,
|
||||||
|
// // // Id = arg.Id,
|
||||||
|
// // // Pages = arg.Pages,
|
||||||
|
// // // CoverImage = arg.CoverImage,
|
||||||
|
// // // })
|
||||||
|
// .Select(arg => new SeriesDto()
|
||||||
|
// {
|
||||||
|
// // Where is failing because this select changes what we select
|
||||||
|
// LibraryId = arg.series.LibraryId,
|
||||||
|
// PagesRead = arg.progress.PagesRead,
|
||||||
|
// UserRating = arg.rating.Rating,
|
||||||
|
// UserReview = arg.rating.Review,
|
||||||
|
// Summary = arg.series.Summary,
|
||||||
|
// Name = arg.series.Name,
|
||||||
|
// OriginalName = arg.series.OriginalName,
|
||||||
|
// SortName = arg.series.SortName,
|
||||||
|
// Id = arg.series.Id,
|
||||||
|
// Pages = arg.series.Pages,
|
||||||
|
// CoverImage = arg.series.CoverImage,
|
||||||
|
// })
|
||||||
|
// // .Where(s => s.LibraryId == libraryId && s.AppUserId == userId)
|
||||||
|
// // .OrderBy(s => s.SortName)
|
||||||
|
// // .AsNoTracking();
|
||||||
|
//
|
||||||
|
// var query = _context.Series
|
||||||
|
// .FromSqlRaw(@"select S.*, Rating AS UserRating, Review AS UserReview from Series AS S
|
||||||
|
// INNER JOIN AppUserProgresses AS AP ON AP.SeriesId = S.Id AND AP.AppUserId = {0}
|
||||||
|
// LEFT OUTER JOIN AppUserRating AUR on AUR.SeriesId = S.Id AND AUR.AppUserId = {0}
|
||||||
|
// WHERE (S.LibraryId = {1}) AND (AP.AppUserId = {0})
|
||||||
|
// ORDER BY S.SortName", userId, libraryId)
|
||||||
|
// .Select(series => new SeriesDto()
|
||||||
|
// {
|
||||||
|
// // Where is failing because this select changes what we select
|
||||||
|
// PagesRead = series.PagesRead,
|
||||||
|
// UserRating = series.UserRating,
|
||||||
|
// UserReview = arg.rating.Review,
|
||||||
|
// Summary = arg.series.Summary,
|
||||||
|
// Name = arg.series.Name,
|
||||||
|
// OriginalName = arg.series.OriginalName,
|
||||||
|
// SortName = arg.series.SortName,
|
||||||
|
// Id = arg.series.Id,
|
||||||
|
// Pages = arg.series.Pages,
|
||||||
|
// CoverImage = arg.series.CoverImage,
|
||||||
|
// })
|
||||||
|
// .AsNoTracking();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* select S.*, Rating AS UserRating, Review AS UserReview from Series AS S
|
||||||
|
INNER JOIN AppUserProgresses AS AP ON AP.SeriesId = S.Id AND AP.AppUserId = 1
|
||||||
|
LEFT OUTER JOIN AppUserRating AUR on AUR.SeriesId = S.Id AND AUR.AppUserId = 1;
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
//await AddSeriesModifiers(userId, series);
|
||||||
|
// var userProgress = await _context.AppUserProgresses
|
||||||
|
// .Where(p => p.AppUserId == userId && series.Select(s => s.Id).Contains(p.SeriesId) )
|
||||||
|
// .ToListAsync();
|
||||||
|
//
|
||||||
|
// var userRatings = await _context.AppUserRating
|
||||||
|
// .Where(r => r.AppUserId == userId && series.Select(s => s.Id).Contains(r.SeriesId))
|
||||||
|
// .ToListAsync();
|
||||||
|
//
|
||||||
|
// foreach (var s in series)
|
||||||
|
// {
|
||||||
|
// s.PagesRead = userProgress.Where(p => p.SeriesId == s.Id).Sum(p => p.PagesRead);
|
||||||
|
// var rating = userRatings.SingleOrDefault(r => r.SeriesId == s.Id);
|
||||||
|
// if (rating == null) continue;
|
||||||
|
// s.UserRating = rating.Rating;
|
||||||
|
// s.UserReview = rating.Review;
|
||||||
|
// }
|
||||||
|
|
||||||
var query = _context.Series
|
var query = _context.Series
|
||||||
.Where(s => s.LibraryId == libraryId)
|
.Where(s => s.LibraryId == libraryId)
|
||||||
.OrderBy(s => s.SortName)
|
.OrderBy(s => s.SortName)
|
||||||
@ -77,10 +244,6 @@ namespace API.Data
|
|||||||
.AsNoTracking();
|
.AsNoTracking();
|
||||||
|
|
||||||
|
|
||||||
// TODO: Refactor this into JOINs
|
|
||||||
//await AddSeriesModifiers(userId, series);
|
|
||||||
|
|
||||||
|
|
||||||
_logger.LogDebug("Processed GetSeriesDtoForLibraryIdAsync in {ElapsedMilliseconds} milliseconds", sw.ElapsedMilliseconds);
|
_logger.LogDebug("Processed GetSeriesDtoForLibraryIdAsync in {ElapsedMilliseconds} milliseconds", sw.ElapsedMilliseconds);
|
||||||
return await PagedList<SeriesDto>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
|
return await PagedList<SeriesDto>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
|
||||||
}
|
}
|
||||||
@ -222,7 +385,7 @@ namespace API.Data
|
|||||||
return chapterIds.ToArray();
|
return chapterIds.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddSeriesModifiers(int userId, List<SeriesDto> series)
|
public async Task AddSeriesModifiers(int userId, List<SeriesDto> series)
|
||||||
{
|
{
|
||||||
var userProgress = await _context.AppUserProgresses
|
var userProgress = await _context.AppUserProgresses
|
||||||
.Where(p => p.AppUserId == userId && series.Select(s => s.Id).Contains(p.SeriesId))
|
.Where(p => p.AppUserId == userId && series.Select(s => s.Id).Contains(p.SeriesId))
|
||||||
|
73
API/Extensions/LeftJoinExtensions.cs
Normal file
73
API/Extensions/LeftJoinExtensions.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace API.Extensions
|
||||||
|
{
|
||||||
|
public static class LeftJoinExtensions
|
||||||
|
{
|
||||||
|
public static IQueryable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
|
||||||
|
this IQueryable<TOuter> outer,
|
||||||
|
IQueryable<TInner> inner,
|
||||||
|
Expression<Func<TOuter, TKey>> outerKeySelector,
|
||||||
|
Expression<Func<TInner, TKey>> innerKeySelector,
|
||||||
|
Expression<Func<TOuter, TInner, TResult>> resultSelector)
|
||||||
|
{
|
||||||
|
MethodInfo groupJoin = typeof (Queryable).GetMethods()
|
||||||
|
.Single(m => m.ToString() == "System.Linq.IQueryable`1[TResult] GroupJoin[TOuter,TInner,TKey,TResult](System.Linq.IQueryable`1[TOuter], System.Collections.Generic.IEnumerable`1[TInner], System.Linq.Expressions.Expression`1[System.Func`2[TOuter,TKey]], System.Linq.Expressions.Expression`1[System.Func`2[TInner,TKey]], System.Linq.Expressions.Expression`1[System.Func`3[TOuter,System.Collections.Generic.IEnumerable`1[TInner],TResult]])")
|
||||||
|
.MakeGenericMethod(typeof (TOuter), typeof (TInner), typeof (TKey), typeof (LeftJoinIntermediate<TOuter, TInner>));
|
||||||
|
MethodInfo selectMany = typeof (Queryable).GetMethods()
|
||||||
|
.Single(m => m.ToString() == "System.Linq.IQueryable`1[TResult] SelectMany[TSource,TCollection,TResult](System.Linq.IQueryable`1[TSource], System.Linq.Expressions.Expression`1[System.Func`2[TSource,System.Collections.Generic.IEnumerable`1[TCollection]]], System.Linq.Expressions.Expression`1[System.Func`3[TSource,TCollection,TResult]])")
|
||||||
|
.MakeGenericMethod(typeof (LeftJoinIntermediate<TOuter, TInner>), typeof (TInner), typeof (TResult));
|
||||||
|
|
||||||
|
var groupJoinResultSelector = (Expression<Func<TOuter, IEnumerable<TInner>, LeftJoinIntermediate<TOuter, TInner>>>)
|
||||||
|
((oneOuter, manyInners) => new LeftJoinIntermediate<TOuter, TInner> {OneOuter = oneOuter, ManyInners = manyInners});
|
||||||
|
|
||||||
|
MethodCallExpression exprGroupJoin = Expression.Call(groupJoin, outer.Expression, inner.Expression, outerKeySelector, innerKeySelector, groupJoinResultSelector);
|
||||||
|
|
||||||
|
var selectManyCollectionSelector = (Expression<Func<LeftJoinIntermediate<TOuter, TInner>, IEnumerable<TInner>>>)
|
||||||
|
(t => t.ManyInners.DefaultIfEmpty());
|
||||||
|
|
||||||
|
ParameterExpression paramUser = resultSelector.Parameters.First();
|
||||||
|
|
||||||
|
ParameterExpression paramNew = Expression.Parameter(typeof (LeftJoinIntermediate<TOuter, TInner>), "t");
|
||||||
|
MemberExpression propExpr = Expression.Property(paramNew, "OneOuter");
|
||||||
|
|
||||||
|
LambdaExpression selectManyResultSelector = Expression.Lambda(new Replacer(paramUser, propExpr).Visit(resultSelector.Body), paramNew, resultSelector.Parameters.Skip(1).First());
|
||||||
|
|
||||||
|
MethodCallExpression exprSelectMany = Expression.Call(selectMany, exprGroupJoin, selectManyCollectionSelector, selectManyResultSelector);
|
||||||
|
|
||||||
|
return outer.Provider.CreateQuery<TResult>(exprSelectMany);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LeftJoinIntermediate<TOuter, TInner>
|
||||||
|
{
|
||||||
|
public TOuter OneOuter { get; set; }
|
||||||
|
public IEnumerable<TInner> ManyInners { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Replacer : ExpressionVisitor
|
||||||
|
{
|
||||||
|
private readonly ParameterExpression _oldParam;
|
||||||
|
private readonly Expression _replacement;
|
||||||
|
|
||||||
|
public Replacer(ParameterExpression oldParam, Expression replacement)
|
||||||
|
{
|
||||||
|
_oldParam = oldParam;
|
||||||
|
_replacement = replacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Expression Visit(Expression exp)
|
||||||
|
{
|
||||||
|
if (exp == _oldParam)
|
||||||
|
{
|
||||||
|
return _replacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Visit(exp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -39,5 +39,12 @@ namespace API.Interfaces
|
|||||||
Task<Volume> GetVolumeByIdAsync(int volumeId);
|
Task<Volume> GetVolumeByIdAsync(int volumeId);
|
||||||
Task<Series> GetSeriesByIdAsync(int seriesId);
|
Task<Series> GetSeriesByIdAsync(int seriesId);
|
||||||
Task<int[]> GetChapterIdsForSeriesAsync(int[] seriesIds);
|
Task<int[]> GetChapterIdsForSeriesAsync(int[] seriesIds);
|
||||||
|
/// <summary>
|
||||||
|
/// Used to add Progress/Rating information to series list.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <param name="series"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task AddSeriesModifiers(int userId, List<SeriesDto> series);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
|
using API.Entities;
|
||||||
|
|
||||||
namespace API.Interfaces.Services
|
namespace API.Interfaces.Services
|
||||||
{
|
{
|
||||||
@ -9,5 +10,6 @@ namespace API.Interfaces.Services
|
|||||||
int GetNumberOfPagesFromArchive(string archivePath);
|
int GetNumberOfPagesFromArchive(string archivePath);
|
||||||
byte[] GetCoverImage(string filepath, bool createThumbnail = false);
|
byte[] GetCoverImage(string filepath, bool createThumbnail = false);
|
||||||
bool IsValidArchive(string archivePath);
|
bool IsValidArchive(string archivePath);
|
||||||
|
string GetSummaryInfo(string archivePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -144,6 +144,11 @@ namespace API.Services
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetSummaryInfo(string archivePath)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extracts an archive to a temp cache directory. Returns path to new directory. If temp cache directory already exists,
|
/// Extracts an archive to a temp cache directory. Returns path to new directory. If temp cache directory already exists,
|
||||||
/// will return that without performing an extraction. Returns empty string if there are any invalidations which would
|
/// will return that without performing an extraction. Returns empty string if there are any invalidations which would
|
||||||
|
@ -68,7 +68,7 @@ namespace API.Services
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(series.Summary) || forceUpdate)
|
if (string.IsNullOrEmpty(series.Summary) || forceUpdate)
|
||||||
{
|
{
|
||||||
series.Summary = "";
|
series.Summary = _archiveService.GetSummaryInfo(series.Volumes.First().Chapters.First().Files.First().FilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Debug",
|
"Default": "Debug",
|
||||||
"Microsoft": "Error",
|
"Microsoft": "Information",
|
||||||
"Microsoft.Hosting.Lifetime": "Error",
|
"Microsoft.Hosting.Lifetime": "Error",
|
||||||
"Hangfire": "Information"
|
"Hangfire": "Information"
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user