Allow requests to be cancelled.

Make workers have a CancellationTokenSource
This commit is contained in:
2025-09-01 23:26:49 +02:00
parent 6c61869e66
commit 3b8570cf57
31 changed files with 296 additions and 251 deletions

View File

@@ -1,6 +1,7 @@
using API.Schema.MangaContext;
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using static Microsoft.AspNetCore.Http.StatusCodes;
// ReSharper disable InconsistentNaming
@@ -15,11 +16,14 @@ public class FileLibraryController(MangaContext context) : Controller
/// Returns all <see cref="FileLibrary"/>
/// </summary>
/// <response code="200"></response>
/// <response code="500">Error during Database Operation</response>
[HttpGet]
[ProducesResponseType<FileLibrary[]>(Status200OK, "application/json")]
public IActionResult GetFileLibraries()
public async Task<IActionResult> GetFileLibraries ()
{
return Ok(context.FileLibraries.ToArray());
if(await context.FileLibraries.ToArrayAsync(HttpContext.RequestAborted) is not { } result)
return StatusCode(Status500InternalServerError);
return Ok(result);
}
/// <summary>
@@ -31,9 +35,9 @@ public class FileLibraryController(MangaContext context) : Controller
[HttpGet("{FileLibraryId}")]
[ProducesResponseType<FileLibrary>(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetFileLibrary(string FileLibraryId)
public async Task<IActionResult> GetFileLibrary (string FileLibraryId)
{
if (context.FileLibraries.Find(FileLibraryId) is not { } library)
if(await context.FileLibraries.FirstOrDefaultAsync(l => l.Key == FileLibraryId, HttpContext.RequestAborted) is not { } library)
return NotFound();
return Ok(library);
@@ -51,15 +55,15 @@ public class FileLibraryController(MangaContext context) : Controller
[ProducesResponseType(Status200OK)]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult ChangeLibraryBasePath(string FileLibraryId, [FromBody]string newBasePath)
public async Task<IActionResult> ChangeLibraryBasePath (string FileLibraryId, [FromBody]string newBasePath)
{
if (context.FileLibraries.Find(FileLibraryId) is not { } library)
if(await context.FileLibraries.FirstOrDefaultAsync(l => l.Key == FileLibraryId, HttpContext.RequestAborted) is not { } library)
return NotFound();
//TODO Path check
library.BasePath = newBasePath;
if(context.Sync() is { success: false } result)
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
return StatusCode(Status500InternalServerError, result.exceptionMessage);
return Ok();
}
@@ -77,21 +81,21 @@ public class FileLibraryController(MangaContext context) : Controller
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType(Status400BadRequest)]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult ChangeLibraryName(string FileLibraryId, [FromBody] string newName)
public async Task<IActionResult> ChangeLibraryName (string FileLibraryId, [FromBody] string newName)
{
if (context.FileLibraries.Find(FileLibraryId) is not { } library)
if(await context.FileLibraries.FirstOrDefaultAsync(l => l.Key == FileLibraryId, HttpContext.RequestAborted) is not { } library)
return NotFound();
//TODO Name check
library.LibraryName = newName;
if(context.Sync() is { success: false } result)
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
return StatusCode(Status500InternalServerError, result.exceptionMessage);
return Ok();
}
/// <summary>
/// Creates new <see cref="FileLibraryId"/>
/// Creates new <see cref="FileLibrary"/>
/// </summary>
/// <param name="library">New <see cref="FileLibrary"/> to add</param>
/// <response code="200"></response>
@@ -99,13 +103,12 @@ public class FileLibraryController(MangaContext context) : Controller
[HttpPut]
[ProducesResponseType(Status201Created)]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult CreateNewLibrary([FromBody]FileLibrary library)
public async Task<IActionResult> CreateNewLibrary ([FromBody]FileLibrary library)
{
//TODO Parameter check
context.FileLibraries.Add(library);
if(context.Sync() is { success: false } result)
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
return StatusCode(Status500InternalServerError, result.exceptionMessage);
return Created();
}
@@ -120,14 +123,14 @@ public class FileLibraryController(MangaContext context) : Controller
[ProducesResponseType(Status200OK)]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult DeleteLocalLibrary(string FileLibraryId)
public async Task<IActionResult> DeleteLocalLibrary (string FileLibraryId)
{
if (context.FileLibraries.Find(FileLibraryId) is not { } library)
if(await context.FileLibraries.FirstOrDefaultAsync(l => l.Key == FileLibraryId, HttpContext.RequestAborted) is not { } library)
return NotFound();
context.FileLibraries.Remove(library);
if(context.Sync() is { success: false } result)
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
return StatusCode(Status500InternalServerError, result.exceptionMessage);
return Ok();
}

View File

@@ -2,6 +2,7 @@
using API.Schema.LibraryContext.LibraryConnectors;
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using static Microsoft.AspNetCore.Http.StatusCodes;
// ReSharper disable InconsistentNaming
@@ -16,11 +17,13 @@ public class LibraryConnectorController(LibraryContext context) : Controller
/// Gets all configured <see cref="LibraryConnector"/>
/// </summary>
/// <response code="200"></response>
/// <response code="500">Error during Database Operation</response>
[HttpGet]
[ProducesResponseType<LibraryConnector[]>(Status200OK, "application/json")]
public IActionResult GetAllConnectors()
public async Task<IActionResult> GetAllConnectors ()
{
LibraryConnector[] connectors = context.LibraryConnectors.ToArray();
if (await context.LibraryConnectors.ToArrayAsync(HttpContext.RequestAborted) is not { } connectors)
return StatusCode(Status500InternalServerError);
return Ok(connectors);
}
@@ -34,9 +37,9 @@ public class LibraryConnectorController(LibraryContext context) : Controller
[HttpGet("{LibraryConnectorId}")]
[ProducesResponseType<LibraryConnector>(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetConnector(string LibraryConnectorId)
public async Task<IActionResult> GetConnector (string LibraryConnectorId)
{
if (context.LibraryConnectors.Find(LibraryConnectorId) is not { } connector)
if (await context.LibraryConnectors.FirstOrDefaultAsync(l => l.Key == LibraryConnectorId) is not { } connector)
return NotFound();
return Ok(connector);
@@ -51,12 +54,12 @@ public class LibraryConnectorController(LibraryContext context) : Controller
[HttpPut]
[ProducesResponseType(Status201Created)]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult CreateConnector([FromBody]LibraryConnector libraryConnector)
public async Task<IActionResult> CreateConnector ([FromBody]LibraryConnector libraryConnector)
{
context.LibraryConnectors.Add(libraryConnector);
if(context.Sync() is { success: false } result)
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
return StatusCode(Status500InternalServerError, result.exceptionMessage);
return Created();
}
@@ -66,20 +69,20 @@ public class LibraryConnectorController(LibraryContext context) : Controller
/// </summary>
/// <param name="LibraryConnectorId">ToFileLibrary-Connector-ID</param>
/// <response code="200"></response>
/// <response code="404"><see cref="LibraryConnector"/> with <<paramref name="LibraryConnectorId"/> not found.</response>
/// <response code="404"><see cref="LibraryConnector"/> with <paramref name="LibraryConnectorId"/> not found.</response>
/// <response code="500">Error during Database Operation</response>
[HttpDelete("{LibraryConnectorId}")]
[ProducesResponseType(Status200OK)]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult DeleteConnector(string LibraryConnectorId)
public async Task<IActionResult> DeleteConnector (string LibraryConnectorId)
{
if (context.LibraryConnectors.Find(LibraryConnectorId) is not { } connector)
if (await context.LibraryConnectors.FirstOrDefaultAsync(l => l.Key == LibraryConnectorId) is not { } connector)
return NotFound();
context.LibraryConnectors.Remove(connector);
if(context.Sync() is { success: false } result)
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
return StatusCode(Status500InternalServerError, result.exceptionMessage);
return Ok();
}

View File

@@ -21,15 +21,17 @@ public class MaintenanceController(MangaContext mangaContext) : Controller
[HttpPost("CleanupNoDownloadManga")]
[ProducesResponseType(Status200OK)]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult CleanupNoDownloadManga()
public async Task<IActionResult> CleanupNoDownloadManga()
{
Manga[] noDownloads = mangaContext.Mangas
.Include(m => m.MangaConnectorIds)
.Where(m => !m.MangaConnectorIds.Any(id => id.UseForDownload))
.ToArray();
if (await mangaContext.Mangas
.Include(m => m.MangaConnectorIds)
.Where(m => !m.MangaConnectorIds.Any(id => id.UseForDownload))
.ToArrayAsync(HttpContext.RequestAborted) is not { } noDownloads)
return StatusCode(Status500InternalServerError);
mangaContext.Mangas.RemoveRange(noDownloads);
if(mangaContext.Sync() is { success: false } result)
if(await mangaContext.Sync(HttpContext.RequestAborted) is { success: false } result)
return StatusCode(Status500InternalServerError, result.exceptionMessage);
return Ok();
}

View File

@@ -75,14 +75,14 @@ public class MangaConnectorController(MangaContext context) : Controller
[ProducesResponseType(Status202Accepted)]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult SetEnabled(string MangaConnectorName, bool Enabled)
public async Task<IActionResult> SetEnabled(string MangaConnectorName, bool Enabled)
{
if(Tranga.MangaConnectors.FirstOrDefault(c => c.Name.Equals(MangaConnectorName, StringComparison.InvariantCultureIgnoreCase)) is not { } connector)
return NotFound();
connector.Enabled = Enabled;
if(context.Sync() is { success: false } result)
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
return StatusCode(Status500InternalServerError, result.exceptionMessage);
return Accepted();
}

View File

@@ -25,49 +25,65 @@ public class MangaController(MangaContext context) : Controller
/// Returns all cached <see cref="Manga"/>
/// </summary>
/// <response code="200"></response>
/// <response code="500">Error during Database Operation</response>
[HttpGet]
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
public IActionResult GetAllManga()
public async Task<IActionResult> GetAllManga ()
{
return Ok(context.Mangas.ToArray());
if(await context.Mangas.ToArrayAsync(HttpContext.RequestAborted) is not { } result)
return StatusCode(Status500InternalServerError);
return Ok(result);
}
/// <summary>
/// Returns all cached <see cref="Manga"/>.Keys
/// </summary>
/// <response code="200"><see cref="Manga"/> Keys/IDs</response>
/// <response code="500">Error during Database Operation</response>
[HttpGet("Keys")]
[ProducesResponseType<string[]>(Status200OK, "application/json")]
public IActionResult GetAllMangaKeys()
public async Task<IActionResult> GetAllMangaKeys ()
{
return Ok(context.Mangas.Select(m => m.Key).ToArray());
if(await context.Mangas.Select(m => m.Key).ToArrayAsync(HttpContext.RequestAborted) is not { } result)
return StatusCode(Status500InternalServerError);
return Ok(result);
}
/// <summary>
/// Returns all <see cref="Manga"/> that are being downloaded from at least one <see cref="MangaConnector"/>
/// </summary>
/// <response code="200"></response>
/// <response code="500">Error during Database Operation</response>
[HttpGet("Downloading")]
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
public IActionResult GetMangaDownloading()
public async Task<IActionResult> GetMangaDownloading ()
{
Manga[] ret = context.MangaIncludeAll()
.Where(m => m.MangaConnectorIds.Any(id => id.UseForDownload))
.ToArray();
return Ok(ret);
if(await context.MangaIncludeAll()
.Where(m => m.MangaConnectorIds.Any(id => id.UseForDownload))
.ToArrayAsync(HttpContext.RequestAborted) is not { } result)
return StatusCode(Status500InternalServerError);
return Ok(result);
}
/// <summary>
/// Returns all cached <see cref="Manga"/> with <paramref name="MangaIds"/>
/// </summary>
/// <param name="MangaIds">Array of <<see cref="Manga"/>.Key</param>
/// <param name="MangaIds">Array of <see cref="Manga"/>.Key</param>
/// <response code="200"></response>
/// <response code="500">Error during Database Operation</response>
[HttpPost("WithIDs")]
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
public IActionResult GetManga([FromBody]string[] MangaIds)
public async Task<IActionResult> GetManga ([FromBody]string[] MangaIds)
{
Manga[] ret = context.MangaIncludeAll().Where(m => MangaIds.Contains(m.Key)).ToArray();
return Ok(ret);
if(await context.MangaIncludeAll()
.Where(m => MangaIds.Contains(m.Key))
.ToArrayAsync(HttpContext.RequestAborted) is not { } result)
return StatusCode(Status500InternalServerError);
return Ok(result);
}
/// <summary>
@@ -79,10 +95,11 @@ public class MangaController(MangaContext context) : Controller
[HttpGet("{MangaId}")]
[ProducesResponseType<Manga>(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetManga(string MangaId)
public async Task<IActionResult> GetManga (string MangaId)
{
if (context.MangaIncludeAll().FirstOrDefault(m => m.Key == MangaId) is not { } manga)
if (await context.MangaIncludeAll().FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
return NotFound(nameof(MangaId));
return Ok(manga);
}
@@ -97,14 +114,14 @@ public class MangaController(MangaContext context) : Controller
[ProducesResponseType(Status200OK)]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult DeleteManga(string MangaId)
public async Task<IActionResult> DeleteManga (string MangaId)
{
if (context.Mangas.Find(MangaId) is not { } manga)
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
return NotFound(nameof(MangaId));
context.Mangas.Remove(manga);
if(context.Sync() is { success: false } result)
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
return StatusCode(Status500InternalServerError, result.exceptionMessage);
return Ok();
}
@@ -120,21 +137,20 @@ public class MangaController(MangaContext context) : Controller
[HttpPatch("{MangaIdFrom}/MergeInto/{MangaIdInto}")]
[ProducesResponseType<byte[]>(Status200OK,"image/jpeg")]
[ProducesResponseType(Status404NotFound)]
public IActionResult MergeIntoManga(string MangaIdFrom, string MangaIdInto)
public async Task<IActionResult> MergeIntoManga (string MangaIdFrom, string MangaIdInto)
{
if (context.Mangas.Find(MangaIdFrom) is not { } from)
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaIdFrom, HttpContext.RequestAborted) is not { } from)
return NotFound(nameof(MangaIdFrom));
if (context.Mangas.Find(MangaIdInto) is not { } into)
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaIdInto, HttpContext.RequestAborted) is not { } into)
return NotFound(nameof(MangaIdInto));
foreach (CollectionEntry collectionEntry in context.Entry(from).Collections)
collectionEntry.Load();
context.Entry(from).Navigation(nameof(Manga.Library)).Load();
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
await context.Entry(from).Navigation(nameof(Manga.Library)).LoadAsync(HttpContext.RequestAborted);
foreach (CollectionEntry collectionEntry in context.Entry(into).Collections)
collectionEntry.Load();
context.Entry(into).Navigation(nameof(Manga.Library)).Load();
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
await context.Entry(into).Navigation(nameof(Manga.Library)).LoadAsync(HttpContext.RequestAborted);
BaseWorker[] newJobs = into.MergeFrom(from, context);
Tranga.AddWorkers(newJobs);
@@ -159,9 +175,9 @@ public class MangaController(MangaContext context) : Controller
[ProducesResponseType(Status400BadRequest)]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType<int>(Status503ServiceUnavailable, "text/plain")]
public IActionResult GetCover(string MangaId, [FromQuery]int? width, [FromQuery]int? height)
public async Task<IActionResult> GetCover (string MangaId, [FromQuery]int? width, [FromQuery]int? height)
{
if (context.Mangas.Find(MangaId) is not { } manga)
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
return NotFound(nameof(MangaId));
if (!System.IO.File.Exists(manga.CoverFileNameInCache))
@@ -175,7 +191,7 @@ public class MangaController(MangaContext context) : Controller
return NoContent();
}
Image image = Image.Load(manga.CoverFileNameInCache);
Image image = await Image.LoadAsync(manga.CoverFileNameInCache, HttpContext.RequestAborted);
if (width is { } w && height is { } h)
{
@@ -189,7 +205,7 @@ public class MangaController(MangaContext context) : Controller
}
using MemoryStream ms = new();
image.Save(ms, new JpegEncoder(){Quality = 100});
await image.SaveAsync(ms, new JpegEncoder(){Quality = 100}, HttpContext.RequestAborted);
DateTime lastModified = new FileInfo(manga.CoverFileNameInCache).LastWriteTime;
HttpContext.Response.Headers.CacheControl = "public";
return File(ms.GetBuffer(), "image/jpeg", new DateTimeOffset(lastModified), EntityTagHeaderValue.Parse($"\"{lastModified.Ticks}\""));
@@ -204,12 +220,12 @@ public class MangaController(MangaContext context) : Controller
[HttpGet("{MangaId}/Chapters")]
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetChapters(string MangaId)
public async Task<IActionResult> GetChapters (string MangaId)
{
if (context.Mangas.Find(MangaId) is not { } manga)
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
return NotFound(nameof(MangaId));
context.Entry(manga).Collection(m => m.Chapters).Load();
await context.Entry(manga).Collection(m => m.Chapters).LoadAsync();
Chapter[] chapters = manga.Chapters.ToArray();
return Ok(chapters);
@@ -226,12 +242,12 @@ public class MangaController(MangaContext context) : Controller
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
[ProducesResponseType(Status204NoContent)]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetChaptersDownloaded(string MangaId)
public async Task<IActionResult> GetChaptersDownloaded (string MangaId)
{
if (context.Mangas.Find(MangaId) is not { } manga)
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
return NotFound(nameof(MangaId));
context.Entry(manga).Collection(m => m.Chapters).Load();
await context.Entry(manga).Collection(m => m.Chapters).LoadAsync();
List<Chapter> chapters = manga.Chapters.Where(c => c.Downloaded).ToList();
if (chapters.Count == 0)
@@ -251,12 +267,12 @@ public class MangaController(MangaContext context) : Controller
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
[ProducesResponseType(Status204NoContent)]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetChaptersNotDownloaded(string MangaId)
public async Task<IActionResult> GetChaptersNotDownloaded (string MangaId)
{
if (context.Mangas.Find(MangaId) is not { } manga)
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
return NotFound(nameof(MangaId));
context.Entry(manga).Collection(m => m.Chapters).Load();
await context.Entry(manga).Collection(m => m.Chapters).LoadAsync(HttpContext.RequestAborted);
List<Chapter> chapters = manga.Chapters.Where(c => c.Downloaded == false).ToList();
if (chapters.Count == 0)
@@ -280,12 +296,12 @@ public class MangaController(MangaContext context) : Controller
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
[ProducesResponseType<int>(Status503ServiceUnavailable, "text/plain")]
public IActionResult GetLatestChapter(string MangaId)
public async Task<IActionResult> GetLatestChapter (string MangaId)
{
if (context.Mangas.Find(MangaId) is not { } manga)
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
return NotFound(nameof(MangaId));
context.Entry(manga).Collection(m => m.Chapters).Load();
await context.Entry(manga).Collection(m => m.Chapters).LoadAsync(HttpContext.RequestAborted);
List<Chapter> chapters = manga.Chapters.ToList();
if (chapters.Count == 0)
@@ -303,8 +319,8 @@ public class MangaController(MangaContext context) : Controller
return StatusCode(Status500InternalServerError, "Max chapter could not be found");
foreach (CollectionEntry collectionEntry in context.Entry(max).Collections)
collectionEntry.Load();
context.Entry(max).Navigation(nameof(Chapter.ParentManga)).Load();
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
await context.Entry(max).Navigation(nameof(Chapter.ParentManga)).LoadAsync(HttpContext.RequestAborted);
return Ok(max);
}
@@ -324,12 +340,12 @@ public class MangaController(MangaContext context) : Controller
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
[ProducesResponseType<int>(Status503ServiceUnavailable, "text/plain")]
public IActionResult GetLatestChapterDownloaded(string MangaId)
public async Task<IActionResult> GetLatestChapterDownloaded (string MangaId)
{
if (context.Mangas.Find(MangaId) is not { } manga)
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
return NotFound(nameof(MangaId));
context.Entry(manga).Collection(m => m.Chapters).Load();
await context.Entry(manga).Collection(m => m.Chapters).LoadAsync(HttpContext.RequestAborted);
List<Chapter> chapters = manga.Chapters.ToList();
if (chapters.Count == 0)
@@ -347,8 +363,8 @@ public class MangaController(MangaContext context) : Controller
return StatusCode(Status412PreconditionFailed, "Max chapter could not be found");
foreach (CollectionEntry collectionEntry in context.Entry(max).Collections)
collectionEntry.Load();
context.Entry(max).Navigation(nameof(Chapter.ParentManga)).Load();
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
await context.Entry(max).Navigation(nameof(Chapter.ParentManga)).LoadAsync(HttpContext.RequestAborted);
return Ok(max);
}
@@ -365,13 +381,13 @@ public class MangaController(MangaContext context) : Controller
[ProducesResponseType(Status202Accepted)]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult IgnoreChaptersBefore(string MangaId, [FromBody]float chapterThreshold)
public async Task<IActionResult> IgnoreChaptersBefore (string MangaId, [FromBody]float chapterThreshold)
{
if (context.Mangas.Find(MangaId) is not { } manga)
return NotFound();
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
return NotFound(nameof(MangaId));
manga.IgnoreChaptersBefore = chapterThreshold;
if(context.Sync() is { success: false } result)
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
return StatusCode(Status500InternalServerError, result.exceptionMessage);
return Accepted();
@@ -387,16 +403,16 @@ public class MangaController(MangaContext context) : Controller
[HttpPost("{MangaId}/ChangeLibrary/{LibraryId}")]
[ProducesResponseType(Status202Accepted)]
[ProducesResponseType(Status404NotFound)]
public IActionResult ChangeLibrary(string MangaId, string LibraryId)
public async Task<IActionResult> ChangeLibrary (string MangaId, string LibraryId)
{
if (context.Mangas.Find(MangaId) is not { } manga)
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
return NotFound(nameof(MangaId));
if(context.FileLibraries.Find(LibraryId) is not { } library)
if (await context.FileLibraries.FirstOrDefaultAsync(l => l.Key == LibraryId, HttpContext.RequestAborted) is not { } library)
return NotFound(nameof(LibraryId));
foreach (CollectionEntry collectionEntry in context.Entry(manga).Collections)
collectionEntry.Load();
context.Entry(manga).Navigation(nameof(Manga.Library)).Load();
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
await context.Entry(manga).Navigation(nameof(Manga.Library)).LoadAsync(HttpContext.RequestAborted);
MoveMangaLibraryWorker moveLibrary = new(manga, library);
@@ -422,11 +438,11 @@ public class MangaController(MangaContext context) : Controller
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
[ProducesResponseType<string>(Status428PreconditionRequired, "text/plain")]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult MarkAsRequested(string MangaId, string MangaConnectorName, bool IsRequested)
public async Task<IActionResult> MarkAsRequested (string MangaId, string MangaConnectorName, bool IsRequested)
{
if (context.Mangas.Find(MangaId) is null)
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } _)
return NotFound(nameof(MangaId));
if(!Tranga.TryGetMangaConnector(MangaConnectorName, out MangaConnector? mangaConnector))
if(!Tranga.TryGetMangaConnector(MangaConnectorName, out MangaConnector? _))
return NotFound(nameof(MangaConnectorName));
if (context.MangaConnectorToManga
@@ -440,7 +456,7 @@ public class MangaController(MangaContext context) : Controller
}
mcId.UseForDownload = IsRequested;
if(context.Sync() is { success: false } result)
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
return StatusCode(Status500InternalServerError, result.exceptionMessage);
@@ -463,9 +479,9 @@ public class MangaController(MangaContext context) : Controller
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType(Status406NotAcceptable)]
public IActionResult SearchOnDifferentConnector(string MangaId, string MangaConnectorName)
public async Task<IActionResult> SearchOnDifferentConnector (string MangaId, string MangaConnectorName)
{
if (context.Mangas.Find(MangaId) is not { } manga)
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
return NotFound(nameof(MangaId));
return new SearchController(context).SearchManga(MangaConnectorName, manga.Name);
@@ -479,10 +495,10 @@ public class MangaController(MangaContext context) : Controller
/// <response code="404"><see cref="Author"/> with <paramref name="AuthorId"/></response>
[HttpGet("WithAuthorId/{AuthorId}")]
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
public IActionResult GetMangaWithAuthorIds(string AuthorId)
public async Task<IActionResult> GetMangaWithAuthorIds (string AuthorId)
{
if (context.Authors.Find(AuthorId) is not { } author)
return NotFound();
if (await context.Authors.FirstOrDefaultAsync(a => a.Key == AuthorId, HttpContext.RequestAborted) is not { } author)
return NotFound(nameof(AuthorId));
return Ok(context.Mangas.Where(m => m.Authors.Contains(author)));
}
@@ -495,10 +511,10 @@ public class MangaController(MangaContext context) : Controller
/// <response code="404"><see cref="Tag"/> not found</response>
[HttpGet("WithTag/{Tag}")]
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
public IActionResult GetMangasWithTag(string Tag)
public async Task<IActionResult> GetMangasWithTag (string Tag)
{
if (context.Tags.Find(Tag) is not { } tag)
return NotFound();
if (await context.Tags.FirstOrDefaultAsync(t => t.Tag == Tag, HttpContext.RequestAborted) is not { } tag)
return NotFound(nameof(Tag));
return Ok(context.Mangas.Where(m => m.MangaTags.Contains(tag)));
}

View File

@@ -3,6 +3,7 @@ using API.Schema.MangaContext.MetadataFetchers;
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.EntityFrameworkCore;
using static Microsoft.AspNetCore.Http.StatusCodes;
// ReSharper disable InconsistentNaming
@@ -19,7 +20,7 @@ public class MetadataFetcherController(MangaContext context) : Controller
/// <response code="200">Names of <see cref="MetadataFetcher"/> (Metadata-Sites)</response>
[HttpGet]
[ProducesResponseType<string[]>(Status200OK, "application/json")]
public IActionResult GetConnectors()
public IActionResult GetConnectors ()
{
return Ok(Tranga.MetadataFetchers.Select(m => m.Name).ToArray());
}
@@ -28,11 +29,15 @@ public class MetadataFetcherController(MangaContext context) : Controller
/// Returns all <see cref="MetadataEntry"/>
/// </summary>
/// <response code="200"></response>
/// <response code="500">Error during Database Operation</response>
[HttpGet("Links")]
[ProducesResponseType<MetadataEntry[]>(Status200OK, "application/json")]
public IActionResult GetLinkedEntries()
public async Task<IActionResult> GetLinkedEntries ()
{
return Ok(context.MetadataEntries.ToArray());
if (await context.MetadataEntries.ToArrayAsync() is not { } result)
return StatusCode(Status500InternalServerError);
return Ok(result);
}
/// <summary>
@@ -48,10 +53,10 @@ public class MetadataFetcherController(MangaContext context) : Controller
[ProducesResponseType<MetadataSearchResult[]>(Status200OK, "application/json")]
[ProducesResponseType(Status400BadRequest)]
[ProducesResponseType(Status404NotFound)]
public IActionResult SearchMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]string? searchTerm = null)
public async Task<IActionResult> SearchMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody (EmptyBodyBehavior = EmptyBodyBehavior.Allow)]string? searchTerm = null)
{
if(context.Mangas.Find(MangaId) is not { } manga)
return NotFound();
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
return NotFound(nameof(MangaId));
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is not { } fetcher)
return BadRequest();
@@ -74,18 +79,18 @@ public class MetadataFetcherController(MangaContext context) : Controller
[ProducesResponseType(Status400BadRequest)]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult LinkMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody]string Identifier)
public async Task<IActionResult> LinkMangaMetadata (string MangaId, string MetadataFetcherName, [FromBody]string Identifier)
{
if(context.Mangas.Find(MangaId) is not { } manga)
return NotFound();
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
return NotFound(nameof(MangaId));
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is not { } fetcher)
return BadRequest();
MetadataEntry entry = fetcher.CreateMetadataEntry(manga, Identifier);
context.MetadataEntries.Add(entry);
if(context.Sync() is { } errorMessage)
return StatusCode(Status500InternalServerError, errorMessage);
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
return StatusCode(Status500InternalServerError, result.exceptionMessage);
return Ok(entry);
}
@@ -103,10 +108,10 @@ public class MetadataFetcherController(MangaContext context) : Controller
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult UnlinkMangaMetadata(string MangaId, string MetadataFetcherName)
public async Task<IActionResult> UnlinkMangaMetadata (string MangaId, string MetadataFetcherName)
{
if(context.Mangas.Find(MangaId) is null)
return NotFound();
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } _)
return NotFound(nameof(MangaId));
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is null)
return BadRequest();
if(context.MetadataEntries.FirstOrDefault(e => e.MangaId == MangaId && e.MetadataFetcherName == MetadataFetcherName) is not { } entry)
@@ -114,7 +119,7 @@ public class MetadataFetcherController(MangaContext context) : Controller
context.Remove(entry);
if(context.Sync() is { success: false } result)
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
return StatusCode(Status500InternalServerError, result.exceptionMessage);
return Ok();
}

View File

@@ -4,6 +4,7 @@ using API.Schema.NotificationsContext;
using API.Schema.NotificationsContext.NotificationConnectors;
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using static Microsoft.AspNetCore.Http.StatusCodes;
// ReSharper disable InconsistentNaming
@@ -19,12 +20,15 @@ public class NotificationConnectorController(NotificationsContext context) : Con
/// Gets all configured <see cref="NotificationConnector"/>
/// </summary>
/// <response code="200"></response>
/// <response code="500">Error during Database Operation</response>
[HttpGet]
[ProducesResponseType<NotificationConnector[]>(Status200OK, "application/json")]
public IActionResult GetAllConnectors()
public async Task<IActionResult> GetAllConnectors ()
{
if(await context.NotificationConnectors.ToArrayAsync(HttpContext.RequestAborted) is not { } result)
return StatusCode(Status500InternalServerError);
return Ok(context.NotificationConnectors.ToArray());
return Ok(result);
}
/// <summary>
@@ -36,10 +40,10 @@ public class NotificationConnectorController(NotificationsContext context) : Con
[HttpGet("{Name}")]
[ProducesResponseType<NotificationConnector>(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetConnector(string Name)
public async Task<IActionResult> GetConnector (string Name)
{
if(context.NotificationConnectors.Find(Name) is not { } connector)
return NotFound();
if (await context.NotificationConnectors.FirstOrDefaultAsync(c => c.Name == Name, HttpContext.RequestAborted) is not { } connector)
return NotFound(nameof(Name));
return Ok(connector);
}
@@ -53,12 +57,12 @@ public class NotificationConnectorController(NotificationsContext context) : Con
[HttpPut]
[ProducesResponseType<string>(Status200OK, "text/plain")]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult CreateConnector([FromBody]NotificationConnector notificationConnector)
public async Task<IActionResult> CreateConnector ([FromBody]NotificationConnector notificationConnector)
{
context.NotificationConnectors.Add(notificationConnector);
context.Notifications.Add(new ("Added new Notification Connector!", notificationConnector.Name, NotificationUrgency.High));
if(context.Sync() is { success: false } result)
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
return StatusCode(Status500InternalServerError, result.exceptionMessage);
return Ok(notificationConnector.Name);
}
@@ -72,7 +76,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con
[HttpPut("Gotify")]
[ProducesResponseType<string>(Status200OK, "text/plain")]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult CreateGotifyConnector([FromBody]GotifyRecord gotifyData)
public async Task<IActionResult> CreateGotifyConnector ([FromBody]GotifyRecord gotifyData)
{
//TODO Validate Data
@@ -81,7 +85,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con
new Dictionary<string, string>() { { "X-Gotify-Key", gotifyData.AppToken } },
"POST",
$"{{\"message\": \"%text\", \"title\": \"%title\", \"Priority\": {gotifyData.Priority}}}");
return CreateConnector(gotifyConnector);
return await CreateConnector(gotifyConnector);
}
/// <summary>
@@ -93,7 +97,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con
[HttpPut("Ntfy")]
[ProducesResponseType<string>(Status200OK, "text/plain")]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult CreateNtfyConnector([FromBody]NtfyRecord ntfyRecord)
public async Task<IActionResult> CreateNtfyConnector ([FromBody]NtfyRecord ntfyRecord)
{
//TODO Validate Data
@@ -108,7 +112,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con
},
"POST",
$"{{\"message\": \"%text\", \"title\": \"%title\", \"Priority\": {ntfyRecord.Priority} \"Topic\": \"{ntfyRecord.Topic}\"}}");
return CreateConnector(ntfyConnector);
return await CreateConnector(ntfyConnector);
}
/// <summary>
@@ -120,7 +124,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con
[HttpPut("Pushover")]
[ProducesResponseType<string>(Status200OK, "text/plain")]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult CreatePushoverConnector([FromBody]PushoverRecord pushoverRecord)
public async Task<IActionResult> CreatePushoverConnector ([FromBody]PushoverRecord pushoverRecord)
{
//TODO Validate Data
@@ -129,7 +133,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con
new Dictionary<string, string>(),
"POST",
$"{{\"token\": \"{pushoverRecord.AppToken}\", \"user\": \"{pushoverRecord.User}\", \"message:\":\"%text\", \"%title\" }}");
return CreateConnector(pushoverConnector);
return await CreateConnector(pushoverConnector);
}
/// <summary>
@@ -143,14 +147,14 @@ public class NotificationConnectorController(NotificationsContext context) : Con
[ProducesResponseType(Status200OK)]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
public IActionResult DeleteConnector(string Name)
public async Task<IActionResult> DeleteConnector (string Name)
{
if(context.NotificationConnectors.Find(Name) is not { } connector)
return NotFound();
if (await context.NotificationConnectors.FirstOrDefaultAsync(c => c.Name == Name, HttpContext.RequestAborted) is not { } connector)
return NotFound(nameof(Name));
context.NotificationConnectors.Remove(connector);
if(context.Sync() is { success: false } result)
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
return StatusCode(Status500InternalServerError, result.exceptionMessage);
return Ok();
}

View File

@@ -1,7 +1,7 @@
using API.MangaConnectors;
using API.Schema.MangaContext;
using API.Schema.MangaContext;
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Soenneker.Utils.String.NeedlemanWunsch;
using static Microsoft.AspNetCore.Http.StatusCodes;
// ReSharper disable InconsistentNaming
@@ -22,10 +22,10 @@ public class QueryController(MangaContext context) : Controller
[HttpGet("Author/{AuthorId}")]
[ProducesResponseType<Author>(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetAuthor(string AuthorId)
public async Task<IActionResult> GetAuthor (string AuthorId)
{
if (context.Authors.Find(AuthorId) is not { } author)
return NotFound();
if (await context.Authors.FirstOrDefaultAsync(a => a.Key == AuthorId, HttpContext.RequestAborted) is not { } author)
return NotFound(nameof(AuthorId));
return Ok(author);
}
@@ -39,10 +39,10 @@ public class QueryController(MangaContext context) : Controller
[HttpGet("Chapter/{ChapterId}")]
[ProducesResponseType<Chapter>(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetChapter(string ChapterId)
public async Task<IActionResult> GetChapter (string ChapterId)
{
if (context.Chapters.Find(ChapterId) is not { } chapter)
return NotFound();
if (await context.Chapters.FirstOrDefaultAsync(c => c.Key == ChapterId, HttpContext.RequestAborted) is not { } chapter)
return NotFound(nameof(ChapterId));
return Ok(chapter);
}
@@ -56,10 +56,10 @@ public class QueryController(MangaContext context) : Controller
[HttpGet("Manga/MangaConnectorId/{MangaConnectorIdId}")]
[ProducesResponseType<MangaConnectorId<Manga>>(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetMangaMangaConnectorId(string MangaConnectorIdId)
public async Task<IActionResult> GetMangaMangaConnectorId (string MangaConnectorIdId)
{
if(context.MangaConnectorToManga.Find(MangaConnectorIdId) is not { } mcIdManga)
return NotFound();
if (await context.MangaConnectorToManga.FirstOrDefaultAsync(c => c.Key == MangaConnectorIdId, HttpContext.RequestAborted) is not { } mcIdManga)
return NotFound(nameof(MangaConnectorIdId));
return Ok(mcIdManga);
}
@@ -70,18 +70,24 @@ public class QueryController(MangaContext context) : Controller
/// <param name="MangaId">Key of <see cref="Manga"/></param>
/// <response code="200"></response>
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found</response>
/// <response code="500">Error during Database Operation</response>
[HttpGet("Manga/{MangaId}/SimilarName")]
[ProducesResponseType<string[]>(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetSimilarManga(string MangaId)
public async Task<IActionResult> GetSimilarManga (string MangaId)
{
if (context.Mangas.Find(MangaId) is not { } manga)
return NotFound();
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
return NotFound(nameof(MangaId));
string name = manga.Name;
Dictionary<string, string> mangaNames = context.Mangas.Where(m => m.Key != MangaId).ToDictionary(m => m.Key, m => m.Name);
if(await context.Mangas.Where(m => m.Key != MangaId).ToDictionaryAsync(m => m.Key, m => m.Name, HttpContext.RequestAborted) is not { } mangaNames)
return StatusCode(Status500InternalServerError);
string[] similarIds = mangaNames
.Where(kv => NeedlemanWunschStringUtil.CalculateSimilarityPercentage(name, kv.Value) > 0.8)
.Select(kv => kv.Key).ToArray();
return Ok(similarIds);
}
@@ -94,10 +100,10 @@ public class QueryController(MangaContext context) : Controller
[HttpGet("Chapter/MangaConnectorId/{MangaConnectorIdId}")]
[ProducesResponseType<MangaConnectorId<Chapter>>(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
public IActionResult GetChapterMangaConnectorId(string MangaConnectorIdId)
public async Task<IActionResult> GetChapterMangaConnectorId (string MangaConnectorIdId)
{
if(context.MangaConnectorToChapter.Find(MangaConnectorIdId) is not { } mcIdChapter)
return NotFound();
if (await context.MangaConnectorToManga.FirstOrDefaultAsync(c => c.Key == MangaConnectorIdId, HttpContext.RequestAborted) is not { } mcIdChapter)
return NotFound(nameof(MangaConnectorIdId));
return Ok(mcIdChapter);
}

View File

@@ -24,7 +24,7 @@ public class SearchController(MangaContext context) : Controller
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType(Status406NotAcceptable)]
public IActionResult SearchManga(string MangaConnectorName, string Query)
public IActionResult SearchManga (string MangaConnectorName, string Query)
{
if(Tranga.MangaConnectors.FirstOrDefault(c => c.Name.Equals(MangaConnectorName, StringComparison.InvariantCultureIgnoreCase)) is not { } connector)
return NotFound();
@@ -35,7 +35,7 @@ public class SearchController(MangaContext context) : Controller
List<Manga> retMangas = new();
foreach ((Manga manga, MangaConnectorId<Manga> mcId) manga in mangas)
{
if(Tranga.AddMangaToContext(manga, context, out Manga? add))
if(Tranga.AddMangaToContext(manga, context, out Manga? add, HttpContext.RequestAborted))
retMangas.Add(add);
}
@@ -54,7 +54,7 @@ public class SearchController(MangaContext context) : Controller
[ProducesResponseType<Manga>(Status200OK, "application/json")]
[ProducesResponseType(Status404NotFound)]
[ProducesResponseType(Status500InternalServerError)]
public IActionResult GetMangaFromUrl([FromBody]string url)
public IActionResult GetMangaFromUrl ([FromBody]string url)
{
if(Tranga.MangaConnectors.FirstOrDefault(c => c.Name.Equals("Global", StringComparison.InvariantCultureIgnoreCase)) is not { } connector)
return StatusCode(Status500InternalServerError, "Could not find Global Connector.");
@@ -62,7 +62,7 @@ public class SearchController(MangaContext context) : Controller
if(connector.GetMangaFromUrl(url) is not { } manga)
return NotFound();
if(Tranga.AddMangaToContext(manga, context, out Manga? add) == false)
if(Tranga.AddMangaToContext(manga, context, out Manga? add, HttpContext.RequestAborted) == false)
return StatusCode(Status500InternalServerError);
return Ok(add);

View File

@@ -1,5 +1,4 @@
using API.APIEndpointRecords;
using API.Workers;
using API.Workers;
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
using static Microsoft.AspNetCore.Http.StatusCodes;

View File

@@ -1,6 +1,5 @@
using System.Reflection;
using API;
using API.MangaConnectors;
using API.Schema.LibraryContext;
using API.Schema.MangaContext;
using API.Schema.NotificationsContext;
@@ -76,7 +75,7 @@ builder.Services.AddControllers().AddNewtonsoftJson(opts =>
opts.SerializerSettings.Converters.Add(new StringEnumConverter());
opts.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
builder.Services.AddScoped<ILog>(opts => LogManager.GetLogger("API"));
builder.Services.AddScoped<ILog>(_ => LogManager.GetLogger("API"));
builder.WebHost.UseUrls("http://*:6531");
@@ -112,7 +111,7 @@ using (IServiceScope scope = app.Services.CreateScope())
if (!context.FileLibraries.Any())
context.FileLibraries.Add(new FileLibrary(Tranga.Settings.DownloadLocation, "Default FileLibrary"));
context.Sync();
await context.Sync(CancellationToken.None);
}
using (IServiceScope scope = app.Services.CreateScope())
@@ -124,7 +123,7 @@ using (IServiceScope scope = app.Services.CreateScope())
string[] emojis = { "(•‿•)", "(づ \u25d5‿\u25d5 )づ", "( \u02d8\u25bd\u02d8)っ\u2668", "=\uff3e\u25cf \u22cf \u25cf\uff3e=", "(ΦωΦ)", "(\u272a\u3268\u272a)", "( ノ・o・ )ノ", "(〜^\u2207^ )〜", "~(\u2267ω\u2266)~","૮ \u00b4• ﻌ \u00b4• ა", "(\u02c3ᆺ\u02c2)", "(=\ud83d\udf66 \u0f1d \ud83d\udf66=)"};
context.Notifications.Add(new Notification("Tranga Started", emojis[Random.Shared.Next(0, emojis.Length - 1)], NotificationUrgency.High));
context.Sync();
await context.Sync(CancellationToken.None);
}
using (IServiceScope scope = app.Services.CreateScope())
@@ -132,7 +131,7 @@ using (IServiceScope scope = app.Services.CreateScope())
LibraryContext context = scope.ServiceProvider.GetRequiredService<LibraryContext>();
context.Database.Migrate();
context.Sync();
await context.Sync(CancellationToken.None);
}
Tranga.SetServiceProvider(app.Services);

View File

@@ -6,12 +6,12 @@ namespace API.Schema;
[PrimaryKey("Key")]
public abstract class Identifiable
{
public Identifiable()
protected Identifiable()
{
this.Key = TokenGen.CreateToken(this.GetType());
}
public Identifiable(string key)
protected Identifiable(string key)
{
this.Key = key;
}

View File

@@ -104,15 +104,15 @@ public class MangaContext(DbContextOptions<MangaContext> options) : TrangaBaseCo
.OnDelete(DeleteBehavior.Cascade);
}
public Manga? FindMangaLike(Manga other)
public async Task<Manga?> FindMangaLike(Manga other, CancellationToken token)
{
if (MangaIncludeAll().FirstOrDefault(m => m.Key == other.Key) is { } f)
if (await MangaIncludeAll().FirstOrDefaultAsync(m => m.Key == other.Key, token) is { } f)
return f;
return MangaIncludeAll()
.FirstOrDefault(m => m.Links.Any(l => l.Key == other.Key) ||
m.AltTitles.Any(t => other.AltTitles.Select(ot => ot.Title)
.Any(s => s.Equals(t.Title))));
return await MangaIncludeAll()
.FirstOrDefaultAsync(m =>
m.Links.Any(l => l.Key == other.Key) ||
m.AltTitles.Any(t => other.AltTitles.Select(ot => ot.Title).Any(s => s.Equals(t.Title))), token);
}
public IIncludableQueryable<Manga, ICollection<MangaConnectorId<Manga>>> MangaIncludeAll() => Mangas.Include(m => m.Library)

View File

@@ -31,5 +31,5 @@ public abstract class MetadataFetcher
/// <summary>
/// Updates the Manga linked in the MetadataEntry
/// </summary>
public abstract void UpdateMetadata(MetadataEntry metadataEntry, MangaContext dbContext);
public abstract Task UpdateMetadata(MetadataEntry metadataEntry, MangaContext dbContext, CancellationToken token);
}

View File

@@ -43,21 +43,25 @@ public class MyAnimeList : MetadataFetcher
/// </summary>
/// <param name="metadataEntry"></param>
/// <param name="dbContext"></param>
/// <param name="token"></param>
/// <exception cref="FormatException"></exception>
/// <exception cref="DbUpdateException"></exception>
public override void UpdateMetadata(MetadataEntry metadataEntry, MangaContext dbContext)
public override async Task UpdateMetadata(MetadataEntry metadataEntry, MangaContext dbContext, CancellationToken token)
{
Manga dbManga = dbContext.Mangas.Find(metadataEntry.MangaId)!;
if (await dbContext.Mangas.FirstOrDefaultAsync(m => m.Key == metadataEntry.MangaId, token) is not { } dbManga)
throw new DbUpdateException("Manga not found");
foreach (CollectionEntry collectionEntry in dbContext.Entry(dbManga).Collections)
collectionEntry.Load();
dbContext.Entry(dbManga).Navigation(nameof(Manga.Library)).Load();
await collectionEntry.LoadAsync(token);
await dbContext.Entry(dbManga).Navigation(nameof(Manga.Library)).LoadAsync(token);
MangaFull resultData;
try
{
long id = long.Parse(metadataEntry.Identifier);
resultData = Jikan.GetMangaFullDataAsync(id).Result.Data;
if(await Jikan.GetMangaFullDataAsync(id, token) is not { } response)
throw new DbUpdateException("Manga Data not found");
resultData = response.Data;
}
catch (Exception)
{
@@ -71,7 +75,7 @@ public class MyAnimeList : MetadataFetcher
dbManga.Authors.Clear();
dbManga.Authors = resultData.Authors.Select(a => new Author(a.Name)).ToList();
dbContext.Sync();
await dbContext.Sync(token);
}
}

View File

@@ -22,11 +22,11 @@ public abstract class TrangaBaseContext<T> : DbContext where T : DbContext
}, Array.Empty<string>(), LogLevel.Warning, DbContextLoggerOptions.Level | DbContextLoggerOptions.Category | DbContextLoggerOptions.UtcTime);
}
internal (bool success, string? exceptionMessage) Sync()
internal async Task<(bool success, string? exceptionMessage)> Sync(CancellationToken token)
{
try
{
this.SaveChanges();
await this.SaveChangesAsync(token);
return (true, null);
}
catch (Exception e)

View File

@@ -158,12 +158,13 @@ public static class Tranga
RunningWorkers.Remove(worker, out _);
}
internal static bool AddMangaToContext((Manga, MangaConnectorId<Manga>) addManga, MangaContext context, [NotNullWhen(true)]out Manga? manga) => AddMangaToContext(addManga.Item1, addManga.Item2, context, out manga);
internal static bool AddMangaToContext((Manga, MangaConnectorId<Manga>) addManga, MangaContext context, [NotNullWhen(true)]out Manga? manga, CancellationToken token) =>
AddMangaToContext(addManga.Item1, addManga.Item2, context, out manga, token);
internal static bool AddMangaToContext(Manga addManga, MangaConnectorId<Manga> addMcId, MangaContext context, [NotNullWhen(true)]out Manga? manga)
internal static bool AddMangaToContext(Manga addManga, MangaConnectorId<Manga> addMcId, MangaContext context, [NotNullWhen(true)]out Manga? manga, CancellationToken token)
{
context.ChangeTracker.Clear();
manga = context.FindMangaLike(addManga);
manga = context.FindMangaLike(addManga, token).Result;
if (manga is not null)
{
foreach (MangaConnectorId<Manga> mcId in addManga.MangaConnectorIds)
@@ -198,7 +199,7 @@ public static class Tranga
context.Mangas.Add(manga);
}
if (context.Sync() is { success: false })
if (context.Sync(token).Result is { success: false })
return false;
DownloadCoverFromMangaconnectorWorker downloadCoverWorker = new (addMcId);
@@ -207,10 +208,10 @@ public static class Tranga
return true;
}
internal static bool AddChapterToContext((Chapter, MangaConnectorId<Chapter>) addChapter, MangaContext context,
[NotNullWhen(true)] out Chapter? chapter) => AddChapterToContext(addChapter.Item1, addChapter.Item2, context, out chapter);
internal static bool AddChapterToContext((Chapter, MangaConnectorId<Chapter>) addChapter, MangaContext context, [NotNullWhen(true)] out Chapter? chapter, CancellationToken token) =>
AddChapterToContext(addChapter.Item1, addChapter.Item2, context, out chapter, token);
internal static bool AddChapterToContext(Chapter addChapter, MangaConnectorId<Chapter> addChId, MangaContext context, [NotNullWhen(true)] out Chapter? chapter)
internal static bool AddChapterToContext(Chapter addChapter, MangaConnectorId<Chapter> addChId, MangaContext context, [NotNullWhen(true)] out Chapter? chapter, CancellationToken token)
{
chapter = context.Chapters.Where(ch => ch.Key == addChapter.Key)
.Include(ch => ch.ParentManga)
@@ -226,7 +227,7 @@ public static class Tranga
chapter = addChapter;
}
if (context.Sync() is { success: false })
if (context.Sync(token).Result is { success: false })
return false;
return true;
}

View File

@@ -26,7 +26,7 @@ public abstract class BaseWorker : Identifiable
public IEnumerable<BaseWorker> MissingDependencies => DependsOn.Where(d => d.State < WorkerExecutionState.Completed);
public bool AllDependenciesFulfilled => DependsOn.All(d => d.State >= WorkerExecutionState.Completed);
internal WorkerExecutionState State { get; private set; }
private CancellationTokenSource? CancellationTokenSource = null;
protected CancellationTokenSource CancellationTokenSource = new ();
protected ILog Log { get; init; }
/// <summary>
@@ -36,7 +36,7 @@ public abstract class BaseWorker : Identifiable
{
Log.Debug($"Cancelled {this}");
this.State = WorkerExecutionState.Cancelled;
CancellationTokenSource?.Cancel();
CancellationTokenSource.Cancel();
}
/// <summary>
@@ -46,7 +46,7 @@ public abstract class BaseWorker : Identifiable
{
Log.Debug($"Failed {this}");
this.State = WorkerExecutionState.Failed;
CancellationTokenSource?.Cancel();
CancellationTokenSource.Cancel();
}
public BaseWorker(IEnumerable<BaseWorker>? dependsOn = null)
@@ -89,10 +89,9 @@ public abstract class BaseWorker : Identifiable
// Run the actual work
Log.Info($"Running {this}");
DateTime startTime = DateTime.UtcNow;
Task<BaseWorker[]> task = new (DoWorkInternal, CancellationTokenSource.Token);
Task<BaseWorker[]> task = DoWorkInternal();
task.GetAwaiter().OnCompleted(Finish(startTime, callback));
this.State = WorkerExecutionState.Running;
task.Start();
return task;
}
@@ -106,12 +105,12 @@ public abstract class BaseWorker : Identifiable
callback?.Invoke();
};
protected abstract BaseWorker[] DoWorkInternal();
protected abstract Task<BaseWorker[]> DoWorkInternal();
private BaseWorker[] WaitForDependencies()
{
Log.Info($"Waiting for {MissingDependencies.Count()} Dependencies {this}:\n\t{string.Join("\n\t", MissingDependencies.Select(d => d.ToString()))}");
while (CancellationTokenSource?.IsCancellationRequested == false && MissingDependencies.Any())
while (CancellationTokenSource.IsCancellationRequested == false && MissingDependencies.Any())
{
Thread.Sleep(Tranga.Settings.WorkCycleTimeoutMs);
}

View File

@@ -3,6 +3,7 @@ using System.Runtime.InteropServices;
using API.MangaConnectors;
using API.MangaDownloadClients;
using API.Schema.MangaContext;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
@@ -16,14 +17,14 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId<Chapter> c
: BaseWorkerWithContext<MangaContext>(dependsOn)
{
internal readonly string MangaConnectorIdId = chId.Key;
protected override BaseWorker[] DoWorkInternal()
protected override async Task<BaseWorker[]> DoWorkInternal()
{
if (DbContext.MangaConnectorToChapter.Find(MangaConnectorIdId) is not { } mangaConnectorId)
if(await DbContext.MangaConnectorToChapter.FirstOrDefaultAsync(c => c.Key == MangaConnectorIdId, CancellationTokenSource.Token) is not { } mangaConnectorId)
return []; //TODO Exception?
if (!Tranga.TryGetMangaConnector(mangaConnectorId.MangaConnectorName, out MangaConnector? mangaConnector))
return []; //TODO Exception?
DbContext.Entry(mangaConnectorId).Navigation(nameof(MangaConnectorId<Chapter>.Obj)).Load();
await DbContext.Entry(mangaConnectorId).Navigation(nameof(MangaConnectorId<Chapter>.Obj)).LoadAsync(CancellationTokenSource.Token);
Chapter chapter = mangaConnectorId.Obj;
if (chapter.Downloaded)
{
@@ -31,8 +32,8 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId<Chapter> c
return [];
}
DbContext.Entry(chapter).Navigation(nameof(Chapter.ParentManga)).Load();
DbContext.Entry(chapter.ParentManga).Navigation(nameof(Manga.Library)).Load();
await DbContext.Entry(chapter).Navigation(nameof(Chapter.ParentManga)).LoadAsync(CancellationTokenSource.Token);
await DbContext.Entry(chapter.ParentManga).Navigation(nameof(Manga.Library)).LoadAsync(CancellationTokenSource.Token);
if (chapter.ParentManga.LibraryId is null)
{
@@ -92,13 +93,13 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId<Chapter> c
}
}
CopyCoverFromCacheToDownloadLocation(chapter.ParentManga);
await CopyCoverFromCacheToDownloadLocation(chapter.ParentManga);
Log.Debug($"Creating ComicInfo.xml {chapter}");
foreach (CollectionEntry collectionEntry in DbContext.Entry(chapter.ParentManga).Collections)
collectionEntry.Load();
DbContext.Entry(chapter.ParentManga).Navigation(nameof(Manga.Library)).Load();
File.WriteAllText(Path.Join(tempFolder, "ComicInfo.xml"), chapter.GetComicInfoXmlString());
await collectionEntry.LoadAsync(CancellationTokenSource.Token);
await DbContext.Entry(chapter.ParentManga).Navigation(nameof(Manga.Library)).LoadAsync(CancellationTokenSource.Token);
await File.WriteAllTextAsync(Path.Join(tempFolder, "ComicInfo.xml"), chapter.GetComicInfoXmlString(), CancellationTokenSource.Token);
Log.Debug($"Packaging images to archive {chapter}");
//ZIP-it and ship-it
@@ -108,7 +109,7 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId<Chapter> c
Directory.Delete(tempFolder, true); //Cleanup
chapter.Downloaded = true;
DbContext.Sync();
await DbContext.Sync(CancellationTokenSource.Token);
return [];
}
@@ -151,7 +152,7 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId<Chapter> c
}
}
private void CopyCoverFromCacheToDownloadLocation(Manga manga)
private async Task CopyCoverFromCacheToDownloadLocation(Manga manga)
{
//Check if Publication already has a Folder and cover
string publicationFolder = manga.CreatePublicationFolder();
@@ -163,7 +164,7 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId<Chapter> c
}
//TODO MangaConnector Selection
DbContext.Entry(manga).Collection(m => m.MangaConnectorIds).Load();
await DbContext.Entry(manga).Collection(m => m.MangaConnectorIds).LoadAsync(CancellationTokenSource.Token);
MangaConnectorId<Manga> mangaConnectorId = manga.MangaConnectorIds.First();
if (!Tranga.TryGetMangaConnector(mangaConnectorId.MangaConnectorName, out MangaConnector? mangaConnector))
{
@@ -172,7 +173,7 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId<Chapter> c
}
Log.Info($"Copying cover to {publicationFolder}");
DbContext.Entry(mangaConnectorId).Navigation(nameof(MangaConnectorId<Manga>.Obj)).Load();
await DbContext.Entry(mangaConnectorId).Navigation(nameof(MangaConnectorId<Manga>.Obj)).LoadAsync(CancellationTokenSource.Token);
string? fileInCache = manga.CoverFileNameInCache ?? mangaConnector.SaveCoverImageToCache(mangaConnectorId);
if (fileInCache is null)
{

View File

@@ -1,5 +1,6 @@
using API.MangaConnectors;
using API.Schema.MangaContext;
using Microsoft.EntityFrameworkCore;
namespace API.Workers;
@@ -7,19 +8,19 @@ public class DownloadCoverFromMangaconnectorWorker(MangaConnectorId<Manga> mcId,
: BaseWorkerWithContext<MangaContext>(dependsOn)
{
internal readonly string MangaConnectorIdId = mcId.Key;
protected override BaseWorker[] DoWorkInternal()
protected override async Task<BaseWorker[]> DoWorkInternal()
{
if (DbContext.MangaConnectorToManga.Find(MangaConnectorIdId) is not { } mangaConnectorId)
if (await DbContext.MangaConnectorToManga.FirstOrDefaultAsync(c => c.Key == MangaConnectorIdId) is not { } mangaConnectorId)
return []; //TODO Exception?
if (!Tranga.TryGetMangaConnector(mangaConnectorId.MangaConnectorName, out MangaConnector? mangaConnector))
return []; //TODO Exception?
DbContext.Entry(mangaConnectorId).Navigation(nameof(MangaConnectorId<Manga>.Obj)).Load();
await DbContext.Entry(mangaConnectorId).Navigation(nameof(MangaConnectorId<Manga>.Obj)).LoadAsync(CancellationTokenSource.Token);
Manga manga = mangaConnectorId.Obj;
manga.CoverFileNameInCache = mangaConnector.SaveCoverImageToCache(mangaConnectorId);
DbContext.Sync();
await DbContext.Sync(CancellationTokenSource.Token);
return [];
}

View File

@@ -1,5 +1,6 @@
using API.MangaConnectors;
using API.Schema.MangaContext;
using Microsoft.EntityFrameworkCore;
namespace API.Workers;
@@ -7,16 +8,16 @@ public class RetrieveMangaChaptersFromMangaconnectorWorker(MangaConnectorId<Mang
: BaseWorkerWithContext<MangaContext>(dependsOn)
{
internal readonly string MangaConnectorIdId = mcId.Key;
protected override BaseWorker[] DoWorkInternal()
protected override async Task<BaseWorker[]> DoWorkInternal()
{
if (DbContext.MangaConnectorToManga.Find(MangaConnectorIdId) is not { } mangaConnectorId)
if (await DbContext.MangaConnectorToManga.FirstOrDefaultAsync(c => c.Key == MangaConnectorIdId) is not { } mangaConnectorId)
return []; //TODO Exception?
if (!Tranga.TryGetMangaConnector(mangaConnectorId.MangaConnectorName, out MangaConnector? mangaConnector))
return []; //TODO Exception?
DbContext.Entry(mangaConnectorId).Navigation(nameof(MangaConnectorId<Manga>.Obj)).Load();
await DbContext.Entry(mangaConnectorId).Navigation(nameof(MangaConnectorId<Manga>.Obj)).LoadAsync(CancellationTokenSource.Token);
Manga manga = mangaConnectorId.Obj;
DbContext.Entry(manga).Collection(m => m.Chapters).Load();
await DbContext.Entry(manga).Collection(m => m.Chapters).LoadAsync(CancellationTokenSource.Token);
// This gets all chapters that are not downloaded
(Chapter, MangaConnectorId<Chapter>)[] allChapters =
@@ -25,13 +26,13 @@ public class RetrieveMangaChaptersFromMangaconnectorWorker(MangaConnectorId<Mang
int addedChapters = 0;
foreach ((Chapter chapter, MangaConnectorId<Chapter> mcId) newChapter in allChapters)
{
if (Tranga.AddChapterToContext(newChapter, DbContext, out Chapter? addedChapter) == false)
if (Tranga.AddChapterToContext(newChapter, DbContext, out Chapter? addedChapter, CancellationTokenSource.Token) == false)
continue;
manga.Chapters.Add(addedChapter);
}
Log.Info($"{manga.Chapters.Count} existing + {addedChapters} new chapters.");
DbContext.Sync();
await DbContext.Sync(CancellationTokenSource.Token);
return [];
}

View File

@@ -6,7 +6,7 @@ public class MoveFileOrFolderWorker(string toLocation, string fromLocation, IEnu
public readonly string FromLocation = fromLocation;
public readonly string ToLocation = toLocation;
protected override BaseWorker[] DoWorkInternal()
protected override Task<BaseWorker[]> DoWorkInternal()
{
try
{
@@ -14,13 +14,13 @@ public class MoveFileOrFolderWorker(string toLocation, string fromLocation, IEnu
if (!fi.Exists)
{
Log.Error($"File does not exist at {FromLocation}");
return [];
return new Task<BaseWorker[]>(() => []);
}
if (File.Exists(ToLocation))//Do not override existing
{
Log.Error($"File already exists at {ToLocation}");
return [];
return new Task<BaseWorker[]>(() => []);
}
if(fi.Attributes.HasFlag(FileAttributes.Directory))
MoveDirectory(fi, ToLocation);
@@ -32,7 +32,7 @@ public class MoveFileOrFolderWorker(string toLocation, string fromLocation, IEnu
Log.Error(e);
}
return [];
return new Task<BaseWorker[]>(() => []);
}
private void MoveDirectory(FileInfo from, string toLocation)

View File

@@ -1,4 +1,5 @@
using API.Schema.MangaContext;
using Microsoft.EntityFrameworkCore;
namespace API.Workers;
@@ -7,20 +8,20 @@ public class MoveMangaLibraryWorker(Manga manga, FileLibrary toLibrary, IEnumera
{
internal readonly string MangaId = manga.Key;
internal readonly string LibraryId = toLibrary.Key;
protected override BaseWorker[] DoWorkInternal()
protected override async Task<BaseWorker[]> DoWorkInternal()
{
if (DbContext.Mangas.Find(MangaId) is not { } manga)
if (await DbContext.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, CancellationTokenSource.Token) is not { } manga)
return []; //TODO Exception?
if (DbContext.FileLibraries.Find(LibraryId) is not { } toLibrary)
if (await DbContext.FileLibraries.FirstOrDefaultAsync(l => l.Key == LibraryId, CancellationTokenSource.Token) is not { } toLibrary)
return []; //TODO Exception?
DbContext.Entry(manga).Collection(m => m.Chapters).Load();
DbContext.Entry(manga).Navigation(nameof(Manga.Library)).Load();
await DbContext.Entry(manga).Collection(m => m.Chapters).LoadAsync(CancellationTokenSource.Token);
await DbContext.Entry(manga).Navigation(nameof(Manga.Library)).LoadAsync(CancellationTokenSource.Token);
Dictionary<Chapter, string> oldPath = manga.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath);
manga.Library = toLibrary;
if (DbContext.Sync() is { success: false })
if (await DbContext.Sync(CancellationTokenSource.Token) is { success: false })
return [];
return manga.Chapters.Select(c => new MoveFileOrFolderWorker(c.FullArchiveFilePath, oldPath[c])).ToArray<BaseWorker>();

View File

@@ -9,7 +9,7 @@ public class CheckForNewChaptersWorker(TimeSpan? interval = null, IEnumerable<Ba
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
public TimeSpan Interval { get; set; } = interval??TimeSpan.FromMinutes(60);
protected override BaseWorker[] DoWorkInternal()
protected override Task<BaseWorker[]> DoWorkInternal()
{
IQueryable<MangaConnectorId<Manga>> connectorIdsManga = DbContext.MangaConnectorToManga
.Include(id => id.Obj)
@@ -19,7 +19,7 @@ public class CheckForNewChaptersWorker(TimeSpan? interval = null, IEnumerable<Ba
foreach (MangaConnectorId<Manga> mangaConnectorId in connectorIdsManga)
newWorkers.Add(new RetrieveMangaChaptersFromMangaconnectorWorker(mangaConnectorId, Tranga.Settings.DownloadLanguage));
return newWorkers.ToArray();
return new Task<BaseWorker[]>(() => newWorkers.ToArray());
}
}

View File

@@ -8,11 +8,11 @@ public class CleanupMangaCoversWorker(TimeSpan? interval = null, IEnumerable<Bas
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(24);
protected override BaseWorker[] DoWorkInternal()
protected override Task<BaseWorker[]> DoWorkInternal()
{
Log.Info("Removing stale files...");
if (!Directory.Exists(TrangaSettings.coverImageCache))
return [];
return new Task<BaseWorker[]>(() => []);
string[] usedFiles = DbContext.Mangas.Select(m => m.CoverFileNameInCache).Where(s => s != null).ToArray()!;
string[] extraneousFiles = new DirectoryInfo(TrangaSettings.coverImageCache).GetFiles()
.Where(f => usedFiles.Contains(f.FullName) == false)
@@ -23,7 +23,6 @@ public class CleanupMangaCoversWorker(TimeSpan? interval = null, IEnumerable<Bas
Log.Info($"Deleting {path}");
File.Delete(path);
}
return [];
return new Task<BaseWorker[]>(() => []);
}
}

View File

@@ -8,11 +8,12 @@ public class RemoveOldNotificationsWorker(TimeSpan? interval = null, IEnumerable
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(1);
protected override BaseWorker[] DoWorkInternal()
protected override async Task<BaseWorker[]> DoWorkInternal()
{
IQueryable<Notification> toRemove = DbContext.Notifications.Where(n => n.IsSent || DateTime.UtcNow - n.Date > Interval);
DbContext.RemoveRange(toRemove);
DbContext.Sync();
await DbContext.Sync(CancellationTokenSource.Token);
return [];
}

View File

@@ -8,7 +8,7 @@ public class SendNotificationsWorker(TimeSpan? interval = null, IEnumerable<Base
{
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
public TimeSpan Interval { get; set; } = interval??TimeSpan.FromMinutes(1);
protected override BaseWorker[] DoWorkInternal()
protected override async Task<BaseWorker[]> DoWorkInternal()
{
NotificationConnector[] connectors = DbContext.NotificationConnectors.ToArray();
Notification[] notifications = DbContext.Notifications.Where(n => n.IsSent == false).ToArray();
@@ -22,7 +22,7 @@ public class SendNotificationsWorker(TimeSpan? interval = null, IEnumerable<Base
}
}
DbContext.Sync();
await DbContext.Sync(CancellationTokenSource.Token);
return [];
}

View File

@@ -9,7 +9,7 @@ public class StartNewChapterDownloadsWorker(TimeSpan? interval = null, IEnumerab
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromMinutes(1);
protected override BaseWorker[] DoWorkInternal()
protected override Task<BaseWorker[]> DoWorkInternal()
{
IQueryable<MangaConnectorId<Chapter>> mangaConnectorIds = DbContext.MangaConnectorToChapter
.Include(id => id.Obj)
@@ -19,6 +19,6 @@ public class StartNewChapterDownloadsWorker(TimeSpan? interval = null, IEnumerab
foreach (MangaConnectorId<Chapter> mangaConnectorId in mangaConnectorIds)
newWorkers.Add(new DownloadChapterFromMangaconnectorWorker(mangaConnectorId));
return newWorkers.ToArray();
return new Task<BaseWorker[]>(() => newWorkers.ToArray());
}
}

View File

@@ -8,12 +8,12 @@ public class UpdateChaptersDownloadedWorker(TimeSpan? interval = null, IEnumerab
{
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
public TimeSpan Interval { get; set; } = interval??TimeSpan.FromMinutes(60);
protected override BaseWorker[] DoWorkInternal()
protected override async Task<BaseWorker[]> DoWorkInternal()
{
foreach (Chapter dbContextChapter in DbContext.Chapters.Include(c => c.ParentManga))
dbContextChapter.Downloaded = dbContextChapter.CheckDownloaded();
DbContext.Sync();
await DbContext.Sync(CancellationTokenSource.Token);
return [];
}
}

View File

@@ -9,11 +9,11 @@ public class UpdateCoversWorker(TimeSpan? interval = null, IEnumerable<BaseWorke
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(6);
protected override BaseWorker[] DoWorkInternal()
protected override Task<BaseWorker[]> DoWorkInternal()
{
List<BaseWorker> workers = new();
foreach (MangaConnectorId<Manga> mangaConnectorId in DbContext.MangaConnectorToManga)
workers.Add(new DownloadCoverFromMangaconnectorWorker(mangaConnectorId));
return workers.ToArray();
return new Task<BaseWorker[]>(() => workers.ToArray());
}
}

View File

@@ -11,7 +11,7 @@ public class UpdateMetadataWorker(TimeSpan? interval = null, IEnumerable<BaseWor
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(12);
protected override BaseWorker[] DoWorkInternal()
protected override async Task<BaseWorker[]> DoWorkInternal()
{
IQueryable<string> mangaIds = DbContext.MangaConnectorToManga
.Where(m => m.UseForDownload)
@@ -22,9 +22,9 @@ public class UpdateMetadataWorker(TimeSpan? interval = null, IEnumerable<BaseWor
mangaIds.Any(id => id == e.MangaId));
foreach (MetadataEntry metadataEntry in metadataEntriesToUpdate)
metadataEntry.MetadataFetcher.UpdateMetadata(metadataEntry, DbContext);
await metadataEntry.MetadataFetcher.UpdateMetadata(metadataEntry, DbContext, CancellationTokenSource.Token);
DbContext.Sync();
await DbContext.Sync(CancellationTokenSource.Token);
return [];
}