mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-08-07 09:01:25 -04:00
commit
4052306c97
@ -49,6 +49,7 @@ namespace API.Tests
|
|||||||
[InlineData("Kedouin Makoto - Corpse Party Musume, Chapter 12", "0")]
|
[InlineData("Kedouin Makoto - Corpse Party Musume, Chapter 12", "0")]
|
||||||
[InlineData("VanDread-v01-c001[MD].zip", "1")]
|
[InlineData("VanDread-v01-c001[MD].zip", "1")]
|
||||||
[InlineData("Ichiban_Ushiro_no_Daimaou_v04_ch27_[VISCANS].zip", "4")]
|
[InlineData("Ichiban_Ushiro_no_Daimaou_v04_ch27_[VISCANS].zip", "4")]
|
||||||
|
[InlineData("Mob Psycho 100 v02 (2019) (Digital) (Shizu).cbz", "2")]
|
||||||
public void ParseVolumeTest(string filename, string expected)
|
public void ParseVolumeTest(string filename, string expected)
|
||||||
{
|
{
|
||||||
Assert.Equal(expected, ParseVolume(filename));
|
Assert.Equal(expected, ParseVolume(filename));
|
||||||
@ -130,6 +131,7 @@ namespace API.Tests
|
|||||||
[InlineData("Yumekui_Merry_v01_c01[Bakayarou-Kuu].rar", "1")]
|
[InlineData("Yumekui_Merry_v01_c01[Bakayarou-Kuu].rar", "1")]
|
||||||
[InlineData("Yumekui-Merry_DKThias_Chapter11v2.zip", "11")]
|
[InlineData("Yumekui-Merry_DKThias_Chapter11v2.zip", "11")]
|
||||||
[InlineData("Yumekui-Merry DKThiasScanlations Chapter51v2", "51")]
|
[InlineData("Yumekui-Merry DKThiasScanlations Chapter51v2", "51")]
|
||||||
|
[InlineData("Yumekui-Merry_DKThiasScanlations&RenzokuseiScans_Chapter61", "61")]
|
||||||
[InlineData("Goblin Slayer Side Story - Year One 017.5", "17.5")]
|
[InlineData("Goblin Slayer Side Story - Year One 017.5", "17.5")]
|
||||||
[InlineData("Beelzebub_53[KSH].zip", "53")]
|
[InlineData("Beelzebub_53[KSH].zip", "53")]
|
||||||
[InlineData("Black Bullet - v4 c20.5 [batoto]", "20.5")]
|
[InlineData("Black Bullet - v4 c20.5 [batoto]", "20.5")]
|
||||||
@ -139,7 +141,6 @@ namespace API.Tests
|
|||||||
[InlineData("Vol 1", "0")]
|
[InlineData("Vol 1", "0")]
|
||||||
[InlineData("VanDread-v01-c001[MD].zip", "1")]
|
[InlineData("VanDread-v01-c001[MD].zip", "1")]
|
||||||
[InlineData("Goblin Slayer Side Story - Year One 025.5", "25.5")]
|
[InlineData("Goblin Slayer Side Story - Year One 025.5", "25.5")]
|
||||||
//[InlineData("[Tempus Edax Rerum] Epigraph of the Closed Curve - Chapter 6.zip", "6")]
|
|
||||||
public void ParseChaptersTest(string filename, string expected)
|
public void ParseChaptersTest(string filename, string expected)
|
||||||
{
|
{
|
||||||
Assert.Equal(expected, ParseChapter(filename));
|
Assert.Equal(expected, ParseChapter(filename));
|
||||||
@ -303,9 +304,6 @@ namespace API.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
foreach (var file in expected.Keys)
|
foreach (var file in expected.Keys)
|
||||||
{
|
{
|
||||||
var expectedInfo = expected[file];
|
var expectedInfo = expected[file];
|
||||||
|
@ -6,12 +6,17 @@
|
|||||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
|
<DebugSymbols>false</DebugSymbols>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.0" />
|
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.0" />
|
||||||
<PackageReference Include="Hangfire" Version="1.7.18" />
|
<PackageReference Include="Hangfire" Version="1.7.18" />
|
||||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.18" />
|
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.18" />
|
||||||
<PackageReference Include="Hangfire.LiteDB" Version="0.4.0" />
|
<PackageReference Include="Hangfire.LiteDB" Version="0.4.0" />
|
||||||
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
|
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
|
||||||
|
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.1" NoWarn="NU1605" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.1" NoWarn="NU1605" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.1" NoWarn="NU1605" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.1" NoWarn="NU1605" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.1" />
|
||||||
|
@ -2,15 +2,26 @@
|
|||||||
|
|
||||||
namespace API.Comparators
|
namespace API.Comparators
|
||||||
{
|
{
|
||||||
public class ChapterSortComparer : IComparer<int>
|
public class ChapterSortComparer : IComparer<float>
|
||||||
{
|
{
|
||||||
public int Compare(int x, int y)
|
// public int Compare(int x, int y)
|
||||||
|
// {
|
||||||
|
// if (x == 0 && y == 0) return 0;
|
||||||
|
// // if x is 0, it comes second
|
||||||
|
// if (x == 0) return 1;
|
||||||
|
// // if y is 0, it comes second
|
||||||
|
// if (y == 0) return -1;
|
||||||
|
//
|
||||||
|
// return x.CompareTo(y);
|
||||||
|
// }
|
||||||
|
|
||||||
|
public int Compare(float x, float y)
|
||||||
{
|
{
|
||||||
if (x == 0 && y == 0) return 0;
|
if (x == 0.0 && y == 0.0) return 0;
|
||||||
// if x is 0, it comes second
|
// if x is 0, it comes second
|
||||||
if (x == 0) return 1;
|
if (x == 0.0) return 1;
|
||||||
// if y is 0, it comes second
|
// if y is 0, it comes second
|
||||||
if (y == 0) return -1;
|
if (y == 0.0) return -1;
|
||||||
|
|
||||||
return x.CompareTo(y);
|
return x.CompareTo(y);
|
||||||
}
|
}
|
||||||
|
@ -126,5 +126,29 @@ namespace API.Controllers
|
|||||||
|
|
||||||
return BadRequest("There was an error with updating the series");
|
return BadRequest("There was an error with updating the series");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("recently-added")]
|
||||||
|
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetRecentlyAdded(int libraryId = 0, int limit = 20)
|
||||||
|
{
|
||||||
|
return Ok(await _unitOfWork.SeriesRepository.GetRecentlyAdded(libraryId, limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("in-progress")]
|
||||||
|
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetInProgress(int libraryId = 0, int limit = 20)
|
||||||
|
{
|
||||||
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||||
|
return Ok(await _unitOfWork.SeriesRepository.GetInProgress(user.Id, libraryId, limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("continue-reading")]
|
||||||
|
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetContinueReading(int libraryId = 0, int limit = 20)
|
||||||
|
{
|
||||||
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||||
|
return Ok(await _unitOfWork.VolumeRepository.GetContinueReading(user.Id, libraryId, limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
23
API/DTOs/InProgressChapterDto.cs
Normal file
23
API/DTOs/InProgressChapterDto.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
namespace API.DTOs
|
||||||
|
{
|
||||||
|
public class InProgressChapterDto
|
||||||
|
{
|
||||||
|
public int Id { get; init; }
|
||||||
|
/// <summary>
|
||||||
|
/// Range of chapters. Chapter 2-4 -> "2-4". Chapter 2 -> "2".
|
||||||
|
/// </summary>
|
||||||
|
public string Range { get; init; }
|
||||||
|
/// <summary>
|
||||||
|
/// Smallest number of the Range.
|
||||||
|
/// </summary>
|
||||||
|
public string Number { get; init; }
|
||||||
|
/// <summary>
|
||||||
|
/// Total number of pages in all MangaFiles
|
||||||
|
/// </summary>
|
||||||
|
public int Pages { get; init; }
|
||||||
|
public int SeriesId { get; init; }
|
||||||
|
public int LibraryId { get; init; }
|
||||||
|
public string SeriesName { get; init; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -21,5 +21,7 @@
|
|||||||
/// Review from logged in user. Calculated at API-time.
|
/// Review from logged in user. Calculated at API-time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string UserReview { get; set; }
|
public string UserReview { get; set; }
|
||||||
|
|
||||||
|
public int LibraryId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -112,5 +113,7 @@ namespace API.Data
|
|||||||
.Include(l => l.Folders)
|
.Include(l => l.Folders)
|
||||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider).ToListAsync();
|
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider).ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,23 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
namespace API.Data.Migrations
|
|
||||||
{
|
|
||||||
public partial class SearchIndex : Migration
|
|
||||||
{
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_Series_Name_NormalizedName_LocalizedName",
|
|
||||||
table: "Series",
|
|
||||||
columns: new[] { "Name", "NormalizedName", "LocalizedName" },
|
|
||||||
unique: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropIndex(
|
|
||||||
name: "IX_Series_Name_NormalizedName_LocalizedName",
|
|
||||||
table: "Series");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,8 +9,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||||||
namespace API.Data.Migrations
|
namespace API.Data.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(DataContext))]
|
[DbContext(typeof(DataContext))]
|
||||||
[Migration("20210313001830_SearchIndex")]
|
[Migration("20210315134028_SearchIndexAndProgressDates")]
|
||||||
partial class SearchIndex
|
partial class SearchIndexAndProgressDates
|
||||||
{
|
{
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@ -161,6 +161,12 @@ namespace API.Data.Migrations
|
|||||||
b.Property<int>("ChapterId")
|
b.Property<int>("ChapterId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastModified")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<int>("PagesRead")
|
b.Property<int>("PagesRead")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
@ -367,7 +373,7 @@ namespace API.Data.Migrations
|
|||||||
|
|
||||||
b.HasIndex("LibraryId");
|
b.HasIndex("LibraryId");
|
||||||
|
|
||||||
b.HasIndex("Name", "NormalizedName", "LocalizedName")
|
b.HasIndex("Name", "NormalizedName", "LocalizedName", "LibraryId")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("Series");
|
b.ToTable("Series");
|
@ -0,0 +1,46 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace API.Data.Migrations
|
||||||
|
{
|
||||||
|
public partial class SearchIndexAndProgressDates : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<DateTime>(
|
||||||
|
name: "Created",
|
||||||
|
table: "AppUserProgresses",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<DateTime>(
|
||||||
|
name: "LastModified",
|
||||||
|
table: "AppUserProgresses",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Series_Name_NormalizedName_LocalizedName_LibraryId",
|
||||||
|
table: "Series",
|
||||||
|
columns: new[] { "Name", "NormalizedName", "LocalizedName", "LibraryId" },
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Series_Name_NormalizedName_LocalizedName_LibraryId",
|
||||||
|
table: "Series");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Created",
|
||||||
|
table: "AppUserProgresses");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "LastModified",
|
||||||
|
table: "AppUserProgresses");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -159,6 +159,12 @@ namespace API.Data.Migrations
|
|||||||
b.Property<int>("ChapterId")
|
b.Property<int>("ChapterId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastModified")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<int>("PagesRead")
|
b.Property<int>("PagesRead")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
@ -365,7 +371,7 @@ namespace API.Data.Migrations
|
|||||||
|
|
||||||
b.HasIndex("LibraryId");
|
b.HasIndex("LibraryId");
|
||||||
|
|
||||||
b.HasIndex("Name", "NormalizedName", "LocalizedName")
|
b.HasIndex("Name", "NormalizedName", "LocalizedName", "LibraryId")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("Series");
|
b.ToTable("Series");
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -9,6 +11,7 @@ using API.Interfaces;
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using AutoMapper.QueryableExtensions;
|
using AutoMapper.QueryableExtensions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace API.Data
|
namespace API.Data
|
||||||
@ -275,5 +278,54 @@ namespace API.Data
|
|||||||
v.PagesRead = userProgress.Where(p => p.VolumeId == v.Id).Sum(p => p.PagesRead);
|
v.PagesRead = userProgress.Where(p => p.VolumeId == v.Id).Sum(p => p.PagesRead);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a list of Series that were added, ordered by Created desc
|
||||||
|
/// </summary>
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
return await _context.Series
|
||||||
|
.Where(s => (libraryId <= 0 || s.LibraryId == libraryId))
|
||||||
|
.Take(limit)
|
||||||
|
.OrderByDescending(s => s.Created)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <param name="libraryId"></param>
|
||||||
|
/// <param name="limit"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<IEnumerable<SeriesDto>> GetInProgress(int userId, int libraryId, int limit)
|
||||||
|
{
|
||||||
|
//var twoWeeksAgo = DateTime.Today.Subtract(TimeSpan.FromDays(14)); // TODO: Think about moving this to a setting
|
||||||
|
var series = await _context.Series
|
||||||
|
.Join(_context.AppUserProgresses, s => s.Id, progress => progress.SeriesId, (s, progress) => new
|
||||||
|
{
|
||||||
|
Series = s,
|
||||||
|
progress.PagesRead,
|
||||||
|
progress.AppUserId,
|
||||||
|
progress.LastModified
|
||||||
|
})
|
||||||
|
.Where(s => s.AppUserId == userId
|
||||||
|
&& s.PagesRead > 0
|
||||||
|
&& s.PagesRead < s.Series.Pages
|
||||||
|
&& (libraryId <= 0 || s.Series.LibraryId == libraryId) )
|
||||||
|
.Take(limit)
|
||||||
|
.OrderByDescending(s => s.LastModified)
|
||||||
|
.AsNoTracking()
|
||||||
|
.Select(s => s.Series)
|
||||||
|
.Distinct()
|
||||||
|
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||||
|
.ToListAsync();
|
||||||
|
return series;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,21 +12,21 @@ namespace API.Data
|
|||||||
private readonly DataContext _context;
|
private readonly DataContext _context;
|
||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
private readonly UserManager<AppUser> _userManager;
|
private readonly UserManager<AppUser> _userManager;
|
||||||
private readonly ILogger<UnitOfWork> _seriesLogger;
|
private readonly ILogger<UnitOfWork> _logger;
|
||||||
|
|
||||||
public UnitOfWork(DataContext context, IMapper mapper, UserManager<AppUser> userManager, ILogger<UnitOfWork> seriesLogger)
|
public UnitOfWork(DataContext context, IMapper mapper, UserManager<AppUser> userManager, ILogger<UnitOfWork> logger)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_seriesLogger = seriesLogger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISeriesRepository SeriesRepository => new SeriesRepository(_context, _mapper, _seriesLogger);
|
public ISeriesRepository SeriesRepository => new SeriesRepository(_context, _mapper, _logger);
|
||||||
public IUserRepository UserRepository => new UserRepository(_context, _userManager);
|
public IUserRepository UserRepository => new UserRepository(_context, _userManager);
|
||||||
public ILibraryRepository LibraryRepository => new LibraryRepository(_context, _mapper);
|
public ILibraryRepository LibraryRepository => new LibraryRepository(_context, _mapper);
|
||||||
|
|
||||||
public IVolumeRepository VolumeRepository => new VolumeRepository(_context, _mapper);
|
public IVolumeRepository VolumeRepository => new VolumeRepository(_context, _mapper, _logger);
|
||||||
|
|
||||||
public ISettingsRepository SettingsRepository => new SettingsRepository(_context, _mapper);
|
public ISettingsRepository SettingsRepository => new SettingsRepository(_context, _mapper);
|
||||||
|
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using API.Comparators;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
|
using API.Extensions;
|
||||||
using API.Interfaces;
|
using API.Interfaces;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using AutoMapper.QueryableExtensions;
|
using AutoMapper.QueryableExtensions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace API.Data
|
namespace API.Data
|
||||||
{
|
{
|
||||||
@ -14,11 +17,13 @@ namespace API.Data
|
|||||||
{
|
{
|
||||||
private readonly DataContext _context;
|
private readonly DataContext _context;
|
||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public VolumeRepository(DataContext context, IMapper mapper)
|
public VolumeRepository(DataContext context, IMapper mapper, ILogger logger)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(Volume volume)
|
public void Update(Volume volume)
|
||||||
@ -84,5 +89,53 @@ namespace API.Data
|
|||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the first (ordered) volume/chapter in a series where the user has progress on it. Only completed volumes/chapters, next entity shouldn't
|
||||||
|
/// have any read progress on it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <param name="libraryId"></param>
|
||||||
|
/// <param name="limit"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<IEnumerable<InProgressChapterDto>> GetContinueReading(int userId, int libraryId, int limit)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Get Continue Reading");
|
||||||
|
var chapters = await _context.Chapter
|
||||||
|
.Join(_context.AppUserProgresses, c => c.Id, p => p.ChapterId,
|
||||||
|
(chapter, progress) =>
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Chapter = chapter,
|
||||||
|
Progress = progress
|
||||||
|
})
|
||||||
|
.Join(_context.Series, arg => arg.Progress.SeriesId, series => series.Id, (arg, series) =>
|
||||||
|
new
|
||||||
|
{
|
||||||
|
arg.Chapter,
|
||||||
|
arg.Progress,
|
||||||
|
Series = series
|
||||||
|
})
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(arg => arg.Progress.AppUserId == userId
|
||||||
|
&& arg.Progress.PagesRead < arg.Chapter.Pages)
|
||||||
|
.OrderByDescending(d => d.Progress.LastModified)
|
||||||
|
.Take(limit)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return chapters
|
||||||
|
.OrderBy(c => float.Parse(c.Chapter.Number), new ChapterSortComparer())
|
||||||
|
.DistinctBy(p => p.Series.Id)
|
||||||
|
.Select(arg => new InProgressChapterDto()
|
||||||
|
{
|
||||||
|
Id = arg.Chapter.Id,
|
||||||
|
Number = arg.Chapter.Number,
|
||||||
|
Range = arg.Chapter.Range,
|
||||||
|
SeriesId = arg.Progress.SeriesId,
|
||||||
|
SeriesName = arg.Series.Name,
|
||||||
|
LibraryId = arg.Series.LibraryId,
|
||||||
|
Pages = arg.Chapter.Pages,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,20 +1,25 @@
|
|||||||
|
|
||||||
|
using System;
|
||||||
|
using API.Entities.Interfaces;
|
||||||
|
|
||||||
namespace API.Entities
|
namespace API.Entities
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the progress a single user has on a given Volume. Progress is realistically tracked against the Volume's chapters.
|
/// Represents the progress a single user has on a given Volume. Progress is realistically tracked against the Volume's chapters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AppUserProgress
|
public class AppUserProgress : IEntityDate
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int PagesRead { get; set; }
|
public int PagesRead { get; set; }
|
||||||
public int VolumeId { get; set; }
|
public int VolumeId { get; set; }
|
||||||
public int SeriesId { get; set; }
|
public int SeriesId { get; set; }
|
||||||
|
|
||||||
public int ChapterId { get; set; }
|
public int ChapterId { get; set; }
|
||||||
|
|
||||||
// Relationships
|
// Relationships
|
||||||
public AppUser AppUser { get; set; }
|
public AppUser AppUser { get; set; }
|
||||||
public int AppUserId { get; set; }
|
public int AppUserId { get; set; }
|
||||||
|
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
public DateTime LastModified { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
|
|
||||||
namespace API.Entities
|
namespace API.Entities
|
||||||
{
|
{
|
||||||
[Index(nameof(Name), nameof(NormalizedName), nameof(LocalizedName), IsUnique = true)]
|
[Index(nameof(Name), nameof(NormalizedName), nameof(LocalizedName), nameof(LibraryId), IsUnique = true)]
|
||||||
public class Series : IEntityDate
|
public class Series : IEntityDate
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
@ -36,7 +36,6 @@ namespace API.Entities
|
|||||||
public DateTime Created { get; set; }
|
public DateTime Created { get; set; }
|
||||||
public DateTime LastModified { get; set; }
|
public DateTime LastModified { get; set; }
|
||||||
public byte[] CoverImage { get; set; }
|
public byte[] CoverImage { get; set; }
|
||||||
// NOTE: Do I want to store a thumbImage for search results?
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sum of all Volume page counts
|
/// Sum of all Volume page counts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -7,6 +7,7 @@ using API.Services.Tasks;
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
using Hangfire.LiteDB;
|
using Hangfire.LiteDB;
|
||||||
|
using Hangfire.MemoryStorage;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@ -46,14 +47,6 @@ namespace API.Extensions
|
|||||||
loggingBuilder.AddFile(loggingSection);
|
loggingBuilder.AddFile(loggingSection);
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddHangfire(configuration => configuration
|
|
||||||
.UseSimpleAssemblyNameTypeSerializer()
|
|
||||||
.UseRecommendedSerializerSettings()
|
|
||||||
.UseLiteDbStorage());
|
|
||||||
|
|
||||||
// Add the processing server as IHostedService
|
|
||||||
services.AddHangfireServer();
|
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
API/Extensions/EnumerableExtensions.cs
Normal file
21
API/Extensions/EnumerableExtensions.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace API.Extensions
|
||||||
|
{
|
||||||
|
public static class EnumerableExtensions
|
||||||
|
{
|
||||||
|
public static IEnumerable<TSource> DistinctBy<TSource, TKey>
|
||||||
|
(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
|
||||||
|
{
|
||||||
|
var seenKeys = new HashSet<TKey>();
|
||||||
|
foreach (var element in source)
|
||||||
|
{
|
||||||
|
if (seenKeys.Add(keySelector(element)))
|
||||||
|
{
|
||||||
|
yield return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
API/Extensions/ServiceCollectionExtensions.cs
Normal file
12
API/Extensions/ServiceCollectionExtensions.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using API.Interfaces.Services;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace API.Extensions
|
||||||
|
{
|
||||||
|
public static class ServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddStartupTask<T>(this IServiceCollection services)
|
||||||
|
where T : class, IStartupTask
|
||||||
|
=> services.AddTransient<IStartupTask, T>();
|
||||||
|
}
|
||||||
|
}
|
@ -55,5 +55,7 @@ namespace API.Interfaces
|
|||||||
|
|
||||||
Task<byte[]> GetVolumeCoverImageAsync(int volumeId);
|
Task<byte[]> GetVolumeCoverImageAsync(int volumeId);
|
||||||
Task<byte[]> GetSeriesCoverImageAsync(int seriesId);
|
Task<byte[]> GetSeriesCoverImageAsync(int seriesId);
|
||||||
|
Task<IEnumerable<SeriesDto>> GetInProgress(int userId, int libraryId, int limit);
|
||||||
|
Task<IEnumerable<SeriesDto>> GetRecentlyAdded(int libraryId, int limit);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,5 +13,6 @@ namespace API.Interfaces
|
|||||||
Task<IList<MangaFile>> GetFilesForChapter(int chapterId);
|
Task<IList<MangaFile>> GetFilesForChapter(int chapterId);
|
||||||
Task<IList<Chapter>> GetChaptersAsync(int volumeId);
|
Task<IList<Chapter>> GetChaptersAsync(int volumeId);
|
||||||
Task<byte[]> GetChapterCoverImageAsync(int chapterId);
|
Task<byte[]> GetChapterCoverImageAsync(int chapterId);
|
||||||
|
Task<IEnumerable<InProgressChapterDto>> GetContinueReading(int userId, int libraryId, int limit);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -25,6 +25,7 @@ namespace API.Middleware
|
|||||||
|
|
||||||
public async Task InvokeAsync(HttpContext context)
|
public async Task InvokeAsync(HttpContext context)
|
||||||
{
|
{
|
||||||
|
// BUG: I think Hangfire timeouts are triggering the middleware to hijack an API call
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _next(context); // downstream middlewares or http call
|
await _next(context); // downstream middlewares or http call
|
||||||
|
@ -124,9 +124,10 @@ namespace API.Parser
|
|||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
// [Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip
|
// [Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip
|
||||||
new Regex(
|
new Regex(
|
||||||
|
|
||||||
@"v\d+\.(?<Chapter>\d+(?:.\d+|-\d+)?)",
|
@"v\d+\.(?<Chapter>\d+(?:.\d+|-\d+)?)",
|
||||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
|
// Mob Psycho 100
|
||||||
|
|
||||||
// Hinowa ga CRUSH! 018 (2019) (Digital) (LuCaZ).cbz, Hinowa ga CRUSH! 018.5 (2019) (Digital) (LuCaZ).cbz
|
// Hinowa ga CRUSH! 018 (2019) (Digital) (LuCaZ).cbz, Hinowa ga CRUSH! 018.5 (2019) (Digital) (LuCaZ).cbz
|
||||||
new Regex(
|
new Regex(
|
||||||
@"^(?!Vol)(?<Series>.*) (?<Chapter>\d+(?:.\d+|-\d+)?)(?: \(\d{4}\))?",
|
@"^(?!Vol)(?<Series>.*) (?<Chapter>\d+(?:.\d+|-\d+)?)(?: \(\d{4}\))?",
|
||||||
|
@ -3,6 +3,8 @@ using System.Threading.Tasks;
|
|||||||
using API.Data;
|
using API.Data;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Interfaces;
|
using API.Interfaces;
|
||||||
|
using API.Interfaces.Services;
|
||||||
|
using API.Services;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -41,6 +43,12 @@ namespace API
|
|||||||
logger.LogError(ex, "An error occurred during migration");
|
logger.LogError(ex, "An error occurred during migration");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load all tasks from DI and initialize them (TODO: This is not working - WarmupServicesStartupTask is Null)
|
||||||
|
var startupTasks = host.Services.GetServices<WarmupServicesStartupTask>();
|
||||||
|
foreach (var startupTask in startupTasks)
|
||||||
|
{
|
||||||
|
await startupTask.ExecuteAsync();
|
||||||
|
}
|
||||||
|
|
||||||
await host.RunAsync();
|
await host.RunAsync();
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,7 @@ namespace API.Services
|
|||||||
return chapter;
|
return chapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Performing cleanup of Cache directory");
|
_logger.LogInformation("Performing cleanup of Cache directory");
|
||||||
|
@ -6,6 +6,8 @@ using API.Helpers.Converters;
|
|||||||
using API.Interfaces;
|
using API.Interfaces;
|
||||||
using API.Interfaces.Services;
|
using API.Interfaces.Services;
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace API.Services
|
namespace API.Services
|
||||||
@ -21,15 +23,15 @@ namespace API.Services
|
|||||||
private readonly ICleanupService _cleanupService;
|
private readonly ICleanupService _cleanupService;
|
||||||
private readonly IDirectoryService _directoryService;
|
private readonly IDirectoryService _directoryService;
|
||||||
|
|
||||||
public BackgroundJobServer Client => new BackgroundJobServer();
|
public static BackgroundJobServer Client => new BackgroundJobServer(new BackgroundJobServerOptions()
|
||||||
// new BackgroundJobServerOptions()
|
{
|
||||||
// {
|
WorkerCount = 1
|
||||||
// WorkerCount = 1
|
});
|
||||||
// }
|
|
||||||
|
|
||||||
public TaskScheduler(ICacheService cacheService, ILogger<TaskScheduler> logger, IScannerService scannerService,
|
public TaskScheduler(ICacheService cacheService, ILogger<TaskScheduler> logger, IScannerService scannerService,
|
||||||
IUnitOfWork unitOfWork, IMetadataService metadataService, IBackupService backupService, ICleanupService cleanupService,
|
IUnitOfWork unitOfWork, IMetadataService metadataService, IBackupService backupService, ICleanupService cleanupService,
|
||||||
IDirectoryService directoryService)
|
IDirectoryService directoryService, IWebHostEnvironment env)
|
||||||
{
|
{
|
||||||
_cacheService = cacheService;
|
_cacheService = cacheService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -40,15 +42,23 @@ namespace API.Services
|
|||||||
_cleanupService = cleanupService;
|
_cleanupService = cleanupService;
|
||||||
_directoryService = directoryService;
|
_directoryService = directoryService;
|
||||||
|
|
||||||
//Hangfire.RecurringJob.RemoveIfExists();
|
if (!env.IsDevelopment())
|
||||||
ScheduleTasks();
|
{
|
||||||
//JobStorage.Current.GetMonitoringApi().EnqueuedJobs()
|
ScheduleTasks();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RecurringJob.RemoveIfExists("scan-libraries");
|
||||||
|
RecurringJob.RemoveIfExists("backup");
|
||||||
|
RecurringJob.RemoveIfExists("cleanup");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ScheduleTasks()
|
public void ScheduleTasks()
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Scheduling reoccurring tasks");
|
_logger.LogInformation("Scheduling reoccurring tasks");
|
||||||
|
|
||||||
string setting = null;
|
string setting = null;
|
||||||
setting = Task.Run(() => _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskScan)).Result.Value;
|
setting = Task.Run(() => _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.TaskScan)).Result.Value;
|
||||||
if (setting != null)
|
if (setting != null)
|
||||||
@ -80,8 +90,7 @@ namespace API.Services
|
|||||||
|
|
||||||
_logger.LogInformation("Enqueuing library scan for: {LibraryId}", libraryId);
|
_logger.LogInformation("Enqueuing library scan for: {LibraryId}", libraryId);
|
||||||
BackgroundJob.Enqueue(() => _scannerService.ScanLibrary(libraryId, forceUpdate));
|
BackgroundJob.Enqueue(() => _scannerService.ScanLibrary(libraryId, forceUpdate));
|
||||||
//BackgroundJob.Enqueue(() => _cleanupService.Cleanup()); // When we do a scan, force cache to re-unpack in case page numbers change
|
BackgroundJob.Enqueue(() => _cleanupService.Cleanup()); // When we do a scan, force cache to re-unpack in case page numbers change
|
||||||
RecurringJob.Trigger("cleanup"); // TODO: Alternate way to trigger jobs. Test this out and see if we should switch.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CleanupChapters(int[] chapterIds)
|
public void CleanupChapters(int[] chapterIds)
|
||||||
|
@ -3,11 +3,13 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Interfaces;
|
using API.Interfaces;
|
||||||
using API.Interfaces.Services;
|
using API.Interfaces.Services;
|
||||||
|
using Hangfire;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@ -57,6 +59,7 @@ namespace API.Services.Tasks
|
|||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AutomaticRetry(Attempts = 3, LogEvents = false, OnAttemptsExceeded = AttemptsExceededAction.Fail)]
|
||||||
public void BackupDatabase()
|
public void BackupDatabase()
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Beginning backup of Database at {BackupTime}", DateTime.Now);
|
_logger.LogInformation("Beginning backup of Database at {BackupTime}", DateTime.Now);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using API.Interfaces.Services;
|
using API.Interfaces.Services;
|
||||||
|
using Hangfire;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace API.Services.Tasks
|
namespace API.Services.Tasks
|
||||||
@ -20,6 +21,7 @@ namespace API.Services.Tasks
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[AutomaticRetry(Attempts = 3, LogEvents = false, OnAttemptsExceeded = AttemptsExceededAction.Fail)]
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Cleaning temp directory");
|
_logger.LogInformation("Cleaning temp directory");
|
||||||
|
@ -33,7 +33,8 @@ namespace API.Services.Tasks
|
|||||||
_metadataService = metadataService;
|
_metadataService = metadataService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DisableConcurrentExecution(timeoutInSeconds: 120)]
|
//[DisableConcurrentExecution(timeoutInSeconds: 5)]
|
||||||
|
[AutomaticRetry(Attempts = 0, LogEvents = false, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
|
||||||
public void ScanLibraries()
|
public void ScanLibraries()
|
||||||
{
|
{
|
||||||
var libraries = Task.Run(() => _unitOfWork.LibraryRepository.GetLibrariesAsync()).Result.ToList();
|
var libraries = Task.Run(() => _unitOfWork.LibraryRepository.GetLibrariesAsync()).Result.ToList();
|
||||||
@ -63,7 +64,7 @@ namespace API.Services.Tasks
|
|||||||
_scannedSeries = null;
|
_scannedSeries = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DisableConcurrentExecution(5)]
|
//[DisableConcurrentExecution(5)]
|
||||||
[AutomaticRetry(Attempts = 0, LogEvents = false, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
|
[AutomaticRetry(Attempts = 0, LogEvents = false, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
|
||||||
public void ScanLibrary(int libraryId, bool forceUpdate)
|
public void ScanLibrary(int libraryId, bool forceUpdate)
|
||||||
{
|
{
|
||||||
|
@ -18,14 +18,13 @@ namespace API.Services
|
|||||||
_provider = provider;
|
_provider = provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ExecuteAsync(CancellationToken cancellationToken)
|
public Task ExecuteAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
using (var scope = _provider.CreateScope())
|
using var scope = _provider.CreateScope();
|
||||||
|
foreach (var singleton in GetServices(_services))
|
||||||
{
|
{
|
||||||
foreach (var singleton in GetServices(_services))
|
Console.WriteLine("DI preloading of " + singleton.FullName);
|
||||||
{
|
scope.ServiceProvider.GetServices(singleton);
|
||||||
scope.ServiceProvider.GetServices(singleton);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using API.Data;
|
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
|
using API.Interfaces.Services;
|
||||||
using API.Middleware;
|
using API.Middleware;
|
||||||
using API.Services;
|
using API.Services;
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
|
using Hangfire.LiteDB;
|
||||||
|
using Hangfire.MemoryStorage;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
@ -16,7 +18,6 @@ using Microsoft.Extensions.Configuration;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
|
|
||||||
namespace API
|
namespace API
|
||||||
@ -24,10 +25,12 @@ namespace API
|
|||||||
public class Startup
|
public class Startup
|
||||||
{
|
{
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
|
private readonly IWebHostEnvironment _env;
|
||||||
|
|
||||||
public Startup(IConfiguration config)
|
public Startup(IConfiguration config, IWebHostEnvironment env)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_env = env;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to add services to the container.
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
@ -62,15 +65,30 @@ namespace API
|
|||||||
|
|
||||||
services.AddResponseCaching();
|
services.AddResponseCaching();
|
||||||
|
|
||||||
|
if (_env.IsDevelopment())
|
||||||
|
{
|
||||||
|
services.AddHangfire(configuration => configuration
|
||||||
|
.UseSimpleAssemblyNameTypeSerializer()
|
||||||
|
.UseRecommendedSerializerSettings()
|
||||||
|
.UseMemoryStorage());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
services.AddHangfire(configuration => configuration
|
||||||
|
.UseSimpleAssemblyNameTypeSerializer()
|
||||||
|
.UseRecommendedSerializerSettings()
|
||||||
|
.UseLiteDbStorage());
|
||||||
|
}
|
||||||
|
|
||||||
services
|
// Add the processing server as IHostedService
|
||||||
.AddStartupTask<WarmupServicesStartupTask>()
|
services.AddHangfireServer();
|
||||||
.TryAddSingleton(services);
|
|
||||||
|
|
||||||
|
//services.AddStartupTask<WarmupServicesStartupTask>(services).
|
||||||
|
services.AddTransient<IStartupTask, WarmupServicesStartupTask>().TryAddSingleton(services);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobs, IWebHostEnvironment env)
|
public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobs, IWebHostEnvironment env, IHostApplicationLifetime applicationLifetime)
|
||||||
{
|
{
|
||||||
app.UseMiddleware<ExceptionMiddleware>();
|
app.UseMiddleware<ExceptionMiddleware>();
|
||||||
|
|
||||||
@ -125,6 +143,20 @@ namespace API
|
|||||||
endpoints.MapHangfireDashboard();
|
endpoints.MapHangfireDashboard();
|
||||||
endpoints.MapFallbackToController("Index", "Fallback");
|
endpoints.MapFallbackToController("Index", "Fallback");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
applicationLifetime.ApplicationStopping.Register(OnShutdown);
|
||||||
|
applicationLifetime.ApplicationStarted.Register(() =>
|
||||||
|
{
|
||||||
|
Console.WriteLine("Kavita - v0.3");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnShutdown()
|
||||||
|
{
|
||||||
|
Console.WriteLine("Server is shutting down. Going to dispose Hangfire");
|
||||||
|
//this code is called when the application stops
|
||||||
|
TaskScheduler.Client.Dispose();
|
||||||
|
System.Threading.Thread.Sleep(1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user