mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-08-07 09:01:25 -04:00
Feature/feedback (#185)
* 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
This commit is contained in:
parent
35a47f5d88
commit
6d74215262
@ -158,6 +158,19 @@ namespace API.Controllers
|
||||
foreach (var styleLinks in styleNodes)
|
||||
{
|
||||
var key = BookService.CleanContentKeys(styleLinks.Attributes["href"].Value);
|
||||
// Some epubs are malformed the key in content.opf might be: content/resources/filelist_0_0.xml but the actual html links to resources/filelist_0_0.xml
|
||||
// In this case, we will do a search for the key that ends with
|
||||
if (!book.Content.Css.ContainsKey(key))
|
||||
{
|
||||
var correctedKey = book.Content.Css.Keys.SingleOrDefault(s => s.EndsWith(key));
|
||||
if (correctedKey == null)
|
||||
{
|
||||
_logger.LogError("Epub is Malformed, key: {Key} is not matching OPF file", key);
|
||||
continue;
|
||||
}
|
||||
|
||||
key = correctedKey;
|
||||
}
|
||||
var styleContent = await _bookService.ScopeStyles(await book.Content.Css[key].ReadContentAsync(), apiBase);
|
||||
body.PrependChild(HtmlNode.CreateNode($"<style>{styleContent}</style>"));
|
||||
}
|
||||
@ -183,6 +196,14 @@ namespace API.Controllers
|
||||
if (image.Attributes["src"] != null)
|
||||
{
|
||||
var imageFile = image.Attributes["src"].Value;
|
||||
if (!book.Content.Images.ContainsKey(imageFile))
|
||||
{
|
||||
var correctedKey = book.Content.Images.Keys.SingleOrDefault(s => s.EndsWith(imageFile));
|
||||
if (correctedKey != null)
|
||||
{
|
||||
imageFile = correctedKey;
|
||||
}
|
||||
}
|
||||
image.Attributes.Remove("src");
|
||||
image.Attributes.Add("src", $"{apiBase}" + imageFile);
|
||||
}
|
||||
@ -199,6 +220,14 @@ namespace API.Controllers
|
||||
if (image.Attributes["xlink:href"] != null)
|
||||
{
|
||||
var imageFile = image.Attributes["xlink:href"].Value;
|
||||
if (!book.Content.Images.ContainsKey(imageFile))
|
||||
{
|
||||
var correctedKey = book.Content.Images.Keys.SingleOrDefault(s => s.EndsWith(imageFile));
|
||||
if (correctedKey != null)
|
||||
{
|
||||
imageFile = correctedKey;
|
||||
}
|
||||
}
|
||||
image.Attributes.Remove("xlink:href");
|
||||
image.Attributes.Add("xlink:href", $"{apiBase}" + imageFile);
|
||||
}
|
||||
|
@ -145,7 +145,8 @@ namespace API.Controllers
|
||||
[HttpGet("recently-added")]
|
||||
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetRecentlyAdded(int libraryId = 0, int limit = 20)
|
||||
{
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetRecentlyAdded(libraryId, limit));
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetRecentlyAdded(user.Id, libraryId, limit));
|
||||
}
|
||||
|
||||
[HttpGet("in-progress")]
|
||||
@ -154,5 +155,13 @@ namespace API.Controllers
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetInProgress(user.Id, libraryId, limit));
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost("refresh-metadata")]
|
||||
public ActionResult RefreshSeriesMetadata(RefreshSeriesDto refreshSeriesDto)
|
||||
{
|
||||
_taskScheduler.RefreshSeriesMetadata(refreshSeriesDto.LibraryId, refreshSeriesDto.SeriesId);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
}
|
8
API/DTOs/RefreshSeriesDto.cs
Normal file
8
API/DTOs/RefreshSeriesDto.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace API.DTOs
|
||||
{
|
||||
public class RefreshSeriesDto
|
||||
{
|
||||
public int LibraryId { get; set; }
|
||||
public int SeriesId { get; set; }
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
namespace API.DTOs
|
||||
using System;
|
||||
|
||||
namespace API.DTOs
|
||||
{
|
||||
public class SeriesDto
|
||||
{
|
||||
@ -21,6 +23,8 @@
|
||||
/// Review from logged in user. Calculated at API-time.
|
||||
/// </summary>
|
||||
public string UserReview { get; set; }
|
||||
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
public int LibraryId { get; set; }
|
||||
public string LibraryName { get; set; }
|
||||
|
@ -80,15 +80,12 @@ namespace API.Data
|
||||
|
||||
public async Task<PagedList<SeriesDto>> GetSeriesDtoForLibraryIdAsync(int libraryId, int userId, UserParams userParams)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
var query = _context.Series
|
||||
.Where(s => s.LibraryId == libraryId)
|
||||
.OrderBy(s => s.SortName)
|
||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking();
|
||||
|
||||
|
||||
_logger.LogDebug("Processed GetSeriesDtoForLibraryIdAsync in {ElapsedMilliseconds} milliseconds", sw.ElapsedMilliseconds);
|
||||
return await PagedList<SeriesDto>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
|
||||
}
|
||||
|
||||
@ -295,15 +292,35 @@ namespace API.Data
|
||||
/// <param name="libraryId">Library to restrict to, if 0, will apply to all libraries</param>
|
||||
/// <param name="limit">How many series to pick.</param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<SeriesDto>> GetRecentlyAdded(int libraryId, int limit)
|
||||
public async Task<IEnumerable<SeriesDto>> GetRecentlyAdded(int userId, int libraryId, int limit)
|
||||
{
|
||||
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();
|
||||
|
||||
return await _context.Series
|
||||
.Where(s => userLibraries.Contains(s.LibraryId))
|
||||
.AsNoTracking()
|
||||
.OrderByDescending(s => s.Created)
|
||||
.Take(limit)
|
||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
return await _context.Series
|
||||
.Where(s => (libraryId <= 0 || s.LibraryId == libraryId))
|
||||
.Take(limit)
|
||||
.OrderByDescending(s => s.Created)
|
||||
.Where(s => s.LibraryId == libraryId)
|
||||
.AsNoTracking()
|
||||
.OrderByDescending(s => s.Created)
|
||||
.Take(limit)
|
||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -325,7 +342,7 @@ namespace API.Data
|
||||
})
|
||||
.Where(s => s.AppUserId == userId
|
||||
&& s.PagesRead > 0
|
||||
&& s.PagesRead < s.Series.Pages
|
||||
&& 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
|
||||
&& (libraryId <= 0 || s.Series.LibraryId == libraryId))
|
||||
.Take(limit)
|
||||
.OrderByDescending(s => s.LastModified)
|
||||
|
@ -12,7 +12,7 @@ namespace API.Entities
|
||||
/// <summary>
|
||||
/// Manga Reader Option: How should the image be scaled to screen
|
||||
/// </summary>
|
||||
public ScalingOption ScalingOption { get; set; } = ScalingOption.FitToHeight;
|
||||
public ScalingOption ScalingOption { get; set; } = ScalingOption.Automatic;
|
||||
/// <summary>
|
||||
/// Manga Reader Option: Which side of a split image should we show first
|
||||
/// </summary>
|
||||
|
@ -24,6 +24,7 @@ namespace API.Helpers
|
||||
|
||||
public static async Task<PagedList<T>> CreateAsync(IQueryable<T> source, int pageNumber, int pageSize)
|
||||
{
|
||||
// NOTE: OrderBy warning being thrown here even if query has the orderby statement
|
||||
var count = await source.CountAsync();
|
||||
var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
|
||||
return new PagedList<T>(items, count, pageNumber, pageSize);
|
||||
|
@ -58,6 +58,6 @@ namespace API.Interfaces
|
||||
Task<byte[]> GetVolumeCoverImageAsync(int volumeId);
|
||||
Task<byte[]> GetSeriesCoverImageAsync(int seriesId);
|
||||
Task<IEnumerable<SeriesDto>> GetInProgress(int userId, int libraryId, int limit);
|
||||
Task<IEnumerable<SeriesDto>> GetRecentlyAdded(int libraryId, int limit);
|
||||
Task<IEnumerable<SeriesDto>> GetRecentlyAdded(int userId, int libraryId, int limit);
|
||||
}
|
||||
}
|
@ -10,5 +10,6 @@
|
||||
void CleanupChapters(int[] chapterIds);
|
||||
void RefreshMetadata(int libraryId, bool forceUpdate = true);
|
||||
void CleanupTemp();
|
||||
void RefreshSeriesMetadata(int libraryId, int seriesId);
|
||||
}
|
||||
}
|
@ -14,5 +14,11 @@ namespace API.Interfaces.Services
|
||||
public void UpdateMetadata(Chapter chapter, bool forceUpdate);
|
||||
public void UpdateMetadata(Volume volume, bool forceUpdate);
|
||||
public void UpdateMetadata(Series series, bool forceUpdate);
|
||||
/// <summary>
|
||||
/// Performs a forced refresh of metatdata just for a series and it's nested entities
|
||||
/// </summary>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <param name="seriesId"></param>
|
||||
void RefreshMetadataForSeries(int libraryId, int seriesId);
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.Interfaces;
|
||||
using API.Interfaces;
|
||||
using API.Parser;
|
||||
using ExCSS;
|
||||
@ -13,7 +12,6 @@ using HtmlAgilityPack;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NetVips;
|
||||
using VersOne.Epub;
|
||||
using VersOne.Epub.Schema;
|
||||
|
||||
namespace API.Services
|
||||
{
|
||||
|
@ -122,7 +122,7 @@ namespace API.Services
|
||||
// NOTE: This suffers from code changes not taking effect due to stale data
|
||||
var firstFile = firstChapter?.Files.FirstOrDefault();
|
||||
if (firstFile != null &&
|
||||
(forceUpdate || !firstFile.HasFileBeenModified()))
|
||||
(forceUpdate || firstFile.HasFileBeenModified())) // !new FileInfo(firstFile.FilePath).IsLastWriteLessThan(firstFile.LastModified)
|
||||
{
|
||||
series.Summary = isBook ? _bookService.GetSummaryInfo(firstFile.FilePath) : _archiveService.GetSummaryInfo(firstFile.FilePath);
|
||||
|
||||
@ -160,5 +160,38 @@ namespace API.Services
|
||||
_logger.LogInformation("Updated metadata for {LibraryName} in {ElapsedMilliseconds} milliseconds", library.Name, sw.ElapsedMilliseconds);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void RefreshMetadataForSeries(int libraryId, int seriesId)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
var library = Task.Run(() => _unitOfWork.LibraryRepository.GetFullLibraryForIdAsync(libraryId)).GetAwaiter().GetResult();
|
||||
|
||||
var series = library.Series.SingleOrDefault(s => s.Id == seriesId);
|
||||
if (series == null)
|
||||
{
|
||||
_logger.LogError("Series {SeriesId} was not found on Library {LibraryName}", seriesId, libraryId);
|
||||
return;
|
||||
}
|
||||
_logger.LogInformation("Beginning metadata refresh of {SeriesName}", series.Name);
|
||||
foreach (var volume in series.Volumes)
|
||||
{
|
||||
foreach (var chapter in volume.Chapters)
|
||||
{
|
||||
UpdateMetadata(chapter, true);
|
||||
}
|
||||
|
||||
UpdateMetadata(volume, true);
|
||||
}
|
||||
|
||||
UpdateMetadata(series, true);
|
||||
_unitOfWork.SeriesRepository.Update(series);
|
||||
|
||||
|
||||
if (_unitOfWork.HasChanges() && Task.Run(() => _unitOfWork.Complete()).Result)
|
||||
{
|
||||
_logger.LogInformation("Updated metadata for {SeriesName} in {ElapsedMilliseconds} milliseconds", series.Name, sw.ElapsedMilliseconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -90,6 +90,12 @@ namespace API.Services
|
||||
BackgroundJob.Enqueue((() => DirectoryService.ClearDirectory(tempDirectory)));
|
||||
}
|
||||
|
||||
public void RefreshSeriesMetadata(int libraryId, int seriesId)
|
||||
{
|
||||
_logger.LogInformation("Enqueuing series metadata refresh for: {SeriesId}", seriesId);
|
||||
BackgroundJob.Enqueue((() => _metadataService.RefreshMetadataForSeries(libraryId, seriesId)));
|
||||
}
|
||||
|
||||
public void BackupDatabase()
|
||||
{
|
||||
BackgroundJob.Enqueue(() => _backupService.BackupDatabase());
|
||||
|
@ -9,7 +9,6 @@ using API.Comparators;
|
||||
using API.Data;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.Interfaces;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
|
Loading…
x
Reference in New Issue
Block a user