From 3b8570cf57cbcae75d383e594dd721b4b039ded6 Mon Sep 17 00:00:00 2001 From: glax Date: Mon, 1 Sep 2025 23:26:49 +0200 Subject: [PATCH] Allow requests to be cancelled. Make workers have a CancellationTokenSource --- API/Controllers/FileLibraryController.cs | 37 +++-- API/Controllers/LibraryConnectorController.cs | 23 +-- API/Controllers/MaintenanceController.cs | 14 +- API/Controllers/MangaConnectorController.cs | 4 +- API/Controllers/MangaController.cs | 156 ++++++++++-------- API/Controllers/MetadataFetcherController.cs | 35 ++-- .../NotificationConnectorController.cs | 38 +++-- API/Controllers/QueryController.cs | 42 +++-- API/Controllers/SearchController.cs | 8 +- API/Controllers/WorkerController.cs | 3 +- API/Program.cs | 9 +- API/Schema/Identifiable.cs | 6 +- API/Schema/MangaContext/MangaContext.cs | 12 +- .../MetadataFetchers/MetadataFetcher.cs | 2 +- .../MetadataFetchers/MyAnimeList.cs | 16 +- API/Schema/TrangaBaseContext.cs | 4 +- API/Tranga.cs | 17 +- API/Workers/BaseWorker.cs | 13 +- ...DownloadChapterFromMangaconnectorWorker.cs | 27 +-- .../DownloadCoverFromMangaconnectorWorker.cs | 9 +- ...veMangaChaptersFromMangaconnectorWorker.cs | 13 +- API/Workers/MoveFileOrFolderWorker.cs | 8 +- API/Workers/MoveMangaLibraryWorker.cs | 13 +- .../CheckForNewChaptersWorker.cs | 4 +- .../CleanupMangaCoversWorker.cs | 7 +- .../RemoveOldNotificationsWorker.cs | 5 +- .../SendNotificationsWorker.cs | 4 +- .../StartNewChapterDownloadsWorker.cs | 4 +- .../UpdateChaptersDownloadedWorker.cs | 4 +- .../PeriodicWorkers/UpdateCoversWorker.cs | 4 +- .../PeriodicWorkers/UpdateMetadataWorker.cs | 6 +- 31 files changed, 296 insertions(+), 251 deletions(-) diff --git a/API/Controllers/FileLibraryController.cs b/API/Controllers/FileLibraryController.cs index 84e52bf..bd9df1d 100644 --- a/API/Controllers/FileLibraryController.cs +++ b/API/Controllers/FileLibraryController.cs @@ -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 /// /// + /// Error during Database Operation [HttpGet] [ProducesResponseType(Status200OK, "application/json")] - public IActionResult GetFileLibraries() + public async Task GetFileLibraries () { - return Ok(context.FileLibraries.ToArray()); + if(await context.FileLibraries.ToArrayAsync(HttpContext.RequestAborted) is not { } result) + return StatusCode(Status500InternalServerError); + return Ok(result); } /// @@ -31,9 +35,9 @@ public class FileLibraryController(MangaContext context) : Controller [HttpGet("{FileLibraryId}")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] - public IActionResult GetFileLibrary(string FileLibraryId) + public async Task 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(Status500InternalServerError, "text/plain")] - public IActionResult ChangeLibraryBasePath(string FileLibraryId, [FromBody]string newBasePath) + public async Task 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(Status500InternalServerError, "text/plain")] - public IActionResult ChangeLibraryName(string FileLibraryId, [FromBody] string newName) + public async Task 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(); } /// - /// Creates new + /// Creates new /// /// New to add /// @@ -99,13 +103,12 @@ public class FileLibraryController(MangaContext context) : Controller [HttpPut] [ProducesResponseType(Status201Created)] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult CreateNewLibrary([FromBody]FileLibrary library) + public async Task 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(Status500InternalServerError, "text/plain")] - public IActionResult DeleteLocalLibrary(string FileLibraryId) + public async Task 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(); } diff --git a/API/Controllers/LibraryConnectorController.cs b/API/Controllers/LibraryConnectorController.cs index d1f934c..f2865fa 100644 --- a/API/Controllers/LibraryConnectorController.cs +++ b/API/Controllers/LibraryConnectorController.cs @@ -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 /// /// + /// Error during Database Operation [HttpGet] [ProducesResponseType(Status200OK, "application/json")] - public IActionResult GetAllConnectors() + public async Task 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(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] - public IActionResult GetConnector(string LibraryConnectorId) + public async Task 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(Status500InternalServerError, "text/plain")] - public IActionResult CreateConnector([FromBody]LibraryConnector libraryConnector) + public async Task 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 /// /// ToFileLibrary-Connector-ID /// - /// with < not found. + /// with not found. /// Error during Database Operation [HttpDelete("{LibraryConnectorId}")] [ProducesResponseType(Status200OK)] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult DeleteConnector(string LibraryConnectorId) + public async Task 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(); } diff --git a/API/Controllers/MaintenanceController.cs b/API/Controllers/MaintenanceController.cs index 844bb6d..caab816 100644 --- a/API/Controllers/MaintenanceController.cs +++ b/API/Controllers/MaintenanceController.cs @@ -21,15 +21,17 @@ public class MaintenanceController(MangaContext mangaContext) : Controller [HttpPost("CleanupNoDownloadManga")] [ProducesResponseType(Status200OK)] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult CleanupNoDownloadManga() + public async Task 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(); } diff --git a/API/Controllers/MangaConnectorController.cs b/API/Controllers/MangaConnectorController.cs index 23d4a7b..b04a0f6 100644 --- a/API/Controllers/MangaConnectorController.cs +++ b/API/Controllers/MangaConnectorController.cs @@ -75,14 +75,14 @@ public class MangaConnectorController(MangaContext context) : Controller [ProducesResponseType(Status202Accepted)] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult SetEnabled(string MangaConnectorName, bool Enabled) + public async Task 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(); } diff --git a/API/Controllers/MangaController.cs b/API/Controllers/MangaController.cs index 1082adb..5f0d222 100644 --- a/API/Controllers/MangaController.cs +++ b/API/Controllers/MangaController.cs @@ -25,49 +25,65 @@ public class MangaController(MangaContext context) : Controller /// Returns all cached /// /// + /// Error during Database Operation [HttpGet] [ProducesResponseType(Status200OK, "application/json")] - public IActionResult GetAllManga() + public async Task GetAllManga () { - return Ok(context.Mangas.ToArray()); + if(await context.Mangas.ToArrayAsync(HttpContext.RequestAborted) is not { } result) + return StatusCode(Status500InternalServerError); + + return Ok(result); } /// /// Returns all cached .Keys /// /// Keys/IDs + /// Error during Database Operation [HttpGet("Keys")] [ProducesResponseType(Status200OK, "application/json")] - public IActionResult GetAllMangaKeys() + public async Task 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); } /// /// Returns all that are being downloaded from at least one /// /// + /// Error during Database Operation [HttpGet("Downloading")] [ProducesResponseType(Status200OK, "application/json")] - public IActionResult GetMangaDownloading() + public async Task 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); } /// /// Returns all cached with /// - /// Array of <.Key + /// Array of .Key /// + /// Error during Database Operation [HttpPost("WithIDs")] [ProducesResponseType(Status200OK, "application/json")] - public IActionResult GetManga([FromBody]string[] MangaIds) + public async Task 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); } /// @@ -79,10 +95,11 @@ public class MangaController(MangaContext context) : Controller [HttpGet("{MangaId}")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] - public IActionResult GetManga(string MangaId) + public async Task 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(Status500InternalServerError, "text/plain")] - public IActionResult DeleteManga(string MangaId) + public async Task 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(Status200OK,"image/jpeg")] [ProducesResponseType(Status404NotFound)] - public IActionResult MergeIntoManga(string MangaIdFrom, string MangaIdInto) + public async Task 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(Status503ServiceUnavailable, "text/plain")] - public IActionResult GetCover(string MangaId, [FromQuery]int? width, [FromQuery]int? height) + public async Task 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(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] - public IActionResult GetChapters(string MangaId) + public async Task 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(Status200OK, "application/json")] [ProducesResponseType(Status204NoContent)] [ProducesResponseType(Status404NotFound)] - public IActionResult GetChaptersDownloaded(string MangaId) + public async Task 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 chapters = manga.Chapters.Where(c => c.Downloaded).ToList(); if (chapters.Count == 0) @@ -251,12 +267,12 @@ public class MangaController(MangaContext context) : Controller [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status204NoContent)] [ProducesResponseType(Status404NotFound)] - public IActionResult GetChaptersNotDownloaded(string MangaId) + public async Task 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 chapters = manga.Chapters.Where(c => c.Downloaded == false).ToList(); if (chapters.Count == 0) @@ -280,12 +296,12 @@ public class MangaController(MangaContext context) : Controller [ProducesResponseType(Status404NotFound, "text/plain")] [ProducesResponseType(Status500InternalServerError, "text/plain")] [ProducesResponseType(Status503ServiceUnavailable, "text/plain")] - public IActionResult GetLatestChapter(string MangaId) + public async Task 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 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(Status412PreconditionFailed, "text/plain")] [ProducesResponseType(Status503ServiceUnavailable, "text/plain")] - public IActionResult GetLatestChapterDownloaded(string MangaId) + public async Task 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 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(Status500InternalServerError, "text/plain")] - public IActionResult IgnoreChaptersBefore(string MangaId, [FromBody]float chapterThreshold) + public async Task 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 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(Status412PreconditionFailed, "text/plain")] [ProducesResponseType(Status428PreconditionRequired, "text/plain")] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult MarkAsRequested(string MangaId, string MangaConnectorName, bool IsRequested) + public async Task 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(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status406NotAcceptable)] - public IActionResult SearchOnDifferentConnector(string MangaId, string MangaConnectorName) + public async Task 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 /// with [HttpGet("WithAuthorId/{AuthorId}")] [ProducesResponseType(Status200OK, "application/json")] - public IActionResult GetMangaWithAuthorIds(string AuthorId) + public async Task 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 /// not found [HttpGet("WithTag/{Tag}")] [ProducesResponseType(Status200OK, "application/json")] - public IActionResult GetMangasWithTag(string Tag) + public async Task 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))); } diff --git a/API/Controllers/MetadataFetcherController.cs b/API/Controllers/MetadataFetcherController.cs index 6950a3c..c215b7a 100644 --- a/API/Controllers/MetadataFetcherController.cs +++ b/API/Controllers/MetadataFetcherController.cs @@ -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 /// Names of (Metadata-Sites) [HttpGet] [ProducesResponseType(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 /// /// + /// Error during Database Operation [HttpGet("Links")] [ProducesResponseType(Status200OK, "application/json")] - public IActionResult GetLinkedEntries() + public async Task GetLinkedEntries () { - return Ok(context.MetadataEntries.ToArray()); + if (await context.MetadataEntries.ToArrayAsync() is not { } result) + return StatusCode(Status500InternalServerError); + + return Ok(result); } /// @@ -48,10 +53,10 @@ public class MetadataFetcherController(MangaContext context) : Controller [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status400BadRequest)] [ProducesResponseType(Status404NotFound)] - public IActionResult SearchMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]string? searchTerm = null) + public async Task 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(Status500InternalServerError, "text/plain")] - public IActionResult LinkMangaMetadata(string MangaId, string MetadataFetcherName, [FromBody]string Identifier) + public async Task 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(Status412PreconditionFailed, "text/plain")] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult UnlinkMangaMetadata(string MangaId, string MetadataFetcherName) + public async Task 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(); } diff --git a/API/Controllers/NotificationConnectorController.cs b/API/Controllers/NotificationConnectorController.cs index 70f74ef..95a98c6 100644 --- a/API/Controllers/NotificationConnectorController.cs +++ b/API/Controllers/NotificationConnectorController.cs @@ -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 /// /// + /// Error during Database Operation [HttpGet] [ProducesResponseType(Status200OK, "application/json")] - public IActionResult GetAllConnectors() + public async Task GetAllConnectors () { + if(await context.NotificationConnectors.ToArrayAsync(HttpContext.RequestAborted) is not { } result) + return StatusCode(Status500InternalServerError); - return Ok(context.NotificationConnectors.ToArray()); + return Ok(result); } /// @@ -36,10 +40,10 @@ public class NotificationConnectorController(NotificationsContext context) : Con [HttpGet("{Name}")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] - public IActionResult GetConnector(string Name) + public async Task 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(Status200OK, "text/plain")] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult CreateConnector([FromBody]NotificationConnector notificationConnector) + public async Task 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(Status200OK, "text/plain")] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult CreateGotifyConnector([FromBody]GotifyRecord gotifyData) + public async Task CreateGotifyConnector ([FromBody]GotifyRecord gotifyData) { //TODO Validate Data @@ -81,7 +85,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con new Dictionary() { { "X-Gotify-Key", gotifyData.AppToken } }, "POST", $"{{\"message\": \"%text\", \"title\": \"%title\", \"Priority\": {gotifyData.Priority}}}"); - return CreateConnector(gotifyConnector); + return await CreateConnector(gotifyConnector); } /// @@ -93,7 +97,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con [HttpPut("Ntfy")] [ProducesResponseType(Status200OK, "text/plain")] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult CreateNtfyConnector([FromBody]NtfyRecord ntfyRecord) + public async Task 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); } /// @@ -120,7 +124,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con [HttpPut("Pushover")] [ProducesResponseType(Status200OK, "text/plain")] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult CreatePushoverConnector([FromBody]PushoverRecord pushoverRecord) + public async Task CreatePushoverConnector ([FromBody]PushoverRecord pushoverRecord) { //TODO Validate Data @@ -129,7 +133,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con new Dictionary(), "POST", $"{{\"token\": \"{pushoverRecord.AppToken}\", \"user\": \"{pushoverRecord.User}\", \"message:\":\"%text\", \"%title\" }}"); - return CreateConnector(pushoverConnector); + return await CreateConnector(pushoverConnector); } /// @@ -143,14 +147,14 @@ public class NotificationConnectorController(NotificationsContext context) : Con [ProducesResponseType(Status200OK)] [ProducesResponseType(Status404NotFound)] [ProducesResponseType(Status500InternalServerError, "text/plain")] - public IActionResult DeleteConnector(string Name) + public async Task 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(); } diff --git a/API/Controllers/QueryController.cs b/API/Controllers/QueryController.cs index 27b1af1..0318b5d 100644 --- a/API/Controllers/QueryController.cs +++ b/API/Controllers/QueryController.cs @@ -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(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] - public IActionResult GetAuthor(string AuthorId) + public async Task 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(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] - public IActionResult GetChapter(string ChapterId) + public async Task 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>(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] - public IActionResult GetMangaMangaConnectorId(string MangaConnectorIdId) + public async Task 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 /// Key of /// /// with not found + /// Error during Database Operation [HttpGet("Manga/{MangaId}/SimilarName")] [ProducesResponseType(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] - public IActionResult GetSimilarManga(string MangaId) + public async Task 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 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>(Status200OK, "application/json")] [ProducesResponseType(Status404NotFound)] - public IActionResult GetChapterMangaConnectorId(string MangaConnectorIdId) + public async Task 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); } diff --git a/API/Controllers/SearchController.cs b/API/Controllers/SearchController.cs index 4f89d1a..7d38834 100644 --- a/API/Controllers/SearchController.cs +++ b/API/Controllers/SearchController.cs @@ -24,7 +24,7 @@ public class SearchController(MangaContext context) : Controller [ProducesResponseType(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 retMangas = new(); foreach ((Manga manga, MangaConnectorId 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(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); diff --git a/API/Controllers/WorkerController.cs b/API/Controllers/WorkerController.cs index 9c8f807..871df70 100644 --- a/API/Controllers/WorkerController.cs +++ b/API/Controllers/WorkerController.cs @@ -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; diff --git a/API/Program.cs b/API/Program.cs index 4a53e16..5004004 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -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(opts => LogManager.GetLogger("API")); +builder.Services.AddScoped(_ => 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(); context.Database.Migrate(); - context.Sync(); + await context.Sync(CancellationToken.None); } Tranga.SetServiceProvider(app.Services); diff --git a/API/Schema/Identifiable.cs b/API/Schema/Identifiable.cs index fe05db6..93b1980 100644 --- a/API/Schema/Identifiable.cs +++ b/API/Schema/Identifiable.cs @@ -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; } diff --git a/API/Schema/MangaContext/MangaContext.cs b/API/Schema/MangaContext/MangaContext.cs index 9a1f2ca..38a0f5c 100644 --- a/API/Schema/MangaContext/MangaContext.cs +++ b/API/Schema/MangaContext/MangaContext.cs @@ -104,15 +104,15 @@ public class MangaContext(DbContextOptions options) : TrangaBaseCo .OnDelete(DeleteBehavior.Cascade); } - public Manga? FindMangaLike(Manga other) + public async Task 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>> MangaIncludeAll() => Mangas.Include(m => m.Library) diff --git a/API/Schema/MangaContext/MetadataFetchers/MetadataFetcher.cs b/API/Schema/MangaContext/MetadataFetchers/MetadataFetcher.cs index 37c36b8..e436795 100644 --- a/API/Schema/MangaContext/MetadataFetchers/MetadataFetcher.cs +++ b/API/Schema/MangaContext/MetadataFetchers/MetadataFetcher.cs @@ -31,5 +31,5 @@ public abstract class MetadataFetcher /// /// Updates the Manga linked in the MetadataEntry /// - public abstract void UpdateMetadata(MetadataEntry metadataEntry, MangaContext dbContext); + public abstract Task UpdateMetadata(MetadataEntry metadataEntry, MangaContext dbContext, CancellationToken token); } \ No newline at end of file diff --git a/API/Schema/MangaContext/MetadataFetchers/MyAnimeList.cs b/API/Schema/MangaContext/MetadataFetchers/MyAnimeList.cs index 6748868..f4fd305 100644 --- a/API/Schema/MangaContext/MetadataFetchers/MyAnimeList.cs +++ b/API/Schema/MangaContext/MetadataFetchers/MyAnimeList.cs @@ -43,21 +43,25 @@ public class MyAnimeList : MetadataFetcher /// /// /// + /// /// /// - 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); } } \ No newline at end of file diff --git a/API/Schema/TrangaBaseContext.cs b/API/Schema/TrangaBaseContext.cs index 58f6239..c5c6261 100644 --- a/API/Schema/TrangaBaseContext.cs +++ b/API/Schema/TrangaBaseContext.cs @@ -22,11 +22,11 @@ public abstract class TrangaBaseContext : DbContext where T : DbContext }, Array.Empty(), 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) diff --git a/API/Tranga.cs b/API/Tranga.cs index a2500d7..71e63c2 100644 --- a/API/Tranga.cs +++ b/API/Tranga.cs @@ -158,12 +158,13 @@ public static class Tranga RunningWorkers.Remove(worker, out _); } - internal static bool AddMangaToContext((Manga, MangaConnectorId) addManga, MangaContext context, [NotNullWhen(true)]out Manga? manga) => AddMangaToContext(addManga.Item1, addManga.Item2, context, out manga); + internal static bool AddMangaToContext((Manga, MangaConnectorId) 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 addMcId, MangaContext context, [NotNullWhen(true)]out Manga? manga) + internal static bool AddMangaToContext(Manga addManga, MangaConnectorId 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 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) addChapter, MangaContext context, - [NotNullWhen(true)] out Chapter? chapter) => AddChapterToContext(addChapter.Item1, addChapter.Item2, context, out chapter); + internal static bool AddChapterToContext((Chapter, MangaConnectorId) 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 addChId, MangaContext context, [NotNullWhen(true)] out Chapter? chapter) + internal static bool AddChapterToContext(Chapter addChapter, MangaConnectorId 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; } diff --git a/API/Workers/BaseWorker.cs b/API/Workers/BaseWorker.cs index 662826a..99f8658 100644 --- a/API/Workers/BaseWorker.cs +++ b/API/Workers/BaseWorker.cs @@ -26,7 +26,7 @@ public abstract class BaseWorker : Identifiable public IEnumerable 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; } /// @@ -36,7 +36,7 @@ public abstract class BaseWorker : Identifiable { Log.Debug($"Cancelled {this}"); this.State = WorkerExecutionState.Cancelled; - CancellationTokenSource?.Cancel(); + CancellationTokenSource.Cancel(); } /// @@ -46,7 +46,7 @@ public abstract class BaseWorker : Identifiable { Log.Debug($"Failed {this}"); this.State = WorkerExecutionState.Failed; - CancellationTokenSource?.Cancel(); + CancellationTokenSource.Cancel(); } public BaseWorker(IEnumerable? dependsOn = null) @@ -89,10 +89,9 @@ public abstract class BaseWorker : Identifiable // Run the actual work Log.Info($"Running {this}"); DateTime startTime = DateTime.UtcNow; - Task task = new (DoWorkInternal, CancellationTokenSource.Token); + Task 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 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); } diff --git a/API/Workers/MangaDownloadWorkers/DownloadChapterFromMangaconnectorWorker.cs b/API/Workers/MangaDownloadWorkers/DownloadChapterFromMangaconnectorWorker.cs index 213f2a2..e0abf5c 100644 --- a/API/Workers/MangaDownloadWorkers/DownloadChapterFromMangaconnectorWorker.cs +++ b/API/Workers/MangaDownloadWorkers/DownloadChapterFromMangaconnectorWorker.cs @@ -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 c : BaseWorkerWithContext(dependsOn) { internal readonly string MangaConnectorIdId = chId.Key; - protected override BaseWorker[] DoWorkInternal() + protected override async Task 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.Obj)).Load(); + await DbContext.Entry(mangaConnectorId).Navigation(nameof(MangaConnectorId.Obj)).LoadAsync(CancellationTokenSource.Token); Chapter chapter = mangaConnectorId.Obj; if (chapter.Downloaded) { @@ -31,8 +32,8 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId 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 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 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 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 c } //TODO MangaConnector Selection - DbContext.Entry(manga).Collection(m => m.MangaConnectorIds).Load(); + await DbContext.Entry(manga).Collection(m => m.MangaConnectorIds).LoadAsync(CancellationTokenSource.Token); MangaConnectorId mangaConnectorId = manga.MangaConnectorIds.First(); if (!Tranga.TryGetMangaConnector(mangaConnectorId.MangaConnectorName, out MangaConnector? mangaConnector)) { @@ -172,7 +173,7 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId c } Log.Info($"Copying cover to {publicationFolder}"); - DbContext.Entry(mangaConnectorId).Navigation(nameof(MangaConnectorId.Obj)).Load(); + await DbContext.Entry(mangaConnectorId).Navigation(nameof(MangaConnectorId.Obj)).LoadAsync(CancellationTokenSource.Token); string? fileInCache = manga.CoverFileNameInCache ?? mangaConnector.SaveCoverImageToCache(mangaConnectorId); if (fileInCache is null) { diff --git a/API/Workers/MangaDownloadWorkers/DownloadCoverFromMangaconnectorWorker.cs b/API/Workers/MangaDownloadWorkers/DownloadCoverFromMangaconnectorWorker.cs index 194cbea..c6581b3 100644 --- a/API/Workers/MangaDownloadWorkers/DownloadCoverFromMangaconnectorWorker.cs +++ b/API/Workers/MangaDownloadWorkers/DownloadCoverFromMangaconnectorWorker.cs @@ -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 mcId, : BaseWorkerWithContext(dependsOn) { internal readonly string MangaConnectorIdId = mcId.Key; - protected override BaseWorker[] DoWorkInternal() + protected override async Task 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.Obj)).Load(); + await DbContext.Entry(mangaConnectorId).Navigation(nameof(MangaConnectorId.Obj)).LoadAsync(CancellationTokenSource.Token); Manga manga = mangaConnectorId.Obj; manga.CoverFileNameInCache = mangaConnector.SaveCoverImageToCache(mangaConnectorId); - DbContext.Sync(); + await DbContext.Sync(CancellationTokenSource.Token); return []; } diff --git a/API/Workers/MangaDownloadWorkers/RetrieveMangaChaptersFromMangaconnectorWorker.cs b/API/Workers/MangaDownloadWorkers/RetrieveMangaChaptersFromMangaconnectorWorker.cs index 716aad3..64434df 100644 --- a/API/Workers/MangaDownloadWorkers/RetrieveMangaChaptersFromMangaconnectorWorker.cs +++ b/API/Workers/MangaDownloadWorkers/RetrieveMangaChaptersFromMangaconnectorWorker.cs @@ -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(dependsOn) { internal readonly string MangaConnectorIdId = mcId.Key; - protected override BaseWorker[] DoWorkInternal() + protected override async Task 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.Obj)).Load(); + await DbContext.Entry(mangaConnectorId).Navigation(nameof(MangaConnectorId.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)[] allChapters = @@ -25,13 +26,13 @@ public class RetrieveMangaChaptersFromMangaconnectorWorker(MangaConnectorId 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 []; } diff --git a/API/Workers/MoveFileOrFolderWorker.cs b/API/Workers/MoveFileOrFolderWorker.cs index a1cda9e..acd8ff8 100644 --- a/API/Workers/MoveFileOrFolderWorker.cs +++ b/API/Workers/MoveFileOrFolderWorker.cs @@ -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 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(() => []); } if (File.Exists(ToLocation))//Do not override existing { Log.Error($"File already exists at {ToLocation}"); - return []; + return new Task(() => []); } 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(() => []); } private void MoveDirectory(FileInfo from, string toLocation) diff --git a/API/Workers/MoveMangaLibraryWorker.cs b/API/Workers/MoveMangaLibraryWorker.cs index 255c43b..cb6f3cb 100644 --- a/API/Workers/MoveMangaLibraryWorker.cs +++ b/API/Workers/MoveMangaLibraryWorker.cs @@ -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 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 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(); diff --git a/API/Workers/PeriodicWorkers/CheckForNewChaptersWorker.cs b/API/Workers/PeriodicWorkers/CheckForNewChaptersWorker.cs index 5866e5c..bbd86d3 100644 --- a/API/Workers/PeriodicWorkers/CheckForNewChaptersWorker.cs +++ b/API/Workers/PeriodicWorkers/CheckForNewChaptersWorker.cs @@ -9,7 +9,7 @@ public class CheckForNewChaptersWorker(TimeSpan? interval = null, IEnumerable DoWorkInternal() { IQueryable> connectorIdsManga = DbContext.MangaConnectorToManga .Include(id => id.Obj) @@ -19,7 +19,7 @@ public class CheckForNewChaptersWorker(TimeSpan? interval = null, IEnumerable mangaConnectorId in connectorIdsManga) newWorkers.Add(new RetrieveMangaChaptersFromMangaconnectorWorker(mangaConnectorId, Tranga.Settings.DownloadLanguage)); - return newWorkers.ToArray(); + return new Task(() => newWorkers.ToArray()); } } \ No newline at end of file diff --git a/API/Workers/PeriodicWorkers/MaintenanceWorkers/CleanupMangaCoversWorker.cs b/API/Workers/PeriodicWorkers/MaintenanceWorkers/CleanupMangaCoversWorker.cs index a507fe4..c30a96a 100644 --- a/API/Workers/PeriodicWorkers/MaintenanceWorkers/CleanupMangaCoversWorker.cs +++ b/API/Workers/PeriodicWorkers/MaintenanceWorkers/CleanupMangaCoversWorker.cs @@ -8,11 +8,11 @@ public class CleanupMangaCoversWorker(TimeSpan? interval = null, IEnumerable DoWorkInternal() { Log.Info("Removing stale files..."); if (!Directory.Exists(TrangaSettings.coverImageCache)) - return []; + return new Task(() => []); 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(() => []); } } \ No newline at end of file diff --git a/API/Workers/PeriodicWorkers/MaintenanceWorkers/RemoveOldNotificationsWorker.cs b/API/Workers/PeriodicWorkers/MaintenanceWorkers/RemoveOldNotificationsWorker.cs index e925105..b50e579 100644 --- a/API/Workers/PeriodicWorkers/MaintenanceWorkers/RemoveOldNotificationsWorker.cs +++ b/API/Workers/PeriodicWorkers/MaintenanceWorkers/RemoveOldNotificationsWorker.cs @@ -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 DoWorkInternal() { IQueryable toRemove = DbContext.Notifications.Where(n => n.IsSent || DateTime.UtcNow - n.Date > Interval); DbContext.RemoveRange(toRemove); - DbContext.Sync(); + + await DbContext.Sync(CancellationTokenSource.Token); return []; } diff --git a/API/Workers/PeriodicWorkers/SendNotificationsWorker.cs b/API/Workers/PeriodicWorkers/SendNotificationsWorker.cs index 0dd27fd..0e4edd1 100644 --- a/API/Workers/PeriodicWorkers/SendNotificationsWorker.cs +++ b/API/Workers/PeriodicWorkers/SendNotificationsWorker.cs @@ -8,7 +8,7 @@ public class SendNotificationsWorker(TimeSpan? interval = null, IEnumerable 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 DoWorkInternal() { IQueryable> mangaConnectorIds = DbContext.MangaConnectorToChapter .Include(id => id.Obj) @@ -19,6 +19,6 @@ public class StartNewChapterDownloadsWorker(TimeSpan? interval = null, IEnumerab foreach (MangaConnectorId mangaConnectorId in mangaConnectorIds) newWorkers.Add(new DownloadChapterFromMangaconnectorWorker(mangaConnectorId)); - return newWorkers.ToArray(); + return new Task(() => newWorkers.ToArray()); } } \ No newline at end of file diff --git a/API/Workers/PeriodicWorkers/UpdateChaptersDownloadedWorker.cs b/API/Workers/PeriodicWorkers/UpdateChaptersDownloadedWorker.cs index 9248b3e..e493d17 100644 --- a/API/Workers/PeriodicWorkers/UpdateChaptersDownloadedWorker.cs +++ b/API/Workers/PeriodicWorkers/UpdateChaptersDownloadedWorker.cs @@ -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 DoWorkInternal() { foreach (Chapter dbContextChapter in DbContext.Chapters.Include(c => c.ParentManga)) dbContextChapter.Downloaded = dbContextChapter.CheckDownloaded(); - DbContext.Sync(); + await DbContext.Sync(CancellationTokenSource.Token); return []; } } \ No newline at end of file diff --git a/API/Workers/PeriodicWorkers/UpdateCoversWorker.cs b/API/Workers/PeriodicWorkers/UpdateCoversWorker.cs index e872a87..5f7be48 100644 --- a/API/Workers/PeriodicWorkers/UpdateCoversWorker.cs +++ b/API/Workers/PeriodicWorkers/UpdateCoversWorker.cs @@ -9,11 +9,11 @@ public class UpdateCoversWorker(TimeSpan? interval = null, IEnumerable DoWorkInternal() { List workers = new(); foreach (MangaConnectorId mangaConnectorId in DbContext.MangaConnectorToManga) workers.Add(new DownloadCoverFromMangaconnectorWorker(mangaConnectorId)); - return workers.ToArray(); + return new Task(() => workers.ToArray()); } } \ No newline at end of file diff --git a/API/Workers/PeriodicWorkers/UpdateMetadataWorker.cs b/API/Workers/PeriodicWorkers/UpdateMetadataWorker.cs index 6ded373..e3aa9e0 100644 --- a/API/Workers/PeriodicWorkers/UpdateMetadataWorker.cs +++ b/API/Workers/PeriodicWorkers/UpdateMetadataWorker.cs @@ -11,7 +11,7 @@ public class UpdateMetadataWorker(TimeSpan? interval = null, IEnumerable DoWorkInternal() { IQueryable mangaIds = DbContext.MangaConnectorToManga .Where(m => m.UseForDownload) @@ -22,9 +22,9 @@ public class UpdateMetadataWorker(TimeSpan? interval = null, IEnumerable 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 []; }