Reader Bugs + New Features (#1536)
* Updated a typo in manage tasks of Reoccuring -> Recurring * Fixed a bug in MinimumNumberFromRange where a regex wasn't properly constructed which could skew results. * Fixed a bug where Volume numbers that were a float wouldn't render correctly in the manga reader menu. * Added the ability to double click on the image to bookmark it. Optimized the bookmark and unbookmark flows to remove 2 DB calls and reworked some flow of calls to speed it up. Fixed some logic where when using double (manga) flow, both of the images wouldn't show the bookmark effect, despite both of them being saved. Likewise, fixed a bug where both images weren't updating UI state, so switching from double (manga) to single, the second image wouldn't show as bookmarked without a refresh. * Double click works perfectly for bookmarking * Collection cover image chooser will now prompt with all series covers by default. Reset button is now moved up to the first slot if applicable. * When a Completed series is fully read by a user, a nightly task will now remove that series from their Want to Read list. * Added ability to trigger Want to Read cleanup from Tasks page. * Moved the brightness readout to the label line and fixed a bootstrap migration bug where small buttons weren't actually small. * Implemented ability to filter against release year (min or max or both). * Fixed a log message that wasn't properly formatted when scan finished an no files changes. * Cleaned up some code and merged some methods * Implemented sort by Release year metadata filter. * Fixed the code that finds ComicInfo.xml inside archives to only check the root and check explicitly for casing, so it must be ComicInfo.xml. * Dependency updates * Refactored some strings into consts and used TriggerJob rather than just enqueuing * Fixed the prefetcher which wasn't properly loading in the correct order as it was designed. * Cleaned up all traces of CircularArray from MangaReader * Removed a debug code * Fixed a bug with webtoon reader in fullscreen mode where continuous reader wouldn't trigger * When cleaning up series from users' want to read lists, include both completed and cancelled. * Fixed a bug where small images wouldn't have the pagination area extend to the bottom on manga reader * Added a new method for hashing during prod builds and ensure we always use aot * Fixed a bug where the save button wouldn't enable when color change occured. * Cleaned up some issues in one of contributor's PR.
This commit is contained in:
parent
52c10510b2
commit
9cf4cf742b
49 changed files with 408 additions and 221 deletions
|
@ -52,46 +52,46 @@
|
|||
<PackageReference Include="ExCSS" Version="4.1.0" />
|
||||
<PackageReference Include="Flurl" Version="3.0.6" />
|
||||
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
||||
<PackageReference Include="Hangfire" Version="1.7.30" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.30" />
|
||||
<PackageReference Include="Hangfire" Version="1.7.31" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.31" />
|
||||
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
|
||||
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
|
||||
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.3.2" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.43" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
|
||||
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.9">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.2.1" />
|
||||
<PackageReference Include="NetVips" Version="2.2.0" />
|
||||
<PackageReference Include="NetVips.Native" Version="8.13.0" />
|
||||
<PackageReference Include="NetVips.Native" Version="8.13.1" />
|
||||
<PackageReference Include="NReco.Logging.File" Version="1.1.5" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
<PackageReference Include="Serilog" Version="2.12.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="6.0.1" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="3.4.0" />
|
||||
<PackageReference Include="Serilog.Sinks.AspNetCore.SignalR" Version="0.4.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.SignalR.Core" Version="0.1.2" />
|
||||
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.43.0.51858">
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.44.0.52574">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.22.0" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="17.0.24" />
|
||||
<PackageReference Include="VersOne.Epub" Version="3.1.2" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.23.1" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="17.2.1" />
|
||||
<PackageReference Include="VersOne.Epub" Version="3.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -61,13 +61,10 @@ public class BookController : BaseApiController
|
|||
break;
|
||||
}
|
||||
case MangaFormat.Image:
|
||||
break;
|
||||
case MangaFormat.Archive:
|
||||
break;
|
||||
case MangaFormat.Unknown:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
break;
|
||||
}
|
||||
|
||||
return Ok(new BookInfoDto()
|
||||
|
|
|
@ -17,6 +17,7 @@ using Hangfire;
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
|
@ -657,6 +658,7 @@ public class ReaderController : BaseApiController
|
|||
/// <summary>
|
||||
/// Bookmarks a page against a Chapter
|
||||
/// </summary>
|
||||
/// <remarks>This has a side effect of caching the chapter files to disk</remarks>
|
||||
/// <param name="bookmarkDto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("bookmark")]
|
||||
|
@ -669,18 +671,16 @@ public class ReaderController : BaseApiController
|
|||
if (!await _accountService.HasBookmarkPermission(user))
|
||||
return BadRequest("You do not have permission to bookmark");
|
||||
|
||||
bookmarkDto.Page = await _readerService.CapPageToChapter(bookmarkDto.ChapterId, bookmarkDto.Page);
|
||||
var chapter = await _cacheService.Ensure(bookmarkDto.ChapterId);
|
||||
if (chapter == null) return BadRequest("Could not find cached image. Reload and try again.");
|
||||
|
||||
bookmarkDto.Page = _readerService.CapPageToChapter(chapter, bookmarkDto.Page);
|
||||
var path = _cacheService.GetCachedPagePath(chapter, bookmarkDto.Page);
|
||||
|
||||
if (await _bookmarkService.BookmarkPage(user, bookmarkDto, path))
|
||||
{
|
||||
BackgroundJob.Enqueue(() => _cacheService.CleanupBookmarkCache(bookmarkDto.SeriesId));
|
||||
return Ok();
|
||||
}
|
||||
if (!await _bookmarkService.BookmarkPage(user, bookmarkDto, path)) return BadRequest("Could not save bookmark");
|
||||
|
||||
return BadRequest("Could not save bookmark");
|
||||
BackgroundJob.Enqueue(() => _cacheService.CleanupBookmarkCache(bookmarkDto.SeriesId));
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -693,18 +693,15 @@ public class ReaderController : BaseApiController
|
|||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
|
||||
if (user == null) return new UnauthorizedResult();
|
||||
if (user.Bookmarks == null) return Ok();
|
||||
if (user.Bookmarks.IsNullOrEmpty()) return Ok();
|
||||
|
||||
if (!await _accountService.HasBookmarkPermission(user))
|
||||
return BadRequest("You do not have permission to unbookmark");
|
||||
|
||||
if (await _bookmarkService.RemoveBookmarkPage(user, bookmarkDto))
|
||||
{
|
||||
BackgroundJob.Enqueue(() => _cacheService.CleanupBookmarkCache(bookmarkDto.SeriesId));
|
||||
return Ok();
|
||||
}
|
||||
|
||||
return BadRequest("Could not remove bookmark");
|
||||
if (!await _bookmarkService.RemoveBookmarkPage(user, bookmarkDto))
|
||||
return BadRequest("Could not remove bookmark");
|
||||
BackgroundJob.Enqueue(() => _cacheService.CleanupBookmarkCache(bookmarkDto.SeriesId));
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -46,6 +46,7 @@ public class ReadingListController : BaseApiController
|
|||
/// Returns reading lists (paginated) for a given user.
|
||||
/// </summary>
|
||||
/// <param name="includePromoted">Defaults to true</param>
|
||||
/// <param name="userParams">Pagination parameters</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("lists")]
|
||||
public async Task<ActionResult<IEnumerable<ReadingListDto>>> GetListsForUser([FromQuery] UserParams userParams, [FromQuery] bool includePromoted = true)
|
||||
|
|
|
@ -77,6 +77,19 @@ public class ServerController : BaseApiController
|
|||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an ad-hoc cleanup of Want To Read, by removing want to read series for users, where the series are fully read and in Completed publication status.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("cleanup-want-to-read")]
|
||||
public ActionResult CleanupWantToRead()
|
||||
{
|
||||
_logger.LogInformation("{UserName} is clearing running want to read cleanup from admin dashboard", User.GetUsername());
|
||||
RecurringJob.TriggerJob(API.Services.TaskScheduler.RemoveFromWantToReadTaskId);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an ad-hoc backup of the Database
|
||||
/// </summary>
|
||||
|
@ -85,7 +98,7 @@ public class ServerController : BaseApiController
|
|||
public ActionResult BackupDatabase()
|
||||
{
|
||||
_logger.LogInformation("{UserName} is backing up database of server from admin dashboard", User.GetUsername());
|
||||
RecurringJob.Trigger("backup");
|
||||
RecurringJob.TriggerJob(API.Services.TaskScheduler.BackupTaskId);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
|
|
@ -99,4 +99,8 @@ public class FilterDto
|
|||
/// An optional name string to filter by. Empty string will ignore.
|
||||
/// </summary>
|
||||
public string SeriesNameQuery { get; init; } = string.Empty;
|
||||
/// <summary>
|
||||
/// An optional release year to filter by. Null will ignore. You can pass 0 for an individual field to ignore it.
|
||||
/// </summary>
|
||||
public Range<int>? ReleaseYearRange { get; init; } = null;
|
||||
}
|
||||
|
|
14
API/DTOs/Filtering/Range.cs
Normal file
14
API/DTOs/Filtering/Range.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
namespace API.DTOs.Filtering;
|
||||
/// <summary>
|
||||
/// Represents a range between two int/float/double
|
||||
/// </summary>
|
||||
public class Range<T>
|
||||
{
|
||||
public T Min { get; set; }
|
||||
public T Max { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Min}-{Max}";
|
||||
}
|
||||
}
|
|
@ -21,5 +21,9 @@ public enum SortField
|
|||
/// <summary>
|
||||
/// Time it takes to read. Uses Average.
|
||||
/// </summary>
|
||||
TimeToRead = 5
|
||||
TimeToRead = 5,
|
||||
/// <summary>
|
||||
/// Release Year of the Series
|
||||
/// </summary>
|
||||
ReleaseYear = 6
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ public class ChapterRepository : IChapterRepository
|
|||
.Join(_context.Volume, c => c.VolumeId, v => v.Id, (chapter, volume) => new
|
||||
{
|
||||
ChapterNumber = chapter.Range,
|
||||
VolumeNumber = volume.Number,
|
||||
VolumeNumber = volume.Name,
|
||||
VolumeId = volume.Id,
|
||||
chapter.IsSpecial,
|
||||
chapter.TitleName,
|
||||
|
|
|
@ -605,7 +605,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
private IList<MangaFormat> ExtractFilters(int libraryId, int userId, FilterDto filter, ref List<int> userLibraries,
|
||||
out List<int> allPeopleIds, out bool hasPeopleFilter, out bool hasGenresFilter, out bool hasCollectionTagFilter,
|
||||
out bool hasRatingFilter, out bool hasProgressFilter, out IList<int> seriesIds, out bool hasAgeRating, out bool hasTagsFilter,
|
||||
out bool hasLanguageFilter, out bool hasPublicationFilter, out bool hasSeriesNameFilter)
|
||||
out bool hasLanguageFilter, out bool hasPublicationFilter, out bool hasSeriesNameFilter, out bool hasReleaseYearMinFilter, out bool hasReleaseYearMaxFilter)
|
||||
{
|
||||
var formats = filter.GetSqlFilter();
|
||||
|
||||
|
@ -640,6 +640,9 @@ public class SeriesRepository : ISeriesRepository
|
|||
hasLanguageFilter = filter.Languages.Count > 0;
|
||||
hasPublicationFilter = filter.PublicationStatus.Count > 0;
|
||||
|
||||
hasReleaseYearMinFilter = filter.ReleaseYearRange != null && filter.ReleaseYearRange.Min != 0;
|
||||
hasReleaseYearMaxFilter = filter.ReleaseYearRange != null && filter.ReleaseYearRange.Max != 0;
|
||||
|
||||
|
||||
bool ProgressComparison(int pagesRead, int totalPages)
|
||||
{
|
||||
|
@ -731,7 +734,8 @@ public class SeriesRepository : ISeriesRepository
|
|||
var formats = ExtractFilters(libraryId, userId, filter, ref userLibraries,
|
||||
out var allPeopleIds, out var hasPeopleFilter, out var hasGenresFilter,
|
||||
out var hasCollectionTagFilter, out var hasRatingFilter, out var hasProgressFilter,
|
||||
out var seriesIds, out var hasAgeRating, out var hasTagsFilter, out var hasLanguageFilter, out var hasPublicationFilter, out var hasSeriesNameFilter);
|
||||
out var seriesIds, out var hasAgeRating, out var hasTagsFilter, out var hasLanguageFilter,
|
||||
out var hasPublicationFilter, out var hasSeriesNameFilter, out var hasReleaseYearMinFilter, out var hasReleaseYearMaxFilter);
|
||||
|
||||
var query = _context.Series
|
||||
.Where(s => userLibraries.Contains(s.LibraryId)
|
||||
|
@ -745,6 +749,8 @@ public class SeriesRepository : ISeriesRepository
|
|||
&& (!hasAgeRating || filter.AgeRating.Contains(s.Metadata.AgeRating))
|
||||
&& (!hasTagsFilter || s.Metadata.Tags.Any(t => filter.Tags.Contains(t.Id)))
|
||||
&& (!hasLanguageFilter || filter.Languages.Contains(s.Metadata.Language))
|
||||
&& (!hasReleaseYearMinFilter || s.Metadata.ReleaseYear >= filter.ReleaseYearRange.Min)
|
||||
&& (!hasReleaseYearMaxFilter || s.Metadata.ReleaseYear <= filter.ReleaseYearRange.Max)
|
||||
&& (!hasPublicationFilter || filter.PublicationStatus.Contains(s.Metadata.PublicationStatus)))
|
||||
.Where(s => !hasSeriesNameFilter ||
|
||||
EF.Functions.Like(s.Name, $"%{filter.SeriesNameQuery}%")
|
||||
|
@ -768,6 +774,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
SortField.LastModifiedDate => query.OrderBy(s => s.LastModified),
|
||||
SortField.LastChapterAdded => query.OrderBy(s => s.LastChapterAdded),
|
||||
SortField.TimeToRead => query.OrderBy(s => s.AvgHoursToRead),
|
||||
SortField.ReleaseYear => query.OrderBy(s => s.Metadata.ReleaseYear),
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
|
@ -780,6 +787,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
SortField.LastModifiedDate => query.OrderByDescending(s => s.LastModified),
|
||||
SortField.LastChapterAdded => query.OrderByDescending(s => s.LastChapterAdded),
|
||||
SortField.TimeToRead => query.OrderByDescending(s => s.AvgHoursToRead),
|
||||
SortField.ReleaseYear => query.OrderByDescending(s => s.Metadata.ReleaseYear),
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
|
@ -793,7 +801,8 @@ public class SeriesRepository : ISeriesRepository
|
|||
var formats = ExtractFilters(libraryId, userId, filter, ref userLibraries,
|
||||
out var allPeopleIds, out var hasPeopleFilter, out var hasGenresFilter,
|
||||
out var hasCollectionTagFilter, out var hasRatingFilter, out var hasProgressFilter,
|
||||
out var seriesIds, out var hasAgeRating, out var hasTagsFilter, out var hasLanguageFilter, out var hasPublicationFilter, out var hasSeriesNameFilter);
|
||||
out var seriesIds, out var hasAgeRating, out var hasTagsFilter, out var hasLanguageFilter,
|
||||
out var hasPublicationFilter, out var hasSeriesNameFilter, out var hasReleaseYearMinFilter, out var hasReleaseYearMaxFilter);
|
||||
|
||||
var query = sQuery
|
||||
.Where(s => userLibraries.Contains(s.LibraryId)
|
||||
|
@ -807,6 +816,8 @@ public class SeriesRepository : ISeriesRepository
|
|||
&& (!hasAgeRating || filter.AgeRating.Contains(s.Metadata.AgeRating))
|
||||
&& (!hasTagsFilter || s.Metadata.Tags.Any(t => filter.Tags.Contains(t.Id)))
|
||||
&& (!hasLanguageFilter || filter.Languages.Contains(s.Metadata.Language))
|
||||
&& (!hasReleaseYearMinFilter || s.Metadata.ReleaseYear >= filter.ReleaseYearRange.Min)
|
||||
&& (!hasReleaseYearMaxFilter || s.Metadata.ReleaseYear <= filter.ReleaseYearRange.Max)
|
||||
&& (!hasPublicationFilter || filter.PublicationStatus.Contains(s.Metadata.PublicationStatus)))
|
||||
.Where(s => !hasSeriesNameFilter ||
|
||||
EF.Functions.Like(s.Name, $"%{filter.SeriesNameQuery}%")
|
||||
|
@ -1069,14 +1080,6 @@ public class SeriesRepository : ISeriesRepository
|
|||
.ToListAsync();
|
||||
}
|
||||
|
||||
private IQueryable<int> GetLibraryIdsForUser(int userId)
|
||||
{
|
||||
return _context.AppUser
|
||||
.Where(u => u.Id == userId)
|
||||
.AsSplitQuery()
|
||||
.SelectMany(l => l.Libraries.Select(lib => lib.Id));
|
||||
}
|
||||
|
||||
public async Task<PagedList<SeriesDto>> GetMoreIn(int userId, int libraryId, int genreId, UserParams userParams)
|
||||
{
|
||||
var libraryIds = GetLibraryIdsForUser(userId, libraryId);
|
||||
|
@ -1219,6 +1222,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
/// <param name="seriesName"></param>
|
||||
/// <param name="localizedName"></param>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="withFullIncludes">Defaults to true. This will query against all foreign keys (deep). If false, just the series will come back</param>
|
||||
/// <returns></returns>
|
||||
public Task<Series> GetFullSeriesByAnyName(string seriesName, string localizedName, int libraryId, MangaFormat format, bool withFullIncludes = true)
|
||||
|
@ -1375,11 +1379,20 @@ public class SeriesRepository : ISeriesRepository
|
|||
/// <param name="userId"></param>
|
||||
/// <param name="libraryId">0 for no library filter</param>
|
||||
/// <returns></returns>
|
||||
private IQueryable<int> GetLibraryIdsForUser(int userId, int libraryId)
|
||||
private IQueryable<int> GetLibraryIdsForUser(int userId, int libraryId = 0)
|
||||
{
|
||||
return _context.AppUser
|
||||
.Where(u => u.Id == userId)
|
||||
.SelectMany(l => l.Libraries.Where(l => l.Id == libraryId || libraryId == 0).Select(lib => lib.Id));
|
||||
var query = _context.AppUser
|
||||
.AsSplitQuery()
|
||||
.AsNoTracking()
|
||||
.Where(u => u.Id == userId);
|
||||
|
||||
if (libraryId == 0)
|
||||
{
|
||||
return query.SelectMany(l => l.Libraries.Select(lib => lib.Id));
|
||||
}
|
||||
|
||||
return query.SelectMany(l =>
|
||||
l.Libraries.Where(lib => lib.Id == libraryId).Select(lib => lib.Id));
|
||||
}
|
||||
|
||||
public async Task<RelatedSeriesDto> GetRelatedSeries(int userId, int seriesId)
|
||||
|
|
|
@ -35,6 +35,7 @@ public interface IUserRepository
|
|||
void Update(AppUser user);
|
||||
void Update(AppUserPreferences preferences);
|
||||
void Update(AppUserBookmark bookmark);
|
||||
void Add(AppUserBookmark bookmark);
|
||||
public void Delete(AppUser user);
|
||||
void Delete(AppUserBookmark bookmark);
|
||||
Task<IEnumerable<MemberDto>> GetEmailConfirmedMemberDtosAsync();
|
||||
|
@ -90,6 +91,11 @@ public class UserRepository : IUserRepository
|
|||
_context.Entry(bookmark).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
public void Add(AppUserBookmark bookmark)
|
||||
{
|
||||
_context.AppUserBookmark.Add(bookmark);
|
||||
}
|
||||
|
||||
public void Delete(AppUser user)
|
||||
{
|
||||
_context.AppUser.Remove(user);
|
||||
|
@ -229,7 +235,8 @@ public class UserRepository : IUserRepository
|
|||
|
||||
public async Task<IEnumerable<AppUser>> GetAllUsers()
|
||||
{
|
||||
return await _context.AppUser.ToListAsync();
|
||||
return await _context.AppUser
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AppUserPreferences>> GetAllPreferencesByThemeAsync(int themeId)
|
||||
|
|
|
@ -82,11 +82,11 @@ namespace API.Helpers.Filters;
|
|||
// }
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class ETagFilter : Attribute, IActionFilter
|
||||
public class ETagFilterAttribute : Attribute, IActionFilter
|
||||
{
|
||||
private readonly int[] _statusCodes;
|
||||
|
||||
public ETagFilter(params int[] statusCodes)
|
||||
public ETagFilterAttribute(params int[] statusCodes)
|
||||
{
|
||||
_statusCodes = statusCodes;
|
||||
if (statusCodes.Length == 0) _statusCodes = new[] { 200 };
|
||||
|
@ -94,6 +94,7 @@ public class ETagFilter : Attribute, IActionFilter
|
|||
|
||||
public void OnActionExecuting(ActionExecutingContext context)
|
||||
{
|
||||
/* Nothing needs to be done here */
|
||||
}
|
||||
|
||||
public void OnActionExecuted(ActionExecutedContext context)
|
||||
|
@ -101,16 +102,13 @@ public class ETagFilter : Attribute, IActionFilter
|
|||
if (context.HttpContext.Request.Method != "GET" || context.HttpContext.Request.Method != "HEAD") return;
|
||||
if (!_statusCodes.Contains(context.HttpContext.Response.StatusCode)) return;
|
||||
|
||||
var etag = string.Empty;;
|
||||
var etag = string.Empty;
|
||||
//I just serialize the result to JSON, could do something less costly
|
||||
if (context.Result is PhysicalFileResult)
|
||||
if (context.Result is PhysicalFileResult fileResult)
|
||||
{
|
||||
// Do a cheap LastWriteTime etag gen
|
||||
if (context.Result is PhysicalFileResult fileResult)
|
||||
{
|
||||
etag = ETagGenerator.GenerateEtagFromFilename(fileResult.FileName);
|
||||
context.HttpContext.Response.Headers.LastModified = File.GetLastWriteTimeUtc(fileResult.FileName).ToLongDateString();
|
||||
}
|
||||
etag = ETagGenerator.GenerateEtagFromFilename(fileResult.FileName);
|
||||
context.HttpContext.Response.Headers.LastModified = File.GetLastWriteTimeUtc(fileResult.FileName).ToLongDateString();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(etag))
|
||||
|
|
|
@ -64,7 +64,7 @@ public static class LogLevelOptions
|
|||
AspNetCoreLogLevelSwitch.MinimumLevel = LogEventLevel.Debug;
|
||||
break;
|
||||
case "Information":
|
||||
LogLevelSwitch.MinimumLevel = LogEventLevel.Information;
|
||||
LogLevelSwitch.MinimumLevel = LogEventLevel.Error;
|
||||
MicrosoftHostingLifetimeLogLevelSwitch.MinimumLevel = LogEventLevel.Error;
|
||||
AspNetCoreLogLevelSwitch.MinimumLevel = LogEventLevel.Error;
|
||||
break;
|
||||
|
@ -79,7 +79,7 @@ public static class LogLevelOptions
|
|||
AspNetCoreLogLevelSwitch.MinimumLevel = LogEventLevel.Error;
|
||||
break;
|
||||
case "Critical":
|
||||
LogLevelSwitch.MinimumLevel = LogEventLevel.Error;
|
||||
LogLevelSwitch.MinimumLevel = LogEventLevel.Fatal;
|
||||
MicrosoftHostingLifetimeLogLevelSwitch.MinimumLevel = LogEventLevel.Error;
|
||||
AspNetCoreLogLevelSwitch.MinimumLevel = LogEventLevel.Error;
|
||||
break;
|
||||
|
|
|
@ -19,7 +19,7 @@ public interface IAccountService
|
|||
Task<IEnumerable<ApiException>> ValidateUsername(string username);
|
||||
Task<IEnumerable<ApiException>> ValidateEmail(string email);
|
||||
Task<bool> HasBookmarkPermission(AppUser user);
|
||||
Task<bool> HasDownloadPermission(AppUser appuser);
|
||||
Task<bool> HasDownloadPermission(AppUser user);
|
||||
}
|
||||
|
||||
public class AccountService : IAccountService
|
||||
|
|
|
@ -44,7 +44,7 @@ public class ArchiveService : IArchiveService
|
|||
private readonly ILogger<ArchiveService> _logger;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
private readonly IImageService _imageService;
|
||||
private const string ComicInfoFilename = "comicinfo";
|
||||
private const string ComicInfoFilename = "ComicInfo.xml";
|
||||
|
||||
public ArchiveService(ILogger<ArchiveService> logger, IDirectoryService directoryService, IImageService imageService)
|
||||
{
|
||||
|
@ -332,9 +332,8 @@ public class ArchiveService : IArchiveService
|
|||
{
|
||||
var filenameWithoutExtension = Path.GetFileNameWithoutExtension(name).ToLower();
|
||||
return !Tasks.Scanner.Parser.Parser.HasBlacklistedFolderInPath(fullName)
|
||||
&& filenameWithoutExtension.Equals(ComicInfoFilename, StringComparison.InvariantCultureIgnoreCase)
|
||||
&& !filenameWithoutExtension.StartsWith(Tasks.Scanner.Parser.Parser.MacOsMetadataFileStartsWith)
|
||||
&& Tasks.Scanner.Parser.Parser.IsXml(name);
|
||||
&& fullName.Equals(ComicInfoFilename)
|
||||
&& !filenameWithoutExtension.StartsWith(Tasks.Scanner.Parser.Parser.MacOsMetadataFileStartsWith);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -561,8 +561,6 @@ public class BookService : IBookService
|
|||
var seriesIndex = string.Empty;
|
||||
var series = string.Empty;
|
||||
var specialName = string.Empty;
|
||||
var groupPosition = string.Empty;
|
||||
var titleSort = string.Empty;
|
||||
|
||||
|
||||
foreach (var metadataItem in epubBook.Schema.Package.Metadata.MetaItems)
|
||||
|
@ -578,7 +576,6 @@ public class BookService : IBookService
|
|||
break;
|
||||
case "calibre:title_sort":
|
||||
specialName = metadataItem.Content;
|
||||
titleSort = metadataItem.Content;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -592,7 +589,7 @@ public class BookService : IBookService
|
|||
series = metadataItem.Content;
|
||||
break;
|
||||
case "collection-type":
|
||||
groupPosition = metadataItem.Content;
|
||||
// These look to be genres from https://manual.calibre-ebook.com/sub_groups.html
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -965,7 +962,7 @@ public class BookService : IBookService
|
|||
}
|
||||
catch (Exception)
|
||||
{
|
||||
/* Swallow exception. Some css doesn't have style rules ending in ; */
|
||||
/* Swallow exception. Some css don't have style rules ending in ; */
|
||||
}
|
||||
|
||||
body = Regex.Replace(body, @"([\s:]0)(px|pt|%|em)", "$1");
|
||||
|
|
|
@ -79,15 +79,14 @@ public class BookmarkService : IBookmarkService
|
|||
/// <returns>If the save to DB and copy was successful</returns>
|
||||
public async Task<bool> BookmarkPage(AppUser userWithBookmarks, BookmarkDto bookmarkDto, string imageToBookmark)
|
||||
{
|
||||
if (userWithBookmarks == null || userWithBookmarks.Bookmarks == null) return false;
|
||||
try
|
||||
{
|
||||
var userBookmark =
|
||||
await _unitOfWork.UserRepository.GetBookmarkForPage(bookmarkDto.Page, bookmarkDto.ChapterId, userWithBookmarks.Id);
|
||||
|
||||
var userBookmark = userWithBookmarks.Bookmarks.SingleOrDefault(b => b.Page == bookmarkDto.Page && b.ChapterId == bookmarkDto.ChapterId);
|
||||
if (userBookmark != null)
|
||||
{
|
||||
_logger.LogError("Bookmark already exists for Series {SeriesId}, Volume {VolumeId}, Chapter {ChapterId}, Page {PageNum}", bookmarkDto.SeriesId, bookmarkDto.VolumeId, bookmarkDto.ChapterId, bookmarkDto.Page);
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var fileInfo = _directoryService.FileSystem.FileInfo.FromFileName(imageToBookmark);
|
||||
|
@ -101,14 +100,13 @@ public class BookmarkService : IBookmarkService
|
|||
VolumeId = bookmarkDto.VolumeId,
|
||||
SeriesId = bookmarkDto.SeriesId,
|
||||
ChapterId = bookmarkDto.ChapterId,
|
||||
FileName = Path.Join(targetFolderStem, fileInfo.Name)
|
||||
FileName = Path.Join(targetFolderStem, fileInfo.Name),
|
||||
AppUserId = userWithBookmarks.Id
|
||||
};
|
||||
|
||||
_directoryService.CopyFileToDirectory(imageToBookmark, targetFilepath);
|
||||
userWithBookmarks.Bookmarks ??= new List<AppUserBookmark>();
|
||||
userWithBookmarks.Bookmarks.Add(bookmark);
|
||||
|
||||
_unitOfWork.UserRepository.Update(userWithBookmarks);
|
||||
_unitOfWork.UserRepository.Add(bookmark);
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
if (settings.ConvertBookmarkToWebP)
|
||||
|
@ -136,15 +134,12 @@ public class BookmarkService : IBookmarkService
|
|||
public async Task<bool> RemoveBookmarkPage(AppUser userWithBookmarks, BookmarkDto bookmarkDto)
|
||||
{
|
||||
if (userWithBookmarks.Bookmarks == null) return true;
|
||||
var bookmarkToDelete = userWithBookmarks.Bookmarks.SingleOrDefault(x =>
|
||||
x.ChapterId == bookmarkDto.ChapterId && x.Page == bookmarkDto.Page);
|
||||
try
|
||||
{
|
||||
var bookmarkToDelete = userWithBookmarks.Bookmarks.SingleOrDefault(x =>
|
||||
x.ChapterId == bookmarkDto.ChapterId && x.AppUserId == userWithBookmarks.Id && x.Page == bookmarkDto.Page &&
|
||||
x.SeriesId == bookmarkDto.SeriesId);
|
||||
|
||||
if (bookmarkToDelete != null)
|
||||
{
|
||||
await DeleteBookmarkFiles(new[] {bookmarkToDelete});
|
||||
_unitOfWork.UserRepository.Delete(bookmarkToDelete);
|
||||
}
|
||||
|
||||
|
@ -152,10 +147,10 @@ public class BookmarkService : IBookmarkService
|
|||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await _unitOfWork.RollbackAsync();
|
||||
return false;
|
||||
}
|
||||
|
||||
await DeleteBookmarkFiles(new[] {bookmarkToDelete});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -368,15 +368,22 @@ public class DirectoryService : IDirectoryService
|
|||
{
|
||||
var di = FileSystem.DirectoryInfo.FromDirectoryName(directoryPath);
|
||||
if (!di.Exists) return;
|
||||
try
|
||||
{
|
||||
foreach (var file in di.EnumerateFiles())
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
foreach (var dir in di.EnumerateDirectories())
|
||||
{
|
||||
dir.Delete(true);
|
||||
}
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogError(ex, "[ClearDirectory] Could not delete {DirectoryPath} due to permission issue", directoryPath);
|
||||
}
|
||||
|
||||
foreach (var file in di.EnumerateFiles())
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
foreach (var dir in di.EnumerateDirectories())
|
||||
{
|
||||
dir.Delete(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -128,7 +128,7 @@ public class ImageService : IImageService
|
|||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
/* Swallow Exception */
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ public interface IReaderService
|
|||
Task MarkChaptersAsUnread(AppUser user, int seriesId, IEnumerable<Chapter> chapters);
|
||||
Task<bool> SaveReadingProgress(ProgressDto progressDto, int userId);
|
||||
Task<int> CapPageToChapter(int chapterId, int page);
|
||||
int CapPageToChapter(Chapter chapter, int page);
|
||||
Task<int> GetNextChapterIdAsync(int seriesId, int volumeId, int currentChapterId, int userId);
|
||||
Task<int> GetPrevChapterIdAsync(int seriesId, int volumeId, int currentChapterId, int userId);
|
||||
Task<ChapterDto> GetContinuePoint(int seriesId, int userId);
|
||||
|
@ -273,6 +274,21 @@ public class ReaderService : IReaderService
|
|||
return page;
|
||||
}
|
||||
|
||||
public int CapPageToChapter(Chapter chapter, int page)
|
||||
{
|
||||
if (page > chapter.Pages)
|
||||
{
|
||||
page = chapter.Pages;
|
||||
}
|
||||
|
||||
if (page < 0)
|
||||
{
|
||||
page = 0;
|
||||
}
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to find the next logical Chapter
|
||||
/// </summary>
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Entities.Enums;
|
||||
using API.Helpers.Converters;
|
||||
using API.Services.Tasks;
|
||||
using API.Services.Tasks.Metadata;
|
||||
using API.Services.Tasks.Scanner;
|
||||
using Hangfire;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
|
@ -49,9 +48,13 @@ public class TaskScheduler : ITaskScheduler
|
|||
public static BackgroundJobServer Client => new BackgroundJobServer();
|
||||
public const string ScanQueue = "scan";
|
||||
public const string DefaultQueue = "default";
|
||||
public const string RemoveFromWantToReadTaskId = "remove-from-want-to-read";
|
||||
public const string CleanupDbTaskId = "cleanup-db";
|
||||
public const string CleanupTaskId = "cleanup";
|
||||
public const string BackupTaskId = "backup";
|
||||
public const string ScanLibrariesTaskId = "scan-libraries";
|
||||
|
||||
public static readonly IList<string> ScanTasks = new List<string>()
|
||||
{"ScannerService", "ScanLibrary", "ScanLibraries", "ScanFolder", "ScanSeries"};
|
||||
private static readonly ImmutableArray<string> ScanTasks = ImmutableArray.Create("ScannerService", "ScanLibrary", "ScanLibraries", "ScanFolder", "ScanSeries");
|
||||
|
||||
private static readonly Random Rnd = new Random();
|
||||
|
||||
|
@ -83,27 +86,28 @@ public class TaskScheduler : ITaskScheduler
|
|||
{
|
||||
var scanLibrarySetting = setting;
|
||||
_logger.LogDebug("Scheduling Scan Library Task for {Setting}", scanLibrarySetting);
|
||||
RecurringJob.AddOrUpdate("scan-libraries", () => _scannerService.ScanLibraries(),
|
||||
RecurringJob.AddOrUpdate(ScanLibrariesTaskId, () => _scannerService.ScanLibraries(),
|
||||
() => CronConverter.ConvertToCronNotation(scanLibrarySetting), TimeZoneInfo.Local);
|
||||
}
|
||||
else
|
||||
{
|
||||
RecurringJob.AddOrUpdate("scan-libraries", () => ScanLibraries(), Cron.Daily, TimeZoneInfo.Local);
|
||||
RecurringJob.AddOrUpdate(ScanLibrariesTaskId, () => ScanLibraries(), Cron.Daily, TimeZoneInfo.Local);
|
||||
}
|
||||
|
||||
setting = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskBackup)).Value;
|
||||
if (setting != null)
|
||||
{
|
||||
_logger.LogDebug("Scheduling Backup Task for {Setting}", setting);
|
||||
RecurringJob.AddOrUpdate("backup", () => _backupService.BackupDatabase(), () => CronConverter.ConvertToCronNotation(setting), TimeZoneInfo.Local);
|
||||
RecurringJob.AddOrUpdate(BackupTaskId, () => _backupService.BackupDatabase(), () => CronConverter.ConvertToCronNotation(setting), TimeZoneInfo.Local);
|
||||
}
|
||||
else
|
||||
{
|
||||
RecurringJob.AddOrUpdate("backup", () => _backupService.BackupDatabase(), Cron.Weekly, TimeZoneInfo.Local);
|
||||
RecurringJob.AddOrUpdate(BackupTaskId, () => _backupService.BackupDatabase(), Cron.Weekly, TimeZoneInfo.Local);
|
||||
}
|
||||
|
||||
RecurringJob.AddOrUpdate("cleanup", () => _cleanupService.Cleanup(), Cron.Daily, TimeZoneInfo.Local);
|
||||
RecurringJob.AddOrUpdate("cleanup-db", () => _cleanupService.CleanupDbEntries(), Cron.Daily, TimeZoneInfo.Local);
|
||||
RecurringJob.AddOrUpdate(CleanupTaskId, () => _cleanupService.Cleanup(), Cron.Daily, TimeZoneInfo.Local);
|
||||
RecurringJob.AddOrUpdate(CleanupDbTaskId, () => _cleanupService.CleanupDbEntries(), Cron.Daily, TimeZoneInfo.Local);
|
||||
RecurringJob.AddOrUpdate(RemoveFromWantToReadTaskId, () => _cleanupService.CleanupWantToRead(), Cron.Daily, TimeZoneInfo.Local);
|
||||
}
|
||||
|
||||
#region StatsTasks
|
||||
|
@ -154,7 +158,6 @@ public class TaskScheduler : ITaskScheduler
|
|||
BackgroundJob.Enqueue(() => _themeService.Scan());
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region UpdateTasks
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs.Filtering;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Helpers;
|
||||
using API.SignalR;
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
@ -21,6 +26,11 @@ public interface ICleanupService
|
|||
Task DeleteTagCoverImages();
|
||||
Task CleanupBackups();
|
||||
void CleanupTemp();
|
||||
/// <summary>
|
||||
/// Responsible to remove Series from Want To Read when user's have fully read the series and the series has Publication Status of Completed or Cancelled.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task CleanupWantToRead();
|
||||
}
|
||||
/// <summary>
|
||||
/// Cleans up after operations on reoccurring basis
|
||||
|
@ -195,4 +205,43 @@ public class CleanupService : ICleanupService
|
|||
|
||||
_logger.LogInformation("Temp directory purged");
|
||||
}
|
||||
|
||||
public async Task CleanupWantToRead()
|
||||
{
|
||||
_logger.LogInformation("Performing cleanup of Series that are Completed and have been fully read that are in Want To Read list");
|
||||
|
||||
var libraryIds = (await _unitOfWork.LibraryRepository.GetLibrariesAsync()).Select(l => l.Id).ToList();
|
||||
var filter = new FilterDto()
|
||||
{
|
||||
PublicationStatus = new List<PublicationStatus>()
|
||||
{
|
||||
PublicationStatus.Completed,
|
||||
PublicationStatus.Cancelled
|
||||
},
|
||||
Libraries = libraryIds,
|
||||
ReadStatus = new ReadStatus()
|
||||
{
|
||||
Read = true,
|
||||
InProgress = false,
|
||||
NotRead = false
|
||||
}
|
||||
};
|
||||
foreach (var user in await _unitOfWork.UserRepository.GetAllUsersAsync(AppUserIncludes.WantToRead))
|
||||
{
|
||||
var series = await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(0, user.Id, new UserParams(), filter);
|
||||
var seriesIds = series.Select(s => s.Id).ToList();
|
||||
if (seriesIds.Count == 0) continue;
|
||||
|
||||
user.WantToRead ??= new List<Series>();
|
||||
user.WantToRead = user.WantToRead.Where(s => !seriesIds.Contains(s.Id)).ToList();
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
}
|
||||
|
||||
if (_unitOfWork.HasChanges())
|
||||
{
|
||||
await _unitOfWork.CommitAsync();
|
||||
}
|
||||
|
||||
_logger.LogInformation("Performing cleanup of Series that are Completed and have been fully read that are in Want To Read list, completed");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -215,6 +215,10 @@ public class ParseScannedFiles
|
|||
/// <param name="libraryType"></param>
|
||||
/// <param name="folders"></param>
|
||||
/// <param name="libraryName"></param>
|
||||
/// <param name="isLibraryScan">If true, does a directory scan first (resulting in folders being tackled in parallel), else does an immediate scan files</param>
|
||||
/// <param name="seriesPaths">A map of Series names -> existing folder paths to handle skipping folders</param>
|
||||
/// <param name="processSeriesInfos">Action which returns if the folder was skipped and the infos from said folder</param>
|
||||
/// <param name="forceCheck">Defaults to false</param>
|
||||
/// <returns></returns>
|
||||
public async Task ScanLibrariesForSeries(LibraryType libraryType,
|
||||
IEnumerable<string> folders, string libraryName, bool isLibraryScan,
|
||||
|
|
|
@ -1029,7 +1029,7 @@ public static class Parser
|
|||
{
|
||||
try
|
||||
{
|
||||
if (!Regex.IsMatch(range, @"^[\d-.]+$"))
|
||||
if (!Regex.IsMatch(range, @"^[\d\-.]+$"))
|
||||
{
|
||||
return (float) 0.0;
|
||||
}
|
||||
|
@ -1047,7 +1047,7 @@ public static class Parser
|
|||
{
|
||||
try
|
||||
{
|
||||
if (!Regex.IsMatch(range, @"^[\d-.]+$"))
|
||||
if (!Regex.IsMatch(range, @"^[\d\-.]+$"))
|
||||
{
|
||||
return (float) 0.0;
|
||||
}
|
||||
|
|
|
@ -210,13 +210,13 @@ public class ProcessSeries : IProcessSeries
|
|||
if (!library.Folders.Select(f => f.Path).Contains(seriesDirs.Keys.First()))
|
||||
{
|
||||
series.FolderPath = Parser.Parser.NormalizePath(seriesDirs.Keys.First());
|
||||
_logger.LogDebug("Updating {Series} FolderPath to {FolderPath}", series.Name, series.FolderPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EnqueuePostSeriesProcessTasks(int libraryId, int seriesId, bool forceUpdate = false)
|
||||
{
|
||||
//BackgroundJob.Enqueue(() => _metadataService.GenerateCoversForSeries(libraryId, seriesId, forceUpdate));
|
||||
BackgroundJob.Enqueue(() => _wordCountAnalyzerService.ScanSeries(libraryId, seriesId, forceUpdate));
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ public interface IScannerService
|
|||
/// cover images if forceUpdate is true.
|
||||
/// </summary>
|
||||
/// <param name="libraryId">Library to scan against</param>
|
||||
/// <param name="forceUpdate">Don't perform optimization checks, defaults to false</param>
|
||||
[Queue(TaskScheduler.ScanQueue)]
|
||||
[DisableConcurrentExecution(60 * 60 * 60)]
|
||||
[AutomaticRetry(Attempts = 0, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
|
||||
|
@ -396,6 +397,7 @@ public class ScannerService : IScannerService
|
|||
/// ie) all entities will be rechecked for new cover images and comicInfo.xml changes
|
||||
/// </summary>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <param name="forceUpdate">Defaults to false</param>
|
||||
[Queue(TaskScheduler.ScanQueue)]
|
||||
[DisableConcurrentExecution(60 * 60 * 60)]
|
||||
[AutomaticRetry(Attempts = 0, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
|
||||
|
@ -484,7 +486,7 @@ public class ScannerService : IScannerService
|
|||
{
|
||||
_logger.LogInformation(
|
||||
"[ScannerService] Finished library scan of {ParsedSeriesCount} series in {ElapsedScanTime} milliseconds for {LibraryName}. There were no changes",
|
||||
totalFiles, seenSeries.Count, sw.ElapsedMilliseconds, library.Name);
|
||||
seenSeries.Count, sw.ElapsedMilliseconds, library.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue