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.Threading.Tasks;
using API.DTOs;
using API.Extensions;
using API.Interfaces;
using API.Interfaces.Services;
using Microsoft.AspNetCore.Mvc;
@ -28,51 +29,33 @@ namespace API.Controllers
[HttpGet("chapter-cover")]
public async Task<ActionResult> GetChapterCoverImage(int chapterId)
{
// TODO: Write custom methods to just get the byte[] as fast as possible
var chapter = await _unitOfWork.VolumeRepository.GetChapterAsync(chapterId);
var content = chapter.CoverImage;
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");
var content = await _unitOfWork.VolumeRepository.GetChapterCoverImageAsync(chapterId);
if (content == null) return BadRequest("No cover image");
const string format = "jpeg";
Response.AddCacheHeader(content);
return File(content, "image/" + format);
}
[HttpGet("volume-cover")]
public async Task<ActionResult> GetVolumeCoverImage(int volumeId)
{
var volume = await _unitOfWork.SeriesRepository.GetVolumeAsync(volumeId);
var content = volume.CoverImage;
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");
var content = await _unitOfWork.SeriesRepository.GetVolumeCoverImageAsync(volumeId);
if (content == null) return BadRequest("No cover image");
const string format = "jpeg";
Response.AddCacheHeader(content);
return File(content, "image/" + format);
}
[HttpGet("series-cover")]
public async Task<ActionResult> GetSeriesCoverImage(int seriesId)
{
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
var content = series.CoverImage;
var format = "jpeg"; //Path.GetExtension("jpeg").Replace(".", "");
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");
var content = await _unitOfWork.SeriesRepository.GetSeriesCoverImageAsync(seriesId);
if (content == null) return BadRequest("No cover image");
const string format = "jpeg";
Response.AddCacheHeader(content);
return File(content, "image/" + format);
}
}

View File

@ -4,28 +4,27 @@ namespace API.DTOs
{
public class ChapterDto
{
public int Id { get; set; }
public int Id { get; init; }
/// <summary>
/// Range of chapters. Chapter 2-4 -> "2-4". Chapter 2 -> "2".
/// </summary>
public string Range { get; set; }
public string Range { get; init; }
/// <summary>
/// Smallest number of the Range.
/// </summary>
public string Number { get; set; }
//public byte[] CoverImage { get; set; }
public string Number { get; init; }
/// <summary>
/// Total number of pages in all MangaFiles
/// </summary>
public int Pages { get; set; }
public int Pages { get; init; }
/// <summary>
/// The files that represent this Chapter
/// </summary>
public ICollection<MangaFileDto> Files { get; set; }
public ICollection<MangaFileDto> Files { get; init; }
/// <summary>
/// Calculated at API time. Number of pages read for this Chapter for logged in user.
/// </summary>
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
{
[Required]
public string Name { get; set; }
public string Name { get; init; }
[Required]
public LibraryType Type { get; set; }
public LibraryType Type { get; init; }
[Required]
[MinLength(1)]
public IEnumerable<string> Folders { get; set; }
public IEnumerable<string> Folders { get; init; }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,8 +6,8 @@
public string Name { get; init; }
public string OriginalName { 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
public string LibraryName { get; set; }

View File

@ -8,7 +8,6 @@
public string LocalizedName { get; init; }
public string SortName { get; init; }
public string Summary { get; init; }
public byte[] CoverImage { get; init; }
public int Pages { get; init; }
/// <summary>
/// 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 Number { get; set; }
public string Name { get; set; }
//public byte[] CoverImage { get; set; }
public int Pages { get; set; }
public int PagesRead { get; set; }
public DateTime LastModified { get; set; }

View File

@ -244,6 +244,25 @@ namespace API.Data
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)
{
var userProgress = await _context.AppUserProgresses

View File

@ -51,6 +51,20 @@ namespace API.Data
.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)
{

View File

@ -1,4 +1,5 @@
using System.Text.Json;
using System.Linq;
using System.Text.Json;
using API.Helpers;
using Microsoft.AspNetCore.Http;
@ -19,5 +20,22 @@ namespace API.Extensions
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>
/// <returns></returns>
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<IList<MangaFile>> GetFilesForChapter(int chapterId);
Task<IList<Chapter>> GetChaptersAsync(int volumeId);
Task<byte[]> GetChapterCoverImageAsync(int chapterId);
}
}