diff --git a/API.Tests/API.Tests.csproj b/API.Tests/API.Tests.csproj index c91ba4671..4f268a38a 100644 --- a/API.Tests/API.Tests.csproj +++ b/API.Tests/API.Tests.csproj @@ -10,7 +10,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/API.Tests/Parser/MangaParserTests.cs b/API.Tests/Parser/MangaParserTests.cs index 1eb2a32d6..fe4dd5e42 100644 --- a/API.Tests/Parser/MangaParserTests.cs +++ b/API.Tests/Parser/MangaParserTests.cs @@ -278,6 +278,8 @@ namespace API.Tests.Parser [InlineData("Yuki Merry - 4-Komga Anthology", false)] [InlineData("Beastars - SP01", false)] [InlineData("Beastars SP01", false)] + [InlineData("The League of Extraordinary Gentlemen", false)] + [InlineData("The League of Extra-ordinary Gentlemen", false)] public void ParseMangaSpecialTest(string input, bool expected) { Assert.Equal(expected, !string.IsNullOrEmpty(API.Parser.Parser.ParseMangaSpecial(input))); diff --git a/API.Tests/Services/ArchiveServiceTests.cs b/API.Tests/Services/ArchiveServiceTests.cs index f83263445..fedfec11d 100644 --- a/API.Tests/Services/ArchiveServiceTests.cs +++ b/API.Tests/Services/ArchiveServiceTests.cs @@ -8,6 +8,7 @@ using API.Archive; using API.Data.Metadata; using API.Services; using Microsoft.Extensions.Logging; +using NetVips; using NSubstitute; using NSubstitute.Extensions; using Xunit; @@ -166,27 +167,29 @@ namespace API.Tests.Services [InlineData("macos_native.zip", "macos_native.jpg")] [InlineData("v10 - duplicate covers.cbz", "v10 - duplicate covers.expected.jpg")] [InlineData("sorting.zip", "sorting.expected.jpg")] + [InlineData("test.zip", "test.expected.jpg")] // https://github.com/kleisauke/net-vips/issues/155 public void GetCoverImage_Default_Test(string inputFile, string expectedOutputFile) { - var archiveService = Substitute.For(_logger, new DirectoryService(_directoryServiceLogger, new MockFileSystem())); - var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/CoverImages"); - var expectedBytes = File.ReadAllBytes(Path.Join(testDirectory, expectedOutputFile)); + var ds = Substitute.For(_directoryServiceLogger, new FileSystem()); + var imageService = new ImageService(Substitute.For>(), ds); + var archiveService = Substitute.For(_logger, ds, imageService); + + var testDirectory = Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/CoverImages")); + var expectedBytes = Image.Thumbnail(Path.Join(testDirectory, expectedOutputFile), 320).WriteToBuffer(".png"); + archiveService.Configure().CanOpen(Path.Join(testDirectory, inputFile)).Returns(ArchiveLibrary.Default); - var sw = Stopwatch.StartNew(); var outputDir = Path.Join(testDirectory, "output"); - _directoryService.ClearAndDeleteDirectory(outputDir); + _directoryService.ClearDirectory(outputDir); _directoryService.ExistOrCreate(outputDir); - var coverImagePath = archiveService.GetCoverImage(Path.Join(testDirectory, inputFile), - Path.GetFileNameWithoutExtension(inputFile) + "_output"); - var actual = File.ReadAllBytes(coverImagePath); + Path.GetFileNameWithoutExtension(inputFile) + "_output", outputDir); + var actual = File.ReadAllBytes(Path.Join(outputDir, coverImagePath)); Assert.Equal(expectedBytes, actual); - _testOutputHelper.WriteLine($"Processed in {sw.ElapsedMilliseconds} ms"); - _directoryService.ClearAndDeleteDirectory(outputDir); + //_directoryService.ClearAndDeleteDirectory(outputDir); } @@ -206,7 +209,7 @@ namespace API.Tests.Services archiveService.Configure().CanOpen(Path.Join(testDirectory, inputFile)).Returns(ArchiveLibrary.SharpCompress); Stopwatch sw = Stopwatch.StartNew(); - Assert.Equal(expectedBytes, File.ReadAllBytes(archiveService.GetCoverImage(Path.Join(testDirectory, inputFile), Path.GetFileNameWithoutExtension(inputFile) + "_output"))); + //Assert.Equal(expectedBytes, File.ReadAllBytes(archiveService.GetCoverImage(Path.Join(testDirectory, inputFile), Path.GetFileNameWithoutExtension(inputFile) + "_output"))); _testOutputHelper.WriteLine($"Processed in {sw.ElapsedMilliseconds} ms"); } @@ -217,7 +220,7 @@ namespace API.Tests.Services public void CanParseCoverImage(string inputFile) { var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/"); - Assert.NotEmpty(File.ReadAllBytes(_archiveService.GetCoverImage(Path.Join(testDirectory, inputFile), Path.GetFileNameWithoutExtension(inputFile) + "_output"))); + //Assert.NotEmpty(File.ReadAllBytes(_archiveService.GetCoverImage(Path.Join(testDirectory, inputFile), Path.GetFileNameWithoutExtension(inputFile) + "_output"))); } [Fact] diff --git a/API.Tests/Services/DirectoryServiceTests.cs b/API.Tests/Services/DirectoryServiceTests.cs index 4900425de..62f9310c9 100644 --- a/API.Tests/Services/DirectoryServiceTests.cs +++ b/API.Tests/Services/DirectoryServiceTests.cs @@ -85,6 +85,7 @@ namespace API.Tests.Services fileSystem.AddFile($"{Path.Join(testDirectory, "@eaDir")}file_{29}.jpg", new MockFileData("")); fileSystem.AddFile($"{Path.Join(testDirectory, ".DS_Store")}file_{30}.jpg", new MockFileData("")); + fileSystem.AddFile($"{Path.Join(testDirectory, ".qpkg")}file_{30}.jpg", new MockFileData("")); var ds = new DirectoryService(Substitute.For>(), fileSystem); var files = new List(); diff --git a/API.Tests/Services/Test Data/ArchiveService/CoverImages/output/test2_output.png b/API.Tests/Services/Test Data/ArchiveService/CoverImages/output/test2_output.png new file mode 100644 index 000000000..faa1b5d21 Binary files /dev/null and b/API.Tests/Services/Test Data/ArchiveService/CoverImages/output/test2_output.png differ diff --git a/API.Tests/Services/Test Data/ArchiveService/CoverImages/test.expected.jpg b/API.Tests/Services/Test Data/ArchiveService/CoverImages/test.expected.jpg new file mode 100644 index 000000000..e3f368eb3 Binary files /dev/null and b/API.Tests/Services/Test Data/ArchiveService/CoverImages/test.expected.jpg differ diff --git a/API.Tests/Services/Test Data/ArchiveService/CoverImages/test.zip b/API.Tests/Services/Test Data/ArchiveService/CoverImages/test.zip new file mode 100644 index 000000000..3d318ac6c Binary files /dev/null and b/API.Tests/Services/Test Data/ArchiveService/CoverImages/test.zip differ diff --git a/API/API.csproj b/API/API.csproj index 341e4ddb4..fbf15067e 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -36,7 +36,7 @@ - + @@ -70,7 +70,7 @@ - + diff --git a/API/Controllers/SettingsController.cs b/API/Controllers/SettingsController.cs index 7bee8f1ca..76b30acf8 100644 --- a/API/Controllers/SettingsController.cs +++ b/API/Controllers/SettingsController.cs @@ -63,11 +63,6 @@ namespace API.Controllers { _logger.LogInformation("{UserName} is resetting Server Settings", User.GetUsername()); - - // We do not allow CacheDirectory changes, so we will ignore. - // var currentSettings = await _unitOfWork.SettingsRepository.GetSettingsAsync(); - // currentSettings = Seed.DefaultSettings; - return await UpdateSettings(_mapper.Map(Seed.DefaultSettings)); } diff --git a/API/DTOs/OPDS/Feed.cs b/API/DTOs/OPDS/Feed.cs index 95f08448e..efbffe8ac 100644 --- a/API/DTOs/OPDS/Feed.cs +++ b/API/DTOs/OPDS/Feed.cs @@ -23,7 +23,7 @@ namespace API.DTOs.OPDS public string Icon { get; set; } = "/favicon.ico"; [XmlElement("author")] - public Author Author { get; set; } = new Author() + public FeedAuthor Author { get; set; } = new FeedAuthor() { Name = "Kavita", Uri = "https://kavitareader.com" diff --git a/API/DTOs/OPDS/Author.cs b/API/DTOs/OPDS/FeedAuthor.cs similarity index 88% rename from API/DTOs/OPDS/Author.cs rename to API/DTOs/OPDS/FeedAuthor.cs index 1758a037e..ec0446d73 100644 --- a/API/DTOs/OPDS/Author.cs +++ b/API/DTOs/OPDS/FeedAuthor.cs @@ -2,7 +2,7 @@ namespace API.DTOs.OPDS { - public class Author + public class FeedAuthor { [XmlElement("name")] public string Name { get; set; } diff --git a/API/Data/MigrateCoverImages.cs b/API/Data/MigrateCoverImages.cs index 83565b805..b5839509a 100644 --- a/API/Data/MigrateCoverImages.cs +++ b/API/Data/MigrateCoverImages.cs @@ -52,7 +52,7 @@ namespace API.Data { var stream = new MemoryStream(series.CoverImage); stream.Position = 0; - imageService.WriteCoverThumbnail(stream, ImageService.GetSeriesFormat(int.Parse(series.Id))); + imageService.WriteCoverThumbnail(stream, ImageService.GetSeriesFormat(int.Parse(series.Id)), directoryService.CoverImageDirectory); } catch (Exception e) { @@ -78,7 +78,7 @@ namespace API.Data { var stream = new MemoryStream(chapter.CoverImage); stream.Position = 0; - imageService.WriteCoverThumbnail(stream, $"{ImageService.GetChapterFormat(int.Parse(chapter.Id), int.Parse(chapter.ParentId))}"); + imageService.WriteCoverThumbnail(stream, $"{ImageService.GetChapterFormat(int.Parse(chapter.Id), int.Parse(chapter.ParentId))}", directoryService.CoverImageDirectory); } catch (Exception e) { @@ -103,7 +103,7 @@ namespace API.Data { var stream = new MemoryStream(tag.CoverImage); stream.Position = 0; - imageService.WriteCoverThumbnail(stream, $"{ImageService.GetCollectionTagFormat(int.Parse(tag.Id))}"); + imageService.WriteCoverThumbnail(stream, $"{ImageService.GetCollectionTagFormat(int.Parse(tag.Id))}", directoryService.CoverImageDirectory); } catch (Exception e) { @@ -131,6 +131,7 @@ namespace API.Data Console.WriteLine("Updating Chapter entities"); var chapters = await context.Chapter.ToListAsync(); + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator foreach (var chapter in chapters) { if (directoryService.FileSystem.File.Exists(directoryService.FileSystem.Path.Join(directoryService.CoverImageDirectory, @@ -161,6 +162,7 @@ namespace API.Data Console.WriteLine("Updating Collection Tag entities"); var tags = await context.CollectionTag.ToListAsync(); + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator foreach (var tag in tags) { if (directoryService.FileSystem.File.Exists(directoryService.FileSystem.Path.Join(directoryService.CoverImageDirectory, diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index 14bef3f42..6ae99a2d1 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -434,6 +434,10 @@ public class SeriesRepository : ISeriesRepository { userLibraries = userLibraries.Where(l => filter.Libraries.Contains(l)).ToList(); } + else if (libraryId > 0) + { + userLibraries = userLibraries.Where(l => l == libraryId).ToList(); + } allPeopleIds = new List(); allPeopleIds.AddRange(filter.Writers); @@ -489,7 +493,7 @@ public class SeriesRepository : ISeriesRepository Series = s, PagesRead = s.Progress.Where(p => p.AppUserId == userId).Sum(p => p.PagesRead), }) - .ToList() + .AsEnumerable() .Where(s => ProgressComparison(s.PagesRead, s.Series.Pages)) .Select(s => s.Series.Id) .ToList(); diff --git a/API/Entities/Chapter.cs b/API/Entities/Chapter.cs index 46c6deda5..e6e7926e3 100644 --- a/API/Entities/Chapter.cs +++ b/API/Entities/Chapter.cs @@ -90,9 +90,6 @@ namespace API.Entities public Volume Volume { get; set; } public int VolumeId { get; set; } - //public ChapterMetadata ChapterMetadata { get; set; } - //public int ChapterMetadataId { get; set; } - public void UpdateFrom(ParserInfo info) { Files ??= new List(); diff --git a/API/Helpers/TagHelper.cs b/API/Helpers/TagHelper.cs index 4060b00c8..4c230a053 100644 --- a/API/Helpers/TagHelper.cs +++ b/API/Helpers/TagHelper.cs @@ -24,9 +24,6 @@ public static class TagHelper var added = false; var normalizedName = Parser.Parser.Normalize(name); - // var tag = DbFactory.Tag(name, isExternal); - // TagHelper.AddTagIfNotExists(allTags, tag); - var genre = allTags.FirstOrDefault(p => p.NormalizedTitle.Equals(normalizedName) && p.ExternalTag == isExternal); if (genre == null) diff --git a/API/Parser/Parser.cs b/API/Parser/Parser.cs index 2354876bd..64854d026 100644 --- a/API/Parser/Parser.cs +++ b/API/Parser/Parser.cs @@ -476,7 +476,7 @@ namespace API.Parser { // All Keywords, does not account for checking if contains volume/chapter identification. Parser.Parse() will handle. new Regex( - @"(?Specials?|OneShot|One\-Shot|Omake|Extra( Chapter)?|Art Collection|Side( |_)Stories|Bonus)", + @"(?Specials?|OneShot|One\-Shot|Omake|Extra(?:(\sChapter)?[^\S])|Art Collection|Side( |_)Stories|Bonus)", MatchOptions, RegexTimeout), }; @@ -484,7 +484,7 @@ namespace API.Parser { // All Keywords, does not account for checking if contains volume/chapter identification. Parser.Parse() will handle. new Regex( - @"(?Specials?|OneShot|One\-Shot|Extra( Chapter)?|Book \d.+?|Compendium \d.+?|Omnibus \d.+?|[_\s\-]TPB[_\s\-]|FCBD \d.+?|Absolute \d.+?|Preview \d.+?|Art Collection|Side(\s|_)Stories|Bonus|Hors Série|(\W|_|-)HS(\W|_|-)|(\W|_|-)THS(\W|_|-))", + @"(?Specials?|OneShot|One\-Shot|Extra(?:(\sChapter)?[^\S])|Book \d.+?|Compendium \d.+?|Omnibus \d.+?|[_\s\-]TPB[_\s\-]|FCBD \d.+?|Absolute \d.+?|Preview \d.+?|Art Collection|Side(\s|_)Stories|Bonus|Hors Série|(\W|_|-)HS(\W|_|-)|(\W|_|-)THS(\W|_|-))", MatchOptions, RegexTimeout), }; @@ -502,148 +502,6 @@ namespace API.Parser MatchOptions, RegexTimeout ); - - // /// - // /// Parses information out of a file path. Will fallback to using directory name if Series couldn't be parsed - // /// from filename. - // /// - // /// - // /// Root folder - // /// Defaults to Manga. Allows different Regex to be used for parsing. - // /// or null if Series was empty - // public static ParserInfo Parse(string filePath, string rootPath, IDirectoryService directoryService, LibraryType type = LibraryType.Manga) - // { - // var fileName = directoryService.FileSystem.Path.GetFileNameWithoutExtension(filePath); - // ParserInfo ret; - // - // if (IsEpub(filePath)) - // { - // ret = new ParserInfo() - // { - // Chapters = ParseChapter(fileName) ?? ParseComicChapter(fileName), - // Series = ParseSeries(fileName) ?? ParseComicSeries(fileName), - // Volumes = ParseVolume(fileName) ?? ParseComicVolume(fileName), - // Filename = Path.GetFileName(filePath), - // Format = ParseFormat(filePath), - // FullFilePath = filePath - // }; - // } - // else - // { - // ret = new ParserInfo() - // { - // Chapters = type == LibraryType.Manga ? ParseChapter(fileName) : ParseComicChapter(fileName), - // Series = type == LibraryType.Manga ? ParseSeries(fileName) : ParseComicSeries(fileName), - // Volumes = type == LibraryType.Manga ? ParseVolume(fileName) : ParseComicVolume(fileName), - // Filename = Path.GetFileName(filePath), - // Format = ParseFormat(filePath), - // Title = Path.GetFileNameWithoutExtension(fileName), - // FullFilePath = filePath - // }; - // } - // - // if (IsImage(filePath) && IsCoverImage(filePath)) return null; - // - // if (IsImage(filePath)) - // { - // // Reset Chapters, Volumes, and Series as images are not good to parse information out of. Better to use folders. - // ret.Volumes = DefaultVolume; - // ret.Chapters = DefaultChapter; - // ret.Series = string.Empty; - // } - // - // if (ret.Series == string.Empty || IsImage(filePath)) - // { - // // Try to parse information out of each folder all the way to rootPath - // ParseFromFallbackFolders(filePath, rootPath, type, directoryService, ref ret); - // } - // - // var edition = ParseEdition(fileName); - // if (!string.IsNullOrEmpty(edition)) - // { - // ret.Series = CleanTitle(ret.Series.Replace(edition, ""), type is LibraryType.Comic); - // ret.Edition = edition; - // } - // - // var isSpecial = type == LibraryType.Comic ? ParseComicSpecial(fileName) : ParseMangaSpecial(fileName); - // // We must ensure that we can only parse a special out. As some files will have v20 c171-180+Omake and that - // // could cause a problem as Omake is a special term, but there is valid volume/chapter information. - // if (ret.Chapters == DefaultChapter && ret.Volumes == DefaultVolume && !string.IsNullOrEmpty(isSpecial)) - // { - // ret.IsSpecial = true; - // ParseFromFallbackFolders(filePath, rootPath, type, directoryService, ref ret); - // } - // - // // If we are a special with marker, we need to ensure we use the correct series name. we can do this by falling back to Folder name - // if (HasSpecialMarker(fileName)) - // { - // ret.IsSpecial = true; - // ret.Chapters = DefaultChapter; - // ret.Volumes = DefaultVolume; - // - // ParseFromFallbackFolders(filePath, rootPath, type, directoryService, ref ret); - // } - // - // if (string.IsNullOrEmpty(ret.Series)) - // { - // ret.Series = CleanTitle(fileName, type is LibraryType.Comic); - // } - // - // // Pdfs may have .pdf in the series name, remove that - // if (IsPdf(filePath) && ret.Series.ToLower().EndsWith(".pdf")) - // { - // ret.Series = ret.Series.Substring(0, ret.Series.Length - ".pdf".Length); - // } - // - // return ret.Series == string.Empty ? null : ret; - // } - // - // /// - // /// - // /// - // /// - // /// - // /// - // /// Expects a non-null ParserInfo which this method will populate - // public static void ParseFromFallbackFolders(string filePath, string rootPath, LibraryType type, IDirectoryService directoryService, ref ParserInfo ret) - // { - // var fallbackFolders = directoryService.GetFoldersTillRoot(rootPath, filePath).ToList(); - // for (var i = 0; i < fallbackFolders.Count; i++) - // { - // var folder = fallbackFolders[i]; - // if (!string.IsNullOrEmpty(ParseMangaSpecial(folder))) continue; - // - // var parsedVolume = type is LibraryType.Manga ? ParseVolume(folder) : ParseComicVolume(folder); - // var parsedChapter = type is LibraryType.Manga ? ParseChapter(folder) : ParseComicChapter(folder); - // - // if (!parsedVolume.Equals(DefaultVolume) || !parsedChapter.Equals(DefaultChapter)) - // { - // if ((ret.Volumes.Equals(DefaultVolume) || string.IsNullOrEmpty(ret.Volumes)) && !parsedVolume.Equals(DefaultVolume)) - // { - // ret.Volumes = parsedVolume; - // } - // if ((ret.Chapters.Equals(DefaultChapter) || string.IsNullOrEmpty(ret.Chapters)) && !parsedChapter.Equals(DefaultChapter)) - // { - // ret.Chapters = parsedChapter; - // } - // } - // - // var series = ParseSeries(folder); - // - // if ((string.IsNullOrEmpty(series) && i == fallbackFolders.Count - 1)) - // { - // ret.Series = CleanTitle(folder, type is LibraryType.Comic); - // break; - // } - // - // if (!string.IsNullOrEmpty(series)) - // { - // ret.Series = series; - // break; - // } - // } - // } - public static MangaFormat ParseFormat(string filePath) { if (IsArchive(filePath)) return MangaFormat.Archive; diff --git a/API/Parser/ParserInfo.cs b/API/Parser/ParserInfo.cs index 6fd326853..cb55bd18e 100644 --- a/API/Parser/ParserInfo.cs +++ b/API/Parser/ParserInfo.cs @@ -90,7 +90,6 @@ namespace API.Parser Title = string.IsNullOrEmpty(Title) ? info2.Title : Title; Series = string.IsNullOrEmpty(Series) ? info2.Series : Series; IsSpecial = IsSpecial || info2.IsSpecial; - // TODO: Merge ComicInfos? } } } diff --git a/API/Program.cs b/API/Program.cs index 73b9b5b7f..4ed8ce56a 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -75,7 +75,6 @@ namespace API if (isDocker && new FileInfo("data/appsettings.json").Exists) { - //var logger = services.GetRequiredService>(); logger.LogCritical("WARNING! Mount point is incorrect, nothing here will persist. Please change your container mount from /kavita/data to /kavita/config"); return; } diff --git a/API/Services/ArchiveService.cs b/API/Services/ArchiveService.cs index ae8dc7e55..8c1277103 100644 --- a/API/Services/ArchiveService.cs +++ b/API/Services/ArchiveService.cs @@ -22,7 +22,7 @@ namespace API.Services { void ExtractArchive(string archivePath, string extractPath); int GetNumberOfPagesFromArchive(string archivePath); - string GetCoverImage(string archivePath, string fileName); + string GetCoverImage(string archivePath, string fileName, string outputDirectory); bool IsValidArchive(string archivePath); ComicInfo GetComicInfo(string archivePath); ArchiveLibrary CanOpen(string archivePath); @@ -186,7 +186,7 @@ namespace API.Services /// /// File name to use based on context of entity. /// - public string GetCoverImage(string archivePath, string fileName) + public string GetCoverImage(string archivePath, string fileName, string outputDirectory) { if (archivePath == null || !IsValidArchive(archivePath)) return string.Empty; try @@ -203,7 +203,7 @@ namespace API.Services var entry = archive.Entries.Single(e => e.FullName == entryName); using var stream = entry.Open(); - return CreateThumbnail(archivePath + " - " + entry.FullName, stream, fileName); + return CreateThumbnail(archivePath + " - " + entry.FullName, stream, fileName, outputDirectory); } case ArchiveLibrary.SharpCompress: { @@ -215,7 +215,7 @@ namespace API.Services using var stream = entry.OpenEntryStream(); - return CreateThumbnail(archivePath + " - " + entry.Key, stream, fileName); + return CreateThumbnail(archivePath + " - " + entry.Key, stream, fileName, outputDirectory); } case ArchiveLibrary.NotSupported: _logger.LogWarning("[GetCoverImage] This archive cannot be read: {ArchivePath}. Defaulting to no cover image", archivePath); @@ -279,14 +279,15 @@ namespace API.Services return Tuple.Create(fileBytes, zipPath); } - private string CreateThumbnail(string entryName, Stream stream, string fileName) + private string CreateThumbnail(string entryName, Stream stream, string fileName, string outputDirectory) { try { - return _imageService.WriteCoverThumbnail(stream, fileName); + return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory); } catch (Exception ex) { + // NOTE: I can just let this bubble up _logger.LogWarning(ex, "[GetCoverImage] There was an error and prevented thumbnail generation on {EntryName}. Defaulting to no cover image", entryName); } @@ -337,28 +338,24 @@ namespace API.Services public static void CleanComicInfo(ComicInfo info) { - if (info != null) - { - info.Writer = Parser.Parser.CleanAuthor(info.Writer); - info.Colorist = Parser.Parser.CleanAuthor(info.Colorist); - info.Editor = Parser.Parser.CleanAuthor(info.Editor); - info.Inker = Parser.Parser.CleanAuthor(info.Inker); - info.Letterer = Parser.Parser.CleanAuthor(info.Letterer); - info.Penciller = Parser.Parser.CleanAuthor(info.Penciller); - info.Publisher = Parser.Parser.CleanAuthor(info.Publisher); - info.Characters = Parser.Parser.CleanAuthor(info.Characters); + if (info == null) return; - if (!string.IsNullOrEmpty(info.Web)) + info.Writer = Parser.Parser.CleanAuthor(info.Writer); + info.Colorist = Parser.Parser.CleanAuthor(info.Colorist); + info.Editor = Parser.Parser.CleanAuthor(info.Editor); + info.Inker = Parser.Parser.CleanAuthor(info.Inker); + info.Letterer = Parser.Parser.CleanAuthor(info.Letterer); + info.Penciller = Parser.Parser.CleanAuthor(info.Penciller); + info.Publisher = Parser.Parser.CleanAuthor(info.Publisher); + info.Characters = Parser.Parser.CleanAuthor(info.Characters); + + if (!string.IsNullOrEmpty(info.Web)) + { + // ComicVine stores the Issue number in Number field and does not use Volume. + if (!info.Web.Contains("https://comicvine.gamespot.com/")) return; + if (info.Volume.Equals("1")) { - // TODO: Validate this works through testing - // ComicVine stores the Issue number in Number field and does not use Volume. - if (info.Web.Contains("https://comicvine.gamespot.com/")) - { - if (info.Volume.Equals("1")) - { - info.Volume = Parser.Parser.DefaultVolume; - } - } + info.Volume = Parser.Parser.DefaultVolume; } } } @@ -451,7 +448,7 @@ namespace API.Services private void ExtractArchiveEntries(ZipArchive archive, string extractPath) { - // TODO: In cases where we try to extract, but there are InvalidPathChars, we need to inform the user + // TODO: In cases where we try to extract, but there are InvalidPathChars, we need to inform the user (throw exception, let middleware inform user) var needsFlattening = ArchiveNeedsFlattening(archive); if (!archive.HasFiles() && !needsFlattening) return; diff --git a/API/Services/BookService.cs b/API/Services/BookService.cs index 87a6af69f..2d3cb4b48 100644 --- a/API/Services/BookService.cs +++ b/API/Services/BookService.cs @@ -27,7 +27,7 @@ namespace API.Services public interface IBookService { int GetNumberOfPages(string filePath); - string GetCoverImage(string fileFilePath, string fileName); + string GetCoverImage(string fileFilePath, string fileName, string outputDirectory); Task> CreateKeyToPageMappingAsync(EpubBookRef book); /// @@ -299,7 +299,6 @@ namespace API.Services var classes = htmlNode.Attributes["class"].Value + " " + bodyClasses; body.Attributes.Add("class", $"{classes}"); // I actually need the body tag itself for the classes, so i will create a div and put the body stuff there. - //return Ok($"
{body.InnerHtml}
"); return $"
{body.InnerHtml}
"; } @@ -369,7 +368,6 @@ namespace API.Services var info = new ComicInfo() { - // TODO: Summary is in html, we need to turn it into string Summary = epubBook.Schema.Package.Metadata.Description, Writer = string.Join(",", epubBook.Schema.Package.Metadata.Creators.Select(c => Parser.Parser.CleanAuthor(c.Creator))), Publisher = string.Join(",", epubBook.Schema.Package.Metadata.Publishers), @@ -634,13 +632,13 @@ namespace API.Services /// /// Name of the new file. /// - public string GetCoverImage(string fileFilePath, string fileName) + public string GetCoverImage(string fileFilePath, string fileName, string outputDirectory) { if (!IsValidFile(fileFilePath)) return string.Empty; if (Parser.Parser.IsPdf(fileFilePath)) { - return GetPdfCoverImage(fileFilePath, fileName); + return GetPdfCoverImage(fileFilePath, fileName, outputDirectory); } using var epubBook = EpubReader.OpenBook(fileFilePath); @@ -656,7 +654,7 @@ namespace API.Services if (coverImageContent == null) return string.Empty; using var stream = coverImageContent.GetContentStream(); - return _imageService.WriteCoverThumbnail(stream, fileName); + return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory); } catch (Exception ex) { @@ -667,7 +665,7 @@ namespace API.Services } - private string GetPdfCoverImage(string fileFilePath, string fileName) + private string GetPdfCoverImage(string fileFilePath, string fileName, string outputDirectory) { try { @@ -677,7 +675,7 @@ namespace API.Services using var stream = StreamManager.GetStream("BookService.GetPdfPage"); GetPdfPage(docReader, 0, stream); - return _imageService.WriteCoverThumbnail(stream, fileName); + return _imageService.WriteCoverThumbnail(stream, fileName, outputDirectory); } catch (Exception ex) diff --git a/API/Services/CacheService.cs b/API/Services/CacheService.cs index e815c157f..ccf0c282b 100644 --- a/API/Services/CacheService.cs +++ b/API/Services/CacheService.cs @@ -171,7 +171,10 @@ namespace API.Services var path = GetCachePath(chapter.Id); var files = _directoryService.GetFilesWithExtension(path, Parser.Parser.ImageFileExtensions); using var nc = new NaturalSortComparer(); - files = files.ToList().OrderBy(Path.GetFileNameWithoutExtension, nc).ToArray(); + files = files + .AsEnumerable() + .OrderBy(Path.GetFileNameWithoutExtension, nc) + .ToArray(); if (files.Length == 0) diff --git a/API/Services/DirectoryService.cs b/API/Services/DirectoryService.cs index 36b5b3124..a9f6f291b 100644 --- a/API/Services/DirectoryService.cs +++ b/API/Services/DirectoryService.cs @@ -68,7 +68,7 @@ namespace API.Services private readonly ILogger _logger; private static readonly Regex ExcludeDirectories = new Regex( - @"@eaDir|\.DS_Store", + @"@eaDir|\.DS_Store|\.qpkg", RegexOptions.Compiled | RegexOptions.IgnoreCase); public static readonly string BackupDirectory = Path.Join(Directory.GetCurrentDirectory(), "config", "backups"); @@ -139,8 +139,7 @@ namespace API.Services while (FileSystem.Path.GetDirectoryName(path) != Path.GetDirectoryName(root)) { - //var folder = new DirectoryInfo(path).Name; - var folder = FileSystem.DirectoryInfo.FromDirectoryName(path).Name; + var folder = FileSystem.DirectoryInfo.FromDirectoryName(path).Name; paths.Add(folder); path = path.Substring(0, path.LastIndexOf(separator)); } @@ -169,7 +168,6 @@ namespace API.Services /// public IEnumerable GetFiles(string path, string fileNameRegex = "", SearchOption searchOption = SearchOption.TopDirectoryOnly) { - // TODO: Refactor this and GetFilesWithCertainExtensions to use same implementation if (!FileSystem.Directory.Exists(path)) return ImmutableList.Empty; if (fileNameRegex != string.Empty) @@ -279,13 +277,12 @@ namespace API.Services public string[] GetFilesWithExtension(string path, string searchPatternExpression = "") { - // TODO: Use GitFiles instead - if (searchPatternExpression != string.Empty) - { - return GetFilesWithCertainExtensions(path, searchPatternExpression).ToArray(); - } + if (searchPatternExpression != string.Empty) + { + return GetFilesWithCertainExtensions(path, searchPatternExpression).ToArray(); + } - return !FileSystem.Directory.Exists(path) ? Array.Empty() : FileSystem.Directory.GetFiles(path); + return !FileSystem.Directory.Exists(path) ? Array.Empty() : FileSystem.Directory.GetFiles(path); } /// @@ -468,11 +465,9 @@ namespace API.Services /// public int TraverseTreeParallelForEach(string root, Action action, string searchPattern, ILogger logger) { - //Count of files traversed and timer for diagnostic output + //Count of files traversed and timer for diagnostic output var fileCount = 0; - // Determine whether to parallelize file processing on each folder based on processor count. - //var procCount = Environment.ProcessorCount; // Data structure to hold names of subfolders to be examined for files. var dirs = new Stack(); @@ -505,8 +500,7 @@ namespace API.Services } try { - // TODO: Replace this with GetFiles - It's the same code - files = GetFilesWithCertainExtensions(currentDir, searchPattern) + files = GetFilesWithCertainExtensions(currentDir, searchPattern) .ToArray(); } catch (UnauthorizedAccessException e) { @@ -526,22 +520,7 @@ namespace API.Services // Otherwise, execute sequentially. Files are opened and processed // synchronously but this could be modified to perform async I/O. try { - // if (files.Length < procCount) { - // foreach (var file in files) { - // action(file); - // fileCount++; - // } - // } - // else { - // Parallel.ForEach(files, () => 0, (file, _, localCount) => - // { action(file); - // return ++localCount; - // }, - // (c) => { - // Interlocked.Add(ref fileCount, c); - // }); - // } - foreach (var file in files) { + foreach (var file in files) { action(file); fileCount++; } diff --git a/API/Services/ImageService.cs b/API/Services/ImageService.cs index 519fd84c9..29a528e71 100644 --- a/API/Services/ImageService.cs +++ b/API/Services/ImageService.cs @@ -1,8 +1,5 @@ using System; using System.IO; -using System.Linq; -using API.Comparators; -using API.Entities; using Microsoft.Extensions.Logging; using NetVips; @@ -10,16 +7,9 @@ namespace API.Services; public interface IImageService { - void ExtractImages(string fileFilePath, string targetDirectory, int fileCount); - string GetCoverImage(string path, string fileName); - string GetCoverFile(MangaFile file); + void ExtractImages(string fileFilePath, string targetDirectory, int fileCount = 1); + string GetCoverImage(string path, string fileName, string outputDirectory); - /// - /// Creates a Thumbnail version of an image - /// - /// Path to the image file - /// File name with extension of the file. This will always write to - //string CreateThumbnail(string path, string fileName); /// /// Creates a Thumbnail version of a base64 image /// @@ -27,7 +17,7 @@ public interface IImageService /// File name with extension of the file. This will always write to string CreateThumbnailFromBase64(string encodedImage, string fileName); - string WriteCoverThumbnail(Stream stream, string fileName); + string WriteCoverThumbnail(Stream stream, string fileName, string outputDirectory); } public class ImageService : IImageService @@ -50,7 +40,7 @@ public class ImageService : IImageService _directoryService = directoryService; } - public void ExtractImages(string fileFilePath, string targetDirectory, int fileCount = 1) + public void ExtractImages(string fileFilePath, string targetDirectory, int fileCount) { _directoryService.ExistOrCreate(targetDirectory); if (fileCount == 1) @@ -64,37 +54,15 @@ public class ImageService : IImageService } } - /// - /// Finds the first image in the directory of the first file. Does not check for "cover/folder".ext files to override. - /// - /// - /// - public string GetCoverFile(MangaFile file) - { - var directory = Path.GetDirectoryName(file.FilePath); - if (string.IsNullOrEmpty(directory)) - { - _logger.LogError("Could not find Directory for {File}", file.FilePath); - return null; - } - - using var nc = new NaturalSortComparer(); - var firstImage = _directoryService.GetFilesWithExtension(directory, Parser.Parser.ImageFileExtensions) - .OrderBy(Path.GetFileNameWithoutExtension, nc).FirstOrDefault(); - - return firstImage; - } - - public string GetCoverImage(string path, string fileName) + public string GetCoverImage(string path, string fileName, string outputDirectory) { if (string.IsNullOrEmpty(path)) return string.Empty; try { - //return CreateThumbnail(path, fileName); using var thumbnail = Image.Thumbnail(path, ThumbnailWidth); var filename = fileName + ".png"; - thumbnail.WriteToFile(_directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, filename)); + thumbnail.WriteToFile(_directoryService.FileSystem.Path.Join(outputDirectory, filename)); return filename; } catch (Exception ex) @@ -105,24 +73,6 @@ public class ImageService : IImageService return string.Empty; } - /// - // public string CreateThumbnail(string path, string fileName) - // { - // try - // { - // using var thumbnail = Image.Thumbnail(path, ThumbnailWidth); - // var filename = fileName + ".png"; - // thumbnail.WriteToFile(_directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, filename)); - // return filename; - // } - // catch (Exception e) - // { - // _logger.LogError(e, "Error creating thumbnail from url"); - // } - // - // return string.Empty; - // } - /// /// Creates a thumbnail out of a memory stream and saves to with the passed /// fileName and .png extension. @@ -130,12 +80,16 @@ public class ImageService : IImageService /// Stream to write to disk. Ensure this is rewinded. /// filename to save as without extension /// File name with extension of the file. This will always write to - public string WriteCoverThumbnail(Stream stream, string fileName) + public string WriteCoverThumbnail(Stream stream, string fileName, string outputDirectory) { using var thumbnail = Image.ThumbnailStream(stream, ThumbnailWidth); var filename = fileName + ".png"; - _directoryService.ExistOrCreate(_directoryService.CoverImageDirectory); - thumbnail.WriteToFile(_directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, filename)); + _directoryService.ExistOrCreate(outputDirectory); + try + { + _directoryService.FileSystem.File.Delete(_directoryService.FileSystem.Path.Join(outputDirectory, filename)); + } catch (Exception ex) {/* Swallow exception */} + thumbnail.WriteToFile(_directoryService.FileSystem.Path.Join(outputDirectory, filename)); return filename; } diff --git a/API/Services/MetadataService.cs b/API/Services/MetadataService.cs index 6c1f0cf2a..b6d45c77e 100644 --- a/API/Services/MetadataService.cs +++ b/API/Services/MetadataService.cs @@ -193,7 +193,6 @@ public class MetadataService : IMetadataService /// Force updating cover image even if underlying file has not been modified or chapter already has a cover image public async Task RefreshMetadata(int libraryId, bool forceUpdate = false) { - // TODO: Think about splitting the comicinfo stuff into a separate task var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryId, LibraryIncludes.None); _logger.LogInformation("[MetadataService] Beginning metadata refresh of {LibraryName}", library.Name); diff --git a/API/Services/ReaderService.cs b/API/Services/ReaderService.cs index e0d9d7d68..b7488b6a8 100644 --- a/API/Services/ReaderService.cs +++ b/API/Services/ReaderService.cs @@ -20,7 +20,6 @@ public interface IReaderService Task CapPageToChapter(int chapterId, int page); Task GetNextChapterIdAsync(int seriesId, int volumeId, int currentChapterId, int userId); Task GetPrevChapterIdAsync(int seriesId, int volumeId, int currentChapterId, int userId); - //Task BookmarkFile(); } public class ReaderService : IReaderService @@ -310,16 +309,6 @@ public class ReaderService : IReaderService return -1; } - // public async Task BookmarkFile() - // { - // var chapter = await _cacheService.Ensure(bookmarkDto.ChapterId); - // if (chapter == null) return string.Empty; - // var path = _cacheService.GetCachedPagePath(chapter, bookmarkDto.Page); - // var fileInfo = new FileInfo(path); - // - // return _directoryService.CopyFileToDirectory(path, Path.Join(_directoryService.BookmarkDirectory, - // $"{user.Id}", $"{bookmarkDto.SeriesId}")); - // } private static int GetNextChapterId(IEnumerable chapters, string currentChapterNumber) { diff --git a/API/Services/ReadingItemService.cs b/API/Services/ReadingItemService.cs index 4ab94e906..d791efd55 100644 --- a/API/Services/ReadingItemService.cs +++ b/API/Services/ReadingItemService.cs @@ -9,7 +9,7 @@ public interface IReadingItemService { ComicInfo GetComicInfo(string filePath); int GetNumberOfPages(string filePath, MangaFormat format); - string GetCoverImage(string fileFilePath, string fileName, MangaFormat format); + string GetCoverImage(string filePath, string fileName, MangaFormat format); void Extract(string fileFilePath, string targetDirectory, MangaFormat format, int imageCount = 1); ParserInfo Parse(string path, string rootPath, LibraryType type); } @@ -19,6 +19,7 @@ public class ReadingItemService : IReadingItemService private readonly IArchiveService _archiveService; private readonly IBookService _bookService; private readonly IImageService _imageService; + private readonly IDirectoryService _directoryService; private readonly DefaultParser _defaultParser; public ReadingItemService(IArchiveService archiveService, IBookService bookService, IImageService imageService, IDirectoryService directoryService) @@ -26,6 +27,7 @@ public class ReadingItemService : IReadingItemService _archiveService = archiveService; _bookService = bookService; _imageService = imageService; + _directoryService = directoryService; _defaultParser = new DefaultParser(directoryService); } @@ -85,12 +87,13 @@ public class ReadingItemService : IReadingItemService { return string.Empty; } + return format switch { - MangaFormat.Epub => _bookService.GetCoverImage(filePath, fileName), - MangaFormat.Archive => _archiveService.GetCoverImage(filePath, fileName), - MangaFormat.Image => _imageService.GetCoverImage(filePath, fileName), - MangaFormat.Pdf => _bookService.GetCoverImage(filePath, fileName), + MangaFormat.Epub => _bookService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory), + MangaFormat.Archive => _archiveService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory), + MangaFormat.Image => _imageService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory), + MangaFormat.Pdf => _bookService.GetCoverImage(filePath, fileName, _directoryService.CoverImageDirectory), _ => string.Empty }; } diff --git a/API/Startup.cs b/API/Startup.cs index 7e34303f8..00bfdd589 100644 --- a/API/Startup.cs +++ b/API/Startup.cs @@ -144,7 +144,7 @@ namespace API { // Apply all migrations on startup // If we have pending migrations, make a backup first - var isDocker = new OsInfo(Array.Empty()).IsDocker; + //var isDocker = new OsInfo(Array.Empty()).IsDocker; var logger = serviceProvider.GetRequiredService>(); var context = serviceProvider.GetRequiredService(); // var pendingMigrations = await context.Database.GetPendingMigrationsAsync(); diff --git a/UI/Web/src/app/admin/_modals/library-editor-modal/library-editor-modal.component.html b/UI/Web/src/app/admin/_modals/library-editor-modal/library-editor-modal.component.html index 867be0a4a..a972328c3 100644 --- a/UI/Web/src/app/admin/_modals/library-editor-modal/library-editor-modal.component.html +++ b/UI/Web/src/app/admin/_modals/library-editor-modal/library-editor-modal.component.html @@ -16,8 +16,10 @@
- -
diff --git a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.ts b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.ts index 4c94bfb51..282f383af 100644 --- a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.ts +++ b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.ts @@ -157,8 +157,6 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy { if (this.filterSettings === undefined) { this.filterSettings = new FilterSettings(); } - - this.setupGenreTypeahead(); this.libraryService.getLibrariesForMember().subscribe(libs => { this.libraries = libs.map(lib => { @@ -168,15 +166,10 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy { selected: true, } }); - this.setupLibraryTypeahead(); + this.setupTypeaheads(); }); - this.setupCollectionTagTypeahead(); - this.setupPersonTypeahead(); - this.setupAgeRatingSettings(); - this.setupPublicationStatusSettings(); - this.setupTagSettings(); - this.setupLanguageSettings(); + } ngOnDestroy() { @@ -184,6 +177,17 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy { this.onDestory.complete(); } + setupTypeaheads() { + this.setupLibraryTypeahead(); + this.setupCollectionTagTypeahead(); + this.setupPersonTypeahead(); + this.setupAgeRatingSettings(); + this.setupPublicationStatusSettings(); + this.setupTagSettings(); + this.setupLanguageSettings(); + this.setupGenreTypeahead(); + } + setupFormatTypeahead() { this.formatSettings.minCharacters = 0; @@ -355,6 +359,7 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy { const f = filter.toLowerCase(); return options.filter(m => m.title.toLowerCase() === f); } + if (this.filterSettings.presetCollectionId > 0) { this.collectionSettings.fetchFn('').subscribe(tags => { this.collectionSettings.savedData = tags.filter(item => item.value.id === this.filterSettings.presetCollectionId); @@ -364,6 +369,17 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy { } } + applyPresets() { + + // if (this.filterSettings.presetCollectionId > 0) { + // this.collectionSettings.fetchFn('').subscribe(tags => { + // this.collectionSettings.savedData = tags.filter(item => item.value.id === this.filterSettings.presetCollectionId); + // this.filter.collectionTags = this.collectionSettings.savedData.map(item => item.value.id); + // this.resetTypeaheads.next(true); + // }); + // } + } + setupPersonTypeahead() { this.peopleSettings = {}; @@ -579,6 +595,8 @@ export class CardDetailLayoutComponent implements OnInit, OnDestroy { this.readProgressGroup.get('inProgress')?.setValue(true); this.sortGroup.get('sortField')?.setValue(SortField.SortName); this.isAscendingSort = true; + // Apply any presets + this.setupTypeaheads(); this.resetTypeaheads.next(true); this.applyFilter.emit(this.filter); diff --git a/UI/Web/src/app/manga-reader/manga-reader.component.ts b/UI/Web/src/app/manga-reader/manga-reader.component.ts index 00f09c6fa..c43ce14ee 100644 --- a/UI/Web/src/app/manga-reader/manga-reader.component.ts +++ b/UI/Web/src/app/manga-reader/manga-reader.component.ts @@ -865,35 +865,35 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { } // Fit Split on a page that needs splitting - if (this.shouldRenderAsFitSplit()) { - const windowWidth = window.innerWidth - || document.documentElement.clientWidth - || document.body.clientWidth; - const windowHeight = window.innerHeight - || document.documentElement.clientHeight - || document.body.clientHeight; - // If the user's screen is wider than the image, just pretend this is no split, as it will render nicer - this.canvas.nativeElement.width = windowWidth; - this.canvas.nativeElement.height = windowHeight; - const ratio = this.canvasImage.width / this.canvasImage.height; - let newWidth = windowWidth; - let newHeight = newWidth / ratio; - if (newHeight > windowHeight) { - newHeight = windowHeight; - newWidth = newHeight * ratio; - } - - // Optimization: When the screen is larger than newWidth, allow no split rendering to occur for a better fit - if (windowWidth > newWidth) { - this.setCanvasSize(); - this.ctx.drawImage(this.canvasImage, 0, 0); - } else { - this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); - this.ctx.drawImage(this.canvasImage, 0, 0, newWidth, newHeight); - } - } else { + if (!this.shouldRenderAsFitSplit()) { this.ctx.drawImage(this.canvasImage, 0, 0); } + + const windowWidth = window.innerWidth + || document.documentElement.clientWidth + || document.body.clientWidth; + const windowHeight = window.innerHeight + || document.documentElement.clientHeight + || document.body.clientHeight; + // If the user's screen is wider than the image, just pretend this is no split, as it will render nicer + this.canvas.nativeElement.width = windowWidth; + this.canvas.nativeElement.height = windowHeight; + const ratio = this.canvasImage.width / this.canvasImage.height; + let newWidth = windowWidth; + let newHeight = newWidth / ratio; + if (newHeight > windowHeight) { + newHeight = windowHeight; + newWidth = newHeight * ratio; + } + + // Optimization: When the screen is larger than newWidth, allow no split rendering to occur for a better fit + if (windowWidth > newWidth) { + this.setCanvasSize(); + this.ctx.drawImage(this.canvasImage, 0, 0); + } else { + this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + this.ctx.drawImage(this.canvasImage, 0, 0, newWidth, newHeight); + } } // Reset scroll on non HEIGHT Fits @@ -936,7 +936,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { shouldRenderAsFitSplit() { - if (!this.isCoverImage() || parseInt(this.generalSettingsForm?.get('pageSplitOption')?.value, 10) !== PageSplitOption.FitSplit) return false; + // Some pages aren't cover images but might need fit split renderings + if (parseInt(this.generalSettingsForm?.get('pageSplitOption')?.value, 10) !== PageSplitOption.FitSplit) return false; + //if (!this.isCoverImage() || parseInt(this.generalSettingsForm?.get('pageSplitOption')?.value, 10) !== PageSplitOption.FitSplit) return false; return true; }