diff --git a/API/API.csproj b/API/API.csproj index 997ca6817..fd4e7f9ac 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -215,4 +215,8 @@ <_ContentIncludedByDefault Remove="wwwroot\vendor.6b2a0912ae80e6fd297f.js.map" /> + + + + diff --git a/API/Controllers/BookController.cs b/API/Controllers/BookController.cs index 0efa6c71e..f8dbaafe8 100644 --- a/API/Controllers/BookController.cs +++ b/API/Controllers/BookController.cs @@ -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 _logger; private readonly IBookService _bookService; private readonly IUnitOfWork _unitOfWork; + private readonly ICacheService _cacheService; private static readonly string BookApiUrl = "book-resources?file="; - public BookController(ILogger logger, IBookService bookService, IUnitOfWork unitOfWork) + public BookController(ILogger 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> 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); + } + } } } diff --git a/API/Interfaces/Services/ICacheService.cs b/API/Interfaces/Services/ICacheService.cs index f2d6cfa71..8499702b1 100644 --- a/API/Interfaces/Services/ICacheService.cs +++ b/API/Interfaces/Services/ICacheService.cs @@ -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); } } diff --git a/API/Services/BookService.cs b/API/Services/BookService.cs index ea8fb10aa..dbc3400dd 100644 --- a/API/Services/BookService.cs +++ b/API/Services/BookService.cs @@ -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"); } diff --git a/API/Services/CacheService.cs b/API/Services/CacheService.cs index 3d557ed7f..89d1cf395 100644 --- a/API/Services/CacheService.cs +++ b/API/Services/CacheService.cs @@ -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 } } + /// + /// Returns the full path to the cached epub file. If the file does not exist, will fallback to the original. + /// + /// + /// + /// + 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 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; } diff --git a/API/Services/DirectoryService.cs b/API/Services/DirectoryService.cs index 8345b32d1..d0e5cce84 100644 --- a/API/Services/DirectoryService.cs +++ b/API/Services/DirectoryService.cs @@ -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); + } } /// diff --git a/Kavita.Common/Kavita.Common.csproj b/Kavita.Common/Kavita.Common.csproj index 7e67cccbb..dd2a776da 100644 --- a/Kavita.Common/Kavita.Common.csproj +++ b/Kavita.Common/Kavita.Common.csproj @@ -4,7 +4,7 @@ net5.0 kavitareader.com Kavita - 0.4.3.2 + 0.4.3.3 en