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 @@
+
= 0; else localizedName" [innerHTML]="item.name">
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;