Performance/cache epub (#438)

Changed: (Performance) Added the ability for epubs to cache, allowing faster page load for users with network mounted storage. (Fixes Investigate caching epubs (benefit for network mounted users) #433 )
This commit is contained in:
Joseph Milazzo 2021-07-25 19:43:37 -05:00 committed by GitHub
parent 3dbe7eec1f
commit 3c9f73ce2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 68 additions and 21 deletions

View File

@ -215,4 +215,8 @@
<_ContentIncludedByDefault Remove="wwwroot\vendor.6b2a0912ae80e6fd297f.js.map" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Drawing.Common" />
</ItemGroup>
</Project>

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using API.DTOs;
@ -18,14 +19,16 @@ namespace API.Controllers
private readonly ILogger<BookController> _logger;
private readonly IBookService _bookService;
private readonly IUnitOfWork _unitOfWork;
private readonly ICacheService _cacheService;
private static readonly string BookApiUrl = "book-resources?file=";
public BookController(ILogger<BookController> logger, IBookService bookService, IUnitOfWork unitOfWork)
public BookController(ILogger<BookController> logger, IBookService bookService, IUnitOfWork unitOfWork, ICacheService cacheService)
{
_logger = logger;
_bookService = bookService;
_unitOfWork = unitOfWork;
_cacheService = cacheService;
}
[HttpGet("{chapterId}/book-info")]
@ -169,9 +172,11 @@ namespace API.Controllers
[HttpGet("{chapterId}/book-page")]
public async Task<ActionResult<string>> GetBookPage(int chapterId, [FromQuery] int page)
{
var chapter = await _unitOfWork.VolumeRepository.GetChapterAsync(chapterId);
var chapter = await _cacheService.Ensure(chapterId);
var path = _cacheService.GetCachedEpubFile(chapter.Id, chapter);
using var book = await EpubReader.OpenBookAsync(chapter.Files.ElementAt(0).FilePath);
using var book = await EpubReader.OpenBookAsync(path);
var mappings = await _bookService.CreateKeyToPageMappingAsync(book);
var counter = 0;
@ -196,12 +201,7 @@ namespace API.Controllers
{
if (doc.ParseErrors.Any())
{
_logger.LogError("{FilePath} has an invalid html file (Page {PageName})", book.FilePath, contentFileRef.FileName);
foreach (var error in doc.ParseErrors)
{
_logger.LogError("Line {LineNumber}, Reason: {Reason}", error.Line, error.Reason);
}
LogBookErrors(book, contentFileRef, doc);
return BadRequest("The file is malformed! Cannot read.");
}
_logger.LogError("{FilePath} has no body tag! Generating one for support. Book may be skewed", book.FilePath);
@ -322,5 +322,14 @@ namespace API.Controllers
return BadRequest("Could not find the appropriate html for that page");
}
private void LogBookErrors(EpubBookRef book, EpubTextContentFileRef contentFileRef, HtmlDocument doc)
{
_logger.LogError("{FilePath} has an invalid html file (Page {PageName})", book.FilePath, contentFileRef.FileName);
foreach (var error in doc.ParseErrors)
{
_logger.LogError("Line {LineNumber}, Reason: {Reason}", error.Line, error.Reason);
}
}
}
}

View File

@ -35,5 +35,6 @@ namespace API.Interfaces.Services
Task<(string path, MangaFile file)> GetCachedPagePath(Chapter chapter, int page);
void EnsureCacheDirectory();
string GetCachedEpubFile(int chapterId, Chapter chapter);
}
}

View File

@ -21,8 +21,6 @@ using HtmlAgilityPack;
using Microsoft.Extensions.Logging;
using Microsoft.IO;
using VersOne.Epub;
using Image = NetVips.Image;
using Point = System.Drawing.Point;
namespace API.Services
{
@ -409,7 +407,7 @@ namespace API.Services
if (!createThumbnail) return coverImageContent.ReadContent();
using var stream = StreamManager.GetStream("BookService.GetCoverImage", coverImageContent.ReadContent());
using var thumbnail = Image.ThumbnailStream(stream, MetadataService.ThumbnailWidth);
using var thumbnail = NetVips.Image.ThumbnailStream(stream, MetadataService.ThumbnailWidth);
return thumbnail.WriteToBuffer(".jpg");
}
@ -433,7 +431,7 @@ namespace API.Services
if (!createThumbnail) return stream.ToArray();
using var thumbnail = Image.ThumbnailStream(stream, MetadataService.ThumbnailWidth);
using var thumbnail = NetVips.Image.ThumbnailStream(stream, MetadataService.ThumbnailWidth);
return thumbnail.WriteToBuffer(".png");
}

View File

@ -9,6 +9,7 @@ using API.Entities.Enums;
using API.Extensions;
using API.Interfaces;
using API.Interfaces.Services;
using Kavita.Common;
using Microsoft.Extensions.Logging;
namespace API.Services
@ -42,6 +43,23 @@ namespace API.Services
}
}
/// <summary>
/// Returns the full path to the cached epub file. If the file does not exist, will fallback to the original.
/// </summary>
/// <param name="chapterId"></param>
/// <param name="chapter"></param>
/// <returns></returns>
public string GetCachedEpubFile(int chapterId, Chapter chapter)
{
var extractPath = GetCachePath(chapterId);
var path = Path.Join(extractPath, Path.GetFileName(chapter.Files.First().FilePath));
if (!(new FileInfo(path).Exists))
{
path = chapter.Files.First().FilePath;
}
return path;
}
public async Task<Chapter> Ensure(int chapterId)
{
EnsureCacheDirectory();
@ -50,6 +68,7 @@ namespace API.Services
var fileCount = files.Count;
var extractPath = GetCachePath(chapterId);
var extraPath = "";
var removeNonImages = true;
if (Directory.Exists(extractPath))
{
@ -83,15 +102,24 @@ namespace API.Services
if (file.Format == MangaFormat.Archive)
{
_archiveService.ExtractArchive(file.FilePath, Path.Join(extractPath, extraPath));
_archiveService.ExtractArchive(file.FilePath, Path.Join(extractPath, extraPath));
} else if (file.Format == MangaFormat.Pdf)
{
_bookService.ExtractPdfImages(file.FilePath, Path.Join(extractPath, extraPath));
} else if (file.Format == MangaFormat.Epub)
{
removeNonImages = false;
DirectoryService.ExistOrCreate(extractPath);
_directoryService.CopyFileToDirectory(files[0].FilePath, extractPath);
}
}
extractDi.Flatten();
extractDi.RemoveNonImages();
if (removeNonImages)
{
extractDi.RemoveNonImages();
}
return chapter;
}

View File

@ -106,11 +106,18 @@ namespace API.Services
public void CopyFileToDirectory(string fullFilePath, string targetDirectory)
{
var fileInfo = new FileInfo(fullFilePath);
if (fileInfo.Exists)
{
fileInfo.CopyTo(Path.Join(targetDirectory, fileInfo.Name));
}
try
{
var fileInfo = new FileInfo(fullFilePath);
if (fileInfo.Exists)
{
fileInfo.CopyTo(Path.Join(targetDirectory, fileInfo.Name), true);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "There was a critical error when copying {File} to {Directory}", fullFilePath, targetDirectory);
}
}
/// <summary>

View File

@ -4,7 +4,7 @@
<TargetFramework>net5.0</TargetFramework>
<Company>kavitareader.com</Company>
<Product>Kavita</Product>
<AssemblyVersion>0.4.3.2</AssemblyVersion>
<AssemblyVersion>0.4.3.3</AssemblyVersion>
<NeutralLanguage>en</NeutralLanguage>
</PropertyGroup>