mirror of
https://github.com/C9Glax/tranga.git
synced 2025-09-10 03:48:19 +02:00
Allow requests to be cancelled.
Make workers have a CancellationTokenSource
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
@@ -15,11 +16,14 @@ public class FileLibraryController(MangaContext context) : Controller
|
|||||||
/// Returns all <see cref="FileLibrary"/>
|
/// Returns all <see cref="FileLibrary"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<FileLibrary[]>(Status200OK, "application/json")]
|
[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>
|
/// <summary>
|
||||||
@@ -31,9 +35,9 @@ public class FileLibraryController(MangaContext context) : Controller
|
|||||||
[HttpGet("{FileLibraryId}")]
|
[HttpGet("{FileLibraryId}")]
|
||||||
[ProducesResponseType<FileLibrary>(Status200OK, "application/json")]
|
[ProducesResponseType<FileLibrary>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[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 NotFound();
|
||||||
|
|
||||||
return Ok(library);
|
return Ok(library);
|
||||||
@@ -51,15 +55,15 @@ public class FileLibraryController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[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();
|
return NotFound();
|
||||||
|
|
||||||
//TODO Path check
|
//TODO Path check
|
||||||
library.BasePath = newBasePath;
|
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 StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
@@ -77,21 +81,21 @@ public class FileLibraryController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[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();
|
return NotFound();
|
||||||
|
|
||||||
//TODO Name check
|
//TODO Name check
|
||||||
library.LibraryName = newName;
|
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 StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates new <see cref="FileLibraryId"/>
|
/// Creates new <see cref="FileLibrary"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="library">New <see cref="FileLibrary"/> to add</param>
|
/// <param name="library">New <see cref="FileLibrary"/> to add</param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
@@ -99,13 +103,12 @@ public class FileLibraryController(MangaContext context) : Controller
|
|||||||
[HttpPut]
|
[HttpPut]
|
||||||
[ProducesResponseType(Status201Created)]
|
[ProducesResponseType(Status201Created)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateNewLibrary([FromBody]FileLibrary library)
|
public async Task<IActionResult> CreateNewLibrary ([FromBody]FileLibrary library)
|
||||||
{
|
{
|
||||||
|
|
||||||
//TODO Parameter check
|
//TODO Parameter check
|
||||||
context.FileLibraries.Add(library);
|
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 StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||||
return Created();
|
return Created();
|
||||||
}
|
}
|
||||||
@@ -120,14 +123,14 @@ public class FileLibraryController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[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();
|
return NotFound();
|
||||||
|
|
||||||
context.FileLibraries.Remove(library);
|
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 StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using API.Schema.LibraryContext.LibraryConnectors;
|
using API.Schema.LibraryContext.LibraryConnectors;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
@@ -16,11 +17,13 @@ public class LibraryConnectorController(LibraryContext context) : Controller
|
|||||||
/// Gets all configured <see cref="LibraryConnector"/>
|
/// Gets all configured <see cref="LibraryConnector"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<LibraryConnector[]>(Status200OK, "application/json")]
|
[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);
|
return Ok(connectors);
|
||||||
}
|
}
|
||||||
@@ -34,9 +37,9 @@ public class LibraryConnectorController(LibraryContext context) : Controller
|
|||||||
[HttpGet("{LibraryConnectorId}")]
|
[HttpGet("{LibraryConnectorId}")]
|
||||||
[ProducesResponseType<LibraryConnector>(Status200OK, "application/json")]
|
[ProducesResponseType<LibraryConnector>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[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 NotFound();
|
||||||
|
|
||||||
return Ok(connector);
|
return Ok(connector);
|
||||||
@@ -51,12 +54,12 @@ public class LibraryConnectorController(LibraryContext context) : Controller
|
|||||||
[HttpPut]
|
[HttpPut]
|
||||||
[ProducesResponseType(Status201Created)]
|
[ProducesResponseType(Status201Created)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateConnector([FromBody]LibraryConnector libraryConnector)
|
public async Task<IActionResult> CreateConnector ([FromBody]LibraryConnector libraryConnector)
|
||||||
{
|
{
|
||||||
|
|
||||||
context.LibraryConnectors.Add(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 StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||||
return Created();
|
return Created();
|
||||||
}
|
}
|
||||||
@@ -66,20 +69,20 @@ public class LibraryConnectorController(LibraryContext context) : Controller
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="LibraryConnectorId">ToFileLibrary-Connector-ID</param>
|
/// <param name="LibraryConnectorId">ToFileLibrary-Connector-ID</param>
|
||||||
/// <response code="200"></response>
|
/// <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>
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpDelete("{LibraryConnectorId}")]
|
[HttpDelete("{LibraryConnectorId}")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[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();
|
return NotFound();
|
||||||
|
|
||||||
context.LibraryConnectors.Remove(connector);
|
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 StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
@@ -21,15 +21,17 @@ public class MaintenanceController(MangaContext mangaContext) : Controller
|
|||||||
[HttpPost("CleanupNoDownloadManga")]
|
[HttpPost("CleanupNoDownloadManga")]
|
||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CleanupNoDownloadManga()
|
public async Task<IActionResult> CleanupNoDownloadManga()
|
||||||
{
|
{
|
||||||
Manga[] noDownloads = mangaContext.Mangas
|
if (await mangaContext.Mangas
|
||||||
.Include(m => m.MangaConnectorIds)
|
.Include(m => m.MangaConnectorIds)
|
||||||
.Where(m => !m.MangaConnectorIds.Any(id => id.UseForDownload))
|
.Where(m => !m.MangaConnectorIds.Any(id => id.UseForDownload))
|
||||||
.ToArray();
|
.ToArrayAsync(HttpContext.RequestAborted) is not { } noDownloads)
|
||||||
|
return StatusCode(Status500InternalServerError);
|
||||||
|
|
||||||
mangaContext.Mangas.RemoveRange(noDownloads);
|
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 StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
@@ -75,14 +75,14 @@ public class MangaConnectorController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType(Status202Accepted)]
|
[ProducesResponseType(Status202Accepted)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[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)
|
if(Tranga.MangaConnectors.FirstOrDefault(c => c.Name.Equals(MangaConnectorName, StringComparison.InvariantCultureIgnoreCase)) is not { } connector)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
connector.Enabled = Enabled;
|
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 StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||||
return Accepted();
|
return Accepted();
|
||||||
}
|
}
|
||||||
|
@@ -25,49 +25,65 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
/// Returns all cached <see cref="Manga"/>
|
/// Returns all cached <see cref="Manga"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[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>
|
/// <summary>
|
||||||
/// Returns all cached <see cref="Manga"/>.Keys
|
/// Returns all cached <see cref="Manga"/>.Keys
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"><see cref="Manga"/> Keys/IDs</response>
|
/// <response code="200"><see cref="Manga"/> Keys/IDs</response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet("Keys")]
|
[HttpGet("Keys")]
|
||||||
[ProducesResponseType<string[]>(Status200OK, "application/json")]
|
[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>
|
/// <summary>
|
||||||
/// Returns all <see cref="Manga"/> that are being downloaded from at least one <see cref="MangaConnector"/>
|
/// Returns all <see cref="Manga"/> that are being downloaded from at least one <see cref="MangaConnector"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet("Downloading")]
|
[HttpGet("Downloading")]
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
||||||
public IActionResult GetMangaDownloading()
|
public async Task<IActionResult> GetMangaDownloading ()
|
||||||
{
|
{
|
||||||
Manga[] ret = context.MangaIncludeAll()
|
if(await context.MangaIncludeAll()
|
||||||
.Where(m => m.MangaConnectorIds.Any(id => id.UseForDownload))
|
.Where(m => m.MangaConnectorIds.Any(id => id.UseForDownload))
|
||||||
.ToArray();
|
.ToArrayAsync(HttpContext.RequestAborted) is not { } result)
|
||||||
return Ok(ret);
|
return StatusCode(Status500InternalServerError);
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all cached <see cref="Manga"/> with <paramref name="MangaIds"/>
|
/// Returns all cached <see cref="Manga"/> with <paramref name="MangaIds"/>
|
||||||
/// </summary>
|
/// </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="200"></response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpPost("WithIDs")]
|
[HttpPost("WithIDs")]
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[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();
|
if(await context.MangaIncludeAll()
|
||||||
return Ok(ret);
|
.Where(m => MangaIds.Contains(m.Key))
|
||||||
|
.ToArrayAsync(HttpContext.RequestAborted) is not { } result)
|
||||||
|
return StatusCode(Status500InternalServerError);
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -79,10 +95,11 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[HttpGet("{MangaId}")]
|
[HttpGet("{MangaId}")]
|
||||||
[ProducesResponseType<Manga>(Status200OK, "application/json")]
|
[ProducesResponseType<Manga>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[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 NotFound(nameof(MangaId));
|
||||||
|
|
||||||
return Ok(manga);
|
return Ok(manga);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,14 +114,14 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[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));
|
return NotFound(nameof(MangaId));
|
||||||
|
|
||||||
context.Mangas.Remove(manga);
|
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 StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
@@ -120,21 +137,20 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[HttpPatch("{MangaIdFrom}/MergeInto/{MangaIdInto}")]
|
[HttpPatch("{MangaIdFrom}/MergeInto/{MangaIdInto}")]
|
||||||
[ProducesResponseType<byte[]>(Status200OK,"image/jpeg")]
|
[ProducesResponseType<byte[]>(Status200OK,"image/jpeg")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[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));
|
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));
|
return NotFound(nameof(MangaIdInto));
|
||||||
|
|
||||||
|
|
||||||
foreach (CollectionEntry collectionEntry in context.Entry(from).Collections)
|
foreach (CollectionEntry collectionEntry in context.Entry(from).Collections)
|
||||||
collectionEntry.Load();
|
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
|
||||||
context.Entry(from).Navigation(nameof(Manga.Library)).Load();
|
await context.Entry(from).Navigation(nameof(Manga.Library)).LoadAsync(HttpContext.RequestAborted);
|
||||||
|
|
||||||
foreach (CollectionEntry collectionEntry in context.Entry(into).Collections)
|
foreach (CollectionEntry collectionEntry in context.Entry(into).Collections)
|
||||||
collectionEntry.Load();
|
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
|
||||||
context.Entry(into).Navigation(nameof(Manga.Library)).Load();
|
await context.Entry(into).Navigation(nameof(Manga.Library)).LoadAsync(HttpContext.RequestAborted);
|
||||||
|
|
||||||
BaseWorker[] newJobs = into.MergeFrom(from, context);
|
BaseWorker[] newJobs = into.MergeFrom(from, context);
|
||||||
Tranga.AddWorkers(newJobs);
|
Tranga.AddWorkers(newJobs);
|
||||||
@@ -159,9 +175,9 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType(Status400BadRequest)]
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<int>(Status503ServiceUnavailable, "text/plain")]
|
[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));
|
return NotFound(nameof(MangaId));
|
||||||
|
|
||||||
if (!System.IO.File.Exists(manga.CoverFileNameInCache))
|
if (!System.IO.File.Exists(manga.CoverFileNameInCache))
|
||||||
@@ -175,7 +191,7 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
Image image = Image.Load(manga.CoverFileNameInCache);
|
Image image = await Image.LoadAsync(manga.CoverFileNameInCache, HttpContext.RequestAborted);
|
||||||
|
|
||||||
if (width is { } w && height is { } h)
|
if (width is { } w && height is { } h)
|
||||||
{
|
{
|
||||||
@@ -189,7 +205,7 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
using MemoryStream ms = new();
|
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;
|
DateTime lastModified = new FileInfo(manga.CoverFileNameInCache).LastWriteTime;
|
||||||
HttpContext.Response.Headers.CacheControl = "public";
|
HttpContext.Response.Headers.CacheControl = "public";
|
||||||
return File(ms.GetBuffer(), "image/jpeg", new DateTimeOffset(lastModified), EntityTagHeaderValue.Parse($"\"{lastModified.Ticks}\""));
|
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")]
|
[HttpGet("{MangaId}/Chapters")]
|
||||||
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
|
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[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));
|
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();
|
Chapter[] chapters = manga.Chapters.ToArray();
|
||||||
return Ok(chapters);
|
return Ok(chapters);
|
||||||
@@ -226,12 +242,12 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
|
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status204NoContent)]
|
[ProducesResponseType(Status204NoContent)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[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));
|
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();
|
List<Chapter> chapters = manga.Chapters.Where(c => c.Downloaded).ToList();
|
||||||
if (chapters.Count == 0)
|
if (chapters.Count == 0)
|
||||||
@@ -251,12 +267,12 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
|
[ProducesResponseType<Chapter[]>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status204NoContent)]
|
[ProducesResponseType(Status204NoContent)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[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));
|
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();
|
List<Chapter> chapters = manga.Chapters.Where(c => c.Downloaded == false).ToList();
|
||||||
if (chapters.Count == 0)
|
if (chapters.Count == 0)
|
||||||
@@ -280,12 +296,12 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
[ProducesResponseType<string>(Status404NotFound, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
[ProducesResponseType<int>(Status503ServiceUnavailable, "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));
|
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();
|
List<Chapter> chapters = manga.Chapters.ToList();
|
||||||
if (chapters.Count == 0)
|
if (chapters.Count == 0)
|
||||||
@@ -303,8 +319,8 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
return StatusCode(Status500InternalServerError, "Max chapter could not be found");
|
return StatusCode(Status500InternalServerError, "Max chapter could not be found");
|
||||||
|
|
||||||
foreach (CollectionEntry collectionEntry in context.Entry(max).Collections)
|
foreach (CollectionEntry collectionEntry in context.Entry(max).Collections)
|
||||||
collectionEntry.Load();
|
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
|
||||||
context.Entry(max).Navigation(nameof(Chapter.ParentManga)).Load();
|
await context.Entry(max).Navigation(nameof(Chapter.ParentManga)).LoadAsync(HttpContext.RequestAborted);
|
||||||
|
|
||||||
return Ok(max);
|
return Ok(max);
|
||||||
}
|
}
|
||||||
@@ -324,12 +340,12 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
||||||
[ProducesResponseType<int>(Status503ServiceUnavailable, "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));
|
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();
|
List<Chapter> chapters = manga.Chapters.ToList();
|
||||||
if (chapters.Count == 0)
|
if (chapters.Count == 0)
|
||||||
@@ -347,8 +363,8 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
return StatusCode(Status412PreconditionFailed, "Max chapter could not be found");
|
return StatusCode(Status412PreconditionFailed, "Max chapter could not be found");
|
||||||
|
|
||||||
foreach (CollectionEntry collectionEntry in context.Entry(max).Collections)
|
foreach (CollectionEntry collectionEntry in context.Entry(max).Collections)
|
||||||
collectionEntry.Load();
|
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
|
||||||
context.Entry(max).Navigation(nameof(Chapter.ParentManga)).Load();
|
await context.Entry(max).Navigation(nameof(Chapter.ParentManga)).LoadAsync(HttpContext.RequestAborted);
|
||||||
|
|
||||||
return Ok(max);
|
return Ok(max);
|
||||||
}
|
}
|
||||||
@@ -365,13 +381,13 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType(Status202Accepted)]
|
[ProducesResponseType(Status202Accepted)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[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)
|
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound();
|
return NotFound(nameof(MangaId));
|
||||||
|
|
||||||
manga.IgnoreChaptersBefore = chapterThreshold;
|
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 StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||||
|
|
||||||
return Accepted();
|
return Accepted();
|
||||||
@@ -387,16 +403,16 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[HttpPost("{MangaId}/ChangeLibrary/{LibraryId}")]
|
[HttpPost("{MangaId}/ChangeLibrary/{LibraryId}")]
|
||||||
[ProducesResponseType(Status202Accepted)]
|
[ProducesResponseType(Status202Accepted)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[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));
|
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));
|
return NotFound(nameof(LibraryId));
|
||||||
|
|
||||||
foreach (CollectionEntry collectionEntry in context.Entry(manga).Collections)
|
foreach (CollectionEntry collectionEntry in context.Entry(manga).Collections)
|
||||||
collectionEntry.Load();
|
await collectionEntry.LoadAsync(HttpContext.RequestAborted);
|
||||||
context.Entry(manga).Navigation(nameof(Manga.Library)).Load();
|
await context.Entry(manga).Navigation(nameof(Manga.Library)).LoadAsync(HttpContext.RequestAborted);
|
||||||
|
|
||||||
MoveMangaLibraryWorker moveLibrary = new(manga, library);
|
MoveMangaLibraryWorker moveLibrary = new(manga, library);
|
||||||
|
|
||||||
@@ -422,11 +438,11 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status428PreconditionRequired, "text/plain")]
|
[ProducesResponseType<string>(Status428PreconditionRequired, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "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));
|
return NotFound(nameof(MangaId));
|
||||||
if(!Tranga.TryGetMangaConnector(MangaConnectorName, out MangaConnector? mangaConnector))
|
if(!Tranga.TryGetMangaConnector(MangaConnectorName, out MangaConnector? _))
|
||||||
return NotFound(nameof(MangaConnectorName));
|
return NotFound(nameof(MangaConnectorName));
|
||||||
|
|
||||||
if (context.MangaConnectorToManga
|
if (context.MangaConnectorToManga
|
||||||
@@ -440,7 +456,7 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
mcId.UseForDownload = IsRequested;
|
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);
|
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||||
|
|
||||||
|
|
||||||
@@ -463,9 +479,9 @@ public class MangaController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType(Status406NotAcceptable)]
|
[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 NotFound(nameof(MangaId));
|
||||||
|
|
||||||
return new SearchController(context).SearchManga(MangaConnectorName, manga.Name);
|
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>
|
/// <response code="404"><see cref="Author"/> with <paramref name="AuthorId"/></response>
|
||||||
[HttpGet("WithAuthorId/{AuthorId}")]
|
[HttpGet("WithAuthorId/{AuthorId}")]
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[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)
|
if (await context.Authors.FirstOrDefaultAsync(a => a.Key == AuthorId, HttpContext.RequestAborted) is not { } author)
|
||||||
return NotFound();
|
return NotFound(nameof(AuthorId));
|
||||||
|
|
||||||
return Ok(context.Mangas.Where(m => m.Authors.Contains(author)));
|
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>
|
/// <response code="404"><see cref="Tag"/> not found</response>
|
||||||
[HttpGet("WithTag/{Tag}")]
|
[HttpGet("WithTag/{Tag}")]
|
||||||
[ProducesResponseType<Manga[]>(Status200OK, "application/json")]
|
[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)
|
if (await context.Tags.FirstOrDefaultAsync(t => t.Tag == Tag, HttpContext.RequestAborted) is not { } tag)
|
||||||
return NotFound();
|
return NotFound(nameof(Tag));
|
||||||
|
|
||||||
return Ok(context.Mangas.Where(m => m.MangaTags.Contains(tag)));
|
return Ok(context.Mangas.Where(m => m.MangaTags.Contains(tag)));
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ using API.Schema.MangaContext.MetadataFetchers;
|
|||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
@@ -28,11 +29,15 @@ public class MetadataFetcherController(MangaContext context) : Controller
|
|||||||
/// Returns all <see cref="MetadataEntry"/>
|
/// Returns all <see cref="MetadataEntry"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet("Links")]
|
[HttpGet("Links")]
|
||||||
[ProducesResponseType<MetadataEntry[]>(Status200OK, "application/json")]
|
[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>
|
/// <summary>
|
||||||
@@ -48,10 +53,10 @@ public class MetadataFetcherController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType<MetadataSearchResult[]>(Status200OK, "application/json")]
|
[ProducesResponseType<MetadataSearchResult[]>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status400BadRequest)]
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[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)
|
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound();
|
return NotFound(nameof(MangaId));
|
||||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is not { } fetcher)
|
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is not { } fetcher)
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
|
|
||||||
@@ -74,18 +79,18 @@ public class MetadataFetcherController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType(Status400BadRequest)]
|
[ProducesResponseType(Status400BadRequest)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[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)
|
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } manga)
|
||||||
return NotFound();
|
return NotFound(nameof(MangaId));
|
||||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is not { } fetcher)
|
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is not { } fetcher)
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
|
|
||||||
MetadataEntry entry = fetcher.CreateMetadataEntry(manga, Identifier);
|
MetadataEntry entry = fetcher.CreateMetadataEntry(manga, Identifier);
|
||||||
context.MetadataEntries.Add(entry);
|
context.MetadataEntries.Add(entry);
|
||||||
|
|
||||||
if(context.Sync() is { } errorMessage)
|
if(await context.Sync(HttpContext.RequestAborted) is { success: false } result)
|
||||||
return StatusCode(Status500InternalServerError, errorMessage);
|
return StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||||
return Ok(entry);
|
return Ok(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,10 +108,10 @@ public class MetadataFetcherController(MangaContext context) : Controller
|
|||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
[ProducesResponseType<string>(Status412PreconditionFailed, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "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)
|
if (await context.Mangas.FirstOrDefaultAsync(m => m.Key == MangaId, HttpContext.RequestAborted) is not { } _)
|
||||||
return NotFound();
|
return NotFound(nameof(MangaId));
|
||||||
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is null)
|
if(Tranga.MetadataFetchers.FirstOrDefault(f => f.Name == MetadataFetcherName) is null)
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
if(context.MetadataEntries.FirstOrDefault(e => e.MangaId == MangaId && e.MetadataFetcherName == MetadataFetcherName) is not { } entry)
|
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);
|
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 StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ using API.Schema.NotificationsContext;
|
|||||||
using API.Schema.NotificationsContext.NotificationConnectors;
|
using API.Schema.NotificationsContext.NotificationConnectors;
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
@@ -19,12 +20,15 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
/// Gets all configured <see cref="NotificationConnector"/>
|
/// Gets all configured <see cref="NotificationConnector"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
|
/// <response code="500">Error during Database Operation</response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType<NotificationConnector[]>(Status200OK, "application/json")]
|
[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>
|
/// <summary>
|
||||||
@@ -36,10 +40,10 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
[HttpGet("{Name}")]
|
[HttpGet("{Name}")]
|
||||||
[ProducesResponseType<NotificationConnector>(Status200OK, "application/json")]
|
[ProducesResponseType<NotificationConnector>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
public IActionResult GetConnector(string Name)
|
public async Task<IActionResult> GetConnector (string Name)
|
||||||
{
|
{
|
||||||
if(context.NotificationConnectors.Find(Name) is not { } connector)
|
if (await context.NotificationConnectors.FirstOrDefaultAsync(c => c.Name == Name, HttpContext.RequestAborted) is not { } connector)
|
||||||
return NotFound();
|
return NotFound(nameof(Name));
|
||||||
|
|
||||||
return Ok(connector);
|
return Ok(connector);
|
||||||
}
|
}
|
||||||
@@ -53,12 +57,12 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
[HttpPut]
|
[HttpPut]
|
||||||
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "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.NotificationConnectors.Add(notificationConnector);
|
||||||
context.Notifications.Add(new ("Added new Notification Connector!", notificationConnector.Name, NotificationUrgency.High));
|
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 StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||||
return Ok(notificationConnector.Name);
|
return Ok(notificationConnector.Name);
|
||||||
}
|
}
|
||||||
@@ -72,7 +76,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
[HttpPut("Gotify")]
|
[HttpPut("Gotify")]
|
||||||
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateGotifyConnector([FromBody]GotifyRecord gotifyData)
|
public async Task<IActionResult> CreateGotifyConnector ([FromBody]GotifyRecord gotifyData)
|
||||||
{
|
{
|
||||||
//TODO Validate Data
|
//TODO Validate Data
|
||||||
|
|
||||||
@@ -81,7 +85,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
new Dictionary<string, string>() { { "X-Gotify-Key", gotifyData.AppToken } },
|
new Dictionary<string, string>() { { "X-Gotify-Key", gotifyData.AppToken } },
|
||||||
"POST",
|
"POST",
|
||||||
$"{{\"message\": \"%text\", \"title\": \"%title\", \"Priority\": {gotifyData.Priority}}}");
|
$"{{\"message\": \"%text\", \"title\": \"%title\", \"Priority\": {gotifyData.Priority}}}");
|
||||||
return CreateConnector(gotifyConnector);
|
return await CreateConnector(gotifyConnector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -93,7 +97,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
[HttpPut("Ntfy")]
|
[HttpPut("Ntfy")]
|
||||||
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreateNtfyConnector([FromBody]NtfyRecord ntfyRecord)
|
public async Task<IActionResult> CreateNtfyConnector ([FromBody]NtfyRecord ntfyRecord)
|
||||||
{
|
{
|
||||||
//TODO Validate Data
|
//TODO Validate Data
|
||||||
|
|
||||||
@@ -108,7 +112,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
},
|
},
|
||||||
"POST",
|
"POST",
|
||||||
$"{{\"message\": \"%text\", \"title\": \"%title\", \"Priority\": {ntfyRecord.Priority} \"Topic\": \"{ntfyRecord.Topic}\"}}");
|
$"{{\"message\": \"%text\", \"title\": \"%title\", \"Priority\": {ntfyRecord.Priority} \"Topic\": \"{ntfyRecord.Topic}\"}}");
|
||||||
return CreateConnector(ntfyConnector);
|
return await CreateConnector(ntfyConnector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -120,7 +124,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
[HttpPut("Pushover")]
|
[HttpPut("Pushover")]
|
||||||
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
[ProducesResponseType<string>(Status200OK, "text/plain")]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
||||||
public IActionResult CreatePushoverConnector([FromBody]PushoverRecord pushoverRecord)
|
public async Task<IActionResult> CreatePushoverConnector ([FromBody]PushoverRecord pushoverRecord)
|
||||||
{
|
{
|
||||||
//TODO Validate Data
|
//TODO Validate Data
|
||||||
|
|
||||||
@@ -129,7 +133,7 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
new Dictionary<string, string>(),
|
new Dictionary<string, string>(),
|
||||||
"POST",
|
"POST",
|
||||||
$"{{\"token\": \"{pushoverRecord.AppToken}\", \"user\": \"{pushoverRecord.User}\", \"message:\":\"%text\", \"%title\" }}");
|
$"{{\"token\": \"{pushoverRecord.AppToken}\", \"user\": \"{pushoverRecord.User}\", \"message:\":\"%text\", \"%title\" }}");
|
||||||
return CreateConnector(pushoverConnector);
|
return await CreateConnector(pushoverConnector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -143,14 +147,14 @@ public class NotificationConnectorController(NotificationsContext context) : Con
|
|||||||
[ProducesResponseType(Status200OK)]
|
[ProducesResponseType(Status200OK)]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
[ProducesResponseType<string>(Status500InternalServerError, "text/plain")]
|
[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)
|
if (await context.NotificationConnectors.FirstOrDefaultAsync(c => c.Name == Name, HttpContext.RequestAborted) is not { } connector)
|
||||||
return NotFound();
|
return NotFound(nameof(Name));
|
||||||
|
|
||||||
context.NotificationConnectors.Remove(connector);
|
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 StatusCode(Status500InternalServerError, result.exceptionMessage);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
using API.MangaConnectors;
|
using API.Schema.MangaContext;
|
||||||
using API.Schema.MangaContext;
|
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Soenneker.Utils.String.NeedlemanWunsch;
|
using Soenneker.Utils.String.NeedlemanWunsch;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
@@ -22,10 +22,10 @@ public class QueryController(MangaContext context) : Controller
|
|||||||
[HttpGet("Author/{AuthorId}")]
|
[HttpGet("Author/{AuthorId}")]
|
||||||
[ProducesResponseType<Author>(Status200OK, "application/json")]
|
[ProducesResponseType<Author>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
public IActionResult GetAuthor(string AuthorId)
|
public async Task<IActionResult> GetAuthor (string AuthorId)
|
||||||
{
|
{
|
||||||
if (context.Authors.Find(AuthorId) is not { } author)
|
if (await context.Authors.FirstOrDefaultAsync(a => a.Key == AuthorId, HttpContext.RequestAborted) is not { } author)
|
||||||
return NotFound();
|
return NotFound(nameof(AuthorId));
|
||||||
|
|
||||||
return Ok(author);
|
return Ok(author);
|
||||||
}
|
}
|
||||||
@@ -39,10 +39,10 @@ public class QueryController(MangaContext context) : Controller
|
|||||||
[HttpGet("Chapter/{ChapterId}")]
|
[HttpGet("Chapter/{ChapterId}")]
|
||||||
[ProducesResponseType<Chapter>(Status200OK, "application/json")]
|
[ProducesResponseType<Chapter>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
public IActionResult GetChapter(string ChapterId)
|
public async Task<IActionResult> GetChapter (string ChapterId)
|
||||||
{
|
{
|
||||||
if (context.Chapters.Find(ChapterId) is not { } chapter)
|
if (await context.Chapters.FirstOrDefaultAsync(c => c.Key == ChapterId, HttpContext.RequestAborted) is not { } chapter)
|
||||||
return NotFound();
|
return NotFound(nameof(ChapterId));
|
||||||
|
|
||||||
return Ok(chapter);
|
return Ok(chapter);
|
||||||
}
|
}
|
||||||
@@ -56,10 +56,10 @@ public class QueryController(MangaContext context) : Controller
|
|||||||
[HttpGet("Manga/MangaConnectorId/{MangaConnectorIdId}")]
|
[HttpGet("Manga/MangaConnectorId/{MangaConnectorIdId}")]
|
||||||
[ProducesResponseType<MangaConnectorId<Manga>>(Status200OK, "application/json")]
|
[ProducesResponseType<MangaConnectorId<Manga>>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
public IActionResult GetMangaMangaConnectorId(string MangaConnectorIdId)
|
public async Task<IActionResult> GetMangaMangaConnectorId (string MangaConnectorIdId)
|
||||||
{
|
{
|
||||||
if(context.MangaConnectorToManga.Find(MangaConnectorIdId) is not { } mcIdManga)
|
if (await context.MangaConnectorToManga.FirstOrDefaultAsync(c => c.Key == MangaConnectorIdId, HttpContext.RequestAborted) is not { } mcIdManga)
|
||||||
return NotFound();
|
return NotFound(nameof(MangaConnectorIdId));
|
||||||
|
|
||||||
return Ok(mcIdManga);
|
return Ok(mcIdManga);
|
||||||
}
|
}
|
||||||
@@ -70,18 +70,24 @@ public class QueryController(MangaContext context) : Controller
|
|||||||
/// <param name="MangaId">Key of <see cref="Manga"/></param>
|
/// <param name="MangaId">Key of <see cref="Manga"/></param>
|
||||||
/// <response code="200"></response>
|
/// <response code="200"></response>
|
||||||
/// <response code="404"><see cref="Manga"/> with <paramref name="MangaId"/> not found</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")]
|
[HttpGet("Manga/{MangaId}/SimilarName")]
|
||||||
[ProducesResponseType<string[]>(Status200OK, "application/json")]
|
[ProducesResponseType<string[]>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
public IActionResult GetSimilarManga(string MangaId)
|
public async Task<IActionResult> GetSimilarManga (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();
|
return NotFound(nameof(MangaId));
|
||||||
|
|
||||||
string name = manga.Name;
|
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
|
string[] similarIds = mangaNames
|
||||||
.Where(kv => NeedlemanWunschStringUtil.CalculateSimilarityPercentage(name, kv.Value) > 0.8)
|
.Where(kv => NeedlemanWunschStringUtil.CalculateSimilarityPercentage(name, kv.Value) > 0.8)
|
||||||
.Select(kv => kv.Key).ToArray();
|
.Select(kv => kv.Key).ToArray();
|
||||||
|
|
||||||
return Ok(similarIds);
|
return Ok(similarIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,10 +100,10 @@ public class QueryController(MangaContext context) : Controller
|
|||||||
[HttpGet("Chapter/MangaConnectorId/{MangaConnectorIdId}")]
|
[HttpGet("Chapter/MangaConnectorId/{MangaConnectorIdId}")]
|
||||||
[ProducesResponseType<MangaConnectorId<Chapter>>(Status200OK, "application/json")]
|
[ProducesResponseType<MangaConnectorId<Chapter>>(Status200OK, "application/json")]
|
||||||
[ProducesResponseType(Status404NotFound)]
|
[ProducesResponseType(Status404NotFound)]
|
||||||
public IActionResult GetChapterMangaConnectorId(string MangaConnectorIdId)
|
public async Task<IActionResult> GetChapterMangaConnectorId (string MangaConnectorIdId)
|
||||||
{
|
{
|
||||||
if(context.MangaConnectorToChapter.Find(MangaConnectorIdId) is not { } mcIdChapter)
|
if (await context.MangaConnectorToManga.FirstOrDefaultAsync(c => c.Key == MangaConnectorIdId, HttpContext.RequestAborted) is not { } mcIdChapter)
|
||||||
return NotFound();
|
return NotFound(nameof(MangaConnectorIdId));
|
||||||
|
|
||||||
return Ok(mcIdChapter);
|
return Ok(mcIdChapter);
|
||||||
}
|
}
|
||||||
|
@@ -35,7 +35,7 @@ public class SearchController(MangaContext context) : Controller
|
|||||||
List<Manga> retMangas = new();
|
List<Manga> retMangas = new();
|
||||||
foreach ((Manga manga, MangaConnectorId<Manga> mcId) manga in mangas)
|
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);
|
retMangas.Add(add);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ public class SearchController(MangaContext context) : Controller
|
|||||||
if(connector.GetMangaFromUrl(url) is not { } manga)
|
if(connector.GetMangaFromUrl(url) is not { } manga)
|
||||||
return NotFound();
|
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 StatusCode(Status500InternalServerError);
|
||||||
|
|
||||||
return Ok(add);
|
return Ok(add);
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
using API.APIEndpointRecords;
|
using API.Workers;
|
||||||
using API.Workers;
|
|
||||||
using Asp.Versioning;
|
using Asp.Versioning;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static Microsoft.AspNetCore.Http.StatusCodes;
|
using static Microsoft.AspNetCore.Http.StatusCodes;
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using API;
|
using API;
|
||||||
using API.MangaConnectors;
|
|
||||||
using API.Schema.LibraryContext;
|
using API.Schema.LibraryContext;
|
||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
using API.Schema.NotificationsContext;
|
using API.Schema.NotificationsContext;
|
||||||
@@ -76,7 +75,7 @@ builder.Services.AddControllers().AddNewtonsoftJson(opts =>
|
|||||||
opts.SerializerSettings.Converters.Add(new StringEnumConverter());
|
opts.SerializerSettings.Converters.Add(new StringEnumConverter());
|
||||||
opts.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
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");
|
builder.WebHost.UseUrls("http://*:6531");
|
||||||
|
|
||||||
@@ -112,7 +111,7 @@ using (IServiceScope scope = app.Services.CreateScope())
|
|||||||
if (!context.FileLibraries.Any())
|
if (!context.FileLibraries.Any())
|
||||||
context.FileLibraries.Add(new FileLibrary(Tranga.Settings.DownloadLocation, "Default FileLibrary"));
|
context.FileLibraries.Add(new FileLibrary(Tranga.Settings.DownloadLocation, "Default FileLibrary"));
|
||||||
|
|
||||||
context.Sync();
|
await context.Sync(CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (IServiceScope scope = app.Services.CreateScope())
|
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=)"};
|
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.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())
|
using (IServiceScope scope = app.Services.CreateScope())
|
||||||
@@ -132,7 +131,7 @@ using (IServiceScope scope = app.Services.CreateScope())
|
|||||||
LibraryContext context = scope.ServiceProvider.GetRequiredService<LibraryContext>();
|
LibraryContext context = scope.ServiceProvider.GetRequiredService<LibraryContext>();
|
||||||
context.Database.Migrate();
|
context.Database.Migrate();
|
||||||
|
|
||||||
context.Sync();
|
await context.Sync(CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
Tranga.SetServiceProvider(app.Services);
|
Tranga.SetServiceProvider(app.Services);
|
||||||
|
@@ -6,12 +6,12 @@ namespace API.Schema;
|
|||||||
[PrimaryKey("Key")]
|
[PrimaryKey("Key")]
|
||||||
public abstract class Identifiable
|
public abstract class Identifiable
|
||||||
{
|
{
|
||||||
public Identifiable()
|
protected Identifiable()
|
||||||
{
|
{
|
||||||
this.Key = TokenGen.CreateToken(this.GetType());
|
this.Key = TokenGen.CreateToken(this.GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Identifiable(string key)
|
protected Identifiable(string key)
|
||||||
{
|
{
|
||||||
this.Key = key;
|
this.Key = key;
|
||||||
}
|
}
|
||||||
|
@@ -104,15 +104,15 @@ public class MangaContext(DbContextOptions<MangaContext> options) : TrangaBaseCo
|
|||||||
.OnDelete(DeleteBehavior.Cascade);
|
.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 f;
|
||||||
|
|
||||||
return MangaIncludeAll()
|
return await MangaIncludeAll()
|
||||||
.FirstOrDefault(m => m.Links.Any(l => l.Key == other.Key) ||
|
.FirstOrDefaultAsync(m =>
|
||||||
m.AltTitles.Any(t => other.AltTitles.Select(ot => ot.Title)
|
m.Links.Any(l => l.Key == other.Key) ||
|
||||||
.Any(s => s.Equals(t.Title))));
|
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)
|
public IIncludableQueryable<Manga, ICollection<MangaConnectorId<Manga>>> MangaIncludeAll() => Mangas.Include(m => m.Library)
|
||||||
|
@@ -31,5 +31,5 @@ public abstract class MetadataFetcher
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the Manga linked in the MetadataEntry
|
/// Updates the Manga linked in the MetadataEntry
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract void UpdateMetadata(MetadataEntry metadataEntry, MangaContext dbContext);
|
public abstract Task UpdateMetadata(MetadataEntry metadataEntry, MangaContext dbContext, CancellationToken token);
|
||||||
}
|
}
|
@@ -43,21 +43,25 @@ public class MyAnimeList : MetadataFetcher
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="metadataEntry"></param>
|
/// <param name="metadataEntry"></param>
|
||||||
/// <param name="dbContext"></param>
|
/// <param name="dbContext"></param>
|
||||||
|
/// <param name="token"></param>
|
||||||
/// <exception cref="FormatException"></exception>
|
/// <exception cref="FormatException"></exception>
|
||||||
/// <exception cref="DbUpdateException"></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)
|
foreach (CollectionEntry collectionEntry in dbContext.Entry(dbManga).Collections)
|
||||||
collectionEntry.Load();
|
await collectionEntry.LoadAsync(token);
|
||||||
dbContext.Entry(dbManga).Navigation(nameof(Manga.Library)).Load();
|
await dbContext.Entry(dbManga).Navigation(nameof(Manga.Library)).LoadAsync(token);
|
||||||
|
|
||||||
MangaFull resultData;
|
MangaFull resultData;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
long id = long.Parse(metadataEntry.Identifier);
|
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)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@@ -71,7 +75,7 @@ public class MyAnimeList : MetadataFetcher
|
|||||||
dbManga.Authors.Clear();
|
dbManga.Authors.Clear();
|
||||||
dbManga.Authors = resultData.Authors.Select(a => new Author(a.Name)).ToList();
|
dbManga.Authors = resultData.Authors.Select(a => new Author(a.Name)).ToList();
|
||||||
|
|
||||||
dbContext.Sync();
|
await dbContext.Sync(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -22,11 +22,11 @@ public abstract class TrangaBaseContext<T> : DbContext where T : DbContext
|
|||||||
}, Array.Empty<string>(), LogLevel.Warning, DbContextLoggerOptions.Level | DbContextLoggerOptions.Category | DbContextLoggerOptions.UtcTime);
|
}, 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
|
try
|
||||||
{
|
{
|
||||||
this.SaveChanges();
|
await this.SaveChangesAsync(token);
|
||||||
return (true, null);
|
return (true, null);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
@@ -158,12 +158,13 @@ public static class Tranga
|
|||||||
RunningWorkers.Remove(worker, out _);
|
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();
|
context.ChangeTracker.Clear();
|
||||||
manga = context.FindMangaLike(addManga);
|
manga = context.FindMangaLike(addManga, token).Result;
|
||||||
if (manga is not null)
|
if (manga is not null)
|
||||||
{
|
{
|
||||||
foreach (MangaConnectorId<Manga> mcId in addManga.MangaConnectorIds)
|
foreach (MangaConnectorId<Manga> mcId in addManga.MangaConnectorIds)
|
||||||
@@ -198,7 +199,7 @@ public static class Tranga
|
|||||||
context.Mangas.Add(manga);
|
context.Mangas.Add(manga);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.Sync() is { success: false })
|
if (context.Sync(token).Result is { success: false })
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
DownloadCoverFromMangaconnectorWorker downloadCoverWorker = new (addMcId);
|
DownloadCoverFromMangaconnectorWorker downloadCoverWorker = new (addMcId);
|
||||||
@@ -207,10 +208,10 @@ public static class Tranga
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool AddChapterToContext((Chapter, MangaConnectorId<Chapter>) addChapter, MangaContext context,
|
internal static bool AddChapterToContext((Chapter, MangaConnectorId<Chapter>) addChapter, MangaContext context, [NotNullWhen(true)] out Chapter? chapter, CancellationToken token) =>
|
||||||
[NotNullWhen(true)] out Chapter? chapter) => AddChapterToContext(addChapter.Item1, addChapter.Item2, context, out chapter);
|
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)
|
chapter = context.Chapters.Where(ch => ch.Key == addChapter.Key)
|
||||||
.Include(ch => ch.ParentManga)
|
.Include(ch => ch.ParentManga)
|
||||||
@@ -226,7 +227,7 @@ public static class Tranga
|
|||||||
chapter = addChapter;
|
chapter = addChapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.Sync() is { success: false })
|
if (context.Sync(token).Result is { success: false })
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@ public abstract class BaseWorker : Identifiable
|
|||||||
public IEnumerable<BaseWorker> MissingDependencies => DependsOn.Where(d => d.State < WorkerExecutionState.Completed);
|
public IEnumerable<BaseWorker> MissingDependencies => DependsOn.Where(d => d.State < WorkerExecutionState.Completed);
|
||||||
public bool AllDependenciesFulfilled => DependsOn.All(d => d.State >= WorkerExecutionState.Completed);
|
public bool AllDependenciesFulfilled => DependsOn.All(d => d.State >= WorkerExecutionState.Completed);
|
||||||
internal WorkerExecutionState State { get; private set; }
|
internal WorkerExecutionState State { get; private set; }
|
||||||
private CancellationTokenSource? CancellationTokenSource = null;
|
protected CancellationTokenSource CancellationTokenSource = new ();
|
||||||
protected ILog Log { get; init; }
|
protected ILog Log { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -36,7 +36,7 @@ public abstract class BaseWorker : Identifiable
|
|||||||
{
|
{
|
||||||
Log.Debug($"Cancelled {this}");
|
Log.Debug($"Cancelled {this}");
|
||||||
this.State = WorkerExecutionState.Cancelled;
|
this.State = WorkerExecutionState.Cancelled;
|
||||||
CancellationTokenSource?.Cancel();
|
CancellationTokenSource.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -46,7 +46,7 @@ public abstract class BaseWorker : Identifiable
|
|||||||
{
|
{
|
||||||
Log.Debug($"Failed {this}");
|
Log.Debug($"Failed {this}");
|
||||||
this.State = WorkerExecutionState.Failed;
|
this.State = WorkerExecutionState.Failed;
|
||||||
CancellationTokenSource?.Cancel();
|
CancellationTokenSource.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseWorker(IEnumerable<BaseWorker>? dependsOn = null)
|
public BaseWorker(IEnumerable<BaseWorker>? dependsOn = null)
|
||||||
@@ -89,10 +89,9 @@ public abstract class BaseWorker : Identifiable
|
|||||||
// Run the actual work
|
// Run the actual work
|
||||||
Log.Info($"Running {this}");
|
Log.Info($"Running {this}");
|
||||||
DateTime startTime = DateTime.UtcNow;
|
DateTime startTime = DateTime.UtcNow;
|
||||||
Task<BaseWorker[]> task = new (DoWorkInternal, CancellationTokenSource.Token);
|
Task<BaseWorker[]> task = DoWorkInternal();
|
||||||
task.GetAwaiter().OnCompleted(Finish(startTime, callback));
|
task.GetAwaiter().OnCompleted(Finish(startTime, callback));
|
||||||
this.State = WorkerExecutionState.Running;
|
this.State = WorkerExecutionState.Running;
|
||||||
task.Start();
|
|
||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,12 +105,12 @@ public abstract class BaseWorker : Identifiable
|
|||||||
callback?.Invoke();
|
callback?.Invoke();
|
||||||
};
|
};
|
||||||
|
|
||||||
protected abstract BaseWorker[] DoWorkInternal();
|
protected abstract Task<BaseWorker[]> DoWorkInternal();
|
||||||
|
|
||||||
private BaseWorker[] WaitForDependencies()
|
private BaseWorker[] WaitForDependencies()
|
||||||
{
|
{
|
||||||
Log.Info($"Waiting for {MissingDependencies.Count()} Dependencies {this}:\n\t{string.Join("\n\t", MissingDependencies.Select(d => d.ToString()))}");
|
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);
|
Thread.Sleep(Tranga.Settings.WorkCycleTimeoutMs);
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ using System.Runtime.InteropServices;
|
|||||||
using API.MangaConnectors;
|
using API.MangaConnectors;
|
||||||
using API.MangaDownloadClients;
|
using API.MangaDownloadClients;
|
||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||||
@@ -16,14 +17,14 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId<Chapter> c
|
|||||||
: BaseWorkerWithContext<MangaContext>(dependsOn)
|
: BaseWorkerWithContext<MangaContext>(dependsOn)
|
||||||
{
|
{
|
||||||
internal readonly string MangaConnectorIdId = chId.Key;
|
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?
|
return []; //TODO Exception?
|
||||||
if (!Tranga.TryGetMangaConnector(mangaConnectorId.MangaConnectorName, out MangaConnector? mangaConnector))
|
if (!Tranga.TryGetMangaConnector(mangaConnectorId.MangaConnectorName, out MangaConnector? mangaConnector))
|
||||||
return []; //TODO Exception?
|
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;
|
Chapter chapter = mangaConnectorId.Obj;
|
||||||
if (chapter.Downloaded)
|
if (chapter.Downloaded)
|
||||||
{
|
{
|
||||||
@@ -31,8 +32,8 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId<Chapter> c
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
DbContext.Entry(chapter).Navigation(nameof(Chapter.ParentManga)).Load();
|
await DbContext.Entry(chapter).Navigation(nameof(Chapter.ParentManga)).LoadAsync(CancellationTokenSource.Token);
|
||||||
DbContext.Entry(chapter.ParentManga).Navigation(nameof(Manga.Library)).Load();
|
await DbContext.Entry(chapter.ParentManga).Navigation(nameof(Manga.Library)).LoadAsync(CancellationTokenSource.Token);
|
||||||
|
|
||||||
if (chapter.ParentManga.LibraryId is null)
|
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}");
|
Log.Debug($"Creating ComicInfo.xml {chapter}");
|
||||||
foreach (CollectionEntry collectionEntry in DbContext.Entry(chapter.ParentManga).Collections)
|
foreach (CollectionEntry collectionEntry in DbContext.Entry(chapter.ParentManga).Collections)
|
||||||
collectionEntry.Load();
|
await collectionEntry.LoadAsync(CancellationTokenSource.Token);
|
||||||
DbContext.Entry(chapter.ParentManga).Navigation(nameof(Manga.Library)).Load();
|
await DbContext.Entry(chapter.ParentManga).Navigation(nameof(Manga.Library)).LoadAsync(CancellationTokenSource.Token);
|
||||||
File.WriteAllText(Path.Join(tempFolder, "ComicInfo.xml"), chapter.GetComicInfoXmlString());
|
await File.WriteAllTextAsync(Path.Join(tempFolder, "ComicInfo.xml"), chapter.GetComicInfoXmlString(), CancellationTokenSource.Token);
|
||||||
|
|
||||||
Log.Debug($"Packaging images to archive {chapter}");
|
Log.Debug($"Packaging images to archive {chapter}");
|
||||||
//ZIP-it and ship-it
|
//ZIP-it and ship-it
|
||||||
@@ -108,7 +109,7 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId<Chapter> c
|
|||||||
Directory.Delete(tempFolder, true); //Cleanup
|
Directory.Delete(tempFolder, true); //Cleanup
|
||||||
|
|
||||||
chapter.Downloaded = true;
|
chapter.Downloaded = true;
|
||||||
DbContext.Sync();
|
await DbContext.Sync(CancellationTokenSource.Token);
|
||||||
|
|
||||||
return [];
|
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
|
//Check if Publication already has a Folder and cover
|
||||||
string publicationFolder = manga.CreatePublicationFolder();
|
string publicationFolder = manga.CreatePublicationFolder();
|
||||||
@@ -163,7 +164,7 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId<Chapter> c
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO MangaConnector Selection
|
//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();
|
MangaConnectorId<Manga> mangaConnectorId = manga.MangaConnectorIds.First();
|
||||||
if (!Tranga.TryGetMangaConnector(mangaConnectorId.MangaConnectorName, out MangaConnector? mangaConnector))
|
if (!Tranga.TryGetMangaConnector(mangaConnectorId.MangaConnectorName, out MangaConnector? mangaConnector))
|
||||||
{
|
{
|
||||||
@@ -172,7 +173,7 @@ public class DownloadChapterFromMangaconnectorWorker(MangaConnectorId<Chapter> c
|
|||||||
}
|
}
|
||||||
|
|
||||||
Log.Info($"Copying cover to {publicationFolder}");
|
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);
|
string? fileInCache = manga.CoverFileNameInCache ?? mangaConnector.SaveCoverImageToCache(mangaConnectorId);
|
||||||
if (fileInCache is null)
|
if (fileInCache is null)
|
||||||
{
|
{
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using API.MangaConnectors;
|
using API.MangaConnectors;
|
||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace API.Workers;
|
namespace API.Workers;
|
||||||
|
|
||||||
@@ -7,19 +8,19 @@ public class DownloadCoverFromMangaconnectorWorker(MangaConnectorId<Manga> mcId,
|
|||||||
: BaseWorkerWithContext<MangaContext>(dependsOn)
|
: BaseWorkerWithContext<MangaContext>(dependsOn)
|
||||||
{
|
{
|
||||||
internal readonly string MangaConnectorIdId = mcId.Key;
|
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?
|
return []; //TODO Exception?
|
||||||
if (!Tranga.TryGetMangaConnector(mangaConnectorId.MangaConnectorName, out MangaConnector? mangaConnector))
|
if (!Tranga.TryGetMangaConnector(mangaConnectorId.MangaConnectorName, out MangaConnector? mangaConnector))
|
||||||
return []; //TODO Exception?
|
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 manga = mangaConnectorId.Obj;
|
||||||
|
|
||||||
manga.CoverFileNameInCache = mangaConnector.SaveCoverImageToCache(mangaConnectorId);
|
manga.CoverFileNameInCache = mangaConnector.SaveCoverImageToCache(mangaConnectorId);
|
||||||
|
|
||||||
DbContext.Sync();
|
await DbContext.Sync(CancellationTokenSource.Token);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using API.MangaConnectors;
|
using API.MangaConnectors;
|
||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace API.Workers;
|
namespace API.Workers;
|
||||||
|
|
||||||
@@ -7,16 +8,16 @@ public class RetrieveMangaChaptersFromMangaconnectorWorker(MangaConnectorId<Mang
|
|||||||
: BaseWorkerWithContext<MangaContext>(dependsOn)
|
: BaseWorkerWithContext<MangaContext>(dependsOn)
|
||||||
{
|
{
|
||||||
internal readonly string MangaConnectorIdId = mcId.Key;
|
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?
|
return []; //TODO Exception?
|
||||||
if (!Tranga.TryGetMangaConnector(mangaConnectorId.MangaConnectorName, out MangaConnector? mangaConnector))
|
if (!Tranga.TryGetMangaConnector(mangaConnectorId.MangaConnectorName, out MangaConnector? mangaConnector))
|
||||||
return []; //TODO Exception?
|
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 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
|
// This gets all chapters that are not downloaded
|
||||||
(Chapter, MangaConnectorId<Chapter>)[] allChapters =
|
(Chapter, MangaConnectorId<Chapter>)[] allChapters =
|
||||||
@@ -25,13 +26,13 @@ public class RetrieveMangaChaptersFromMangaconnectorWorker(MangaConnectorId<Mang
|
|||||||
int addedChapters = 0;
|
int addedChapters = 0;
|
||||||
foreach ((Chapter chapter, MangaConnectorId<Chapter> mcId) newChapter in allChapters)
|
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;
|
continue;
|
||||||
manga.Chapters.Add(addedChapter);
|
manga.Chapters.Add(addedChapter);
|
||||||
}
|
}
|
||||||
Log.Info($"{manga.Chapters.Count} existing + {addedChapters} new chapters.");
|
Log.Info($"{manga.Chapters.Count} existing + {addedChapters} new chapters.");
|
||||||
|
|
||||||
DbContext.Sync();
|
await DbContext.Sync(CancellationTokenSource.Token);
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ public class MoveFileOrFolderWorker(string toLocation, string fromLocation, IEnu
|
|||||||
public readonly string FromLocation = fromLocation;
|
public readonly string FromLocation = fromLocation;
|
||||||
public readonly string ToLocation = toLocation;
|
public readonly string ToLocation = toLocation;
|
||||||
|
|
||||||
protected override BaseWorker[] DoWorkInternal()
|
protected override Task<BaseWorker[]> DoWorkInternal()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -14,13 +14,13 @@ public class MoveFileOrFolderWorker(string toLocation, string fromLocation, IEnu
|
|||||||
if (!fi.Exists)
|
if (!fi.Exists)
|
||||||
{
|
{
|
||||||
Log.Error($"File does not exist at {FromLocation}");
|
Log.Error($"File does not exist at {FromLocation}");
|
||||||
return [];
|
return new Task<BaseWorker[]>(() => []);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (File.Exists(ToLocation))//Do not override existing
|
if (File.Exists(ToLocation))//Do not override existing
|
||||||
{
|
{
|
||||||
Log.Error($"File already exists at {ToLocation}");
|
Log.Error($"File already exists at {ToLocation}");
|
||||||
return [];
|
return new Task<BaseWorker[]>(() => []);
|
||||||
}
|
}
|
||||||
if(fi.Attributes.HasFlag(FileAttributes.Directory))
|
if(fi.Attributes.HasFlag(FileAttributes.Directory))
|
||||||
MoveDirectory(fi, ToLocation);
|
MoveDirectory(fi, ToLocation);
|
||||||
@@ -32,7 +32,7 @@ public class MoveFileOrFolderWorker(string toLocation, string fromLocation, IEnu
|
|||||||
Log.Error(e);
|
Log.Error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return new Task<BaseWorker[]>(() => []);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MoveDirectory(FileInfo from, string toLocation)
|
private void MoveDirectory(FileInfo from, string toLocation)
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using API.Schema.MangaContext;
|
using API.Schema.MangaContext;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace API.Workers;
|
namespace API.Workers;
|
||||||
|
|
||||||
@@ -7,20 +8,20 @@ public class MoveMangaLibraryWorker(Manga manga, FileLibrary toLibrary, IEnumera
|
|||||||
{
|
{
|
||||||
internal readonly string MangaId = manga.Key;
|
internal readonly string MangaId = manga.Key;
|
||||||
internal readonly string LibraryId = toLibrary.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?
|
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?
|
return []; //TODO Exception?
|
||||||
|
|
||||||
DbContext.Entry(manga).Collection(m => m.Chapters).Load();
|
await DbContext.Entry(manga).Collection(m => m.Chapters).LoadAsync(CancellationTokenSource.Token);
|
||||||
DbContext.Entry(manga).Navigation(nameof(Manga.Library)).Load();
|
await DbContext.Entry(manga).Navigation(nameof(Manga.Library)).LoadAsync(CancellationTokenSource.Token);
|
||||||
|
|
||||||
Dictionary<Chapter, string> oldPath = manga.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath);
|
Dictionary<Chapter, string> oldPath = manga.Chapters.ToDictionary(c => c, c => c.FullArchiveFilePath);
|
||||||
manga.Library = toLibrary;
|
manga.Library = toLibrary;
|
||||||
|
|
||||||
if (DbContext.Sync() is { success: false })
|
if (await DbContext.Sync(CancellationTokenSource.Token) is { success: false })
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
return manga.Chapters.Select(c => new MoveFileOrFolderWorker(c.FullArchiveFilePath, oldPath[c])).ToArray<BaseWorker>();
|
return manga.Chapters.Select(c => new MoveFileOrFolderWorker(c.FullArchiveFilePath, oldPath[c])).ToArray<BaseWorker>();
|
||||||
|
@@ -9,7 +9,7 @@ public class CheckForNewChaptersWorker(TimeSpan? interval = null, IEnumerable<Ba
|
|||||||
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
||||||
public TimeSpan Interval { get; set; } = interval??TimeSpan.FromMinutes(60);
|
public TimeSpan Interval { get; set; } = interval??TimeSpan.FromMinutes(60);
|
||||||
|
|
||||||
protected override BaseWorker[] DoWorkInternal()
|
protected override Task<BaseWorker[]> DoWorkInternal()
|
||||||
{
|
{
|
||||||
IQueryable<MangaConnectorId<Manga>> connectorIdsManga = DbContext.MangaConnectorToManga
|
IQueryable<MangaConnectorId<Manga>> connectorIdsManga = DbContext.MangaConnectorToManga
|
||||||
.Include(id => id.Obj)
|
.Include(id => id.Obj)
|
||||||
@@ -19,7 +19,7 @@ public class CheckForNewChaptersWorker(TimeSpan? interval = null, IEnumerable<Ba
|
|||||||
foreach (MangaConnectorId<Manga> mangaConnectorId in connectorIdsManga)
|
foreach (MangaConnectorId<Manga> mangaConnectorId in connectorIdsManga)
|
||||||
newWorkers.Add(new RetrieveMangaChaptersFromMangaconnectorWorker(mangaConnectorId, Tranga.Settings.DownloadLanguage));
|
newWorkers.Add(new RetrieveMangaChaptersFromMangaconnectorWorker(mangaConnectorId, Tranga.Settings.DownloadLanguage));
|
||||||
|
|
||||||
return newWorkers.ToArray();
|
return new Task<BaseWorker[]>(() => newWorkers.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -8,11 +8,11 @@ public class CleanupMangaCoversWorker(TimeSpan? interval = null, IEnumerable<Bas
|
|||||||
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
||||||
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(24);
|
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(24);
|
||||||
|
|
||||||
protected override BaseWorker[] DoWorkInternal()
|
protected override Task<BaseWorker[]> DoWorkInternal()
|
||||||
{
|
{
|
||||||
Log.Info("Removing stale files...");
|
Log.Info("Removing stale files...");
|
||||||
if (!Directory.Exists(TrangaSettings.coverImageCache))
|
if (!Directory.Exists(TrangaSettings.coverImageCache))
|
||||||
return [];
|
return new Task<BaseWorker[]>(() => []);
|
||||||
string[] usedFiles = DbContext.Mangas.Select(m => m.CoverFileNameInCache).Where(s => s != null).ToArray()!;
|
string[] usedFiles = DbContext.Mangas.Select(m => m.CoverFileNameInCache).Where(s => s != null).ToArray()!;
|
||||||
string[] extraneousFiles = new DirectoryInfo(TrangaSettings.coverImageCache).GetFiles()
|
string[] extraneousFiles = new DirectoryInfo(TrangaSettings.coverImageCache).GetFiles()
|
||||||
.Where(f => usedFiles.Contains(f.FullName) == false)
|
.Where(f => usedFiles.Contains(f.FullName) == false)
|
||||||
@@ -23,7 +23,6 @@ public class CleanupMangaCoversWorker(TimeSpan? interval = null, IEnumerable<Bas
|
|||||||
Log.Info($"Deleting {path}");
|
Log.Info($"Deleting {path}");
|
||||||
File.Delete(path);
|
File.Delete(path);
|
||||||
}
|
}
|
||||||
|
return new Task<BaseWorker[]>(() => []);
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -8,11 +8,12 @@ public class RemoveOldNotificationsWorker(TimeSpan? interval = null, IEnumerable
|
|||||||
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
||||||
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(1);
|
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);
|
IQueryable<Notification> toRemove = DbContext.Notifications.Where(n => n.IsSent || DateTime.UtcNow - n.Date > Interval);
|
||||||
DbContext.RemoveRange(toRemove);
|
DbContext.RemoveRange(toRemove);
|
||||||
DbContext.Sync();
|
|
||||||
|
await DbContext.Sync(CancellationTokenSource.Token);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ public class SendNotificationsWorker(TimeSpan? interval = null, IEnumerable<Base
|
|||||||
{
|
{
|
||||||
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
||||||
public TimeSpan Interval { get; set; } = interval??TimeSpan.FromMinutes(1);
|
public TimeSpan Interval { get; set; } = interval??TimeSpan.FromMinutes(1);
|
||||||
protected override BaseWorker[] DoWorkInternal()
|
protected override async Task<BaseWorker[]> DoWorkInternal()
|
||||||
{
|
{
|
||||||
NotificationConnector[] connectors = DbContext.NotificationConnectors.ToArray();
|
NotificationConnector[] connectors = DbContext.NotificationConnectors.ToArray();
|
||||||
Notification[] notifications = DbContext.Notifications.Where(n => n.IsSent == false).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 [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,7 +9,7 @@ public class StartNewChapterDownloadsWorker(TimeSpan? interval = null, IEnumerab
|
|||||||
|
|
||||||
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
||||||
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromMinutes(1);
|
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromMinutes(1);
|
||||||
protected override BaseWorker[] DoWorkInternal()
|
protected override Task<BaseWorker[]> DoWorkInternal()
|
||||||
{
|
{
|
||||||
IQueryable<MangaConnectorId<Chapter>> mangaConnectorIds = DbContext.MangaConnectorToChapter
|
IQueryable<MangaConnectorId<Chapter>> mangaConnectorIds = DbContext.MangaConnectorToChapter
|
||||||
.Include(id => id.Obj)
|
.Include(id => id.Obj)
|
||||||
@@ -19,6 +19,6 @@ public class StartNewChapterDownloadsWorker(TimeSpan? interval = null, IEnumerab
|
|||||||
foreach (MangaConnectorId<Chapter> mangaConnectorId in mangaConnectorIds)
|
foreach (MangaConnectorId<Chapter> mangaConnectorId in mangaConnectorIds)
|
||||||
newWorkers.Add(new DownloadChapterFromMangaconnectorWorker(mangaConnectorId));
|
newWorkers.Add(new DownloadChapterFromMangaconnectorWorker(mangaConnectorId));
|
||||||
|
|
||||||
return newWorkers.ToArray();
|
return new Task<BaseWorker[]>(() => newWorkers.ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -8,12 +8,12 @@ public class UpdateChaptersDownloadedWorker(TimeSpan? interval = null, IEnumerab
|
|||||||
{
|
{
|
||||||
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
||||||
public TimeSpan Interval { get; set; } = interval??TimeSpan.FromMinutes(60);
|
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))
|
foreach (Chapter dbContextChapter in DbContext.Chapters.Include(c => c.ParentManga))
|
||||||
dbContextChapter.Downloaded = dbContextChapter.CheckDownloaded();
|
dbContextChapter.Downloaded = dbContextChapter.CheckDownloaded();
|
||||||
|
|
||||||
DbContext.Sync();
|
await DbContext.Sync(CancellationTokenSource.Token);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -9,11 +9,11 @@ public class UpdateCoversWorker(TimeSpan? interval = null, IEnumerable<BaseWorke
|
|||||||
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
||||||
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(6);
|
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(6);
|
||||||
|
|
||||||
protected override BaseWorker[] DoWorkInternal()
|
protected override Task<BaseWorker[]> DoWorkInternal()
|
||||||
{
|
{
|
||||||
List<BaseWorker> workers = new();
|
List<BaseWorker> workers = new();
|
||||||
foreach (MangaConnectorId<Manga> mangaConnectorId in DbContext.MangaConnectorToManga)
|
foreach (MangaConnectorId<Manga> mangaConnectorId in DbContext.MangaConnectorToManga)
|
||||||
workers.Add(new DownloadCoverFromMangaconnectorWorker(mangaConnectorId));
|
workers.Add(new DownloadCoverFromMangaconnectorWorker(mangaConnectorId));
|
||||||
return workers.ToArray();
|
return new Task<BaseWorker[]>(() => workers.ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -11,7 +11,7 @@ public class UpdateMetadataWorker(TimeSpan? interval = null, IEnumerable<BaseWor
|
|||||||
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
public DateTime LastExecution { get; set; } = DateTime.UnixEpoch;
|
||||||
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(12);
|
public TimeSpan Interval { get; set; } = interval ?? TimeSpan.FromHours(12);
|
||||||
|
|
||||||
protected override BaseWorker[] DoWorkInternal()
|
protected override async Task<BaseWorker[]> DoWorkInternal()
|
||||||
{
|
{
|
||||||
IQueryable<string> mangaIds = DbContext.MangaConnectorToManga
|
IQueryable<string> mangaIds = DbContext.MangaConnectorToManga
|
||||||
.Where(m => m.UseForDownload)
|
.Where(m => m.UseForDownload)
|
||||||
@@ -22,9 +22,9 @@ public class UpdateMetadataWorker(TimeSpan? interval = null, IEnumerable<BaseWor
|
|||||||
mangaIds.Any(id => id == e.MangaId));
|
mangaIds.Any(id => id == e.MangaId));
|
||||||
|
|
||||||
foreach (MetadataEntry metadataEntry in metadataEntriesToUpdate)
|
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 [];
|
return [];
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user