diff --git a/API.Benchmark/ParseScannedFilesBenchmarks.cs b/API.Benchmark/ParseScannedFilesBenchmarks.cs index d3fd19a4e..8681c1261 100644 --- a/API.Benchmark/ParseScannedFilesBenchmarks.cs +++ b/API.Benchmark/ParseScannedFilesBenchmarks.cs @@ -1,6 +1,4 @@ -using System; -using System.IO; -using API.Data; +using System.IO; using API.Entities.Enums; using API.Interfaces.Services; using API.Parser; @@ -57,8 +55,8 @@ namespace API.Benchmark Title = "A Town Where You Live", Volumes = "1" }; - var parsedSeries = _parseScannedFiles.ScanLibrariesForSeries(LibraryType.Manga, new string[] {libraryPath}, - out var totalFiles, out var scanElapsedTime); + _parseScannedFiles.ScanLibrariesForSeries(LibraryType.Manga, new [] {libraryPath}, + out _, out _); _parseScannedFiles.MergeName(p1); } } diff --git a/API.Benchmark/ParserBenchmarks.cs b/API.Benchmark/ParserBenchmarks.cs index 8eaa70a28..ef12331cc 100644 --- a/API.Benchmark/ParserBenchmarks.cs +++ b/API.Benchmark/ParserBenchmarks.cs @@ -36,7 +36,8 @@ namespace API.Benchmark private static void NormalizeNew(string name) { - NormalizeRegex.Replace(name, string.Empty).ToLower(); + // ReSharper disable once UnusedVariable + var ret = NormalizeRegex.Replace(name, string.Empty).ToLower(); } diff --git a/API.Benchmark/Program.cs b/API.Benchmark/Program.cs index f12146a7f..c3ef1b605 100644 --- a/API.Benchmark/Program.cs +++ b/API.Benchmark/Program.cs @@ -10,7 +10,7 @@ namespace API.Benchmark /// public static class Program { - static void Main(string[] args) + private static void Main(string[] args) { //BenchmarkRunner.Run(); //BenchmarkRunner.Run(); diff --git a/API.Tests/Services/ArchiveServiceTests.cs b/API.Tests/Services/ArchiveServiceTests.cs index 80f09a144..fc3e21dd4 100644 --- a/API.Tests/Services/ArchiveServiceTests.cs +++ b/API.Tests/Services/ArchiveServiceTests.cs @@ -2,6 +2,7 @@ using System.IO; using System.IO.Compression; using API.Archive; +using API.Data.Metadata; using API.Interfaces.Services; using API.Services; using Microsoft.Extensions.Logging; @@ -216,8 +217,30 @@ namespace API.Tests.Services var archive = Path.Join(testDirectory, "file in folder.zip"); var summaryInfo = "By all counts, Ryouta Sakamoto is a loser when he's not holed up in his room, bombing things into oblivion in his favorite online action RPG. But his very own uneventful life is blown to pieces when he's abducted and taken to an uninhabited island, where he soon learns the hard way that he's being pitted against others just like him in a explosives-riddled death match! How could this be happening? Who's putting them up to this? And why!? The name, not to mention the objective, of this very real survival game is eerily familiar to Ryouta, who has mastered its virtual counterpart-BTOOOM! Can Ryouta still come out on top when he's playing for his life!?"; - Assert.Equal(summaryInfo, _archiveService.GetSummaryInfo(archive)); + Assert.Equal(summaryInfo, _archiveService.GetComicInfo(archive).Summary); + } + [Fact] + public void CanParseComicInfo() + { + var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/ComicInfos"); + var archive = Path.Join(testDirectory, "ComicInfo.zip"); + var actual = _archiveService.GetComicInfo(archive); + var expected = new ComicInfo() + { + Publisher = "Yen Press", + Genre = "Manga, Movies & TV", + Summary = + "By all counts, Ryouta Sakamoto is a loser when he's not holed up in his room, bombing things into oblivion in his favorite online action RPG. But his very own uneventful life is blown to pieces when he's abducted and taken to an uninhabited island, where he soon learns the hard way that he's being pitted against others just like him in a explosives-riddled death match! How could this be happening? Who's putting them up to this? And why!? The name, not to mention the objective, of this very real survival game is eerily familiar to Ryouta, who has mastered its virtual counterpart-BTOOOM! Can Ryouta still come out on top when he's playing for his life!?", + PageCount = 194, + LanguageISO = "en", + Notes = "Scraped metadata from Comixology [CMXDB450184]", + Series = "BTOOOM!", + Title = "v01", + Web = "https://www.comixology.com/BTOOOM/digital-comic/450184" + }; + + Assert.NotStrictEqual(expected, actual); } } } diff --git a/API.Tests/Services/DirectoryServiceTests.cs b/API.Tests/Services/DirectoryServiceTests.cs index db756ebab..90cf1a217 100644 --- a/API.Tests/Services/DirectoryServiceTests.cs +++ b/API.Tests/Services/DirectoryServiceTests.cs @@ -90,7 +90,7 @@ namespace API.Tests.Services } [Theory] - [InlineData(new string[] {"C:/Manga/"}, new string[] {"C:/Manga/Love Hina/Vol. 01.cbz"}, "C:/Manga/Love Hina")] + [InlineData(new [] {"C:/Manga/"}, new [] {"C:/Manga/Love Hina/Vol. 01.cbz"}, "C:/Manga/Love Hina")] public void FindHighestDirectoriesFromFilesTest(string[] rootDirectories, string[] folders, string expectedDirectory) { var actual = DirectoryService.FindHighestDirectoriesFromFiles(rootDirectories, folders); diff --git a/API.Tests/Services/MetadataServiceTests.cs b/API.Tests/Services/MetadataServiceTests.cs index b921f74b7..5d61ee249 100644 --- a/API.Tests/Services/MetadataServiceTests.cs +++ b/API.Tests/Services/MetadataServiceTests.cs @@ -1,13 +1,7 @@ using System; using System.IO; using API.Entities; -using API.Interfaces; -using API.Interfaces.Services; using API.Services; -using API.SignalR; -using Microsoft.AspNetCore.SignalR; -using Microsoft.Extensions.Logging; -using NSubstitute; using Xunit; namespace API.Tests.Services diff --git a/API.Tests/Services/Test Data/ArchiveService/ComicInfos/ComicInfo.zip b/API.Tests/Services/Test Data/ArchiveService/ComicInfos/ComicInfo.zip new file mode 100644 index 000000000..3ff536270 Binary files /dev/null and b/API.Tests/Services/Test Data/ArchiveService/ComicInfos/ComicInfo.zip differ diff --git a/API/Controllers/ImageController.cs b/API/Controllers/ImageController.cs index f1ddb770e..88bafcff7 100644 --- a/API/Controllers/ImageController.cs +++ b/API/Controllers/ImageController.cs @@ -1,12 +1,9 @@ -using System; -using System.IO; -using System.Net; +using System.IO; using System.Threading.Tasks; using API.Extensions; using API.Interfaces; using API.Services; using Microsoft.AspNetCore.Mvc; -using Microsoft.Net.Http.Headers; namespace API.Controllers { diff --git a/API/Controllers/OPDSController.cs b/API/Controllers/OPDSController.cs index a58ed30e0..c9e527e2c 100644 --- a/API/Controllers/OPDSController.cs +++ b/API/Controllers/OPDSController.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Threading.Tasks; using System.Xml.Serialization; using API.Comparators; -using API.Constants; using API.DTOs; using API.DTOs.Filtering; using API.DTOs.OPDS; @@ -16,7 +15,6 @@ using API.Interfaces; using API.Interfaces.Services; using API.Services; using Kavita.Common; -using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; namespace API.Controllers diff --git a/API/Controllers/SettingsController.cs b/API/Controllers/SettingsController.cs index 41d3fcfbd..d3075de1a 100644 --- a/API/Controllers/SettingsController.cs +++ b/API/Controllers/SettingsController.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; -using API.DTOs; +using API.DTOs.Settings; using API.Entities.Enums; using API.Extensions; using API.Helpers.Converters; diff --git a/API/Controllers/UploadController.cs b/API/Controllers/UploadController.cs index 43c9b8d09..e873c788d 100644 --- a/API/Controllers/UploadController.cs +++ b/API/Controllers/UploadController.cs @@ -7,7 +7,6 @@ using API.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using NetVips; namespace API.Controllers { diff --git a/API/DTOs/OPDS/FeedLink.cs b/API/DTOs/OPDS/FeedLink.cs index 1dc3d5b1e..1589109ad 100644 --- a/API/DTOs/OPDS/FeedLink.cs +++ b/API/DTOs/OPDS/FeedLink.cs @@ -23,7 +23,7 @@ namespace API.DTOs.OPDS public string Title { get; set; } [XmlAttribute("count", Namespace = "http://vaemendis.net/opds-pse/ns")] - public int TotalPages { get; set; } = 0; + public int TotalPages { get; set; } public bool ShouldSerializeTotalPages() { diff --git a/API/DTOs/Reader/IChapterInfoDto.cs b/API/DTOs/Reader/IChapterInfoDto.cs index 63b5c9a62..67aa6caf6 100644 --- a/API/DTOs/Reader/IChapterInfoDto.cs +++ b/API/DTOs/Reader/IChapterInfoDto.cs @@ -1,5 +1,4 @@ using API.Entities.Enums; -using Newtonsoft.Json; namespace API.DTOs.Reader { diff --git a/API/DTOs/Settings/ServerSettingDTO.cs b/API/DTOs/Settings/ServerSettingDTO.cs index c56391778..0876fbfa0 100644 --- a/API/DTOs/Settings/ServerSettingDTO.cs +++ b/API/DTOs/Settings/ServerSettingDTO.cs @@ -1,4 +1,4 @@ -namespace API.DTOs +namespace API.DTOs.Settings { public class ServerSettingDto { diff --git a/API/Data/Metadata/ComicInfo.cs b/API/Data/Metadata/ComicInfo.cs new file mode 100644 index 000000000..9f846ea42 --- /dev/null +++ b/API/Data/Metadata/ComicInfo.cs @@ -0,0 +1,51 @@ +namespace API.Data.Metadata +{ + /// + /// A representation of a ComicInfo.xml file + /// + /// See reference of the loose spec here: https://github.com/Kussie/ComicInfoStandard/blob/main/ComicInfo.xsd + public class ComicInfo + { + public string Summary { get; set; } + public string Title { get; set; } + public string Series { get; set; } + public string Number { get; set; } + public string Volume { get; set; } + public string Notes { get; set; } + public string Genre { get; set; } + public int PageCount { get; set; } + // ReSharper disable once InconsistentNaming + public string LanguageISO { get; set; } + public string Web { get; set; } + public int Month { get; set; } + public int Year { get; set; } + /// + /// Rating based on the content. Think PG-13, R for movies + /// + public string AgeRating { get; set; } + /// + /// User's rating of the content + /// + public float UserRating { get; set; } + + public string AlternateSeries { get; set; } + public string StoryArc { get; set; } + public string SeriesGroup { get; set; } + public string AlternativeSeries { get; set; } + public string AlternativeNumber { get; set; } + + + /// + /// This is the Author. For Books, we map creator tag in OPF to this field. Comma separated if multiple. + /// + public string Writer { get; set; } // TODO: Validate if we should make this a list of writers + public string Penciller { get; set; } + public string Inker { get; set; } + public string Colorist { get; set; } + public string Letterer { get; set; } + public string CoverArtist { get; set; } + public string Editor { get; set; } + public string Publisher { get; set; } + + } +} diff --git a/API/Data/Repositories/ChapterRepository.cs b/API/Data/Repositories/ChapterRepository.cs index f1905eaa8..54c808d9c 100644 --- a/API/Data/Repositories/ChapterRepository.cs +++ b/API/Data/Repositories/ChapterRepository.cs @@ -1,7 +1,5 @@ using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; using System.Threading.Tasks; using API.DTOs; using API.DTOs.Reader; diff --git a/API/Data/Repositories/CollectionTagRepository.cs b/API/Data/Repositories/CollectionTagRepository.cs index 777e82788..2766e6ba7 100644 --- a/API/Data/Repositories/CollectionTagRepository.cs +++ b/API/Data/Repositories/CollectionTagRepository.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; using API.DTOs; diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index 842b0767e..67cd83276 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using API.Comparators; using API.Data.Scanner; using API.DTOs; using API.DTOs.Filtering; diff --git a/API/Data/Repositories/SettingsRepository.cs b/API/Data/Repositories/SettingsRepository.cs index 1eb0165bb..4489cf3bd 100644 --- a/API/Data/Repositories/SettingsRepository.cs +++ b/API/Data/Repositories/SettingsRepository.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using API.DTOs; +using API.DTOs.Settings; using API.Entities; using API.Entities.Enums; using API.Interfaces.Repositories; diff --git a/API/Entities/AppUserProgress.cs b/API/Entities/AppUserProgress.cs index 08fffa540..b3e0a5dfd 100644 --- a/API/Entities/AppUserProgress.cs +++ b/API/Entities/AppUserProgress.cs @@ -1,6 +1,5 @@  using System; -using System.ComponentModel.DataAnnotations; using API.Entities.Interfaces; namespace API.Entities diff --git a/API/Entities/Volume.cs b/API/Entities/Volume.cs index f4f0076db..17dd6d3c3 100644 --- a/API/Entities/Volume.cs +++ b/API/Entities/Volume.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using API.Entities.Interfaces; -using Microsoft.EntityFrameworkCore; namespace API.Entities { diff --git a/API/Helpers/AutoMapperProfiles.cs b/API/Helpers/AutoMapperProfiles.cs index 03445ccb2..ff1a30e34 100644 --- a/API/Helpers/AutoMapperProfiles.cs +++ b/API/Helpers/AutoMapperProfiles.cs @@ -3,6 +3,7 @@ using System.Linq; using API.DTOs; using API.DTOs.Reader; using API.DTOs.ReadingLists; +using API.DTOs.Settings; using API.Entities; using API.Helpers.Converters; using AutoMapper; diff --git a/API/Helpers/Converters/ServerSettingConverter.cs b/API/Helpers/Converters/ServerSettingConverter.cs index db1736b0b..445e92ddb 100644 --- a/API/Helpers/Converters/ServerSettingConverter.cs +++ b/API/Helpers/Converters/ServerSettingConverter.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using API.DTOs; +using API.DTOs.Settings; using API.Entities; using API.Entities.Enums; using AutoMapper; diff --git a/API/Interfaces/Repositories/ISeriesRepository.cs b/API/Interfaces/Repositories/ISeriesRepository.cs index c7ac41c53..9b04d6d9f 100644 --- a/API/Interfaces/Repositories/ISeriesRepository.cs +++ b/API/Interfaces/Repositories/ISeriesRepository.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using API.Data.Scanner; using API.DTOs; diff --git a/API/Interfaces/Repositories/ISettingsRepository.cs b/API/Interfaces/Repositories/ISettingsRepository.cs index f1687743d..95178ea79 100644 --- a/API/Interfaces/Repositories/ISettingsRepository.cs +++ b/API/Interfaces/Repositories/ISettingsRepository.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; -using API.DTOs; +using API.DTOs.Settings; using API.Entities; using API.Entities.Enums; diff --git a/API/Interfaces/Services/IArchiveService.cs b/API/Interfaces/Services/IArchiveService.cs index ae9bddc98..f2567341a 100644 --- a/API/Interfaces/Services/IArchiveService.cs +++ b/API/Interfaces/Services/IArchiveService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO.Compression; using System.Threading.Tasks; using API.Archive; +using API.Data.Metadata; namespace API.Interfaces.Services { @@ -12,7 +13,7 @@ namespace API.Interfaces.Services int GetNumberOfPagesFromArchive(string archivePath); string GetCoverImage(string archivePath, string fileName); bool IsValidArchive(string archivePath); - string GetSummaryInfo(string archivePath); + ComicInfo GetComicInfo(string archivePath); ArchiveLibrary CanOpen(string archivePath); bool ArchiveNeedsFlattening(ZipArchive archive); Task> CreateZipForDownload(IEnumerable files, string tempFolder); diff --git a/API/Interfaces/Services/IBookService.cs b/API/Interfaces/Services/IBookService.cs index cde2cad8e..e78669755 100644 --- a/API/Interfaces/Services/IBookService.cs +++ b/API/Interfaces/Services/IBookService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using API.Data.Metadata; using API.Parser; using VersOne.Epub; @@ -20,7 +21,7 @@ namespace API.Interfaces.Services /// Book Reference, needed for if you expect Import statements /// Task ScopeStyles(string stylesheetHtml, string apiBase, string filename, EpubBookRef book); - string GetSummaryInfo(string filePath); + ComicInfo GetComicInfo(string filePath); ParserInfo ParseInfo(string filePath); /// /// Extracts a PDF file's pages as images to an target directory diff --git a/API/Interfaces/Services/ReaderService.cs b/API/Interfaces/Services/ReaderService.cs index f46ccd7d1..7eb2e1118 100644 --- a/API/Interfaces/Services/ReaderService.cs +++ b/API/Interfaces/Services/ReaderService.cs @@ -1,7 +1,6 @@  using System; using System.Collections.Generic; -using System.Data; using System.Linq; using System.Threading.Tasks; using API.Comparators; diff --git a/API/Program.cs b/API/Program.cs index eddc4cb4e..0587cec98 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -1,16 +1,10 @@ using System; -using System.Collections.Generic; -using System.Data; using System.IO; -using System.Linq; using System.Security.Cryptography; using System.Threading; -using System.Threading.Channels; using System.Threading.Tasks; using API.Data; using API.Entities; -using API.Helpers; -using API.Interfaces; using API.Services; using Kavita.Common; using Kavita.Common.EnvironmentInfo; @@ -21,8 +15,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.IO; -using NetVips; using Sentry; namespace API diff --git a/API/Services/ArchiveService.cs b/API/Services/ArchiveService.cs index 1b68956e5..621d42322 100644 --- a/API/Services/ArchiveService.cs +++ b/API/Services/ArchiveService.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using System.Xml.Serialization; using API.Archive; using API.Comparators; +using API.Data.Metadata; using API.Extensions; using API.Interfaces.Services; using API.Services.Tasks; @@ -293,15 +294,13 @@ namespace API.Services return null; } - public string GetSummaryInfo(string archivePath) + public ComicInfo GetComicInfo(string archivePath) { - var summary = string.Empty; - if (!IsValidArchive(archivePath)) return summary; + if (!IsValidArchive(archivePath)) return null; - ComicInfo info = null; try { - if (!File.Exists(archivePath)) return summary; + if (!File.Exists(archivePath)) return null; var libraryHandler = CanOpen(archivePath); switch (libraryHandler) @@ -309,48 +308,55 @@ namespace API.Services case ArchiveLibrary.Default: { using var archive = ZipFile.OpenRead(archivePath); - var entry = archive.Entries.SingleOrDefault(x => !Parser.Parser.HasBlacklistedFolderInPath(x.FullName) - && Path.GetFileNameWithoutExtension(x.Name)?.ToLower() == ComicInfoFilename - && !Path.GetFileNameWithoutExtension(x.Name).StartsWith(Parser.Parser.MacOsMetadataFileStartsWith) - && Parser.Parser.IsXml(x.FullName)); + var entry = archive.Entries.SingleOrDefault(x => + !Parser.Parser.HasBlacklistedFolderInPath(x.FullName) + && Path.GetFileNameWithoutExtension(x.Name)?.ToLower() == ComicInfoFilename + && !Path.GetFileNameWithoutExtension(x.Name) + .StartsWith(Parser.Parser.MacOsMetadataFileStartsWith) + && Parser.Parser.IsXml(x.FullName)); if (entry != null) { using var stream = entry.Open(); var serializer = new XmlSerializer(typeof(ComicInfo)); - info = (ComicInfo) serializer.Deserialize(stream); + return (ComicInfo) serializer.Deserialize(stream); } + break; } case ArchiveLibrary.SharpCompress: { using var archive = ArchiveFactory.Open(archivePath); - info = FindComicInfoXml(archive.Entries.Where(entry => !entry.IsDirectory - && !Parser.Parser.HasBlacklistedFolderInPath(Path.GetDirectoryName(entry.Key) ?? string.Empty) - && !Path.GetFileNameWithoutExtension(entry.Key).StartsWith(Parser.Parser.MacOsMetadataFileStartsWith) + return FindComicInfoXml(archive.Entries.Where(entry => !entry.IsDirectory + && !Parser.Parser + .HasBlacklistedFolderInPath( + Path.GetDirectoryName( + entry.Key) ?? string.Empty) + && !Path + .GetFileNameWithoutExtension( + entry.Key).StartsWith(Parser + .Parser + .MacOsMetadataFileStartsWith) && Parser.Parser.IsXml(entry.Key))); - break; } case ArchiveLibrary.NotSupported: - _logger.LogWarning("[GetSummaryInfo] This archive cannot be read: {ArchivePath}", archivePath); - return summary; + _logger.LogWarning("[GetComicInfo] This archive cannot be read: {ArchivePath}", archivePath); + return null; default: - _logger.LogWarning("[GetSummaryInfo] There was an exception when reading archive stream: {ArchivePath}", archivePath); - return summary; - } - - if (info != null) - { - return info.Summary; + _logger.LogWarning( + "[GetComicInfo] There was an exception when reading archive stream: {ArchivePath}", + archivePath); + return null; } } catch (Exception ex) { - _logger.LogWarning(ex, "[GetSummaryInfo] There was an exception when reading archive stream: {Filepath}", archivePath); + _logger.LogWarning(ex, "[GetComicInfo] There was an exception when reading archive stream: {Filepath}", archivePath); } - return summary; + return null; } + private static void ExtractArchiveEntities(IEnumerable entries, string extractPath) { DirectoryService.ExistOrCreate(extractPath); diff --git a/API/Services/BookService.cs b/API/Services/BookService.cs index b63a0253e..a114b3bd6 100644 --- a/API/Services/BookService.cs +++ b/API/Services/BookService.cs @@ -4,12 +4,12 @@ using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; -using System.Net; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; +using API.Data.Metadata; using API.Entities.Enums; using API.Interfaces.Services; using API.Parser; @@ -165,22 +165,43 @@ namespace API.Services return RemoveWhiteSpaceFromStylesheets(stylesheet.ToCss()); } - public string GetSummaryInfo(string filePath) + public ComicInfo GetComicInfo(string filePath) { - if (!IsValidFile(filePath) || Parser.Parser.IsPdf(filePath)) return string.Empty; - + if (!IsValidFile(filePath) || Parser.Parser.IsPdf(filePath)) return null; try { using var epubBook = EpubReader.OpenBook(filePath); - return epubBook.Schema.Package.Metadata.Description; + var publicationDate = + epubBook.Schema.Package.Metadata.Dates.FirstOrDefault(date => date.Event == "publication")?.Date; + + var info = new ComicInfo() + { + Summary = epubBook.Schema.Package.Metadata.Description, + Writer = string.Join(",", epubBook.Schema.Package.Metadata.Creators), + Publisher = string.Join(",", epubBook.Schema.Package.Metadata.Publishers), + Month = !string.IsNullOrEmpty(publicationDate) ? DateTime.Parse(publicationDate).Month : 0, + Year = !string.IsNullOrEmpty(publicationDate) ? DateTime.Parse(publicationDate).Year : 0, + }; + // Parse tags not exposed via Library + foreach (var metadataItem in epubBook.Schema.Package.Metadata.MetaItems) + { + switch (metadataItem.Name) + { + case "calibre:rating": + info.UserRating = float.Parse(metadataItem.Content); + break; + } + } + + return info; } catch (Exception ex) { - _logger.LogWarning(ex, "[BookService] There was an exception getting summary, defaulting to empty string"); + _logger.LogWarning(ex, "[GetComicInfo] There was an exception getting metadata"); } - return string.Empty; + return null; } private bool IsValidFile(string filePath) diff --git a/API/Services/ComicInfo.cs b/API/Services/ComicInfo.cs deleted file mode 100644 index 55e823ee4..000000000 --- a/API/Services/ComicInfo.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace API.Services -{ - public class ComicInfo - { - public string Summary { get; set; } - public string Title { get; set; } - public string Series { get; set; } - public string Notes { get; set; } - public string Publisher { get; set; } - public string Genre { get; set; } - public int PageCount { get; set; } - // ReSharper disable once InconsistentNaming - public string LanguageISO { get; set; } - public string Web { get; set; } - } -} \ No newline at end of file diff --git a/API/Services/ImageService.cs b/API/Services/ImageService.cs index fddb3fffe..d17fea327 100644 --- a/API/Services/ImageService.cs +++ b/API/Services/ImageService.cs @@ -95,7 +95,7 @@ namespace API.Services /// File name with extension of the file. This will always write to public static string WriteCoverThumbnail(Stream stream, string fileName) { - using var thumbnail = NetVips.Image.ThumbnailStream(stream, ThumbnailWidth); + using var thumbnail = Image.ThumbnailStream(stream, ThumbnailWidth); var filename = fileName + ".png"; thumbnail.WriteToFile(Path.Join(DirectoryService.CoverImageDirectory, fileName + ".png")); return filename; diff --git a/API/Services/MetadataService.cs b/API/Services/MetadataService.cs index 14be8ce17..40e44f02a 100644 --- a/API/Services/MetadataService.cs +++ b/API/Services/MetadataService.cs @@ -1,10 +1,10 @@ -using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; using API.Comparators; +using API.Data.Metadata; using API.Data.Repositories; using API.Entities; using API.Entities.Enums; @@ -171,6 +171,9 @@ namespace API.Services private bool UpdateSeriesSummary(Series series, bool forceUpdate) { + // NOTE: This can be problematic when the file changes and a summary already exists, but it is likely + // better to let the user kick off a refresh metadata on an individual Series than having overhead of + // checking File last write time. if (!string.IsNullOrEmpty(series.Summary) && !forceUpdate) return false; var isBook = series.Library.Type == LibraryType.Book; @@ -181,16 +184,21 @@ namespace API.Services if (firstFile == null || (!forceUpdate && !firstFile.HasFileBeenModified())) return false; if (Parser.Parser.IsPdf(firstFile.FilePath)) return false; - if (series.Format is MangaFormat.Archive or MangaFormat.Epub) + var comicInfo = GetComicInfo(series.Format, firstFile); + if (string.IsNullOrEmpty(comicInfo.Summary)) return false; + + series.Summary = comicInfo.Summary; + return true; + } + + private ComicInfo GetComicInfo(MangaFormat format, MangaFile firstFile) + { + if (format is MangaFormat.Archive or MangaFormat.Epub) { - var summary = Parser.Parser.IsEpub(firstFile.FilePath) ? _bookService.GetSummaryInfo(firstFile.FilePath) : _archiveService.GetSummaryInfo(firstFile.FilePath); - if (!string.IsNullOrEmpty(series.Summary)) - { - series.Summary = summary; - return true; - } + return Parser.Parser.IsEpub(firstFile.FilePath) ? _bookService.GetComicInfo(firstFile.FilePath) : _archiveService.GetComicInfo(firstFile.FilePath); } - return false; + + return null; } diff --git a/API/Services/Tasks/BackupService.cs b/API/Services/Tasks/BackupService.cs index 35388985a..b4dc3910b 100644 --- a/API/Services/Tasks/BackupService.cs +++ b/API/Services/Tasks/BackupService.cs @@ -125,7 +125,7 @@ namespace API.Services.Tasks _directoryService.CopyFilesToDirectory( chapterImages.Select(s => Path.Join(DirectoryService.CoverImageDirectory, s)), outputTempDir); } - catch (IOException e) + catch (IOException) { // Swallow exception. This can be a duplicate cover being copied as chapter and volumes can share same file. } diff --git a/API/Services/Tasks/CleanupService.cs b/API/Services/Tasks/CleanupService.cs index c1edf2e6b..93f8ec5db 100644 --- a/API/Services/Tasks/CleanupService.cs +++ b/API/Services/Tasks/CleanupService.cs @@ -1,11 +1,9 @@ using System.IO; -using System.Linq; using System.Threading.Tasks; using API.Interfaces; using API.Interfaces.Services; using Hangfire; using Microsoft.Extensions.Logging; -using NetVips; namespace API.Services.Tasks { diff --git a/API/Services/Tasks/ScannerService.cs b/API/Services/Tasks/ScannerService.cs index f1ba9cc59..2f16bf524 100644 --- a/API/Services/Tasks/ScannerService.cs +++ b/API/Services/Tasks/ScannerService.cs @@ -76,7 +76,7 @@ namespace API.Services.Tasks try { _unitOfWork.SeriesRepository.Remove(series); - await CommitAndSend(libraryId, seriesId, totalFiles, parsedSeries, sw, scanElapsedTime, series, chapterIds); + await CommitAndSend(totalFiles, parsedSeries, sw, scanElapsedTime, series); } catch (Exception ex) { @@ -121,7 +121,7 @@ namespace API.Services.Tasks try { UpdateSeries(series, parsedSeries); - await CommitAndSend(libraryId, seriesId, totalFiles, parsedSeries, sw, scanElapsedTime, series, chapterIds); + await CommitAndSend(totalFiles, parsedSeries, sw, scanElapsedTime, series); } catch (Exception ex) { @@ -131,6 +131,9 @@ namespace API.Services.Tasks // Tell UI that this series is done await _messageHub.Clients.All.SendAsync(SignalREvents.ScanSeries, MessageFactory.ScanSeriesEvent(seriesId, series.Name), cancellationToken: token); + await CleanupDbEntities(); + BackgroundJob.Enqueue(() => _cacheService.CleanupChapters(chapterIds)); + BackgroundJob.Enqueue(() => _metadataService.RefreshMetadataForSeries(libraryId, series.Id, false)); } private static void RemoveParsedInfosNotForSeries(Dictionary> parsedSeries, Series series) @@ -143,8 +146,8 @@ namespace API.Services.Tasks } } - private async Task CommitAndSend(int libraryId, int seriesId, int totalFiles, - Dictionary> parsedSeries, Stopwatch sw, long scanElapsedTime, Series series, int[] chapterIds) + private async Task CommitAndSend(int totalFiles, + Dictionary> parsedSeries, Stopwatch sw, long scanElapsedTime, Series series) { if (_unitOfWork.HasChanges()) { @@ -152,10 +155,6 @@ namespace API.Services.Tasks _logger.LogInformation( "Processed {TotalFiles} files and {ParsedSeriesCount} series in {ElapsedScanTime} milliseconds for {SeriesName}", totalFiles, parsedSeries.Keys.Count, sw.ElapsedMilliseconds + scanElapsedTime, series.Name); - - await CleanupDbEntities(); - BackgroundJob.Enqueue(() => _metadataService.RefreshMetadataForSeries(libraryId, seriesId, false)); - BackgroundJob.Enqueue(() => _cacheService.CleanupChapters(chapterIds)); } } @@ -225,7 +224,7 @@ namespace API.Services.Tasks "[ScannerService] There was a critical error that resulted in a failed scan. Please check logs and rescan"); } - await CleanupAbandonedChapters(); + await CleanupDbEntities(); BackgroundJob.Enqueue(() => _metadataService.RefreshMetadata(libraryId, false)); await _messageHub.Clients.All.SendAsync(SignalREvents.ScanLibraryProgress,