diff --git a/API/DTOs/SearchResultDto.cs b/API/DTOs/SearchResultDto.cs index 001883b23..6d7ba9f58 100644 --- a/API/DTOs/SearchResultDto.cs +++ b/API/DTOs/SearchResultDto.cs @@ -1,4 +1,6 @@ -namespace API.DTOs +using API.Entities.Enums; + +namespace API.DTOs { public class SearchResultDto { @@ -7,9 +9,10 @@ public string OriginalName { get; init; } public string SortName { get; init; } public string LocalizedName { get; init; } + public MangaFormat Format { get; init; } // Grouping information public string LibraryName { get; set; } public int LibraryId { get; set; } } -} \ No newline at end of file +} diff --git a/API/Data/SeriesRepository.cs b/API/Data/SeriesRepository.cs index ad4b93e6b..098e0d97d 100644 --- a/API/Data/SeriesRepository.cs +++ b/API/Data/SeriesRepository.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using API.Comparators; diff --git a/API/Extensions/SeriesExtensions.cs b/API/Extensions/SeriesExtensions.cs index 7ff615b00..30a7b1b5b 100644 --- a/API/Extensions/SeriesExtensions.cs +++ b/API/Extensions/SeriesExtensions.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; using API.Entities; -using API.Entities.Enums; using API.Parser; using API.Services.Tasks.Scanner; diff --git a/API/Extensions/VolumeListExtensions.cs b/API/Extensions/VolumeListExtensions.cs index 3647cc21c..4752cce5b 100644 --- a/API/Extensions/VolumeListExtensions.cs +++ b/API/Extensions/VolumeListExtensions.cs @@ -19,12 +19,11 @@ namespace API.Extensions /// If there are both specials and non-specials, then the first non-special will be returned. /// /// - /// + /// /// - public static Volume GetCoverImage(this IList volumes, LibraryType libraryType) + public static Volume GetCoverImage(this IList volumes, MangaFormat seriesFormat) { - // TODO: Refactor this to use MangaFormat Epub instead - if (libraryType == LibraryType.Book) + if (seriesFormat is MangaFormat.Epub or MangaFormat.Pdf) { return volumes.OrderBy(x => x.Number).FirstOrDefault(); } diff --git a/API/Parser/Parser.cs b/API/Parser/Parser.cs index a50e6138d..c0c1b2c78 100644 --- a/API/Parser/Parser.cs +++ b/API/Parser/Parser.cs @@ -11,24 +11,38 @@ namespace API.Parser { public const string DefaultChapter = "0"; public const string DefaultVolume = "0"; + private static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(250); public const string ImageFileExtensions = @"^(\.png|\.jpeg|\.jpg)"; public const string ArchiveFileExtensions = @"\.cbz|\.zip|\.rar|\.cbr|\.tar.gz|\.7zip|\.7z|\.cb7|\.cbt"; public const string BookFileExtensions = @"\.epub|\.pdf"; - public const string MangaComicFileExtensions = ArchiveFileExtensions + "|" + ImageFileExtensions + @"|\.pdf"; public const string SupportedExtensions = ArchiveFileExtensions + "|" + ImageFileExtensions + "|" + BookFileExtensions; - public static readonly Regex FontSrcUrlRegex = new Regex(@"(src:url\(.{1})" + "([^\"']*)" + @"(.{1}\))", RegexOptions.IgnoreCase | RegexOptions.Compiled); - public static readonly Regex CssImportUrlRegex = new Regex("(@import\\s[\"|'])(?[\\w\\d/\\._-]+)([\"|'];?)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + public static readonly Regex FontSrcUrlRegex = new Regex(@"(src:url\(.{1})" + "([^\"']*)" + @"(.{1}\))", + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout); + public static readonly Regex CssImportUrlRegex = new Regex("(@import\\s[\"|'])(?[\\w\\d/\\._-]+)([\"|'];?)", + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout); private static readonly string XmlRegexExtensions = @"\.xml"; - private static readonly Regex ImageRegex = new Regex(ImageFileExtensions, RegexOptions.IgnoreCase | RegexOptions.Compiled); - private static readonly Regex ArchiveFileRegex = new Regex(ArchiveFileExtensions, RegexOptions.IgnoreCase | RegexOptions.Compiled); - private static readonly Regex XmlRegex = new Regex(XmlRegexExtensions, RegexOptions.IgnoreCase | RegexOptions.Compiled); - private static readonly Regex BookFileRegex = new Regex(BookFileExtensions, RegexOptions.IgnoreCase | RegexOptions.Compiled); - private static readonly Regex CoverImageRegex = new Regex(@"(?.*)(\b|_)v(?\d+-?\d+)( |_)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // NEEDLESS_Vol.4_-Simeon_6_v2[SugoiSugoi].rar new Regex( @"(?.*)(\b|_)(?!\[)(vol\.?)(?\d+(-\d+)?)(?!\])", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Historys Strongest Disciple Kenichi_v11_c90-98.zip or Dance in the Vampire Bund v16-17 new Regex( @"(?.*)(\b|_)(?!\[)v(?\d+(-\d+)?)(?!\])", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Kodomo no Jikan vol. 10 new Regex( @"(?.*)(\b|_)(vol\.? ?)(?\d+(-\d+)?)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Killing Bites Vol. 0001 Ch. 0001 - Galactica Scanlations (gb) new Regex( @"(vol\.? ?)(?\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Tonikaku Cawaii [Volume 11].cbz new Regex( @"(volume )(?\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Tower Of God S01 014 (CBT) (digital).cbz new Regex( @"(?.*)(\b|_|)(S(?\d+))", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), }; private static readonly Regex[] MangaSeriesRegex = new[] @@ -68,130 +89,161 @@ namespace API.Parser // Grand Blue Dreaming - SP02 new Regex( @"(?.*)(\b|_|-|\s)(?:sp)\d", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // [SugoiSugoi]_NEEDLESS_Vol.2_-_Disk_The_Informant_5_[ENG].rar, Yuusha Ga Shinda! - Vol.tbd Chapter 27.001 V2 Infection ①.cbz new Regex( @"^(?.*)( |_)Vol\.?(\d+|tbd)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Ichiban_Ushiro_no_Daimaou_v04_ch34_[VISCANS].zip, VanDread-v01-c01.zip new Regex( @"(?.*)(\b|_)v(?\d+-?\d*)(\s|_|-)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Gokukoku no Brynhildr - c001-008 (v01) [TrinityBAKumA], Black Bullet - v4 c17 [batoto] new Regex( @"(?.*)( - )(?:v|vo|c)\d", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Kedouin Makoto - Corpse Party Musume, Chapter 19 [Dametrans].zip new Regex( @"(?.*)(?:, Chapter )(?\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Mad Chimera World - Volume 005 - Chapter 026.cbz (couldn't figure out how to get Volume negative lookaround working on below regex) new Regex( @"(?.*)(\s|_|-)(?:Volume(\s|_|-)+\d+)(\s|_|-)+(?:Chapter)(\s|_|-)(?\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Please Go Home, Akutsu-San! - Chapter 038.5 - Volume Announcement.cbz new Regex( @"(?.*)(\s|_|-)(?!Vol)(\s|_|-)(?:Chapter)(\s|_|-)(?\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // [dmntsf.net] One Piece - Digital Colored Comics Vol. 20 Ch. 177 - 30 Million vs 81 Million.cbz new Regex( @"(?.*) (\b|_|-)(vol)\.?", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), //Knights of Sidonia c000 (S2 LE BD Omake - BLAME!) [Habanero Scans] new Regex( @"(?.*)(\bc\d+\b)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), //Tonikaku Cawaii [Volume 11], Darling in the FranXX - Volume 01.cbz new Regex( @"(?.*)(?: _|-|\[|\()\s?vol(ume)?", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Momo The Blood Taker - Chapter 027 Violent Emotion.cbz, Grand Blue Dreaming - SP02 Extra (2019) (Digital) (danke-Empire).cbz new Regex( @"(?.*)(\b|_|-|\s)(?:(chapter(\b|_|-|\s))|sp)\d", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Historys Strongest Disciple Kenichi_v11_c90-98.zip, Killing Bites Vol. 0001 Ch. 0001 - Galactica Scanlations (gb) new Regex( @"(?.*) (\b|_|-)(v|ch\.?|c)\d+", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), //Ichinensei_ni_Nacchattara_v01_ch01_[Taruby]_v1.1.zip must be before [Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip // due to duplicate version identifiers in file. new Regex( @"(?.*)(v|s)\d+(-\d+)?(_|\s)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), //[Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip new Regex( @"(?.*)(v|s)\d+(-\d+)?", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Hinowa ga CRUSH! 018 (2019) (Digital) (LuCaZ).cbz new Regex( @"(?.*) (?\d+) (?:\(\d{4}\)) ", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Goblin Slayer - Brand New Day 006.5 (2019) (Digital) (danke-Empire) new Regex( @"(?.*) (?\d+(?:.\d+|-\d+)?) \(\d{4}\)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Noblesse - Episode 429 (74 Pages).7z new Regex( @"(?.*)(\s|_)(?:Episode|Ep\.?)(\s|_)(?\d+(?:.\d+|-\d+)?)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ) new Regex( @"(?.*)\(\d", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Tonikaku Kawaii (Ch 59-67) (Ongoing) new Regex( @"(?.*)(\s|_)\((c\s|ch\s|chapter\s)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Black Bullet (This is very loose, keep towards bottom) new Regex( @"(?.*)(_)(v|vo|c|volume)( |_)\d+", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // [Hidoi]_Amaenaideyo_MS_vol01_chp02.rar new Regex( @"(?.*)( |_)(vol\d+)?( |_)(?:Chp\.? ?\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Mahoutsukai to Deshi no Futekisetsu na Kankei Chp. 1 new Regex( @"(?.*)( |_)(?:Chp.? ?\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Corpse Party -The Anthology- Sachikos game of love Hysteric Birthday 2U Chapter 01 new Regex( @"^(?!Vol)(?.*)( |_)Chapter( |_)(\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Fullmetal Alchemist chapters 101-108.cbz new Regex( @"^(?!vol)(?.*)( |_)(chapters( |_)?)\d+-?\d*", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Umineko no Naku Koro ni - Episode 1 - Legend of the Golden Witch #1 new Regex( @"^(?!Vol\.?)(?.*)( |_|-)(?.*)ch\d+-?\d?", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Magi - Ch.252-005.cbz new Regex( @"(?.*)( ?- ?)Ch\.\d+-?\d*", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // [BAA]_Darker_than_Black_Omake-1.zip new Regex( @"^(?!Vol)(?.*)(-)\d+-?\d*", // This catches a lot of stuff ^(?!Vol)(?.*)( |_)(\d+) - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Kodoja #001 (March 2016) new Regex( @"(?.*)(\s|_|-)#", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Baketeriya ch01-05.zip, Akiiro Bousou Biyori - 01.jpg, Beelzebub_172_RHS.zip, Cynthia the Mission 29.rar new Regex( @"^(?!Vol\.?)(?.*)( |_|-)(?.*)( |_|-)(ch?)\d+", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), }; private static readonly Regex[] ComicSeriesRegex = new[] @@ -199,51 +251,63 @@ namespace API.Parser // Invincible Vol 01 Family matters (2005) (Digital) new Regex( @"(?.*)(\b|_)(vol\.?)( |_)(?\d+(-\d+)?)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // 04 - Asterix the Gladiator (1964) (Digital-Empire) (WebP by Doc MaKS) new Regex( @"^(?\d+) (- |_)?(?.*(\d{4})?)( |_)(\(|\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // 01 Spider-Man & Wolverine 01.cbr new Regex( @"^(?\d+) (?:- )?(?.*) (\d+)?", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Batman & Wildcat (1 of 3) new Regex( @"(?.*(\d{4})?)( |_)(?:\((?\d+) of \d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Teen Titans v1 001 (1966-02) (digital) (OkC.O.M.P.U.T.O.-Novus) new Regex( @"^(?.*)(?: |_)v\d+", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Amazing Man Comics chapter 25 new Regex( @"^(?.*)(?: |_)c(hapter) \d+", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Amazing Man Comics issue #25 new Regex( @"^(?.*)(?: |_)i(ssue) #\d+", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Batman & Catwoman - Trail of the Gun 01, Batman & Grendel (1996) 01 - Devil's Bones, Teen Titans v1 001 (1966-02) (digital) (OkC.O.M.P.U.T.O.-Novus) new Regex( @"^(?.*)(?: \d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Batman & Robin the Teen Wonder #0 new Regex( @"^(?.*)(?: |_)#\d+", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Scott Pilgrim 02 - Scott Pilgrim vs. The World (2005) new Regex( @"^(?.*)(?: |_)(?\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // The First Asterix Frieze (WebP by Doc MaKS) new Regex( @"^(?.*)(?: |_)(?!\(\d{4}|\d{4}-\d{2}\))\(", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // MUST BE LAST: Batman & Daredevil - King of New York new Regex( @"^(?.*)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), }; private static readonly Regex[] ComicVolumeRegex = new[] @@ -251,31 +315,38 @@ namespace API.Parser // 04 - Asterix the Gladiator (1964) (Digital-Empire) (WebP by Doc MaKS) new Regex( @"^(?\d+) (- |_)?(?.*(\d{4})?)( |_)(\(|\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // 01 Spider-Man & Wolverine 01.cbr new Regex( @"^(?\d+) (?:- )?(?.*) (\d+)?", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Batman & Wildcat (1 of 3) new Regex( @"(?.*(\d{4})?)( |_)(?:\((?\d+) of \d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Teen Titans v1 001 (1966-02) (digital) (OkC.O.M.P.U.T.O.-Novus) new Regex( @"^(?.*)(?: |_)v(?\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Scott Pilgrim 02 - Scott Pilgrim vs. The World (2005) new Regex( @"^(?.*)(?\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Batman & Catwoman - Trail of the Gun 01, Batman & Grendel (1996) 01 - Devil's Bones, Teen Titans v1 001 (1966-02) (digital) (OkC.O.M.P.U.T.O.-Novus) new Regex( @"^(?.*)(?\d+))", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Batman & Robin the Teen Wonder #0 new Regex( @"^(?.*)(?: |_)#(?\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), }; private static readonly Regex[] ComicChapterRegex = new[] @@ -283,38 +354,46 @@ namespace API.Parser // Batman & Wildcat (1 of 3) new Regex( @"(?.*(\d{4})?)( |_)(?:\((?\d+) of \d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Teen Titans v1 001 (1966-02) (digital) (OkC.O.M.P.U.T.O.-Novus) new Regex( @"^(?.*)(?: |_)v(?\d+)(?: |_)(c? ?)(?(\d+(\.\d)?)-?(\d+(\.\d)?)?)(c? ?)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Batman & Catwoman - Trail of the Gun 01, Batman & Grendel (1996) 01 - Devil's Bones, Teen Titans v1 001 (1966-02) (digital) (OkC.O.M.P.U.T.O.-Novus) new Regex( @"^(?.*)(?: (?\d+))", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Batman & Robin the Teen Wonder #0 new Regex( @"^(?.*)(?: |_)#(?\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Invincible 070.5 - Invincible Returns 1 (2010) (digital) (Minutemen-InnerDemons).cbr new Regex( @"^(?.*)(?: |_)(c? ?)(?(\d+(\.\d)?)-?(\d+(\.\d)?)?)(c? ?)-", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Amazing Man Comics chapter 25 new Regex( @"^(?!Vol)(?.*)( |_)c(hapter)( |_)(?\d*)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Amazing Man Comics issue #25 new Regex( @"^(?!Vol)(?.*)( |_)i(ssue)( |_) #(?\d*)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), }; private static readonly Regex[] ReleaseGroupRegex = new[] { // [TrinityBAKumA Finella&anon], [BAA]_, [SlowManga&OverloadScans], [batoto] new Regex(@"(?:\[(?(?!\s).+?(?(?!\s).+?(?(\d+(\.\d)?)-?(\d+(\.\d)?)?)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // [Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip new Regex( @"v\d+\.(?\d+(?:.\d+|-\d+)?)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Umineko no Naku Koro ni - Episode 3 - Banquet of the Golden Witch #02.cbz (Rare case, if causes issue remove) new Regex( @"^(?.*)(?: |_)#(?\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Green Worldz - Chapter 027 new Regex( @"^(?!Vol)(?.*)\s?(?\d+(?:\.?[\d-])?)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Hinowa ga CRUSH! 018 (2019) (Digital) (LuCaZ).cbz, Hinowa ga CRUSH! 018.5 (2019) (Digital) (LuCaZ).cbz new Regex( @"^(?!Vol)(?.*)\s(?\d+(?:.\d+|-\d+)?)(?:\s\(\d{4}\))?(\b|_|-)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Tower Of God S01 014 (CBT) (digital).cbz new Regex( @"(?.*)\sS(?\d+)\s(?\d+(?:.\d+|-\d+)?)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Beelzebub_01_[Noodles].zip, Beelzebub_153b_RHS.zip new Regex( @"^((?!v|vo|vol|Volume).)*(\s|_)(?\.?\d+(?:.\d+|-\d+)?)(?b)?(\s|_|\[|\()", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Yumekui-Merry_DKThias_Chapter21.zip new Regex( @"Chapter(?\d+(-\d+)?)", //(?:.\d+|-\d+)? - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // [Hidoi]_Amaenaideyo_MS_vol01_chp02.rar new Regex( @"(?.*)(\s|_)(vol\d+)?(\s|_)Chp\.? ?(?\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Vol 1 Chapter 2 new Regex( @"(?((vol|volume|v))?(\s|_)?\.?\d+)(\s|_)(Chp|Chapter)\.?(\s|_)?(?\d+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), }; private static readonly Regex[] MangaEditionRegex = { // Tenjo Tenge {Full Contact Edition} v01 (2011) (Digital) (ASTC).cbz new Regex( @"(?({|\(|\[).* Edition(}|\)|\]))", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Tenjo Tenge {Full Contact Edition} v01 (2011) (Digital) (ASTC).cbz new Regex( @"(\b|_)(?Omnibus(( |_)?Edition)?)(\b|_)?", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // To Love Ru v01 Uncensored (Ch.001-007) new Regex( @"(\b|_)(?Uncensored)(\b|_)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // AKIRA - c003 (v01) [Full Color] [Darkhorse].cbz new Regex( @"(\b|_)(?Full(?: |_)Color)(\b|_)?", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), }; private static readonly Regex[] CleanupRegex = @@ -388,15 +481,18 @@ namespace API.Parser // (), {}, [] new Regex( @"(?(\{\}|\[\]|\(\)))", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // (Complete) new Regex( @"(?(\{Complete\}|\[Complete\]|\(Complete\)))", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), // Anything in parenthesis new Regex( @"\(.*\)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), }; private static readonly Regex[] MangaSpecialRegex = @@ -404,7 +500,8 @@ 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)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), + RegexOptions.IgnoreCase | RegexOptions.Compiled, + RegexTimeout), }; // If SP\d+ is in the filename, we force treat it as a special regardless if volume or chapter might have been found. diff --git a/API/Services/ArchiveService.cs b/API/Services/ArchiveService.cs index af43381fa..951cd7db8 100644 --- a/API/Services/ArchiveService.cs +++ b/API/Services/ArchiveService.cs @@ -387,11 +387,10 @@ namespace API.Services if (!archive.HasFiles() && !needsFlattening) return; archive.ExtractToDirectory(extractPath, true); - if (needsFlattening) - { - _logger.LogDebug("Extracted archive is nested in root folder, flattening..."); - new DirectoryInfo(extractPath).Flatten(); - } + if (!needsFlattening) return; + + _logger.LogDebug("Extracted archive is nested in root folder, flattening..."); + new DirectoryInfo(extractPath).Flatten(); } /// diff --git a/API/Services/BookService.cs b/API/Services/BookService.cs index 84136aa84..190e867cb 100644 --- a/API/Services/BookService.cs +++ b/API/Services/BookService.cs @@ -13,11 +13,13 @@ using API.Entities.Enums; using API.Interfaces.Services; using API.Parser; using Docnet.Core; +using Docnet.Core.Converters; using Docnet.Core.Models; using Docnet.Core.Readers; using ExCSS; using HtmlAgilityPack; using Microsoft.Extensions.Logging; +using Microsoft.IO; using VersOne.Epub; using Image = NetVips.Image; using Point = System.Drawing.Point; @@ -28,6 +30,7 @@ namespace API.Services { private readonly ILogger _logger; private readonly StylesheetParser _cssParser = new (); + private static readonly RecyclableMemoryStreamManager StreamManager = new (); public BookService(ILogger logger) { @@ -375,7 +378,6 @@ namespace API.Services var pages = docReader.GetPageCount(); for (var pageNumber = 0; pageNumber < pages; pageNumber++) { - using var pageReader = docReader.GetPageReader(pageNumber); using var stream = GetPdfPage(docReader, pageNumber); File.WriteAllBytes(Path.Combine(targetDirectory, "Page-" + pageNumber + ".png"), stream.ToArray()); } @@ -405,7 +407,7 @@ namespace API.Services if (!createThumbnail) return coverImageContent.ReadContent(); - using var stream = new MemoryStream(coverImageContent.ReadContent()); + using var stream = StreamManager.GetStream("BookService.GetCoverImage", coverImageContent.ReadContent()); using var thumbnail = Image.ThumbnailStream(stream, MetadataService.ThumbnailWidth); return thumbnail.WriteToBuffer(".jpg"); @@ -447,23 +449,19 @@ namespace API.Services private static MemoryStream GetPdfPage(IDocReader docReader, int pageNumber) { using var pageReader = docReader.GetPageReader(pageNumber); - var rawBytes = pageReader.GetImage(); + var rawBytes = pageReader.GetImage(new NaiveTransparencyRemover()); var width = pageReader.GetPageWidth(); var height = pageReader.GetPageHeight(); - using var doc = new Bitmap(width, height, PixelFormat.Format32bppArgb); using var bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb); AddBytesToBitmap(bmp, rawBytes); - for (int y = 0; y < bmp.Height; y++) + // Removes 1px margin on left/right side after bitmap is copied out + for (var y = 0; y < bmp.Height; y++) { bmp.SetPixel(bmp.Width - 1, y, bmp.GetPixel(bmp.Width - 2, y)); } - using var g = Graphics.FromImage(doc); - g.FillRegion(Brushes.White, new Region(new Rectangle(0, 0, width, height))); - g.DrawImage(bmp, new Point(0, 0)); - g.Save(); - var stream = new MemoryStream(); - doc.Save(stream, ImageFormat.Jpeg); + var stream = StreamManager.GetStream("BookService.GetPdfPage"); + bmp.Save(stream, ImageFormat.Jpeg); return stream; } diff --git a/API/Services/MetadataService.cs b/API/Services/MetadataService.cs index 7b414afec..8c4d71130 100644 --- a/API/Services/MetadataService.cs +++ b/API/Services/MetadataService.cs @@ -95,7 +95,7 @@ namespace API.Services if (ShouldFindCoverImage(series.CoverImage, forceUpdate)) { series.Volumes ??= new List(); - var firstCover = series.Volumes.GetCoverImage(series.Library.Type); + var firstCover = series.Volumes.GetCoverImage(series.Format); byte[] coverImage = null; if (firstCover == null && series.Volumes.Any()) { diff --git a/UI/Web/src/app/_models/search-result.ts b/UI/Web/src/app/_models/search-result.ts index 1a1fe9aa6..216ba60b7 100644 --- a/UI/Web/src/app/_models/search-result.ts +++ b/UI/Web/src/app/_models/search-result.ts @@ -1,3 +1,5 @@ +import { MangaFormat } from "./manga-format"; + export interface SearchResult { seriesId: number; libraryId: number; @@ -6,4 +8,5 @@ export interface SearchResult { originalName: string; sortName: string; coverImage: string; // byte64 encoded + format: MangaFormat; } diff --git a/UI/Web/src/app/_services/server.service.ts b/UI/Web/src/app/_services/server.service.ts index 9d4e9e848..1837b9fe0 100644 --- a/UI/Web/src/app/_services/server.service.ts +++ b/UI/Web/src/app/_services/server.service.ts @@ -16,10 +16,6 @@ export class ServerService { return this.httpClient.post(this.baseUrl + 'server/restart', {}); } - fetchLogs() { - return this.httpClient.get(this.baseUrl + 'server/logs', {responseType: 'blob' as 'text'}); - } - getServerInfo() { return this.httpClient.get(this.baseUrl + 'server/server-info'); } diff --git a/UI/Web/src/app/admin/dashboard/dashboard.component.ts b/UI/Web/src/app/admin/dashboard/dashboard.component.ts index 756ae605a..86333759f 100644 --- a/UI/Web/src/app/admin/dashboard/dashboard.component.ts +++ b/UI/Web/src/app/admin/dashboard/dashboard.component.ts @@ -4,6 +4,7 @@ import { ToastrService } from 'ngx-toastr'; import { ServerService } from 'src/app/_services/server.service'; import { saveAs } from 'file-saver'; import { Title } from '@angular/platform-browser'; +import { DownloadService } from 'src/app/shared/_services/download.service'; @@ -23,7 +24,8 @@ export class DashboardComponent implements OnInit { counter = this.tabs.length + 1; active = this.tabs[0]; - constructor(public route: ActivatedRoute, private serverService: ServerService, private toastr: ToastrService, private titleService: Title) { + constructor(public route: ActivatedRoute, private serverService: ServerService, + private toastr: ToastrService, private titleService: Title, private downloadService: DownloadService) { this.route.fragment.subscribe(frag => { const tab = this.tabs.filter(item => item.fragment === frag); if (tab.length > 0) { @@ -46,10 +48,7 @@ export class DashboardComponent implements OnInit { } fetchLogs() { - this.serverService.fetchLogs().subscribe(res => { - const blob = new Blob([res], {type: 'text/plain;charset=utf-8'}); - saveAs(blob, 'kavita.zip'); - }); + this.downloadService.downloadLogs(); } } diff --git a/UI/Web/src/app/admin/manage-settings/manage-settings.component.scss b/UI/Web/src/app/admin/manage-settings/manage-settings.component.scss index 52237a3ea..bdbc0edee 100644 --- a/UI/Web/src/app/admin/manage-settings/manage-settings.component.scss +++ b/UI/Web/src/app/admin/manage-settings/manage-settings.component.scss @@ -1,11 +1,8 @@ -@import '../../../assets/themes/dark.scss'; - .accent { font-style: italic; font-size: 0.7rem; - background-color: $dark-form-background; + background-color: lightgray; padding: 10px; - color: lightgray; + color: black; border-radius: 6px; - box-shadow: inset 0px 0px 8px 1px $dark-form-background } \ No newline at end of file diff --git a/UI/Web/src/app/nav-header/nav-header.component.html b/UI/Web/src/app/nav-header/nav-header.component.html index aac0bffa8..8d912f51c 100644 --- a/UI/Web/src/app/nav-header/nav-header.component.html +++ b/UI/Web/src/app/nav-header/nav-header.component.html @@ -34,6 +34,7 @@
+ diff --git a/UI/Web/src/app/nav-header/nav-header.component.ts b/UI/Web/src/app/nav-header/nav-header.component.ts index 0323b5bf3..4a42df8ef 100644 --- a/UI/Web/src/app/nav-header/nav-header.component.ts +++ b/UI/Web/src/app/nav-header/nav-header.component.ts @@ -3,6 +3,7 @@ import { Component, HostListener, Inject, OnDestroy, OnInit, ViewChild } from '@ import { Router } from '@angular/router'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +import { UtilityService } from '../shared/_services/utility.service'; import { SearchResult } from '../_models/search-result'; import { AccountService } from '../_services/account.service'; import { ImageService } from '../_services/image.service'; diff --git a/UI/Web/src/app/series-detail/series-detail.component.html b/UI/Web/src/app/series-detail/series-detail.component.html index 39e4ffdff..7530729f8 100644 --- a/UI/Web/src/app/series-detail/series-detail.component.html +++ b/UI/Web/src/app/series-detail/series-detail.component.html @@ -85,7 +85,7 @@
Type
-  {{utilityService.mangaFormat(series.format)}} + {{utilityService.mangaFormat(series.format)}}
diff --git a/UI/Web/src/app/shared/_services/download.service.ts b/UI/Web/src/app/shared/_services/download.service.ts index ad8d7752e..cb6db908e 100644 --- a/UI/Web/src/app/shared/_services/download.service.ts +++ b/UI/Web/src/app/shared/_services/download.service.ts @@ -1,4 +1,4 @@ -import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http'; +import { HttpClient, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Series } from 'src/app/_models/series'; import { environment } from 'src/environments/environment'; @@ -46,14 +46,18 @@ export class DownloadService { return this.httpClient.get(this.baseUrl + 'download/chapter?chapterId=' + chapterId, {observe: 'response', responseType: 'blob' as 'text'}); } + downloadLogs() { + this.httpClient.get(this.baseUrl + 'server/logs', {observe: 'response', responseType: 'blob' as 'text'}).subscribe(resp => { + this.preformSave(resp.body || '', this.getFilenameFromHeader(resp.headers, 'logs')); + }); + } + downloadSeries(series: Series) { this.downloadSeriesSize(series.id).subscribe(async size => { if (size >= this.SIZE_WARNING && !await this.confirmService.confirm('The series is ' + this.humanFileSize(size) + '. Are you sure you want to continue?')) { return; } this.downloadSeriesAPI(series.id).subscribe(resp => { - //const filename = series.name + '.zip'; - //this.preformSave(res, filename); this.preformSave(resp.body || '', this.getFilenameFromHeader(resp.headers, series.name)); }); }); diff --git a/UI/Web/src/app/shared/series-format/series-format.component.html b/UI/Web/src/app/shared/series-format/series-format.component.html new file mode 100644 index 000000000..bafb1421c --- /dev/null +++ b/UI/Web/src/app/shared/series-format/series-format.component.html @@ -0,0 +1,4 @@ + +   + + \ No newline at end of file diff --git a/UI/Web/src/app/shared/series-format/series-format.component.scss b/UI/Web/src/app/shared/series-format/series-format.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/UI/Web/src/app/shared/series-format/series-format.component.ts b/UI/Web/src/app/shared/series-format/series-format.component.ts new file mode 100644 index 000000000..21e7832f1 --- /dev/null +++ b/UI/Web/src/app/shared/series-format/series-format.component.ts @@ -0,0 +1,23 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { MangaFormat } from 'src/app/_models/manga-format'; +import { UtilityService } from '../_services/utility.service'; + +@Component({ + selector: 'app-series-format', + templateUrl: './series-format.component.html', + styleUrls: ['./series-format.component.scss'] +}) +export class SeriesFormatComponent implements OnInit { + + @Input() format: MangaFormat = MangaFormat.UNKNOWN; + + get MangaFormat(): typeof MangaFormat { + return MangaFormat; + } + + constructor(public utilityService: UtilityService) { } + + ngOnInit(): void { + } + +} diff --git a/UI/Web/src/app/shared/shared.module.ts b/UI/Web/src/app/shared/shared.module.ts index 0623a9f37..5e7b60c8a 100644 --- a/UI/Web/src/app/shared/shared.module.ts +++ b/UI/Web/src/app/shared/shared.module.ts @@ -16,6 +16,7 @@ import { TagBadgeComponent } from './tag-badge/tag-badge.component'; import { CardDetailLayoutComponent } from './card-detail-layout/card-detail-layout.component'; import { ShowIfScrollbarDirective } from './show-if-scrollbar.directive'; import { A11yClickDirective } from './a11y-click.directive'; +import { SeriesFormatComponent } from './series-format/series-format.component'; @NgModule({ @@ -31,7 +32,8 @@ import { A11yClickDirective } from './a11y-click.directive'; TagBadgeComponent, CardDetailLayoutComponent, ShowIfScrollbarDirective, - A11yClickDirective + A11yClickDirective, + SeriesFormatComponent ], imports: [ CommonModule, @@ -54,7 +56,8 @@ import { A11yClickDirective } from './a11y-click.directive'; TagBadgeComponent, CardDetailLayoutComponent, ShowIfScrollbarDirective, - A11yClickDirective + A11yClickDirective, + SeriesFormatComponent ] }) export class SharedModule { } diff --git a/UI/Web/src/assets/themes/dark.scss b/UI/Web/src/assets/themes/dark.scss index c02edfdd8..310b1075c 100644 --- a/UI/Web/src/assets/themes/dark.scss +++ b/UI/Web/src/assets/themes/dark.scss @@ -23,6 +23,12 @@ $dark-item-accent-bg: #292d32; color: #4ac694; } + .accent { + background-color: $dark-form-background !important; + color: lightgray !important; + box-shadow: inset 0px 0px 8px 1px $dark-form-background !important; + } + .breadcrumb { background-color: $dark-item-accent-bg;