mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Book Feedback (#190)
* Remove automatic retry for scanLibraries as if something fails, it wont pass magically. Catch exceptions when opening books for parsing and swallow to ignore the file. * Delete extra attempts * Switched to using FirstOrDefault for finding existing series. This will help avoid pointless crashes. * Updated message when duplicate series are found (not sure how this happens) * Fixed a negation for deleting volumes where files still exist. * Implemented the ability to automatically scale the manga reader based on screen size. * Default to automatic scaling * Fix an issue where malformed epubs wouldn't be readable due to incorrect keys in the OPF. We now check if key is valid and if not, try to correct it. This makes a page load about a second on malformed books. * Fixed #176. Refactored the recently added query to be restricted to user's access to libraries. * Fixed a one off bug with In Progress series * Implemented the ability to refresh metadata of just a single series directly * Fixed a parser case where Series c000 (v01) would fail to parse the series * Fixed #189. In Progress now returns data properly for library access and in multiple libraries. * Fixed #188 by adding an extra message for bad login and updating UI * Generate a fallback for table of contents by parsing the toc file (if we can find one)
This commit is contained in:
parent
6d74215262
commit
e2e755145c
@ -60,6 +60,8 @@ namespace API.Tests.Parser
|
|||||||
[InlineData("NEEDLESS_Vol.4_-Simeon_6_v2[SugoiSugoi].rar", "4")]
|
[InlineData("NEEDLESS_Vol.4_-Simeon_6_v2[SugoiSugoi].rar", "4")]
|
||||||
[InlineData("[Hidoi]_Amaenaideyo_MS_vol01_chp02.rar", "1")]
|
[InlineData("[Hidoi]_Amaenaideyo_MS_vol01_chp02.rar", "1")]
|
||||||
[InlineData("NEEDLESS_Vol.4_-_Simeon_6_v2_[SugoiSugoi].rar", "4")]
|
[InlineData("NEEDLESS_Vol.4_-_Simeon_6_v2_[SugoiSugoi].rar", "4")]
|
||||||
|
[InlineData("Okusama wa Shougakusei c003 (v01) [bokuwaNEET]", "1")]
|
||||||
|
|
||||||
public void ParseVolumeTest(string filename, string expected)
|
public void ParseVolumeTest(string filename, string expected)
|
||||||
{
|
{
|
||||||
Assert.Equal(expected, API.Parser.Parser.ParseVolume(filename));
|
Assert.Equal(expected, API.Parser.Parser.ParseVolume(filename));
|
||||||
@ -132,6 +134,7 @@ namespace API.Tests.Parser
|
|||||||
[InlineData("Kimetsu no Yaiba - Digital Colored Comics c162 Three Victorious Stars.cbz", "Kimetsu no Yaiba - Digital Colored Comics")]
|
[InlineData("Kimetsu no Yaiba - Digital Colored Comics c162 Three Victorious Stars.cbz", "Kimetsu no Yaiba - Digital Colored Comics")]
|
||||||
[InlineData("[Hidoi]_Amaenaideyo_MS_vol01_chp02.rar", "Amaenaideyo MS")]
|
[InlineData("[Hidoi]_Amaenaideyo_MS_vol01_chp02.rar", "Amaenaideyo MS")]
|
||||||
[InlineData("NEEDLESS_Vol.4_-_Simeon_6_v2_[SugoiSugoi].rar", "NEEDLESS")]
|
[InlineData("NEEDLESS_Vol.4_-_Simeon_6_v2_[SugoiSugoi].rar", "NEEDLESS")]
|
||||||
|
[InlineData("Okusama wa Shougakusei c003 (v01) [bokuwaNEET]", "Okusama wa Shougakusei")]
|
||||||
public void ParseSeriesTest(string filename, string expected)
|
public void ParseSeriesTest(string filename, string expected)
|
||||||
{
|
{
|
||||||
Assert.Equal(expected, API.Parser.Parser.ParseSeries(filename));
|
Assert.Equal(expected, API.Parser.Parser.ParseSeries(filename));
|
||||||
@ -191,6 +194,7 @@ namespace API.Tests.Parser
|
|||||||
[InlineData("Umineko no Naku Koro ni - Episode 1 - Legend of the Golden Witch #1", "1")]
|
[InlineData("Umineko no Naku Koro ni - Episode 1 - Legend of the Golden Witch #1", "1")]
|
||||||
[InlineData("Kiss x Sis - Ch.00 - Let's Start from 0.cbz", "0")]
|
[InlineData("Kiss x Sis - Ch.00 - Let's Start from 0.cbz", "0")]
|
||||||
[InlineData("[Hidoi]_Amaenaideyo_MS_vol01_chp02.rar", "2")]
|
[InlineData("[Hidoi]_Amaenaideyo_MS_vol01_chp02.rar", "2")]
|
||||||
|
[InlineData("Okusama wa Shougakusei c003 (v01) [bokuwaNEET]", "3")]
|
||||||
public void ParseChaptersTest(string filename, string expected)
|
public void ParseChaptersTest(string filename, string expected)
|
||||||
{
|
{
|
||||||
Assert.Equal(expected, API.Parser.Parser.ParseChapter(filename));
|
Assert.Equal(expected, API.Parser.Parser.ParseChapter(filename));
|
||||||
|
@ -132,7 +132,7 @@ namespace API.Controllers
|
|||||||
var result = await _signInManager
|
var result = await _signInManager
|
||||||
.CheckPasswordSignInAsync(user, loginDto.Password, false);
|
.CheckPasswordSignInAsync(user, loginDto.Password, false);
|
||||||
|
|
||||||
if (!result.Succeeded) return Unauthorized();
|
if (!result.Succeeded) return Unauthorized("Your credentials are not correct.");
|
||||||
|
|
||||||
// Update LastActive on account
|
// Update LastActive on account
|
||||||
user.LastActive = DateTime.Now;
|
user.LastActive = DateTime.Now;
|
||||||
|
@ -64,7 +64,7 @@ namespace API.Controllers
|
|||||||
|
|
||||||
var navItems = await book.GetNavigationAsync();
|
var navItems = await book.GetNavigationAsync();
|
||||||
var chaptersList = new List<BookChapterItem>();
|
var chaptersList = new List<BookChapterItem>();
|
||||||
|
|
||||||
foreach (var navigationItem in navItems)
|
foreach (var navigationItem in navItems)
|
||||||
{
|
{
|
||||||
if (navigationItem.NestedItems.Count > 0)
|
if (navigationItem.NestedItems.Count > 0)
|
||||||
@ -116,6 +116,53 @@ namespace API.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (chaptersList.Count == 0)
|
||||||
|
{
|
||||||
|
// Generate from TOC
|
||||||
|
var tocPage = book.Content.Html.Keys.FirstOrDefault(k => k.ToUpper().Contains("TOC"));
|
||||||
|
if (tocPage == null) return Ok(chaptersList);
|
||||||
|
|
||||||
|
// Find all anchor tags, for each anchor we get inner text, to lower then titlecase on UI. Get href and generate page content
|
||||||
|
var doc = new HtmlDocument();
|
||||||
|
var content = await book.Content.Html[tocPage].ReadContentAsync();
|
||||||
|
doc.LoadHtml(content);
|
||||||
|
var anchors = doc.DocumentNode.SelectNodes("//a");
|
||||||
|
if (anchors == null) return Ok(chaptersList);
|
||||||
|
|
||||||
|
foreach (var anchor in anchors)
|
||||||
|
{
|
||||||
|
if (anchor.Attributes.Contains("href"))
|
||||||
|
{
|
||||||
|
var key = BookService.CleanContentKeys(anchor.Attributes["href"].Value).Split("#")[0];
|
||||||
|
if (!mappings.ContainsKey(key))
|
||||||
|
{
|
||||||
|
// Fallback to searching for key (bad epub metadata)
|
||||||
|
var correctedKey = book.Content.Html.Keys.SingleOrDefault(s => s.EndsWith(key));
|
||||||
|
if (!string.IsNullOrEmpty(correctedKey))
|
||||||
|
{
|
||||||
|
key = correctedKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(key) && mappings.ContainsKey(key))
|
||||||
|
{
|
||||||
|
var part = string.Empty;
|
||||||
|
if (anchor.Attributes["href"].Value.Contains("#"))
|
||||||
|
{
|
||||||
|
part = anchor.Attributes["href"].Value.Split("#")[1];
|
||||||
|
}
|
||||||
|
chaptersList.Add(new BookChapterItem()
|
||||||
|
{
|
||||||
|
Title = anchor.InnerText,
|
||||||
|
Page = mappings[key],
|
||||||
|
Part = part,
|
||||||
|
Children = new List<BookChapterItem>()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
return Ok(chaptersList);
|
return Ok(chaptersList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,26 +332,45 @@ namespace API.Data
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<IEnumerable<SeriesDto>> GetInProgress(int userId, int libraryId, int limit)
|
public async Task<IEnumerable<SeriesDto>> GetInProgress(int userId, int libraryId, int limit)
|
||||||
{
|
{
|
||||||
var series = await _context.Series
|
var series = _context.Series
|
||||||
.Join(_context.AppUserProgresses, s => s.Id, progress => progress.SeriesId, (s, progress) => new
|
.Join(_context.AppUserProgresses, s => s.Id, progress => progress.SeriesId, (s, progress) => new
|
||||||
{
|
{
|
||||||
Series = s,
|
Series = s,
|
||||||
PagesRead = _context.AppUserProgresses.Where(s1 => s1.SeriesId == s.Id).Sum(s1 => s1.PagesRead),
|
PagesRead = _context.AppUserProgresses.Where(s1 => s1.SeriesId == s.Id).Sum(s1 => s1.PagesRead),
|
||||||
progress.AppUserId,
|
progress.AppUserId,
|
||||||
LastModified = _context.AppUserProgresses.Where(p => p.Id == progress.Id).Max(p => p.LastModified)
|
LastModified = _context.AppUserProgresses.Where(p => p.Id == progress.Id).Max(p => p.LastModified)
|
||||||
})
|
});
|
||||||
.Where(s => s.AppUserId == userId
|
if (libraryId == 0)
|
||||||
|
{
|
||||||
|
var userLibraries = _context.Library
|
||||||
|
.Include(l => l.AppUsers)
|
||||||
|
.Where(library => library.AppUsers.Any(user => user.Id == userId))
|
||||||
|
.AsNoTracking()
|
||||||
|
.Select(library => library.Id)
|
||||||
|
.ToList();
|
||||||
|
series = series.Where(s => s.AppUserId == userId
|
||||||
|
&& s.PagesRead > 0
|
||||||
|
&& s.PagesRead <
|
||||||
|
s.Series.Pages -
|
||||||
|
1 // - 1 because when reading, we start at 0 then go to pages - 1. But when summing, pages assumes starting at 1
|
||||||
|
&& userLibraries.Contains(s.Series.LibraryId));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
series = series.Where(s => s.AppUserId == userId
|
||||||
&& s.PagesRead > 0
|
&& s.PagesRead > 0
|
||||||
&& s.PagesRead < (s.Series.Pages - 1) // - 1 because when reading, we start at 0 then go to pages - 1. But when summing, pages assumes starting at 1
|
&& s.PagesRead <
|
||||||
&& (libraryId <= 0 || s.Series.LibraryId == libraryId))
|
(s.Series.Pages - 1) // - 1 because when reading, we start at 0 then go to pages - 1. But when summing, pages assumes starting at 1
|
||||||
.Take(limit)
|
&& (s.Series.LibraryId == libraryId));
|
||||||
|
}
|
||||||
|
var retSeries = await series.Take(limit)
|
||||||
.OrderByDescending(s => s.LastModified)
|
.OrderByDescending(s => s.LastModified)
|
||||||
.Select(s => s.Series)
|
.Select(s => s.Series)
|
||||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
return series.DistinctBy(s => s.Name);
|
return retSeries.DistinctBy(s => s.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -82,14 +82,14 @@ namespace API.Parser
|
|||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.*)(?:, Chapter )(?<Chapter>\d+)",
|
@"(?<Series>.*)(?:, Chapter )(?<Chapter>\d+)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
//Tonikaku Cawaii [Volume 11], Darling in the FranXX - Volume 01.cbz
|
|
||||||
new Regex(
|
|
||||||
@"(?<Series>.*)(?: _|-|\[|\() ?v",
|
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
|
||||||
//Knights of Sidonia c000 (S2 LE BD Omake - BLAME!) [Habanero Scans]
|
//Knights of Sidonia c000 (S2 LE BD Omake - BLAME!) [Habanero Scans]
|
||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.*)(\bc\d+\b)",
|
@"(?<Series>.*)(\bc\d+\b)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
//Tonikaku Cawaii [Volume 11], Darling in the FranXX - Volume 01.cbz
|
||||||
|
new Regex(
|
||||||
|
@"(?<Series>.*)(?: _|-|\[|\() ?v",
|
||||||
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
// Historys Strongest Disciple Kenichi_v11_c90-98.zip, Killing Bites Vol. 0001 Ch. 0001 - Galactica Scanlations (gb)
|
// Historys Strongest Disciple Kenichi_v11_c90-98.zip, Killing Bites Vol. 0001 Ch. 0001 - Galactica Scanlations (gb)
|
||||||
new Regex(
|
new Regex(
|
||||||
@"(?<Series>.*) (\b|_|-)v",
|
@"(?<Series>.*) (\b|_|-)v",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user