Cleanup of lazy loading code. Made some DTOs use init rather than set to keep it clean.

This commit is contained in:
Joseph Milazzo 2021-03-12 13:20:08 -06:00
parent 33515ad865
commit af35d8aad5
18 changed files with 103 additions and 68 deletions

View File

@ -2,6 +2,7 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.DTOs; using API.DTOs;
using API.Extensions;
using API.Interfaces; using API.Interfaces;
using API.Interfaces.Services; using API.Interfaces.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -28,51 +29,33 @@ namespace API.Controllers
[HttpGet("chapter-cover")] [HttpGet("chapter-cover")]
public async Task<ActionResult> GetChapterCoverImage(int chapterId) public async Task<ActionResult> GetChapterCoverImage(int chapterId)
{ {
// TODO: Write custom methods to just get the byte[] as fast as possible var content = await _unitOfWork.VolumeRepository.GetChapterCoverImageAsync(chapterId);
var chapter = await _unitOfWork.VolumeRepository.GetChapterAsync(chapterId); if (content == null) return BadRequest("No cover image");
var content = chapter.CoverImage; const string format = "jpeg";
var format = "jpeg"; //Path.GetExtension("jpeg").Replace(".", "");
// Calculates SHA1 Hash for byte[]
using var sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider();
Response.Headers.Add("ETag", string.Concat(sha1.ComputeHash(content).Select(x => x.ToString("X2"))));
Response.Headers.Add("Cache-Control", "private");
Response.AddCacheHeader(content);
return File(content, "image/" + format); return File(content, "image/" + format);
} }
[HttpGet("volume-cover")] [HttpGet("volume-cover")]
public async Task<ActionResult> GetVolumeCoverImage(int volumeId) public async Task<ActionResult> GetVolumeCoverImage(int volumeId)
{ {
var volume = await _unitOfWork.SeriesRepository.GetVolumeAsync(volumeId); var content = await _unitOfWork.SeriesRepository.GetVolumeCoverImageAsync(volumeId);
var content = volume.CoverImage; if (content == null) return BadRequest("No cover image");
var format = "jpeg"; //Path.GetExtension("jpeg").Replace(".", ""); const string format = "jpeg";
// Calculates SHA1 Hash for byte[]
using var sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider();
Response.Headers.Add("ETag", string.Concat(sha1.ComputeHash(content).Select(x => x.ToString("X2"))));
Response.Headers.Add("Cache-Control", "private");
Response.AddCacheHeader(content);
return File(content, "image/" + format); return File(content, "image/" + format);
} }
[HttpGet("series-cover")] [HttpGet("series-cover")]
public async Task<ActionResult> GetSeriesCoverImage(int seriesId) public async Task<ActionResult> GetSeriesCoverImage(int seriesId)
{ {
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId); var content = await _unitOfWork.SeriesRepository.GetSeriesCoverImageAsync(seriesId);
var content = series.CoverImage; if (content == null) return BadRequest("No cover image");
var format = "jpeg"; //Path.GetExtension("jpeg").Replace(".", ""); const string format = "jpeg";
if (content.Length == 0)
{
// How do I handle?
}
// Calculates SHA1 Hash for byte[]
using var sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider();
Response.Headers.Add("ETag", string.Concat(sha1.ComputeHash(content).Select(x => x.ToString("X2"))));
Response.Headers.Add("Cache-Control", "private");
Response.AddCacheHeader(content);
return File(content, "image/" + format); return File(content, "image/" + format);
} }
} }

View File

@ -4,28 +4,27 @@ namespace API.DTOs
{ {
public class ChapterDto public class ChapterDto
{ {
public int Id { get; set; } public int Id { get; init; }
/// <summary> /// <summary>
/// Range of chapters. Chapter 2-4 -> "2-4". Chapter 2 -> "2". /// Range of chapters. Chapter 2-4 -> "2-4". Chapter 2 -> "2".
/// </summary> /// </summary>
public string Range { get; set; } public string Range { get; init; }
/// <summary> /// <summary>
/// Smallest number of the Range. /// Smallest number of the Range.
/// </summary> /// </summary>
public string Number { get; set; } public string Number { get; init; }
//public byte[] CoverImage { get; set; }
/// <summary> /// <summary>
/// Total number of pages in all MangaFiles /// Total number of pages in all MangaFiles
/// </summary> /// </summary>
public int Pages { get; set; } public int Pages { get; init; }
/// <summary> /// <summary>
/// The files that represent this Chapter /// The files that represent this Chapter
/// </summary> /// </summary>
public ICollection<MangaFileDto> Files { get; set; } public ICollection<MangaFileDto> Files { get; init; }
/// <summary> /// <summary>
/// Calculated at API time. Number of pages read for this Chapter for logged in user. /// Calculated at API time. Number of pages read for this Chapter for logged in user.
/// </summary> /// </summary>
public int PagesRead { get; set; } public int PagesRead { get; set; }
public int VolumeId { get; set; } public int VolumeId { get; init; }
} }
} }

View File

@ -7,11 +7,11 @@ namespace API.DTOs
public class CreateLibraryDto public class CreateLibraryDto
{ {
[Required] [Required]
public string Name { get; set; } public string Name { get; init; }
[Required] [Required]
public LibraryType Type { get; set; } public LibraryType Type { get; init; }
[Required] [Required]
[MinLength(1)] [MinLength(1)]
public IEnumerable<string> Folders { get; set; } public IEnumerable<string> Folders { get; init; }
} }
} }

View File

@ -2,14 +2,14 @@
{ {
public class ImageDto public class ImageDto
{ {
public int Page { get; set; } public int Page { get; init; }
public string Filename { get; init; } public string Filename { get; init; }
public string FullPath { get; init; } public string FullPath { get; init; }
public int Width { get; init; } public int Width { get; init; }
public int Height { get; init; } public int Height { get; init; }
public string Format { get; init; } public string Format { get; init; }
public byte[] Content { get; init; } public byte[] Content { get; init; }
public string MangaFileName { get; set; } public string MangaFileName { get; init; }
public bool NeedsSplitting { get; set; } public bool NeedsSplitting { get; init; }
} }
} }

View File

@ -6,9 +6,9 @@ namespace API.DTOs
public class LibraryDto public class LibraryDto
{ {
public int Id { get; init; } public int Id { get; init; }
public string Name { get; set; } public string Name { get; init; }
public string CoverImage { get; set; } public string CoverImage { get; init; }
public LibraryType Type { get; set; } public LibraryType Type { get; init; }
public ICollection<string> Folders { get; set; } public ICollection<string> Folders { get; init; }
} }
} }

View File

@ -2,7 +2,7 @@
{ {
public class LoginDto public class LoginDto
{ {
public string Username { get; set; } public string Username { get; init; }
public string Password { get; set; } public string Password { get; init; }
} }
} }

View File

@ -4,9 +4,9 @@ namespace API.DTOs
{ {
public class MangaFileDto public class MangaFileDto
{ {
public string FilePath { get; set; } public string FilePath { get; init; }
public int NumberOfPages { get; set; } // TODO: Refactor to Pages public int NumberOfPages { get; init; }
public MangaFormat Format { get; set; } public MangaFormat Format { get; init; }
} }
} }

View File

@ -2,6 +2,6 @@
{ {
public class MarkReadDto public class MarkReadDto
{ {
public int SeriesId { get; set; } public int SeriesId { get; init; }
} }
} }

View File

@ -8,11 +8,11 @@ namespace API.DTOs
/// </summary> /// </summary>
public class MemberDto public class MemberDto
{ {
public int Id { get; set; } public int Id { get; init; }
public string Username { get; set; } public string Username { get; init; }
public DateTime Created { get; set; } public DateTime Created { get; init; }
public DateTime LastActive { get; set; } public DateTime LastActive { get; init; }
public IEnumerable<LibraryDto> Libraries { get; set; } public IEnumerable<LibraryDto> Libraries { get; init; }
public IEnumerable<string> Roles { get; set; } public IEnumerable<string> Roles { get; init; }
} }
} }

View File

@ -5,10 +5,10 @@ namespace API.DTOs
public class RegisterDto public class RegisterDto
{ {
[Required] [Required]
public string Username { get; set; } public string Username { get; init; }
[Required] [Required]
[StringLength(16, MinimumLength = 4)] [StringLength(16, MinimumLength = 4)]
public string Password { get; set; } public string Password { get; init; }
public bool IsAdmin { get; set; } public bool IsAdmin { get; init; }
} }
} }

View File

@ -6,8 +6,8 @@
public string Name { get; init; } public string Name { get; init; }
public string OriginalName { get; init; } public string OriginalName { get; init; }
public string SortName { get; init; } public string SortName { get; init; }
public byte[] CoverImage { get; init; } // This should be optional or a thumbImage (much smaller) public byte[] CoverImage { get; init; } // This should be optional or a thumbImage (much smaller) // TODO: Refactor to lazy loading
public string CoverImageUrl { get; init; }
// Grouping information // Grouping information
public string LibraryName { get; set; } public string LibraryName { get; set; }

View File

@ -8,7 +8,6 @@
public string LocalizedName { get; init; } public string LocalizedName { get; init; }
public string SortName { get; init; } public string SortName { get; init; }
public string Summary { get; init; } public string Summary { get; init; }
public byte[] CoverImage { get; init; }
public int Pages { get; init; } public int Pages { get; init; }
/// <summary> /// <summary>
/// Sum of pages read from linked Volumes. Calculated at API-time. /// Sum of pages read from linked Volumes. Calculated at API-time.

View File

@ -9,7 +9,6 @@ namespace API.DTOs
public int Id { get; set; } public int Id { get; set; }
public int Number { get; set; } public int Number { get; set; }
public string Name { get; set; } public string Name { get; set; }
//public byte[] CoverImage { get; set; }
public int Pages { get; set; } public int Pages { get; set; }
public int PagesRead { get; set; } public int PagesRead { get; set; }
public DateTime LastModified { get; set; } public DateTime LastModified { get; set; }

View File

@ -244,6 +244,25 @@ namespace API.Data
s.UserReview = rating.Review; s.UserReview = rating.Review;
} }
} }
public async Task<byte[]> GetVolumeCoverImageAsync(int volumeId)
{
return await _context.Volume
.Where(v => v.Id == volumeId)
.Select(v => v.CoverImage)
.AsNoTracking()
.SingleOrDefaultAsync();
}
public async Task<byte[]> GetSeriesCoverImageAsync(int seriesId)
{
return await _context.Series
.Where(s => s.Id == seriesId)
.Select(s => s.CoverImage)
.AsNoTracking()
.SingleOrDefaultAsync();
}
private async Task AddVolumeModifiers(int userId, List<VolumeDto> volumes) private async Task AddVolumeModifiers(int userId, List<VolumeDto> volumes)
{ {
var userProgress = await _context.AppUserProgresses var userProgress = await _context.AppUserProgresses

View File

@ -50,7 +50,21 @@ namespace API.Data
.Where(c => c.VolumeId == volumeId) .Where(c => c.VolumeId == volumeId)
.ToListAsync(); .ToListAsync();
} }
/// <summary>
/// Returns the cover image for a chapter id.
/// </summary>
/// <param name="chapterId"></param>
/// <returns></returns>
public async Task<byte[]> GetChapterCoverImageAsync(int chapterId)
{
return await _context.Chapter
.Where(c => c.Id == chapterId)
.Select(c => c.CoverImage)
.AsNoTracking()
.SingleOrDefaultAsync();
}
public async Task<ChapterDto> GetChapterDtoAsync(int chapterId) public async Task<ChapterDto> GetChapterDtoAsync(int chapterId)
{ {

View File

@ -1,4 +1,5 @@
using System.Text.Json; using System.Linq;
using System.Text.Json;
using API.Helpers; using API.Helpers;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -18,6 +19,23 @@ namespace API.Extensions
response.Headers.Add("Pagination", JsonSerializer.Serialize(paginationHeader, options)); response.Headers.Add("Pagination", JsonSerializer.Serialize(paginationHeader, options));
response.Headers.Add("Access-Control-Expose-Headers", "Pagination"); response.Headers.Add("Access-Control-Expose-Headers", "Pagination");
} }
/// <summary>
/// Calculates SHA1 hash for a byte[] and sets as ETag. Ensures Cache-Control: private header is added.
/// </summary>
/// <param name="response"></param>
/// <param name="content"></param>
public static void AddCacheHeader(this HttpResponse response, byte[] content)
{
// Calculates SHA1 Hash for byte[]
if (content != null && content.Length > 0)
{
using var sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider();
response.Headers.Add("ETag", string.Concat(sha1.ComputeHash(content).Select(x => x.ToString("X2"))));
}
response.Headers.Add("Cache-Control", "private");
}
} }
} }

View File

@ -52,5 +52,8 @@ namespace API.Interfaces
/// <param name="series"></param> /// <param name="series"></param>
/// <returns></returns> /// <returns></returns>
Task AddSeriesModifiers(int userId, List<SeriesDto> series); Task AddSeriesModifiers(int userId, List<SeriesDto> series);
Task<byte[]> GetVolumeCoverImageAsync(int volumeId);
Task<byte[]> GetSeriesCoverImageAsync(int seriesId);
} }
} }

View File

@ -12,5 +12,6 @@ namespace API.Interfaces
Task<ChapterDto> GetChapterDtoAsync(int chapterId); Task<ChapterDto> GetChapterDtoAsync(int chapterId);
Task<IList<MangaFile>> GetFilesForChapter(int chapterId); Task<IList<MangaFile>> GetFilesForChapter(int chapterId);
Task<IList<Chapter>> GetChaptersAsync(int volumeId); Task<IList<Chapter>> GetChaptersAsync(int volumeId);
Task<byte[]> GetChapterCoverImageAsync(int chapterId);
} }
} }