From af35d8aad596105bd947d9b4aefce060269cabb8 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Fri, 12 Mar 2021 13:20:08 -0600 Subject: [PATCH] Cleanup of lazy loading code. Made some DTOs use init rather than set to keep it clean. --- API/Controllers/ImageController.cs | 43 +++++++++-------------------- API/DTOs/ChapterDto.cs | 13 ++++----- API/DTOs/CreateLibraryDto.cs | 6 ++-- API/DTOs/ImageDto.cs | 6 ++-- API/DTOs/LibraryDto.cs | 8 +++--- API/DTOs/LoginDto.cs | 4 +-- API/DTOs/MangaFileDto.cs | 6 ++-- API/DTOs/MarkReadDto.cs | 2 +- API/DTOs/MemberDto.cs | 12 ++++---- API/DTOs/RegisterDto.cs | 6 ++-- API/DTOs/SearchResultDto.cs | 4 +-- API/DTOs/SeriesDto.cs | 1 - API/DTOs/VolumeDto.cs | 1 - API/Data/SeriesRepository.cs | 19 +++++++++++++ API/Data/VolumeRepository.cs | 16 ++++++++++- API/Extensions/HttpExtensions.cs | 20 +++++++++++++- API/Interfaces/ISeriesRepository.cs | 3 ++ API/Interfaces/IVolumeRepository.cs | 1 + 18 files changed, 103 insertions(+), 68 deletions(-) diff --git a/API/Controllers/ImageController.cs b/API/Controllers/ImageController.cs index c7812ac63..c117abc9e 100644 --- a/API/Controllers/ImageController.cs +++ b/API/Controllers/ImageController.cs @@ -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 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 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 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); } } diff --git a/API/DTOs/ChapterDto.cs b/API/DTOs/ChapterDto.cs index bfff08ef9..00139a3b2 100644 --- a/API/DTOs/ChapterDto.cs +++ b/API/DTOs/ChapterDto.cs @@ -4,28 +4,27 @@ namespace API.DTOs { public class ChapterDto { - public int Id { get; set; } + public int Id { get; init; } /// /// Range of chapters. Chapter 2-4 -> "2-4". Chapter 2 -> "2". /// - public string Range { get; set; } + public string Range { get; init; } /// /// Smallest number of the Range. /// - public string Number { get; set; } - //public byte[] CoverImage { get; set; } + public string Number { get; init; } /// /// Total number of pages in all MangaFiles /// - public int Pages { get; set; } + public int Pages { get; init; } /// /// The files that represent this Chapter /// - public ICollection Files { get; set; } + public ICollection Files { get; init; } /// /// Calculated at API time. Number of pages read for this Chapter for logged in user. /// public int PagesRead { get; set; } - public int VolumeId { get; set; } + public int VolumeId { get; init; } } } \ No newline at end of file diff --git a/API/DTOs/CreateLibraryDto.cs b/API/DTOs/CreateLibraryDto.cs index f33430a6c..f9aa14639 100644 --- a/API/DTOs/CreateLibraryDto.cs +++ b/API/DTOs/CreateLibraryDto.cs @@ -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 Folders { get; set; } + public IEnumerable Folders { get; init; } } } \ No newline at end of file diff --git a/API/DTOs/ImageDto.cs b/API/DTOs/ImageDto.cs index 1d07c52fd..e66591001 100644 --- a/API/DTOs/ImageDto.cs +++ b/API/DTOs/ImageDto.cs @@ -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; } } } \ No newline at end of file diff --git a/API/DTOs/LibraryDto.cs b/API/DTOs/LibraryDto.cs index ed741642c..fb08a53e8 100644 --- a/API/DTOs/LibraryDto.cs +++ b/API/DTOs/LibraryDto.cs @@ -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 Folders { get; set; } + public string Name { get; init; } + public string CoverImage { get; init; } + public LibraryType Type { get; init; } + public ICollection Folders { get; init; } } } \ No newline at end of file diff --git a/API/DTOs/LoginDto.cs b/API/DTOs/LoginDto.cs index 9983415e0..3da1841bf 100644 --- a/API/DTOs/LoginDto.cs +++ b/API/DTOs/LoginDto.cs @@ -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; } } } \ No newline at end of file diff --git a/API/DTOs/MangaFileDto.cs b/API/DTOs/MangaFileDto.cs index 03f3e9abf..d7f5d5034 100644 --- a/API/DTOs/MangaFileDto.cs +++ b/API/DTOs/MangaFileDto.cs @@ -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; } } } \ No newline at end of file diff --git a/API/DTOs/MarkReadDto.cs b/API/DTOs/MarkReadDto.cs index 01f03bb83..1b39df2a8 100644 --- a/API/DTOs/MarkReadDto.cs +++ b/API/DTOs/MarkReadDto.cs @@ -2,6 +2,6 @@ { public class MarkReadDto { - public int SeriesId { get; set; } + public int SeriesId { get; init; } } } \ No newline at end of file diff --git a/API/DTOs/MemberDto.cs b/API/DTOs/MemberDto.cs index b404af389..88a16aa7c 100644 --- a/API/DTOs/MemberDto.cs +++ b/API/DTOs/MemberDto.cs @@ -8,11 +8,11 @@ namespace API.DTOs /// 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 Libraries { get; set; } - public IEnumerable 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 Libraries { get; init; } + public IEnumerable Roles { get; init; } } } \ No newline at end of file diff --git a/API/DTOs/RegisterDto.cs b/API/DTOs/RegisterDto.cs index 5ff816522..b3b43bf1a 100644 --- a/API/DTOs/RegisterDto.cs +++ b/API/DTOs/RegisterDto.cs @@ -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; } } } \ No newline at end of file diff --git a/API/DTOs/SearchResultDto.cs b/API/DTOs/SearchResultDto.cs index 3e154d3b7..334beafe2 100644 --- a/API/DTOs/SearchResultDto.cs +++ b/API/DTOs/SearchResultDto.cs @@ -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; } diff --git a/API/DTOs/SeriesDto.cs b/API/DTOs/SeriesDto.cs index 7065077e4..593870309 100644 --- a/API/DTOs/SeriesDto.cs +++ b/API/DTOs/SeriesDto.cs @@ -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; } /// /// Sum of pages read from linked Volumes. Calculated at API-time. diff --git a/API/DTOs/VolumeDto.cs b/API/DTOs/VolumeDto.cs index 95df511ce..3fc165a6d 100644 --- a/API/DTOs/VolumeDto.cs +++ b/API/DTOs/VolumeDto.cs @@ -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; } diff --git a/API/Data/SeriesRepository.cs b/API/Data/SeriesRepository.cs index 1af322802..4975c2654 100644 --- a/API/Data/SeriesRepository.cs +++ b/API/Data/SeriesRepository.cs @@ -244,6 +244,25 @@ namespace API.Data s.UserReview = rating.Review; } } + + public async Task GetVolumeCoverImageAsync(int volumeId) + { + return await _context.Volume + .Where(v => v.Id == volumeId) + .Select(v => v.CoverImage) + .AsNoTracking() + .SingleOrDefaultAsync(); + } + + public async Task 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 volumes) { var userProgress = await _context.AppUserProgresses diff --git a/API/Data/VolumeRepository.cs b/API/Data/VolumeRepository.cs index abfb672a8..6b9e541ea 100644 --- a/API/Data/VolumeRepository.cs +++ b/API/Data/VolumeRepository.cs @@ -50,7 +50,21 @@ namespace API.Data .Where(c => c.VolumeId == volumeId) .ToListAsync(); } - + + /// + /// Returns the cover image for a chapter id. + /// + /// + /// + public async Task GetChapterCoverImageAsync(int chapterId) + { + return await _context.Chapter + .Where(c => c.Id == chapterId) + .Select(c => c.CoverImage) + .AsNoTracking() + .SingleOrDefaultAsync(); + } + public async Task GetChapterDtoAsync(int chapterId) { diff --git a/API/Extensions/HttpExtensions.cs b/API/Extensions/HttpExtensions.cs index 3d08cc94a..d4feb5ab9 100644 --- a/API/Extensions/HttpExtensions.cs +++ b/API/Extensions/HttpExtensions.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Linq; +using System.Text.Json; using API.Helpers; using Microsoft.AspNetCore.Http; @@ -18,6 +19,23 @@ namespace API.Extensions response.Headers.Add("Pagination", JsonSerializer.Serialize(paginationHeader, options)); response.Headers.Add("Access-Control-Expose-Headers", "Pagination"); } + + /// + /// Calculates SHA1 hash for a byte[] and sets as ETag. Ensures Cache-Control: private header is added. + /// + /// + /// + 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"); + } } } \ No newline at end of file diff --git a/API/Interfaces/ISeriesRepository.cs b/API/Interfaces/ISeriesRepository.cs index e8b4f1aee..e9d950937 100644 --- a/API/Interfaces/ISeriesRepository.cs +++ b/API/Interfaces/ISeriesRepository.cs @@ -52,5 +52,8 @@ namespace API.Interfaces /// /// Task AddSeriesModifiers(int userId, List series); + + Task GetVolumeCoverImageAsync(int volumeId); + Task GetSeriesCoverImageAsync(int seriesId); } } \ No newline at end of file diff --git a/API/Interfaces/IVolumeRepository.cs b/API/Interfaces/IVolumeRepository.cs index 727133d80..faf18abb8 100644 --- a/API/Interfaces/IVolumeRepository.cs +++ b/API/Interfaces/IVolumeRepository.cs @@ -12,5 +12,6 @@ namespace API.Interfaces Task GetChapterDtoAsync(int chapterId); Task> GetFilesForChapter(int chapterId); Task> GetChaptersAsync(int volumeId); + Task GetChapterCoverImageAsync(int chapterId); } } \ No newline at end of file