diff --git a/API/Controllers/LibraryController.cs b/API/Controllers/LibraryController.cs
index 34bb2e94e..e3f17ec3e 100644
--- a/API/Controllers/LibraryController.cs
+++ b/API/Controllers/LibraryController.cs
@@ -174,6 +174,9 @@ namespace API.Controllers
var series =
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);
return Ok(series);
diff --git a/API/DTOs/SeriesDto.cs b/API/DTOs/SeriesDto.cs
index bf9fc8a45..8359f60b9 100644
--- a/API/DTOs/SeriesDto.cs
+++ b/API/DTOs/SeriesDto.cs
@@ -21,5 +21,14 @@
/// Review from logged in user. Calculated at API-time.
///
public string UserReview { get; set; }
+
+ ///
+ /// DO NOT Use. Used as a hack for
+ ///
+ public int LibraryId { get; set; }
+ ///
+ /// DO NOT Use. Used as a hack for
+ ///
+ public int AppUserId { get; set; }
}
}
\ No newline at end of file
diff --git a/API/Data/SeriesRepository.cs b/API/Data/SeriesRepository.cs
index c501fdc61..738e4eb22 100644
--- a/API/Data/SeriesRepository.cs
+++ b/API/Data/SeriesRepository.cs
@@ -69,7 +69,174 @@ namespace API.Data
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(_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
.Where(s => s.LibraryId == libraryId)
.OrderBy(s => s.SortName)
@@ -77,10 +244,6 @@ namespace API.Data
.AsNoTracking();
- // TODO: Refactor this into JOINs
- //await AddSeriesModifiers(userId, series);
-
-
_logger.LogDebug("Processed GetSeriesDtoForLibraryIdAsync in {ElapsedMilliseconds} milliseconds", sw.ElapsedMilliseconds);
return await PagedList.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
}
@@ -222,7 +385,7 @@ namespace API.Data
return chapterIds.ToArray();
}
- private async Task AddSeriesModifiers(int userId, List series)
+ public async Task AddSeriesModifiers(int userId, List series)
{
var userProgress = await _context.AppUserProgresses
.Where(p => p.AppUserId == userId && series.Select(s => s.Id).Contains(p.SeriesId))
diff --git a/API/Extensions/LeftJoinExtensions.cs b/API/Extensions/LeftJoinExtensions.cs
new file mode 100644
index 000000000..c4ea979a4
--- /dev/null
+++ b/API/Extensions/LeftJoinExtensions.cs
@@ -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 LeftJoin(
+ this IQueryable outer,
+ IQueryable inner,
+ Expression> outerKeySelector,
+ Expression> innerKeySelector,
+ Expression> 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));
+ 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), typeof (TInner), typeof (TResult));
+
+ var groupJoinResultSelector = (Expression, LeftJoinIntermediate>>)
+ ((oneOuter, manyInners) => new LeftJoinIntermediate {OneOuter = oneOuter, ManyInners = manyInners});
+
+ MethodCallExpression exprGroupJoin = Expression.Call(groupJoin, outer.Expression, inner.Expression, outerKeySelector, innerKeySelector, groupJoinResultSelector);
+
+ var selectManyCollectionSelector = (Expression, IEnumerable>>)
+ (t => t.ManyInners.DefaultIfEmpty());
+
+ ParameterExpression paramUser = resultSelector.Parameters.First();
+
+ ParameterExpression paramNew = Expression.Parameter(typeof (LeftJoinIntermediate), "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(exprSelectMany);
+ }
+
+ private class LeftJoinIntermediate
+ {
+ public TOuter OneOuter { get; set; }
+ public IEnumerable 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);
+ }
+ }
+}
+}
\ No newline at end of file
diff --git a/API/Interfaces/ISeriesRepository.cs b/API/Interfaces/ISeriesRepository.cs
index 3fc58824a..6a9845975 100644
--- a/API/Interfaces/ISeriesRepository.cs
+++ b/API/Interfaces/ISeriesRepository.cs
@@ -39,5 +39,12 @@ namespace API.Interfaces
Task GetVolumeByIdAsync(int volumeId);
Task GetSeriesByIdAsync(int seriesId);
Task GetChapterIdsForSeriesAsync(int[] seriesIds);
+ ///
+ /// Used to add Progress/Rating information to series list.
+ ///
+ ///
+ ///
+ ///
+ Task AddSeriesModifiers(int userId, List series);
}
}
\ No newline at end of file
diff --git a/API/Interfaces/Services/IArchiveService.cs b/API/Interfaces/Services/IArchiveService.cs
index b150dd761..73452859f 100644
--- a/API/Interfaces/Services/IArchiveService.cs
+++ b/API/Interfaces/Services/IArchiveService.cs
@@ -1,4 +1,5 @@
using System.IO.Compression;
+using API.Entities;
namespace API.Interfaces.Services
{
@@ -9,5 +10,6 @@ namespace API.Interfaces.Services
int GetNumberOfPagesFromArchive(string archivePath);
byte[] GetCoverImage(string filepath, bool createThumbnail = false);
bool IsValidArchive(string archivePath);
+ string GetSummaryInfo(string archivePath);
}
}
\ No newline at end of file
diff --git a/API/Services/ArchiveService.cs b/API/Services/ArchiveService.cs
index fe3d93e1b..716c0d91b 100644
--- a/API/Services/ArchiveService.cs
+++ b/API/Services/ArchiveService.cs
@@ -144,6 +144,11 @@ namespace API.Services
}
+ public string GetSummaryInfo(string archivePath)
+ {
+ throw new NotImplementedException();
+ }
+
///
/// 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
diff --git a/API/Services/MetadataService.cs b/API/Services/MetadataService.cs
index e8411e212..023c1decc 100644
--- a/API/Services/MetadataService.cs
+++ b/API/Services/MetadataService.cs
@@ -68,7 +68,7 @@ namespace API.Services
if (string.IsNullOrEmpty(series.Summary) || forceUpdate)
{
- series.Summary = "";
+ series.Summary = _archiveService.GetSummaryInfo(series.Volumes.First().Chapters.First().Files.First().FilePath);
}
}
diff --git a/API/appsettings.Development.json b/API/appsettings.Development.json
index d2bf464c4..67e0049ba 100644
--- a/API/appsettings.Development.json
+++ b/API/appsettings.Development.json
@@ -6,7 +6,7 @@
"Logging": {
"LogLevel": {
"Default": "Debug",
- "Microsoft": "Error",
+ "Microsoft": "Information",
"Microsoft.Hosting.Lifetime": "Error",
"Hangfire": "Information"
},